summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2023-12-30 01:27:42 +0100
committerGravatar Nikias Bassen2023-12-30 01:27:42 +0100
commit7f781eb61215c84bd0f79838fbcb5efd66121677 (patch)
tree2741401eec23b6504e068d8640f2ac9237e3795e /tools
parent76f924c2a1c61d05ece9bdc699a350ad165e3761 (diff)
downloadlibimobiledevice-7f781eb61215c84bd0f79838fbcb5efd66121677.tar.gz
libimobiledevice-7f781eb61215c84bd0f79838fbcb5efd66121677.tar.bz2
tools: Add afcclient utility
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am12
-rw-r--r--tools/afcclient.c1294
2 files changed, 1305 insertions, 1 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
index bd93631..4cac1fc 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -31,7 +31,8 @@ bin_PROGRAMS = \
31 idevicedevmodectl \ 31 idevicedevmodectl \
32 idevicenotificationproxy \ 32 idevicenotificationproxy \
33 idevicecrashreport \ 33 idevicecrashreport \
34 idevicesetlocation 34 idevicesetlocation \
35 afcclient
35 36
36idevicebtlogger_SOURCES = idevicebtlogger.c 37idevicebtlogger_SOURCES = idevicebtlogger.c
37iidevicebtlogger_CFLAGS = $(AM_CFLAGS) 38iidevicebtlogger_CFLAGS = $(AM_CFLAGS)
@@ -132,3 +133,12 @@ idevicesetlocation_SOURCES = idevicesetlocation.c
132idevicesetlocation_CFLAGS = $(AM_CFLAGS) 133idevicesetlocation_CFLAGS = $(AM_CFLAGS)
133idevicesetlocation_LDFLAGS = $(AM_LDFLAGS) 134idevicesetlocation_LDFLAGS = $(AM_LDFLAGS)
134idevicesetlocation_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la 135idevicesetlocation_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
136
137afcclient_SOURCES = afcclient.c
138afcclient_CFLAGS = $(AM_CFLAGS)
139afcclient_LDFLAGS = $(AM_LDFLAGS)
140if HAVE_READLINE
141 afcclient_CFLAGS += $(readline_CFLAGS)
142 afcclient_LDFLAGS += $(readline_LIBS)
143endif
144afcclient_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(limd_glue_LIBS)
diff --git a/tools/afcclient.c b/tools/afcclient.c
new file mode 100644
index 0000000..81dc5c5
--- /dev/null
+++ b/tools/afcclient.c
@@ -0,0 +1,1294 @@
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
40#ifdef WIN32
41#include <windows.h>
42#include <sys/time.h>
43#include <conio.h>
44#define sleep(x) Sleep(x*1000)
45#define S_IFMT 0170000 /* [XSI] type of file mask */
46#define S_IFIFO 0010000 /* [XSI] named pipe (fifo) */
47#define S_IFCHR 0020000 /* [XSI] character special */
48#define S_IFBLK 0060000 /* [XSI] block special */
49#define S_IFLNK 0120000 /* [XSI] symbolic link */
50#define S_IFSOCK 0140000 /* [XSI] socket */
51#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */
52#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */
53#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */
54#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */
55#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
56#else
57#include <sys/time.h>
58#include <termios.h>
59#endif
60
61#ifdef HAVE_READLINE
62#include <readline/readline.h>
63#include <readline/history.h>
64#endif
65
66#include <libimobiledevice/libimobiledevice.h>
67#include <libimobiledevice/lockdown.h>
68#include <libimobiledevice/house_arrest.h>
69#include <libimobiledevice/afc.h>
70#include <plist/plist.h>
71
72#include <libimobiledevice-glue/termcolors.h>
73
74#undef st_mtime
75#undef st_birthtime
76struct afc_file_stat {
77 uint16_t st_mode;
78 uint16_t st_nlink;
79 uint64_t st_size;
80 uint64_t st_mtime;
81 uint64_t st_birthtime;
82 uint32_t st_blocks;
83};
84
85static char* udid = NULL;
86static int connected = 0;
87static int use_network = 0;
88static idevice_subscription_context_t context = NULL;
89static char* curdir = NULL;
90static size_t curdir_len = 0;
91
92static void print_usage(int argc, char **argv, int is_error)
93{
94 char *name = strrchr(argv[0], '/');
95 fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
96 fprintf(is_error ? stderr : stdout,
97 "\n"
98 "Interact with AFC/HouseArrest service on a connected device.\n"
99 "\n"
100 "OPTIONS:\n"
101 " -u, --udid UDID target specific device by UDID\n"
102 " -n, --network connect to network device (not recommended!)\n"
103 " --container <appid> Access container of given app\n"
104 " --documents <appid> Access Documents directory of given app\n"
105 " -h, --help prints usage information\n" \
106 " -d, --debug enable communication debugging\n" \
107 " -v, --version prints version information\n" \
108 "\n"
109 );
110 fprintf(is_error ? stderr : stdout,
111 "\n" \
112 "Homepage: <" PACKAGE_URL ">\n"
113 "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
114 );
115}
116
117#define OPT_DOCUMENTS 1
118#define OPT_CONTAINER 2
119
120int stop_requested = 0;
121
122static void handle_signal(int sig)
123{
124 stop_requested++;
125#ifdef WIN32
126 GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
127#else
128 kill(getpid(), SIGINT);
129#endif
130}
131
132static void handle_help(afc_client_t afc, int argc, char** argv)
133{
134 printf("Available commands:\n");
135 printf("help - print list of available commands\n");
136 printf("devinfo - print device information\n");
137 printf("info PATH - print file attributes of file at PATH\n");
138 printf("ls [-l] PATH - print directory contents of PATH\n");
139 printf("mv OLD NEW - rename file OLD to NEW\n");
140 printf("mkdir PATH - create directory at PATH\n");
141 printf("ln [-s] FILE [LINK] - create a (symbolic) link to file named LINKNAME\n");
142 printf(" NOTE: This feature has been disabled in newer versions of iOS.\n");
143 printf("rm PATH - remove item at PATH\n");
144 printf("get PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n");
145 printf("put LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n");
146 printf("\n");
147}
148
149static const char* path_get_basename(const char* path)
150{
151 const char *p = strrchr(path, '/');
152 return p ? p + 1 : path;
153}
154
155static int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
156{
157 /* Perform the carry for the later subtraction by updating y. */
158 if (x->tv_usec < y->tv_usec) {
159 int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
160 y->tv_usec -= 1000000 * nsec;
161 y->tv_sec += nsec;
162 }
163 if (x->tv_usec - y->tv_usec > 1000000) {
164 int nsec = (x->tv_usec - y->tv_usec) / 1000000;
165 y->tv_usec += 1000000 * nsec;
166 y->tv_sec -= nsec;
167 }
168 /* Compute the time remaining to wait.
169 tv_usec is certainly positive. */
170 result->tv_sec = x->tv_sec - y->tv_sec;
171 result->tv_usec = x->tv_usec - y->tv_usec;
172 /* Return 1 if result is negative. */
173 return x->tv_sec < y->tv_sec;
174}
175
176struct str_item {
177 size_t len;
178 char* str;
179};
180
181static char* get_absolute_path(const char *path)
182{
183 if (*path == '/') {
184 return strdup(path);
185 } else {
186 size_t len = curdir_len + 1 + strlen(path) + 1;
187 char* result = (char*)malloc(len);
188 if (!strcmp(curdir, "/")) {
189 snprintf(result, len, "/%s", path);
190 } else {
191 snprintf(result, len, "%s/%s", curdir, path);
192 }
193 return result;
194 }
195}
196
197static char* get_realpath(const char* path)
198{
199 if (!path) return NULL;
200
201 int is_absolute = 0;
202 if (*path == '/') {
203 is_absolute = 1;
204 }
205
206 const char* p = path;
207 if (is_absolute) {
208 while (*p == '/') p++;
209 }
210 if (*p == '\0') {
211 return strdup("/");
212 }
213
214 int c_count = 1;
215 const char* start = p;
216 const char* end = p;
217 struct str_item* comps = NULL;
218
219 while (*p) {
220 if (*p == '/') {
221 p++;
222 end = p-1;
223 while (*p == '/') p++;
224 if (*p == '\0') break;
225 struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
226 if (!newcomps) {
227 free(comps);
228 printf("%s: out of memory?!\n", __func__);
229 return NULL;
230 }
231 comps = newcomps;
232 char *comp = (char*)malloc(end-start+1);
233 strncpy(comp, start, end-start);
234 comp[end-start] = '\0';
235 comps[c_count-1].len = end-start;
236 comps[c_count-1].str = comp;
237 c_count++;
238 start = p;
239 end = p;
240 }
241 p++;
242 }
243 if (p > start) {
244 if (start == end) {
245 end = p;
246 }
247 struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
248 if (!newcomps) {
249 free(comps);
250 printf("%s: out of memory?!\n", __func__);
251 return NULL;
252 }
253 comps = newcomps;
254 char *comp = (char*)malloc(end-start+1);
255 strncpy(comp, start, end-start);
256 comp[end-start] = '\0';
257 comps[c_count-1].len = end-start;
258 comps[c_count-1].str = comp;
259 }
260
261 struct str_item* comps_final = (struct str_item*)malloc(sizeof(struct str_item)*(c_count+1));
262 int o = 1;
263 if (is_absolute) {
264 comps_final[0].len = 1;
265 comps_final[0].str = (char*)"/";
266 } else {
267 comps_final[0].len = curdir_len;
268 comps_final[0].str = curdir;
269 }
270 size_t o_len = comps_final[0].len;
271
272 for (int i = 0; i < c_count; i++) {
273 if (!strcmp(comps[i].str, "..")) {
274 o--;
275 continue;
276 } else if (!strcmp(comps[i].str, ".")) {
277 continue;
278 }
279 o_len += comps[i].len;
280 comps_final[o].str = comps[i].str;
281 comps_final[o].len = comps[i].len;
282 o++;
283 }
284
285 o_len += o;
286 char* result = (char*)malloc(o_len);
287 char* presult = result;
288 for (int i = 0; i < o; i++) {
289 if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) {
290 *presult = '/';
291 presult++;
292 }
293 strncpy(presult, comps_final[i].str, comps_final[i].len);
294 presult+=comps_final[i].len;
295 *presult = '\0';
296 }
297 if (presult == result) {
298 *presult = '/';
299 presult++;
300 *presult = 0;
301 }
302
303 for (int i = 0; i < c_count; i++) {
304 free(comps[i].str);
305 }
306 free(comps);
307 free(comps_final);
308
309 return result;
310}
311
312static void handle_devinfo(afc_client_t afc, int argc, char** argv)
313{
314 char **info = NULL;
315 afc_error_t err = afc_get_device_info(afc, &info);
316 if (err == AFC_E_SUCCESS && info) {
317 int i;
318 for (i = 0; info[i]; i += 2) {
319 printf("%s: %s\n", info[i], info[i+1]);
320 }
321 } else {
322 printf("Error: Failed to get device info: %d\n", err);
323 }
324 afc_dictionary_free(info);
325}
326
327static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf)
328{
329 char **info = NULL;
330 afc_error_t ret = afc_get_file_info(afc, path, &info);
331 memset(stbuf, 0, sizeof(struct afc_file_stat));
332 if (ret != AFC_E_SUCCESS) {
333 return -1;
334 } else if (!info) {
335 return -1;
336 } else {
337 // get file attributes from info list
338 int i;
339 for (i = 0; info[i]; i += 2) {
340 if (!strcmp(info[i], "st_size")) {
341 stbuf->st_size = atoll(info[i+1]);
342 } else if (!strcmp(info[i], "st_blocks")) {
343 stbuf->st_blocks = atoi(info[i+1]);
344 } else if (!strcmp(info[i], "st_ifmt")) {
345 if (!strcmp(info[i+1], "S_IFREG")) {
346 stbuf->st_mode = S_IFREG;
347 } else if (!strcmp(info[i+1], "S_IFDIR")) {
348 stbuf->st_mode = S_IFDIR;
349 } else if (!strcmp(info[i+1], "S_IFLNK")) {
350 stbuf->st_mode = S_IFLNK;
351 } else if (!strcmp(info[i+1], "S_IFBLK")) {
352 stbuf->st_mode = S_IFBLK;
353 } else if (!strcmp(info[i+1], "S_IFCHR")) {
354 stbuf->st_mode = S_IFCHR;
355 } else if (!strcmp(info[i+1], "S_IFIFO")) {
356 stbuf->st_mode = S_IFIFO;
357 } else if (!strcmp(info[i+1], "S_IFSOCK")) {
358 stbuf->st_mode = S_IFSOCK;
359 }
360 } else if (!strcmp(info[i], "st_nlink")) {
361 stbuf->st_nlink = atoi(info[i+1]);
362 } else if (!strcmp(info[i], "st_mtime")) {
363 stbuf->st_mtime = (time_t)(atoll(info[i+1]) / 1000000000);
364 } else if (!strcmp(info[i], "st_birthtime")) { /* available on iOS 7+ */
365 stbuf->st_birthtime = (time_t)(atoll(info[i+1]) / 1000000000);
366 }
367 }
368 afc_dictionary_free(info);
369 }
370 return 0;
371}
372
373static void handle_file_info(afc_client_t afc, int argc, char** argv)
374{
375 if (argc < 1) {
376 printf("Error: Missing PATH.\n");
377 return;
378 }
379
380 char **info = NULL;
381 char* abspath = get_absolute_path(argv[0]);
382 if (!abspath) {
383 printf("Error: Invalid argument\n");
384 return;
385 }
386 afc_error_t err = afc_get_file_info(afc, abspath, &info);
387 if (err == AFC_E_SUCCESS && info) {
388 int i;
389 for (i = 0; info[i]; i += 2) {
390 printf("%s: %s\n", info[i], info[i+1]);
391 }
392 } else {
393 printf("Error: Failed to get file info for %s: %d\n", argv[0], err);
394 }
395 afc_dictionary_free(info);
396 free(abspath);
397}
398
399static void print_file_info(afc_client_t afc, const char* path, int list_verbose)
400{
401 struct afc_file_stat st;
402 get_file_info_stat(afc, path, &st);
403 if (list_verbose) {
404 char timebuf[64];
405 time_t t = st.st_mtime;
406 if (S_ISDIR(st.st_mode)) {
407 printf("drwxr-xr-x");
408 } else if (S_ISLNK(st.st_mode)) {
409 printf("lrwxrwxrwx");
410 } else {
411 if (S_ISFIFO(st.st_mode)) {
412 printf("f");
413 } else if (S_ISBLK(st.st_mode)) {
414 printf("b");
415 } else if (S_ISCHR(st.st_mode)) {
416 printf("c");
417 } else if (S_ISSOCK(st.st_mode)) {
418 printf("s");
419 } else {
420 printf("-");
421 }
422 printf("rw-r--r--");
423 }
424 printf(" ");
425 printf("%4d", st.st_nlink);
426 printf(" ");
427 printf("mobile");
428 printf(" ");
429 printf("mobile");
430 printf(" ");
431 printf("%10lld", (long long)st.st_size);
432 printf(" ");
433#ifdef WIN32
434 strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t));
435#else
436 strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t));
437#endif
438 printf("%s", timebuf);
439 printf(" ");
440 }
441 if (S_ISDIR(st.st_mode)) {
442 cprintf(FG_CYAN);
443 } else if (S_ISLNK(st.st_mode)) {
444 cprintf(FG_MAGENTA);
445 } else if (S_ISREG(st.st_mode)) {
446 cprintf(FG_DEFAULT);
447 } else {
448 cprintf(FG_YELLOW);
449 }
450 cprintf("%s" COLOR_RESET "\n", path_get_basename(path));
451}
452
453static void handle_list(afc_client_t afc, int argc, char** argv)
454{
455 const char* path = NULL;
456 int list_verbose = 0;
457 if (argc < 1) {
458 path = curdir;
459 } else {
460 if (!strcmp(argv[0], "-l")) {
461 list_verbose = 1;
462 if (argc == 2) {
463 path = argv[1];
464 } else {
465 path = curdir;
466 }
467 } else {
468 path = argv[0];
469 }
470 }
471 char* abspath = get_absolute_path(path);
472 if (!abspath) {
473 printf("Error: Invalid argument\n");
474 return;
475 }
476 int abspath_is_root = strcmp(abspath, "/") == 0;
477 size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath);
478 char** entries = NULL;
479 afc_error_t err = afc_read_directory(afc, abspath, &entries);
480 if (err == AFC_E_READ_ERROR) {
481 print_file_info(afc, abspath, list_verbose);
482 return;
483 } else if (err != AFC_E_SUCCESS) {
484 printf("Error: Failed to list '%s': %d\n", path, err);
485 free(abspath);
486 return;
487 }
488
489 char** p = entries;
490 while (p && *p) {
491 if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
492 p++;
493 continue;
494 }
495 size_t len = abspath_len + 1 + strlen(*p) + 1;
496 char* testpath = (char*)malloc(len);
497 if (abspath_is_root) {
498 snprintf(testpath, len, "/%s", *p);
499 } else {
500 snprintf(testpath, len, "%s/%s", abspath, *p);
501 }
502 print_file_info(afc, testpath, list_verbose);
503 free(testpath);
504 p++;
505 }
506 afc_dictionary_free(entries);
507 free(abspath);
508}
509
510static void handle_rename(afc_client_t afc, int argc, char** argv)
511{
512 if (argc != 2) {
513 printf("Error: Invalid number of arguments\n");
514 return;
515 }
516 char* srcpath = get_absolute_path(argv[0]);
517 if (!srcpath) {
518 printf("Error: Invalid argument\n");
519 return;
520 }
521 char* dstpath = get_absolute_path(argv[1]);
522 if (!dstpath) {
523 free(srcpath);
524 printf("Error: Invalid argument\n");
525 return;
526 }
527 afc_error_t err = afc_rename_path(afc, srcpath, dstpath);
528 if (err != AFC_E_SUCCESS) {
529 printf("Error: Failed to rename '%s' -> '%s': %d\n", argv[0], argv[1], err);
530 }
531 free(srcpath);
532 free(dstpath);
533}
534
535static void handle_mkdir(afc_client_t afc, int argc, char** argv)
536{
537 for (int i = 0; i < argc; i++) {
538 char* abspath = get_absolute_path(argv[i]);
539 if (!abspath) {
540 printf("Error: Invalid argument '%s'\n", argv[i]);
541 continue;
542 }
543 afc_error_t err = afc_make_directory(afc, abspath);
544 if (err != AFC_E_SUCCESS) {
545 printf("Error: Failed to create directory '%s': %d\n", argv[i], err);
546 }
547 free(abspath);
548 }
549}
550
551static void handle_link(afc_client_t afc, int argc, char** argv)
552{
553 if (argc < 2) {
554 printf("Error: Invalid number of arguments\n");
555 return;
556 }
557 afc_link_type_t link_type = AFC_HARDLINK;
558 if (!strcmp(argv[0], "-s")) {
559 argc--;
560 argv++;
561 link_type = AFC_SYMLINK;
562 }
563 if (argc < 1 || argc > 2) {
564 printf("Error: Invalid number of arguments\n");
565 return;
566 }
567 const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1];
568 char* abs_link_name = get_absolute_path(link_name);
569 if (!abs_link_name) {
570 printf("Error: Invalid argument\n");
571 return;
572 }
573 afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name);
574 if (err != AFC_E_SUCCESS) {
575 printf("Error: Failed to create %s link for '%s' at '%s': %d\n", (link_type == AFC_HARDLINK) ? "hard" : "symbolic", argv[0], link_name, err);
576 }
577}
578
579static void handle_remove(afc_client_t afc, int argc, char** argv)
580{
581 for (int i = 0; i < argc; i++) {
582 char* abspath = get_absolute_path(argv[i]);
583 if (!abspath) {
584 printf("Error: Invalid argument '%s'\n", argv[i]);
585 continue;
586 }
587 afc_error_t err = afc_remove_path(afc, abspath);
588 if (err != AFC_E_SUCCESS) {
589 printf("Error: Failed to remove '%s': %d\n", argv[i], err);
590 }
591 free(abspath);
592 }
593}
594
595static void handle_get(afc_client_t afc, int argc, char** argv)
596{
597 if (argc < 1 || argc > 2) {
598 printf("Error: Invalid number of arguments\n");
599 return;
600 }
601 char *srcpath = NULL;
602 char* dstpath = NULL;
603 if (argc == 1) {
604 srcpath = get_absolute_path(argv[0]);
605 dstpath = strdup(path_get_basename(argv[0]));
606 } else {
607 srcpath = get_absolute_path(argv[0]);
608 dstpath = strdup(argv[1]);
609 }
610
611 char **info = NULL;
612 uint64_t file_size = 0;
613 afc_get_file_info(afc, srcpath, &info);
614 if (info) {
615 char **p = info;
616 while (p && *p) {
617 if (!strcmp(*p, "st_size")) {
618 p++;
619 file_size = (uint64_t)strtoull(*p, NULL, 10);
620 break;
621 }
622 p++;
623 }
624 }
625 uint64_t fh = 0;
626 afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh);
627 if (err != AFC_E_SUCCESS) {
628 free(srcpath);
629 free(dstpath);
630 printf("Error: Failed to open file '%s': %d\n", argv[0], err);
631 return;
632 }
633 FILE *f = fopen(dstpath, "wb");
634 if (!f && errno == EISDIR) {
635 const char* basen = path_get_basename(argv[0]);
636 size_t len = strlen(dstpath) + 1 + strlen(basen) + 1;
637 char* newdst = (char*)malloc(len);
638 snprintf(newdst, len, "%s/%s", dstpath, basen);
639 f = fopen(newdst, "wb");
640 free(newdst);
641 }
642 if (f) {
643 struct timeval t1;
644 struct timeval t2;
645 struct timeval tdiff;
646 size_t bufsize = 0x100000;
647 char* buf = malloc(bufsize);
648 size_t total = 0;
649 int progress = 0;
650 int lastprog = 0;
651 if (file_size > 0x400000) {
652 progress = 1;
653 gettimeofday(&t1, NULL);
654 }
655 while (err == AFC_E_SUCCESS) {
656 uint32_t bytes_read = 0;
657 size_t chunk = 0;
658 err = afc_file_read(afc, fh, buf, bufsize, &bytes_read);
659 if (bytes_read == 0) {
660 break;
661 }
662 while (chunk < bytes_read) {
663 size_t wr = fwrite(buf+chunk, 1, bytes_read-chunk, f);
664 if (wr == 0) {
665 if (progress) {
666 printf("\n");
667 }
668 printf("Error: Failed to write to local file\n");
669 break;
670 }
671 chunk += wr;
672 }
673 total += chunk;
674 if (progress) {
675 int prog = (int)((double)total / (double)file_size * 100.0f);
676 if (prog > lastprog) {
677 gettimeofday(&t2, NULL);
678 timeval_subtract(&tdiff, &t2, &t1);
679 double time_in_sec = (double)tdiff.tv_sec + (double)tdiff.tv_usec/1000000;
680 printf("\r%d%% (%0.1f MB/s) ", prog, (double)total/1048576.0f / time_in_sec);
681 fflush(stdout);
682 lastprog = prog;
683 }
684 }
685 }
686 if (progress) {
687 printf("\n");
688 }
689 if (err != AFC_E_SUCCESS) {
690 printf("Error: Failed to read from file '%s': %d\n", argv[0], err);
691 }
692 free(buf);
693 fclose(f);
694 } else {
695 printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno));
696 }
697 afc_file_close(afc, fh);
698 free(srcpath);
699}
700
701static void handle_put(afc_client_t afc, int argc, char** argv)
702{
703 if (argc < 1 || argc > 2) {
704 printf("Error: Invalid number of arguments\n");
705 return;
706 }
707
708 char *dstpath = NULL;
709 if (argc == 1) {
710 dstpath = get_absolute_path(path_get_basename(argv[0]));
711 } else {
712 dstpath = get_absolute_path(argv[1]);
713 }
714
715 uint64_t fh = 0;
716 FILE *f = fopen(argv[0], "rb");
717 if (f) {
718 afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
719 if (err == AFC_E_OBJECT_IS_DIR) {
720 const char* basen = path_get_basename(argv[0]);
721 size_t len = strlen(dstpath) + 1 + strlen(basen) + 1;
722 char* newdst = (char*)malloc(len);
723 snprintf(newdst, len, "%s/%s", dstpath, basen);
724 free(dstpath);
725 dstpath = get_absolute_path(newdst);
726 free(newdst);
727 err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
728 }
729 if (err != AFC_E_SUCCESS) {
730 printf("Error: Failed to open file '%s' on device: %d\n", argv[1], err);
731 } else {
732 struct timeval t1;
733 struct timeval t2;
734 struct timeval tdiff;
735 struct stat fst;
736 int progress = 0;
737 size_t bufsize = 0x100000;
738 char* buf = malloc(bufsize);
739
740 fstat(fileno(f), &fst);
741 if (fst.st_size >= 0x400000) {
742 progress = 1;
743 gettimeofday(&t1, NULL);
744 }
745 size_t total = 0;
746 int lastprog = 0;
747 while (err == AFC_E_SUCCESS) {
748 uint32_t bytes_read = fread(buf, 1, bufsize, f);
749 if (bytes_read == 0) {
750 if (!feof(f)) {
751 if (progress) {
752 printf("\n");
753 }
754 printf("Error: Failed to read from local file\n");
755 }
756 break;
757 }
758 uint32_t chunk = 0;
759 while (chunk < bytes_read) {
760 uint32_t bytes_written = 0;
761 err = afc_file_write(afc, fh, buf+chunk, bytes_read-chunk, &bytes_written);
762 if (err != AFC_E_SUCCESS) {
763 if (progress) {
764 printf("\n");
765 }
766 printf("Error: Failed to write to device file\n");
767 break;
768 }
769 chunk += bytes_written;
770 }
771 total += chunk;
772 if (progress) {
773 int prog = (int)((double)total / (double)fst.st_size * 100.0f);
774 if (prog > lastprog) {
775 gettimeofday(&t2, NULL);
776 timeval_subtract(&tdiff, &t2, &t1);
777 double time_in_sec = (double)tdiff.tv_sec + (double)tdiff.tv_usec/1000000;
778 printf("\r%d%% (%0.1f MB/s) ", prog, (double)total/1048576.0f / time_in_sec);
779 fflush(stdout);
780 lastprog = prog;
781 }
782 }
783 }
784 printf("\n");
785 free(buf);
786 afc_file_close(afc, fh);
787 }
788 fclose(f);
789 } else {
790 printf("Error: Failed to open local file '%s': %s\n", argv[0], strerror(errno));
791 }
792 free(dstpath);
793}
794
795static void handle_pwd(afc_client_t afc, int argc, char** argv)
796{
797 printf("%s\n", curdir);
798}
799
800static void handle_cd(afc_client_t afc, int argc, char** argv)
801{
802 if (argc != 1) {
803 printf("Error: Invalid number of arguments\n");
804 return;
805 }
806
807 if (!strcmp(argv[0], ".")) {
808 return;
809 }
810
811 if (!strcmp(argv[0], "..")) {
812 if (!strcmp(curdir, "/")) {
813 return;
814 }
815 char *p = strrchr(curdir, '/');
816 if (!p) {
817 strcpy(curdir, "/");
818 return;
819 }
820 if (p == curdir) {
821 *(p+1) = '\0';
822 } else {
823 *p = '\0';
824 }
825 return;
826 }
827
828 char* path = get_realpath(argv[0]);
829 int is_dir = 0;
830 char **info = NULL;
831 afc_error_t err = afc_get_file_info(afc, path, &info);
832 if (err == AFC_E_SUCCESS && info) {
833 int i;
834 for (i = 0; info[i]; i += 2) {
835 if (!strcmp(info[i], "st_ifmt")) {
836 if (!strcmp(info[i+1], "S_IFDIR")) {
837 is_dir = 1;
838 }
839 break;
840 }
841 }
842 afc_dictionary_free(info);
843 } else {
844 printf("Error: Failed to get file info for %s: %d\n", path, err);
845 free(path);
846 return;
847 }
848
849 if (!is_dir) {
850 printf("Error: '%s' is not a valid directory\n", path);
851 free(path);
852 return;
853 }
854
855 free(curdir);
856 curdir = path;
857 curdir_len = strlen(curdir);
858}
859
860#ifndef HAVE_READLINE
861#ifdef WIN32
862#define BS_CC '\b'
863#else
864#define BS_CC 0x7f
865#define getch getchar
866#endif
867static void get_input(char *buf, int maxlen)
868{
869 int len = 0;
870 int c;
871
872 while ((c = getch())) {
873 if ((c == '\r') || (c == '\n')) {
874 break;
875 }
876 if (isprint(c)) {
877 if (len < maxlen-1)
878 buf[len++] = c;
879 } else if (c == BS_CC) {
880 if (len > 0) {
881 fputs("\b \b", stdout);
882 len--;
883 }
884 }
885 }
886 buf[len] = 0;
887}
888#endif
889
890static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline)
891{
892 char **argv = NULL;
893 int argc = 0;
894 size_t maxlen = strlen(cmdline);
895 const char* pos = cmdline;
896 const char* qpos = NULL;
897 char *tmpbuf = NULL;
898 int tmplen = 0;
899 int is_error = 0;
900
901 /* skip initial whitespace */
902 while (isspace(*pos)) pos++;
903 maxlen -= (pos - cmdline);
904
905 tmpbuf = (char*)malloc(maxlen+1);
906
907 while (!is_error) {
908 if (*pos == '\\') {
909 pos++;
910 switch (*pos) {
911 case '"':
912 case '\'':
913 case '\\':
914 case ' ':
915 tmpbuf[tmplen++] = *pos;
916 pos++;
917 break;
918 default:
919 printf("Error: Invalid escape sequence\n");
920 is_error++;
921 break;
922 }
923 } else if (*pos == '"' || *pos == '\'') {
924 if (!qpos) {
925 qpos = pos;
926 } else {
927 qpos = NULL;
928 }
929 pos++;
930 } else if (*pos == '\0' || (!qpos && isspace(*pos))) {
931 tmpbuf[tmplen] = '\0';
932 if (*pos == '\0' && qpos) {
933 printf("Error: Unmatched `%c`\n", *qpos);
934 is_error++;
935 break;
936 }
937 char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*));
938 if (new_argv == NULL) {
939 printf("Error: Out of memory?!\n");
940 is_error++;
941 break;
942 }
943 argv = new_argv;
944 /* shrink buffer to actual argument size */
945 argv[argc] = (char*)realloc(tmpbuf, tmplen+1);
946 if (!argv[argc]) {
947 printf("Error: Out of memory?!\n");
948 is_error++;
949 break;
950 }
951 argc++;
952 tmpbuf = NULL;
953 if (*pos == '\0') {
954 break;
955 }
956 maxlen -= tmplen;
957 tmpbuf = (char*)malloc(maxlen+1);
958 tmplen = 0;
959 while (isspace(*pos)) pos++;
960 } else {
961 tmpbuf[tmplen++] = *pos;
962 pos++;
963 }
964 }
965 if (tmpbuf) {
966 free(tmpbuf);
967 }
968 if (is_error) {
969 int i;
970 for (i = 0; argv && i < argc; i++) free(argv[i]);
971 free(argv);
972 return;
973 }
974
975 *p_argv = argv;
976 *p_argc = argc;
977}
978
979static int process_args(afc_client_t afc, int argc, char** argv)
980{
981 if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
982 return -1;
983 }
984 else if (!strcmp(argv[0], "help")) {
985 handle_help(afc, argc, argv);
986 }
987 else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) {
988 handle_devinfo(afc, argc-1, argv+1);
989 }
990 else if (!strcmp(argv[0], "info")) {
991 handle_file_info(afc, argc-1, argv+1);
992 }
993 else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) {
994 handle_list(afc, argc-1, argv+1);
995 }
996 else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) {
997 handle_rename(afc, argc-1, argv+1);
998 }
999 else if (!strcmp(argv[0], "mkdir")) {
1000 handle_mkdir(afc, argc-1, argv+1);
1001 }
1002 else if (!strcmp(argv[0], "ln")) {
1003 handle_link(afc, argc-1, argv+1);
1004 }
1005 else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) {
1006 handle_remove(afc, argc-1, argv+1);
1007 }
1008 else if (!strcmp(argv[0], "get")) {
1009 handle_get(afc, argc-1, argv+1);
1010 }
1011 else if (!strcmp(argv[0], "put")) {
1012 handle_put(afc, argc-1, argv+1);
1013 }
1014 else if (!strcmp(argv[0], "pwd")) {
1015 handle_pwd(afc, argc-1, argv+1);
1016 }
1017 else if (!strcmp(argv[0], "cd")) {
1018 handle_cd(afc, argc-1, argv+1);
1019 }
1020 else {
1021 printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]);
1022 }
1023 return 0;
1024}
1025
1026static void start_cmdline(afc_client_t afc)
1027{
1028 while (!stop_requested) {
1029 int argc = 0;
1030 char **argv = NULL;
1031 char prompt[128];
1032 int plen = curdir_len;
1033 char *ppath = curdir;
1034 int plim = (int)(sizeof(prompt)/2)-8;
1035 if (plen > plim) {
1036 ppath = curdir + (plen - plim);
1037 plen = plim;
1038 }
1039 snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath);
1040#ifdef HAVE_READLINE
1041 char* cmd = readline(prompt);
1042 if (!cmd || !*cmd) {
1043 free(cmd);
1044 continue;
1045 }
1046 add_history(cmd);
1047 parse_cmdline(&argc, &argv, cmd);
1048#else
1049 char cmdbuf[4096];
1050 printf("%s", prompt);
1051 fflush(stdout);
1052 get_input(cmdbuf, sizeof(cmdbuf));
1053 parse_cmdline(&argc, &argv, cmdbuf);
1054#endif
1055#ifdef HAVE_READLINE
1056 free(cmd);
1057#endif
1058 /* process arguments */
1059 if (argv && argv[0]) {
1060 if (process_args(afc, argc, argv) < 0) {
1061 break;
1062 }
1063 }
1064 }
1065}
1066
1067static void device_event_cb(const idevice_event_t* event, void* userdata)
1068{
1069 if (use_network && event->conn_type != CONNECTION_NETWORK) {
1070 return;
1071 } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
1072 return;
1073 }
1074 if (event->event == IDEVICE_DEVICE_ADD) {
1075 if (!udid) {
1076 udid = strdup(event->udid);
1077 }
1078 if (strcmp(udid, event->udid) == 0) {
1079 connected = 1;
1080 }
1081 } else if (event->event == IDEVICE_DEVICE_REMOVE) {
1082 if (strcmp(udid, event->udid) == 0) {
1083 connected = 0;
1084 printf("\n[disconnected]\n");
1085 handle_signal(SIGINT);
1086 }
1087 }
1088}
1089
1090int main(int argc, char** argv)
1091{
1092 const char* appid = NULL;
1093 int ret = 0;
1094 idevice_t device = NULL;
1095 lockdownd_client_t lockdown = NULL;
1096 lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
1097 lockdownd_service_descriptor_t service = NULL;
1098 afc_client_t afc = NULL;
1099 house_arrest_client_t house_arrest = NULL;
1100 const char* service_name = AFC_SERVICE_NAME;
1101 int use_container = 0;
1102
1103 int c = 0;
1104 const struct option longopts[] = {
1105 { "udid", required_argument, NULL, 'u' },
1106 { "network", no_argument, NULL, 'n' },
1107 { "help", no_argument, NULL, 'h' },
1108 { "debug", no_argument, NULL, 'd' },
1109 { "version", no_argument, NULL, 'v' },
1110 { "documents", required_argument, NULL, OPT_DOCUMENTS },
1111 { "container", required_argument, NULL, OPT_CONTAINER },
1112 { NULL, 0, NULL, 0}
1113 };
1114
1115 signal(SIGTERM, handle_signal);
1116#ifndef WIN32
1117 signal(SIGQUIT, handle_signal);
1118 signal(SIGPIPE, SIG_IGN);
1119#endif
1120
1121 while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) {
1122 switch (c) {
1123 case 'd':
1124 idevice_set_debug_level(1);
1125 break;
1126 case 'u':
1127 if (!*optarg) {
1128 fprintf(stderr, "ERROR: UDID must not be empty!\n");
1129 print_usage(argc, argv, 1);
1130 return 2;
1131 }
1132 udid = strdup(optarg);
1133 break;
1134 case 'n':
1135 use_network = 1;
1136 break;
1137 case 'h':
1138 print_usage(argc, argv, 0);
1139 return 0;
1140 case 'v':
1141 printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
1142#ifdef HAVE_READLINE
1143 printf(" (readline)");
1144#endif
1145 printf("\n");
1146 return 0;
1147 case OPT_DOCUMENTS:
1148 if (!*optarg) {
1149 fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n");
1150 print_usage(argc, argv, 1);
1151 return 2;
1152 }
1153 appid = optarg;
1154 use_container = 0;
1155 break;
1156 case OPT_CONTAINER:
1157 if (!*optarg) {
1158 fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n");
1159 print_usage(argc, argv, 1);
1160 return 2;
1161 }
1162 appid = optarg;
1163 use_container = 1;
1164 break;
1165 default:
1166 print_usage(argc, argv, 1);
1167 return 2;
1168 }
1169 }
1170
1171 argc -= optind;
1172 argv += optind;
1173
1174 int num = 0;
1175 idevice_info_t *devices = NULL;
1176 idevice_get_device_list_extended(&devices, &num);
1177 int count = 0;
1178 for (int i = 0; i < num; i++) {
1179 if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) {
1180 count++;
1181 } else if (devices[i]->conn_type == CONNECTION_USBMUXD) {
1182 count++;
1183 }
1184 }
1185 idevice_device_list_extended_free(devices);
1186 if (count == 0) {
1187 fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
1188 return 1;
1189 }
1190
1191 idevice_events_subscribe(&context, device_event_cb, NULL);
1192
1193 while (!connected && !stop_requested) {
1194#ifdef WIN32
1195 Sleep(100);
1196#else
1197 usleep(100000);
1198#endif
1199 }
1200 if (stop_requested) {
1201 return 0;
1202 }
1203
1204 ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
1205 if (ret != IDEVICE_E_SUCCESS) {
1206 if (udid) {
1207 fprintf(stderr, "ERROR: Device %s not found!\n", udid);
1208 } else {
1209 fprintf(stderr, "ERROR: No device found!\n");
1210 }
1211 return 1;
1212 }
1213
1214 do {
1215 if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
1216 fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
1217 ret = 1;
1218 break;
1219 }
1220
1221 if (appid) {
1222 service_name = HOUSE_ARREST_SERVICE_NAME;
1223 }
1224
1225 ldret = lockdownd_start_service(lockdown, service_name, &service);
1226 if (ldret != LOCKDOWN_E_SUCCESS) {
1227 fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret);
1228 ret = 1;
1229 break;
1230 }
1231
1232 if (appid) {
1233 house_arrest_client_new(device, service, &house_arrest);
1234 if (!house_arrest) {
1235 fprintf(stderr, "Could not start document sharing service!\n");
1236 ret = 1;
1237 break;
1238 }
1239
1240 if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) {
1241 fprintf(stderr, "Could not send house_arrest command!\n");
1242 ret = 1;
1243 break;
1244 }
1245
1246 plist_t dict = NULL;
1247 if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) {
1248 fprintf(stderr, "Could not get result from document sharing service!\n");
1249 break;
1250 }
1251 plist_t node = plist_dict_get_item(dict, "Error");
1252 if (node) {
1253 char *str = NULL;
1254 plist_get_string_val(node, &str);
1255 fprintf(stderr, "ERROR: %s\n", str);
1256 if (str && !strcmp(str, "InstallationLookupFailed")) {
1257 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);
1258 }
1259 free(str);
1260 plist_free(dict);
1261 break;
1262 }
1263 plist_free(dict);
1264 afc_client_new_from_house_arrest_client(house_arrest, &afc);
1265 } else {
1266 afc_client_new(device, service, &afc);
1267 }
1268 lockdownd_service_descriptor_free(service);
1269 lockdownd_client_free(lockdown);
1270 lockdown = NULL;
1271
1272 curdir = strdup("/");
1273 curdir_len = 1;
1274
1275 if (argc > 0) {
1276 // command line mode
1277 process_args(afc, argc, argv);
1278 } else {
1279 // interactive mode
1280 start_cmdline(afc);
1281 }
1282
1283 } while (0);
1284
1285 if (afc) {
1286 afc_client_free(afc);
1287 }
1288 if (lockdown) {
1289 lockdownd_client_free(lockdown);
1290 }
1291 idevice_free(device);
1292
1293 return ret;
1294}