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