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