summaryrefslogtreecommitdiffstats
path: root/tools/afcclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/afcclient.c')
-rw-r--r--tools/afcclient.c1648
1 files changed, 1648 insertions, 0 deletions
diff --git a/tools/afcclient.c b/tools/afcclient.c
new file mode 100644
index 0000000..c2e2ce3
--- /dev/null
+++ b/tools/afcclient.c
@@ -0,0 +1,1648 @@
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 if (o > 1) {
336 o--;
337 }
338 continue;
339 } else if (!strcmp(comps[i].str, ".")) {
340 continue;
341 }
342 o_len += comps[i].len;
343 comps_final[o].str = comps[i].str;
344 comps_final[o].len = comps[i].len;
345 o++;
346 }
347
348 o_len += o;
349 char* result = (char*)malloc(o_len);
350 char* presult = result;
351 for (int i = 0; i < o; i++) {
352 if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) {
353 *presult = '/';
354 presult++;
355 }
356 strncpy(presult, comps_final[i].str, comps_final[i].len);
357 presult+=comps_final[i].len;
358 *presult = '\0';
359 }
360 if (presult == result) {
361 *presult = '/';
362 presult++;
363 *presult = 0;
364 }
365
366 for (int i = 0; i < c_count; i++) {
367 free(comps[i].str);
368 }
369 free(comps);
370 free(comps_final);
371
372 return result;
373}
374
375static void handle_devinfo(afc_client_t afc, int argc, char** argv)
376{
377 plist_t info = NULL;
378 afc_error_t err = afc_get_device_info_plist(afc, &info);
379 if (err == AFC_E_SUCCESS && info) {
380 if (argc > 0 && !strcmp(argv[0], "--plain")) {
381 plist_write_to_stream(info, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_NONE);
382 } else {
383 plist_write_to_stream(info, stdout, PLIST_FORMAT_JSON, PLIST_OPT_NONE);
384 }
385 } else {
386 printf("Error: Failed to get device info: %s (%d)\n", afc_strerror(err), err);
387 }
388 plist_free(info);
389}
390
391static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf)
392{
393 plist_t info = NULL;
394 afc_error_t ret = afc_get_file_info_plist(afc, path, &info);
395 memset(stbuf, 0, sizeof(struct afc_file_stat));
396 if (ret != AFC_E_SUCCESS) {
397 return -1;
398 } else if (!info) {
399 return -1;
400 }
401 stbuf->st_size = plist_dict_get_uint(info, "st_size");
402 stbuf->st_blocks = plist_dict_get_uint(info, "st_blocks");
403 const char* s_ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
404 if (s_ifmt) {
405 if (!strcmp(s_ifmt, "S_IFREG")) {
406 stbuf->st_mode = S_IFREG;
407 } else if (!strcmp(s_ifmt, "S_IFDIR")) {
408 stbuf->st_mode = S_IFDIR;
409 } else if (!strcmp(s_ifmt, "S_IFLNK")) {
410 stbuf->st_mode = S_IFLNK;
411 } else if (!strcmp(s_ifmt, "S_IFBLK")) {
412 stbuf->st_mode = S_IFBLK;
413 } else if (!strcmp(s_ifmt, "S_IFCHR")) {
414 stbuf->st_mode = S_IFCHR;
415 } else if (!strcmp(s_ifmt, "S_IFIFO")) {
416 stbuf->st_mode = S_IFIFO;
417 } else if (!strcmp(s_ifmt, "S_IFSOCK")) {
418 stbuf->st_mode = S_IFSOCK;
419 }
420 }
421 stbuf->st_nlink = plist_dict_get_uint(info, "st_nlink");
422 stbuf->st_mtime = (time_t)(plist_dict_get_uint(info, "st_mtime") / 1000000000);
423 /* available on iOS 7+ */
424 stbuf->st_birthtime = (time_t)(plist_dict_get_uint(info, "st_birthtime") / 1000000000);
425 plist_free(info);
426 return 0;
427}
428
429static void handle_file_info(afc_client_t afc, int argc, char** argv)
430{
431 if (argc < 1) {
432 printf("Error: Missing PATH.\n");
433 return;
434 }
435
436 plist_t info = NULL;
437 char* abspath = get_absolute_path(argv[0]);
438 if (!abspath) {
439 printf("Error: Invalid argument\n");
440 return;
441 }
442 afc_error_t err = afc_get_file_info_plist(afc, abspath, &info);
443 if (err == AFC_E_SUCCESS && info) {
444 if (argc > 1 && !strcmp(argv[1], "--plain")) {
445 plist_write_to_stream(info, stdout, PLIST_FORMAT_LIMD, PLIST_OPT_NONE);
446 } else {
447 plist_write_to_stream(info, stdout, PLIST_FORMAT_JSON, PLIST_OPT_NONE);
448 }
449 } else {
450 printf("Error: Failed to get file info for %s: %s (%d)\n", argv[0], afc_strerror(err), err);
451 }
452 plist_free(info);
453 free(abspath);
454}
455
456static void print_file_info(afc_client_t afc, const char* path, int list_verbose)
457{
458 struct afc_file_stat st;
459 get_file_info_stat(afc, path, &st);
460 if (list_verbose) {
461 char timebuf[64];
462 time_t t = st.st_mtime;
463 if (S_ISDIR(st.st_mode)) {
464 printf("drwxr-xr-x");
465 } else if (S_ISLNK(st.st_mode)) {
466 printf("lrwxrwxrwx");
467 } else {
468 if (S_ISFIFO(st.st_mode)) {
469 printf("f");
470 } else if (S_ISBLK(st.st_mode)) {
471 printf("b");
472 } else if (S_ISCHR(st.st_mode)) {
473 printf("c");
474 } else if (S_ISSOCK(st.st_mode)) {
475 printf("s");
476 } else {
477 printf("-");
478 }
479 printf("rw-r--r--");
480 }
481 printf(" ");
482 printf("%4d", st.st_nlink);
483 printf(" ");
484 printf("mobile");
485 printf(" ");
486 printf("mobile");
487 printf(" ");
488 printf("%10lld", (long long)st.st_size);
489 printf(" ");
490#ifdef _WIN32
491 strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t));
492#else
493 strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t));
494#endif
495 printf("%s", timebuf);
496 printf(" ");
497 }
498 if (S_ISDIR(st.st_mode)) {
499 cprintf(FG_CYAN);
500 } else if (S_ISLNK(st.st_mode)) {
501 cprintf(FG_MAGENTA);
502 } else if (S_ISREG(st.st_mode)) {
503 cprintf(FG_DEFAULT);
504 } else {
505 cprintf(FG_YELLOW);
506 }
507 cprintf("%s" COLOR_RESET "\n", path_get_basename(path));
508}
509
510static void handle_list(afc_client_t afc, int argc, char** argv)
511{
512 const char* path = NULL;
513 int list_verbose = 0;
514 if (argc < 1) {
515 path = curdir;
516 } else {
517 if (!strcmp(argv[0], "-l")) {
518 list_verbose = 1;
519 if (argc == 2) {
520 path = argv[1];
521 } else {
522 path = curdir;
523 }
524 } else {
525 path = argv[0];
526 }
527 }
528 char* abspath = get_absolute_path(path);
529 if (!abspath) {
530 printf("Error: Invalid argument\n");
531 return;
532 }
533 int abspath_is_root = strcmp(abspath, "/") == 0;
534 size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath);
535 char** entries = NULL;
536 afc_error_t err = afc_read_directory(afc, abspath, &entries);
537 if (err == AFC_E_READ_ERROR) {
538 print_file_info(afc, abspath, list_verbose);
539 return;
540 } else if (err != AFC_E_SUCCESS) {
541 printf("Error: Failed to list '%s': %s (%d)\n", path, afc_strerror(err), err);
542 free(abspath);
543 return;
544 }
545
546 char** p = entries;
547 while (p && *p) {
548 if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
549 p++;
550 continue;
551 }
552 size_t len = abspath_len + 1 + strlen(*p) + 1;
553 char* testpath = (char*)malloc(len);
554 if (abspath_is_root) {
555 snprintf(testpath, len, "/%s", *p);
556 } else {
557 snprintf(testpath, len, "%s/%s", abspath, *p);
558 }
559 print_file_info(afc, testpath, list_verbose);
560 free(testpath);
561 p++;
562 }
563 afc_dictionary_free(entries);
564 free(abspath);
565}
566
567static void handle_rename(afc_client_t afc, int argc, char** argv)
568{
569 if (argc != 2) {
570 printf("Error: Invalid number of arguments\n");
571 return;
572 }
573 char* srcpath = get_absolute_path(argv[0]);
574 if (!srcpath) {
575 printf("Error: Invalid argument\n");
576 return;
577 }
578 char* dstpath = get_absolute_path(argv[1]);
579 if (!dstpath) {
580 free(srcpath);
581 printf("Error: Invalid argument\n");
582 return;
583 }
584 afc_error_t err = afc_rename_path(afc, srcpath, dstpath);
585 if (err != AFC_E_SUCCESS) {
586 printf("Error: Failed to rename '%s' -> '%s': %s (%d)\n", argv[0], argv[1], afc_strerror(err), err);
587 }
588 free(srcpath);
589 free(dstpath);
590}
591
592static void handle_mkdir(afc_client_t afc, int argc, char** argv)
593{
594 for (int i = 0; i < argc; i++) {
595 char* abspath = get_absolute_path(argv[i]);
596 if (!abspath) {
597 printf("Error: Invalid argument '%s'\n", argv[i]);
598 continue;
599 }
600 afc_error_t err = afc_make_directory(afc, abspath);
601 if (err != AFC_E_SUCCESS) {
602 printf("Error: Failed to create directory '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
603 }
604 free(abspath);
605 }
606}
607
608static void handle_link(afc_client_t afc, int argc, char** argv)
609{
610 if (argc < 2) {
611 printf("Error: Invalid number of arguments\n");
612 return;
613 }
614 afc_link_type_t link_type = AFC_HARDLINK;
615 if (!strcmp(argv[0], "-s")) {
616 argc--;
617 argv++;
618 link_type = AFC_SYMLINK;
619 }
620 if (argc < 1 || argc > 2) {
621 printf("Error: Invalid number of arguments\n");
622 return;
623 }
624 const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1];
625 char* abs_link_name = get_absolute_path(link_name);
626 if (!abs_link_name) {
627 printf("Error: Invalid argument\n");
628 return;
629 }
630 afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name);
631 if (err != AFC_E_SUCCESS) {
632 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);
633 }
634}
635
636static int ask_yesno(const char* prompt)
637{
638 int ret = 0;
639#ifdef HAVE_READLINE
640 char* result = readline(prompt);
641 if (result && result[0] == 'y') {
642 ret = 1;
643 }
644#else
645 char cmdbuf[2] = {0, };
646 printf("%s", prompt);
647 fflush(stdout);
648 get_input(cmdbuf, sizeof(cmdbuf));
649 if (cmdbuf[0] == 'y') {
650 ret = 1;
651 }
652#endif
653#ifdef HAVE_READLINE
654 free(result);
655#endif
656 return ret;
657}
658
659static void handle_remove(afc_client_t afc, int argc, char** argv)
660{
661 int recursive = 0;
662 int force = 0;
663 int i = 0;
664 for (i = 0; i < argc; i++) {
665 if (!strcmp(argv[i], "--")) {
666 i++;
667 break;
668 } else if (!strcmp(argv[i], "-r")) {
669 recursive = 1;
670 } else if (!strcmp(argv[i], "-f")) {
671 force = 1;
672 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
673 recursive = 1;
674 force = 1;
675 } else {
676 break;
677 }
678 }
679 if (recursive && !force) {
680 if (!ask_yesno("WARNING: This operation will remove all contents of the given path(s). Continue? [y/N] ")) {
681 printf("Aborted.\n");
682 return;
683 }
684 }
685 for ( ; i < argc; i++) {
686 char* abspath = get_absolute_path(argv[i]);
687 if (!abspath) {
688 printf("Error: Invalid argument '%s'\n", argv[i]);
689 continue;
690 }
691 afc_error_t err;
692 if (recursive) {
693 err = afc_remove_path_and_contents(afc, abspath);
694 } else {
695 err = afc_remove_path(afc, abspath);
696 }
697 if (err != AFC_E_SUCCESS) {
698 printf("Error: Failed to remove '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
699 }
700 free(abspath);
701 }
702}
703
704static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite)
705{
706 uint64_t fh = 0;
707 afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh);
708 if (err != AFC_E_SUCCESS) {
709 printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
710 return 0;
711 }
712 if (file_exists(dstpath) && !force_overwrite) {
713 printf("Error: Failed to overwrite existing file without '-f' option: %s\n", dstpath);
714 return 0;
715 }
716 FILE *f = fopen(dstpath, "wb");
717 if (!f) {
718 printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno));
719 return 0;
720 }
721 struct timeval t1;
722 struct timeval t2;
723 struct timeval tdiff;
724 size_t bufsize = 0x100000;
725 char *buf = malloc(bufsize);
726 size_t total = 0;
727 int progress = 0;
728 int lastprog = 0;
729 if (file_size > 0x400000) {
730 progress = 1;
731 gettimeofday(&t1, NULL);
732 }
733 uint8_t succeed = 1;
734 while (err == AFC_E_SUCCESS) {
735 uint32_t bytes_read = 0;
736 size_t chunk = 0;
737 err = afc_file_read(afc, fh, buf, bufsize, &bytes_read);
738 if (bytes_read == 0) {
739 break;
740 }
741 while (chunk < bytes_read) {
742 size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f);
743 if (wr == 0) {
744 if (progress) {
745 printf("\n");
746 }
747 printf("Error: Failed to write to local file\n");
748 succeed = 0;
749 break;
750 }
751 chunk += wr;
752 }
753 total += chunk;
754 if (progress) {
755 int prog = (int) ((double) total / (double) file_size * 100.0f);
756 if (prog > lastprog) {
757 gettimeofday(&t2, NULL);
758 timeval_subtract(&tdiff, &t2, &t1);
759 double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
760 printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
761 fflush(stdout);
762 lastprog = prog;
763 }
764 }
765 }
766 if (progress) {
767 printf("\n");
768 }
769 if (err != AFC_E_SUCCESS) {
770 printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
771 succeed = 0;
772 }
773 free(buf);
774 fclose(f);
775 afc_file_close(afc, fh);
776 return succeed;
777}
778
779static int __mkdir(const char* path)
780{
781#ifdef _WIN32
782 return mkdir(path);
783#else
784 return mkdir(path, 0755);
785#endif
786}
787
788static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_get)
789{
790 plist_t info = NULL;
791 uint64_t file_size = 0;
792 afc_error_t err = afc_get_file_info_plist(afc, srcpath, &info);
793 if (err == AFC_E_OBJECT_NOT_FOUND) {
794 printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
795 return 0;
796 }
797 uint8_t is_dir = 0;
798 if (info) {
799 file_size = plist_dict_get_uint(info, "st_size");
800 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
801 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
802 plist_free(info);
803 }
804 uint8_t succeed = 1;
805 if (is_dir) {
806 if (!recursive_get) {
807 printf("Error: Failed to get a directory without '-r' option: %s\n", srcpath);
808 return 0;
809 }
810 char **entries = NULL;
811 err = afc_read_directory(afc, srcpath, &entries);
812 if (err != AFC_E_SUCCESS) {
813 printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
814 return 0;
815 }
816 char **p = entries;
817 size_t srcpath_len = strlen(srcpath);
818 uint8_t srcpath_is_root = strcmp(srcpath, "/") == 0;
819 // if directory exists, check force_overwrite flag
820 if (is_directory(dstpath)) {
821 if (!force_overwrite) {
822 printf("Error: Failed to write into existing directory without '-f': %s\n", dstpath);
823 return 0;
824 }
825 } else if (__mkdir(dstpath) != 0) {
826 printf("Error: Failed to create directory '%s': %s\n", dstpath, strerror(errno));
827 afc_dictionary_free(entries);
828 return 0;
829 }
830 while (p && *p) {
831 if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
832 p++;
833 continue;
834 }
835 size_t len = srcpath_is_root ? (strlen(*p) + 2) : (srcpath_len + 1 + strlen(*p) + 1);
836 char *testpath = (char *) malloc(len);
837 if (srcpath_is_root) {
838 snprintf(testpath, len, "/%s", *p);
839 } else {
840 snprintf(testpath, len, "%s/%s", srcpath, *p);
841 }
842 uint8_t dst_is_root = strcmp(srcpath, "/") == 0;
843 size_t dst_len = dst_is_root ? (strlen(*p) + 2) : (strlen(dstpath) + 1 + strlen(*p) + 1);
844 char *newdst = (char *) malloc(dst_len);
845 if (dst_is_root) {
846 snprintf(newdst, dst_len, "/%s", *p);
847 } else {
848 snprintf(newdst, dst_len, "%s/%s", dstpath, *p);
849 }
850 if (!get_file(afc, testpath, newdst, force_overwrite, recursive_get)) {
851 succeed = 0;
852 break;
853 }
854 free(testpath);
855 free(newdst);
856 p++;
857 }
858 afc_dictionary_free(entries);
859 } else {
860 succeed = get_single_file(afc, srcpath, dstpath, file_size, force_overwrite);
861 }
862 return succeed;
863}
864
865static void handle_get(afc_client_t afc, int argc, char **argv)
866{
867 if (argc < 1) {
868 printf("Error: Invalid number of arguments\n");
869 return;
870 }
871 uint8_t force_overwrite = 0, recursive_get = 0;
872 char *srcpath = NULL;
873 char *dstpath = NULL;
874 int i = 0;
875 for ( ; i < argc; i++) {
876 if (!strcmp(argv[i], "--")) {
877 i++;
878 break;
879 } else if (!strcmp(argv[i], "-r")) {
880 recursive_get = 1;
881 } else if (!strcmp(argv[i], "-f")) {
882 force_overwrite = 1;
883 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
884 recursive_get = 1;
885 force_overwrite = 1;
886 } else {
887 break;
888 }
889 }
890 if (argc - i == 1) {
891 char *tmp = strdup(argv[i]);
892 size_t src_len = strlen(tmp);
893 if (src_len > 1 && tmp[src_len - 1] == '/') {
894 tmp[src_len - 1] = '\0';
895 }
896 srcpath = get_absolute_path(tmp);
897 dstpath = strdup(path_get_basename(tmp));
898 free(tmp);
899 } else if (argc - i == 2) {
900 char *tmp = strdup(argv[i]);
901 size_t src_len = strlen(tmp);
902 if (src_len > 1 && tmp[src_len - 1] == '/') {
903 tmp[src_len - 1] = '\0';
904 }
905 srcpath = get_absolute_path(tmp);
906 dstpath = strdup(argv[i + 1]);
907 size_t dst_len = strlen(dstpath);
908 if (dst_len > 1 && dstpath[dst_len - 1] == '/') {
909 dstpath[dst_len - 1] = '\0';
910 }
911 free(tmp);
912 } else {
913 printf("Error: Invalid number of arguments\n");
914 return;
915 }
916
917 // target is a directory, put file under this target
918 if (is_directory(dstpath)) {
919 const char *basen = path_get_basename(srcpath);
920 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
921 size_t len = dst_is_root ? (strlen(basen) + 2) : (strlen(dstpath) + 1 + strlen(basen) + 1);
922 char *newdst = (char *) malloc(len);
923 if (dst_is_root) {
924 snprintf(newdst, len, "/%s", basen);
925 } else {
926 snprintf(newdst, len, "%s/%s", dstpath, basen);
927 }
928 get_file(afc, srcpath, newdst, force_overwrite, recursive_get);
929 free(srcpath);
930 free(newdst);
931 free(dstpath);
932 } else {
933 // target is not a dir or does not exist, just try to create or rewrite it
934 get_file(afc, srcpath, dstpath, force_overwrite, recursive_get);
935 free(srcpath);
936 free(dstpath);
937 }
938}
939
940static uint8_t put_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite)
941{
942 plist_t info = NULL;
943 afc_error_t ret = afc_get_file_info_plist(afc, dstpath, &info);
944 // file exists, only overwrite with '-f' option was set
945 if (ret == AFC_E_SUCCESS && info) {
946 plist_free(info);
947 if (!force_overwrite) {
948 printf("Error: Failed to write into existing file without '-f' option: %s\n", dstpath);
949 return 0;
950 }
951 }
952 FILE *f = fopen(srcpath, "rb");
953 if (!f) {
954 printf("Error: Failed to open local file '%s': %s\n", srcpath, strerror(errno));
955 return 0;
956 }
957 struct timeval t1;
958 struct timeval t2;
959 struct timeval tdiff;
960 struct stat fst;
961 int progress = 0;
962 size_t bufsize = 0x100000;
963 char *buf = malloc(bufsize);
964
965 fstat(fileno(f), &fst);
966 if (fst.st_size >= 0x400000) {
967 progress = 1;
968 gettimeofday(&t1, NULL);
969 }
970 size_t total = 0;
971 int lastprog = 0;
972 uint64_t fh = 0;
973 afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
974 uint8_t succeed = 1;
975 while (err == AFC_E_SUCCESS) {
976 uint32_t bytes_read = fread(buf, 1, bufsize, f);
977 if (bytes_read == 0) {
978 if (!feof(f)) {
979 if (progress) {
980 printf("\n");
981 }
982 printf("Error: Failed to read from local file\n");
983 succeed = 0;
984 }
985 break;
986 }
987 uint32_t chunk = 0;
988 while (chunk < bytes_read) {
989 uint32_t bytes_written = 0;
990 err = afc_file_write(afc, fh, buf + chunk, bytes_read - chunk, &bytes_written);
991 if (err != AFC_E_SUCCESS) {
992 if (progress) {
993 printf("\n");
994 }
995 printf("Error: Failed to write to device file\n");
996 succeed = 0;
997 break;
998 }
999 chunk += bytes_written;
1000 }
1001 total += chunk;
1002 if (progress) {
1003 int prog = (int) ((double) total / (double) fst.st_size * 100.0f);
1004 if (prog > lastprog) {
1005 gettimeofday(&t2, NULL);
1006 timeval_subtract(&tdiff, &t2, &t1);
1007 double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
1008 printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
1009 fflush(stdout);
1010 lastprog = prog;
1011 }
1012 }
1013 }
1014 if (progress) {
1015 printf("\n");
1016 }
1017 free(buf);
1018 afc_file_close(afc, fh);
1019 fclose(f);
1020 return succeed;
1021}
1022
1023static uint8_t put_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_put)
1024{
1025 if (is_directory(srcpath)) {
1026 if (!recursive_put) {
1027 printf("Error: Failed to put directory without '-r' option: %s\n", srcpath);
1028 return 0;
1029 }
1030 plist_t info = NULL;
1031 afc_error_t err = afc_get_file_info_plist(afc, dstpath, &info);
1032 //create if target directory does not exist
1033 plist_free(info);
1034 info = NULL;
1035 if (err == AFC_E_OBJECT_NOT_FOUND) {
1036 err = afc_make_directory(afc, dstpath);
1037 if (err != AFC_E_SUCCESS) {
1038 printf("Error: Failed to create directory '%s': %s (%d)\n", dstpath, afc_strerror(err), err);
1039 return 0;
1040 }
1041 } else if (!force_overwrite) {
1042 printf("Error: Failed to put existing directory without '-f' option: %s\n", dstpath);
1043 return 0;
1044 }
1045 afc_get_file_info_plist(afc, dstpath, &info);
1046 uint8_t is_dir = 0;
1047 if (info) {
1048 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1049 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1050 plist_free(info);
1051 }
1052 if (!is_dir) {
1053 printf("Error: Failed to create or access directory: '%s'\n", dstpath);
1054 return 0;
1055 }
1056
1057 // walk dir recursively to put files
1058 DIR *cur_dir = opendir(srcpath);
1059 if (cur_dir) {
1060 struct dirent *ep;
1061 while ((ep = readdir(cur_dir))) {
1062 if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
1063 continue;
1064 }
1065 char *fpath = string_build_path(srcpath, ep->d_name, NULL);
1066 if (fpath) {
1067 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
1068 size_t len = dst_is_root ? (strlen(ep->d_name) + 2) : (strlen(dstpath) + 1 + strlen(ep->d_name) + 1);
1069 char *newdst = (char *) malloc(len);
1070 if (dst_is_root) {
1071 snprintf(newdst, len, "/%s", ep->d_name);
1072 } else {
1073 snprintf(newdst, len, "%s/%s", dstpath, ep->d_name);
1074 }
1075 if (!put_file(afc, fpath, newdst, force_overwrite, recursive_put)) {
1076 free(newdst);
1077 free(fpath);
1078 return 0;
1079 }
1080 free(newdst);
1081 free(fpath);
1082 }
1083 }
1084 closedir(cur_dir);
1085 } else {
1086 printf("Error: Failed to visit directory: '%s': %s\n", srcpath, strerror(errno));
1087 return 0;
1088 }
1089 } else {
1090 return put_single_file(afc, srcpath, dstpath, force_overwrite);
1091 }
1092 return 1;
1093}
1094
1095static void handle_put(afc_client_t afc, int argc, char **argv)
1096{
1097 if (argc < 1) {
1098 printf("Error: Invalid number of arguments\n");
1099 return;
1100 }
1101 int i = 0;
1102 uint8_t force_overwrite = 0, recursive_put = 0;
1103 for ( ; i < argc; i++) {
1104 if (!strcmp(argv[i], "--")) {
1105 i++;
1106 break;
1107 } else if (!strcmp(argv[i], "-r")) {
1108 recursive_put = 1;
1109 } else if (!strcmp(argv[i], "-f")) {
1110 force_overwrite = 1;
1111 } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
1112 recursive_put = 1;
1113 force_overwrite = 1;
1114 } else {
1115 break;
1116 }
1117 }
1118 if (i >= argc) {
1119 printf("Error: Invalid number of arguments\n");
1120 return;
1121 }
1122 char *srcpath = strdup(argv[i]);
1123 size_t src_len = strlen(srcpath);
1124 if (src_len > 1 && srcpath[src_len - 1] == '/') {
1125 srcpath[src_len - 1] = '\0';
1126 }
1127 char *dstpath = NULL;
1128 if (argc - i == 1) {
1129 dstpath = get_absolute_path(path_get_basename(srcpath));
1130 } else if (argc - i == 2) {
1131 char *tmp = strdup(argv[i + 1]);
1132 size_t dst_len = strlen(tmp);
1133 if (dst_len > 1 && tmp[dst_len - 1] == '/') {
1134 tmp[dst_len - 1] = '\0';
1135 }
1136 dstpath = get_absolute_path(tmp);
1137 free(tmp);
1138 } else {
1139 printf("Error: Invalid number of arguments\n");
1140 return;
1141 }
1142 plist_t info = NULL;
1143 afc_error_t err = afc_get_file_info_plist(afc, dstpath, &info);
1144 // target does not exist, put directly
1145 if (err == AFC_E_OBJECT_NOT_FOUND) {
1146 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1147 free(srcpath);
1148 free(dstpath);
1149 } else {
1150 uint8_t is_dir = 0;
1151 if (info) {
1152 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1153 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1154 plist_free(info);
1155 }
1156 // target is a directory, try to put under this directory
1157 if (is_dir) {
1158 const char *basen = path_get_basename(srcpath);
1159 uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
1160 size_t len = dst_is_root ? (strlen(basen) + 2) : (strlen(dstpath) + 1 + strlen(basen) + 1);
1161 char *newdst = (char *) malloc(len);
1162 if (dst_is_root) {
1163 snprintf(newdst, len, "/%s", basen);
1164 } else {
1165 snprintf(newdst, len, "%s/%s", dstpath, basen);
1166 }
1167 free(dstpath);
1168 dstpath = get_absolute_path(newdst);
1169 free(newdst);
1170 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1171 } else {
1172 //target is common file, rewrite it
1173 put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
1174 }
1175 free(srcpath);
1176 free(dstpath);
1177 }
1178}
1179
1180static void handle_pwd(afc_client_t afc, int argc, char** argv)
1181{
1182 printf("%s\n", curdir);
1183}
1184
1185static void handle_cd(afc_client_t afc, int argc, char** argv)
1186{
1187 if (argc != 1) {
1188 printf("Error: Invalid number of arguments\n");
1189 return;
1190 }
1191
1192 if (!strcmp(argv[0], ".")) {
1193 return;
1194 }
1195
1196 if (!strcmp(argv[0], "..")) {
1197 if (!strcmp(curdir, "/")) {
1198 return;
1199 }
1200 char *p = strrchr(curdir, '/');
1201 if (!p) {
1202 strcpy(curdir, "/");
1203 return;
1204 }
1205 if (p == curdir) {
1206 *(p+1) = '\0';
1207 curdir_len = 1;
1208 } else {
1209 *p = '\0';
1210 curdir_len = strlen(curdir);
1211 }
1212 return;
1213 }
1214
1215 char* path = get_realpath(argv[0]);
1216 int is_dir = 0;
1217 plist_t info = NULL;
1218 afc_error_t err = afc_get_file_info_plist(afc, path, &info);
1219 if (err == AFC_E_SUCCESS && info) {
1220 const char* ifmt = plist_get_string_ptr(plist_dict_get_item(info, "st_ifmt"), NULL);
1221 is_dir = (ifmt && !strcmp(ifmt, "S_IFDIR"));
1222 plist_free(info);
1223 } else {
1224 printf("Error: Failed to get file info for %s: %s (%d)\n", path, afc_strerror(err), err);
1225 free(path);
1226 return;
1227 }
1228
1229 if (!is_dir) {
1230 printf("Error: '%s' is not a valid directory\n", path);
1231 free(path);
1232 return;
1233 }
1234 free(curdir);
1235 curdir = path;
1236 curdir_len = strlen(curdir);
1237}
1238
1239static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline)
1240{
1241 char **argv = NULL;
1242 int argc = 0;
1243 size_t maxlen = strlen(cmdline);
1244 const char* pos = cmdline;
1245 const char* qpos = NULL;
1246 char *tmpbuf = NULL;
1247 int tmplen = 0;
1248 int is_error = 0;
1249
1250 /* skip initial whitespace */
1251 while (isspace(*pos)) pos++;
1252 maxlen -= (pos - cmdline);
1253
1254 tmpbuf = (char*)malloc(maxlen+1);
1255
1256 while (!is_error) {
1257 if (*pos == '\\') {
1258 pos++;
1259 switch (*pos) {
1260 case '"':
1261 case '\'':
1262 case '\\':
1263 case ' ':
1264 tmpbuf[tmplen++] = *pos;
1265 pos++;
1266 break;
1267 default:
1268 printf("Error: Invalid escape sequence\n");
1269 is_error++;
1270 break;
1271 }
1272 } else if (*pos == '"' || *pos == '\'') {
1273 if (!qpos) {
1274 qpos = pos;
1275 } else {
1276 qpos = NULL;
1277 }
1278 pos++;
1279 } else if (*pos == '\0' || (!qpos && isspace(*pos))) {
1280 tmpbuf[tmplen] = '\0';
1281 if (*pos == '\0' && qpos) {
1282 printf("Error: Unmatched `%c`\n", *qpos);
1283 is_error++;
1284 break;
1285 }
1286 char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*));
1287 if (new_argv == NULL) {
1288 printf("Error: Out of memory?!\n");
1289 is_error++;
1290 break;
1291 }
1292 argv = new_argv;
1293 /* shrink buffer to actual argument size */
1294 argv[argc] = (char*)realloc(tmpbuf, tmplen+1);
1295 if (!argv[argc]) {
1296 printf("Error: Out of memory?!\n");
1297 is_error++;
1298 break;
1299 }
1300 argc++;
1301 tmpbuf = NULL;
1302 if (*pos == '\0') {
1303 break;
1304 }
1305 maxlen -= tmplen;
1306 tmpbuf = (char*)malloc(maxlen+1);
1307 tmplen = 0;
1308 while (isspace(*pos)) pos++;
1309 } else {
1310 tmpbuf[tmplen++] = *pos;
1311 pos++;
1312 }
1313 }
1314 if (tmpbuf) {
1315 free(tmpbuf);
1316 }
1317 if (is_error) {
1318 int i;
1319 for (i = 0; argv && i < argc; i++) free(argv[i]);
1320 free(argv);
1321 return;
1322 }
1323
1324 *p_argv = argv;
1325 *p_argc = argc;
1326}
1327
1328static int process_args(afc_client_t afc, int argc, char** argv)
1329{
1330 if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
1331 return -1;
1332 }
1333 else if (!strcmp(argv[0], "help")) {
1334 handle_help(afc, argc, argv);
1335 }
1336 else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) {
1337 handle_devinfo(afc, argc-1, argv+1);
1338 }
1339 else if (!strcmp(argv[0], "info")) {
1340 handle_file_info(afc, argc-1, argv+1);
1341 }
1342 else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) {
1343 handle_list(afc, argc-1, argv+1);
1344 }
1345 else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) {
1346 handle_rename(afc, argc-1, argv+1);
1347 }
1348 else if (!strcmp(argv[0], "mkdir")) {
1349 handle_mkdir(afc, argc-1, argv+1);
1350 }
1351 else if (!strcmp(argv[0], "ln")) {
1352 handle_link(afc, argc-1, argv+1);
1353 }
1354 else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) {
1355 handle_remove(afc, argc-1, argv+1);
1356 }
1357 else if (!strcmp(argv[0], "get")) {
1358 handle_get(afc, argc-1, argv+1);
1359 }
1360 else if (!strcmp(argv[0], "put")) {
1361 handle_put(afc, argc-1, argv+1);
1362 }
1363 else if (!strcmp(argv[0], "pwd")) {
1364 handle_pwd(afc, argc-1, argv+1);
1365 }
1366 else if (!strcmp(argv[0], "cd")) {
1367 handle_cd(afc, argc-1, argv+1);
1368 }
1369 else {
1370 printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]);
1371 }
1372 return 0;
1373}
1374
1375static void start_cmdline(afc_client_t afc)
1376{
1377 while (!stop_requested) {
1378 int argc = 0;
1379 char **argv = NULL;
1380 char prompt[128];
1381 int plen = curdir_len;
1382 char *ppath = curdir;
1383 int plim = (int)(sizeof(prompt)/2)-8;
1384 if (plen > plim) {
1385 ppath = curdir + (plen - plim);
1386 plen = plim;
1387 }
1388 snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath);
1389#ifdef HAVE_READLINE
1390 char* cmd = readline(prompt);
1391 if (!cmd || !*cmd) {
1392 free(cmd);
1393 continue;
1394 }
1395 add_history(cmd);
1396 parse_cmdline(&argc, &argv, cmd);
1397#else
1398 char cmdbuf[4096];
1399 printf("%s", prompt);
1400 fflush(stdout);
1401 get_input(cmdbuf, sizeof(cmdbuf));
1402 parse_cmdline(&argc, &argv, cmdbuf);
1403#endif
1404#ifdef HAVE_READLINE
1405 free(cmd);
1406#endif
1407 /* process arguments */
1408 if (argv && argv[0]) {
1409 if (process_args(afc, argc, argv) < 0) {
1410 break;
1411 }
1412 }
1413 }
1414}
1415
1416static void device_event_cb(const idevice_event_t* event, void* userdata)
1417{
1418 if (use_network && event->conn_type != CONNECTION_NETWORK) {
1419 return;
1420 } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
1421 return;
1422 }
1423 if (event->event == IDEVICE_DEVICE_ADD) {
1424 if (!udid) {
1425 udid = strdup(event->udid);
1426 }
1427 if (strcmp(udid, event->udid) == 0) {
1428 connected = 1;
1429 }
1430 } else if (event->event == IDEVICE_DEVICE_REMOVE) {
1431 if (strcmp(udid, event->udid) == 0) {
1432 connected = 0;
1433 printf("\n[disconnected]\n");
1434 handle_signal(SIGINT);
1435 }
1436 }
1437}
1438
1439int main(int argc, char** argv)
1440{
1441 const char* appid = NULL;
1442 int ret = 0;
1443 idevice_t device = NULL;
1444 lockdownd_client_t lockdown = NULL;
1445 lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
1446 lockdownd_service_descriptor_t service = NULL;
1447 afc_client_t afc = NULL;
1448 house_arrest_client_t house_arrest = NULL;
1449 const char* service_name = AFC_SERVICE_NAME;
1450 int use_container = 0;
1451
1452 int c = 0;
1453 const struct option longopts[] = {
1454 { "udid", required_argument, NULL, 'u' },
1455 { "network", no_argument, NULL, 'n' },
1456 { "help", no_argument, NULL, 'h' },
1457 { "debug", no_argument, NULL, 'd' },
1458 { "version", no_argument, NULL, 'v' },
1459 { "documents", required_argument, NULL, OPT_DOCUMENTS },
1460 { "container", required_argument, NULL, OPT_CONTAINER },
1461 { NULL, 0, NULL, 0}
1462 };
1463
1464 signal(SIGTERM, handle_signal);
1465#ifndef _WIN32
1466 signal(SIGQUIT, handle_signal);
1467 signal(SIGPIPE, SIG_IGN);
1468#endif
1469
1470 while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) {
1471 switch (c) {
1472 case 'd':
1473 idevice_set_debug_level(1);
1474 break;
1475 case 'u':
1476 if (!*optarg) {
1477 fprintf(stderr, "ERROR: UDID must not be empty!\n");
1478 print_usage(argc, argv, 1);
1479 return 2;
1480 }
1481 udid = strdup(optarg);
1482 break;
1483 case 'n':
1484 use_network = 1;
1485 break;
1486 case 'h':
1487 print_usage(argc, argv, 0);
1488 return 0;
1489 case 'v':
1490 printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
1491#ifdef HAVE_READLINE
1492 printf(" (readline)");
1493#endif
1494 printf("\n");
1495 return 0;
1496 case OPT_DOCUMENTS:
1497 if (!*optarg) {
1498 fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n");
1499 print_usage(argc, argv, 1);
1500 return 2;
1501 }
1502 appid = optarg;
1503 use_container = 0;
1504 break;
1505 case OPT_CONTAINER:
1506 if (!*optarg) {
1507 fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n");
1508 print_usage(argc, argv, 1);
1509 return 2;
1510 }
1511 appid = optarg;
1512 use_container = 1;
1513 break;
1514 default:
1515 print_usage(argc, argv, 1);
1516 return 2;
1517 }
1518 }
1519
1520 argc -= optind;
1521 argv += optind;
1522
1523 int num = 0;
1524 idevice_info_t *devices = NULL;
1525 idevice_get_device_list_extended(&devices, &num);
1526 int count = 0;
1527 for (int i = 0; i < num; i++) {
1528 if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) {
1529 count++;
1530 } else if (devices[i]->conn_type == CONNECTION_USBMUXD) {
1531 count++;
1532 }
1533 }
1534 idevice_device_list_extended_free(devices);
1535 if (count == 0) {
1536 fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
1537 return 1;
1538 }
1539
1540 idevice_events_subscribe(&context, device_event_cb, NULL);
1541
1542 while (!connected && !stop_requested) {
1543#ifdef _WIN32
1544 Sleep(100);
1545#else
1546 usleep(100000);
1547#endif
1548 }
1549 if (stop_requested) {
1550 return 0;
1551 }
1552
1553 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
1554 if (ret != IDEVICE_E_SUCCESS) {
1555 if (udid) {
1556 fprintf(stderr, "ERROR: Device %s not found!\n", udid);
1557 } else {
1558 fprintf(stderr, "ERROR: No device found!\n");
1559 }
1560 return 1;
1561 }
1562
1563 do {
1564 if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
1565 fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
1566 ret = 1;
1567 break;
1568 }
1569
1570 if (appid) {
1571 service_name = HOUSE_ARREST_SERVICE_NAME;
1572 }
1573
1574 ldret = lockdownd_start_service(lockdown, service_name, &service);
1575 if (ldret != LOCKDOWN_E_SUCCESS) {
1576 fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret);
1577 ret = 1;
1578 break;
1579 }
1580
1581 if (appid) {
1582 house_arrest_client_new(device, service, &house_arrest);
1583 if (!house_arrest) {
1584 fprintf(stderr, "Could not start document sharing service!\n");
1585 ret = 1;
1586 break;
1587 }
1588
1589 if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) {
1590 fprintf(stderr, "Could not send house_arrest command!\n");
1591 ret = 1;
1592 break;
1593 }
1594
1595 plist_t dict = NULL;
1596 if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) {
1597 fprintf(stderr, "Could not get result from document sharing service!\n");
1598 break;
1599 }
1600 plist_t node = plist_dict_get_item(dict, "Error");
1601 if (node) {
1602 char *str = NULL;
1603 plist_get_string_val(node, &str);
1604 fprintf(stderr, "ERROR: %s\n", str);
1605 if (str && !strcmp(str, "InstallationLookupFailed")) {
1606 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);
1607 }
1608 free(str);
1609 plist_free(dict);
1610 break;
1611 }
1612 plist_free(dict);
1613 afc_client_new_from_house_arrest_client(house_arrest, &afc);
1614 } else {
1615 afc_client_new(device, service, &afc);
1616 }
1617 lockdownd_service_descriptor_free(service);
1618 lockdownd_client_free(lockdown);
1619 lockdown = NULL;
1620
1621 if (appid && !use_container) {
1622 curdir = strdup("/Documents");
1623 curdir_len = 10;
1624 } else {
1625 curdir = strdup("/");
1626 curdir_len = 1;
1627 }
1628
1629 if (argc > 0) {
1630 // command line mode
1631 process_args(afc, argc, argv);
1632 } else {
1633 // interactive mode
1634 start_cmdline(afc);
1635 }
1636
1637 } while (0);
1638
1639 if (afc) {
1640 afc_client_free(afc);
1641 }
1642 if (lockdown) {
1643 lockdownd_client_free(lockdown);
1644 }
1645 idevice_free(device);
1646
1647 return ret;
1648}