summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am25
-rw-r--r--tools/afcclient.c1664
-rw-r--r--tools/idevicebackup.c129
-rw-r--r--tools/idevicecrashreport.c2
-rw-r--r--tools/idevicedevmodectl.c44
-rw-r--r--tools/ideviceimagemounter.c399
-rw-r--r--tools/idevicesyslog.c30
7 files changed, 2112 insertions, 181 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 47e05b2..b0c2769 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -4,6 +4,7 @@ AM_CPPFLAGS = \
AM_CFLAGS = \
$(GLOBAL_CFLAGS) \
+ $(ssl_lib_CFLAGS) \
$(libplist_CFLAGS) \
$(LFS_CFLAGS)
@@ -30,10 +31,11 @@ bin_PROGRAMS = \
idevicedevmodectl \
idevicenotificationproxy \
idevicecrashreport \
- idevicesetlocation
+ idevicesetlocation \
+ afcclient
idevicebtlogger_SOURCES = idevicebtlogger.c
-iidevicebtlogger_CFLAGS = $(AM_CFLAGS)
+idevicebtlogger_CFLAGS = $(AM_CFLAGS)
idevicebtlogger_LDFLAGS = $(top_builddir)/common/libinternalcommon.la $(AM_LDFLAGS)
idevicebtlogger_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
@@ -48,7 +50,7 @@ idevicename_LDFLAGS = $(AM_LDFLAGS)
idevicename_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicepair_SOURCES = idevicepair.c
-idevicepair_CFLAGS = $(AM_CFLAGS) $(ssl_lib_CFLAGS)
+idevicepair_CFLAGS = $(AM_CFLAGS)
idevicepair_LDFLAGS = $(AM_LDFLAGS) $(libusbmuxd_LIBS) $(ssl_lib_LIBS)
idevicepair_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la $(top_builddir)/common/libinternalcommon.la $(limd_glue_LIBS)
@@ -63,8 +65,8 @@ idevice_id_LDFLAGS = $(AM_LDFLAGS)
idevice_id_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicebackup_SOURCES = idevicebackup.c
-idevicebackup_CFLAGS = $(AM_CFLAGS) $(ssl_lib_CFLAGS) $(limd_glue_CFLAGS)
-idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(ssl_lib_LIBS) $(limd_glue_LIBS)
+idevicebackup_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
+idevicebackup_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
idevicebackup_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicebackup2_SOURCES = idevicebackup2.c
@@ -73,8 +75,8 @@ idevicebackup2_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
idevicebackup2_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
ideviceimagemounter_SOURCES = ideviceimagemounter.c
-ideviceimagemounter_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS)
-ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS)
+ideviceimagemounter_CFLAGS = $(AM_CFLAGS) $(limd_glue_CFLAGS) $(libtatsu_CFLAGS)
+ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS) $(limd_glue_LIBS) $(ssl_lib_LIBS) $(libtatsu_LIBS)
ideviceimagemounter_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
idevicescreenshot_SOURCES = idevicescreenshot.c
@@ -131,3 +133,12 @@ idevicesetlocation_SOURCES = idevicesetlocation.c
idevicesetlocation_CFLAGS = $(AM_CFLAGS)
idevicesetlocation_LDFLAGS = $(AM_LDFLAGS)
idevicesetlocation_LDADD = $(top_builddir)/src/libimobiledevice-1.0.la
+
+afcclient_SOURCES = afcclient.c
+afcclient_CFLAGS = $(AM_CFLAGS)
+afcclient_LDFLAGS = $(AM_LDFLAGS)
+if HAVE_READLINE
+ afcclient_CFLAGS += $(readline_CFLAGS)
+ afcclient_LDFLAGS += $(readline_LIBS)
+endif
+afcclient_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..8f49831
--- /dev/null
+++ b/tools/afcclient.c
@@ -0,0 +1,1664 @@
+/*
+ * afcclient.c
+ * Utility to interact with AFC/HoustArrest service on the device
+ *
+ * Inspired by https://github.com/emonti/afcclient
+ * But entirely rewritten from scratch.
+ *
+ * Copyright (c) 2023 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define TOOL_NAME "afcclient"
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <time.h>
+
+#ifdef WIN32
+#include <windows.h>
+#include <sys/time.h>
+#include <conio.h>
+#define sleep(x) Sleep(x*1000)
+#define S_IFMT 0170000 /* [XSI] type of file mask */
+#define S_IFIFO 0010000 /* [XSI] named pipe (fifo) */
+#define S_IFCHR 0020000 /* [XSI] character special */
+#define S_IFBLK 0060000 /* [XSI] block special */
+#define S_IFLNK 0120000 /* [XSI] symbolic link */
+#define S_IFSOCK 0140000 /* [XSI] socket */
+#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */
+#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */
+#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */
+#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */
+#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
+#else
+#include <sys/time.h>
+#include <termios.h>
+#endif
+
+#ifdef HAVE_READLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/house_arrest.h>
+#include <libimobiledevice/afc.h>
+#include <plist/plist.h>
+
+#include <libimobiledevice-glue/termcolors.h>
+#include <libimobiledevice-glue/utils.h>
+
+#undef st_mtime
+#undef st_birthtime
+struct afc_file_stat {
+ uint16_t st_mode;
+ uint16_t st_nlink;
+ uint64_t st_size;
+ uint64_t st_mtime;
+ uint64_t st_birthtime;
+ uint32_t st_blocks;
+};
+
+static char* udid = NULL;
+static int connected = 0;
+static int use_network = 0;
+static idevice_subscription_context_t context = NULL;
+static char* curdir = NULL;
+static size_t curdir_len = 0;
+
+static int file_exists(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0);
+#else
+ return (lstat(path, &tst) == 0);
+#endif
+}
+
+static int is_directory(const char* path)
+{
+ struct stat tst;
+#ifdef WIN32
+ return (stat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
+#else
+ return (lstat(path, &tst) == 0) && S_ISDIR(tst.st_mode);
+#endif
+}
+
+static void print_usage(int argc, char **argv, int is_error)
+{
+ char *name = strrchr(argv[0], '/');
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS]\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout,
+ "\n"
+ "Interact with AFC/HouseArrest service on a connected device.\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID target specific device by UDID\n"
+ " -n, --network connect to network device (not recommended!)\n"
+ " --container <appid> Access container of given app\n"
+ " --documents <appid> Access Documents directory of given app\n"
+ " -h, --help prints usage information\n" \
+ " -d, --debug enable communication debugging\n" \
+ " -v, --version prints version information\n" \
+ "\n"
+ );
+ fprintf(is_error ? stderr : stdout,
+ "\n" \
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
+ );
+}
+
+#ifndef HAVE_READLINE
+#ifdef WIN32
+#define BS_CC '\b'
+#else
+#define BS_CC 0x7f
+#define getch getchar
+#endif
+static void get_input(char *buf, int maxlen)
+{
+ int len = 0;
+ int c;
+
+ while ((c = getch())) {
+ if ((c == '\r') || (c == '\n')) {
+ break;
+ }
+ if (isprint(c)) {
+ if (len < maxlen-1)
+ buf[len++] = c;
+ } else if (c == BS_CC) {
+ if (len > 0) {
+ fputs("\b \b", stdout);
+ len--;
+ }
+ }
+ }
+ buf[len] = 0;
+}
+#endif
+
+#define OPT_DOCUMENTS 1
+#define OPT_CONTAINER 2
+
+int stop_requested = 0;
+
+static void handle_signal(int sig)
+{
+ stop_requested++;
+#ifdef WIN32
+ GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
+#else
+ kill(getpid(), SIGINT);
+#endif
+}
+
+static void handle_help(afc_client_t afc, int argc, char** argv)
+{
+ printf("Available commands:\n");
+ printf("help - print list of available commands\n");
+ printf("devinfo - print device information\n");
+ printf("info PATH - print file attributes of file at PATH\n");
+ printf("ls [-l] PATH - print directory contents of PATH\n");
+ printf("mv OLD NEW - rename file OLD to NEW\n");
+ printf("mkdir PATH - create directory at PATH\n");
+ printf("ln [-s] FILE [LINK] - create a (symbolic) link to file named LINKNAME\n");
+ printf(" NOTE: This feature has been disabled in newer versions of iOS.\n");
+ printf("rm PATH - remove item at PATH\n");
+ printf("get [-rf] PATH [LOCALPATH] - transfer file at PATH from device to LOCALPATH\n");
+ printf("put [-rf] LOCALPATH [PATH] - transfer local file at LOCALPATH to device at PATH\n");
+ printf("\n");
+}
+
+static const char* path_get_basename(const char* path)
+{
+ const char *p = strrchr(path, '/');
+ return p ? p + 1 : path;
+}
+
+static int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
+{
+ /* Perform the carry for the later subtraction by updating y. */
+ if (x->tv_usec < y->tv_usec) {
+ int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
+ y->tv_usec -= 1000000 * nsec;
+ y->tv_sec += nsec;
+ }
+ if (x->tv_usec - y->tv_usec > 1000000) {
+ int nsec = (x->tv_usec - y->tv_usec) / 1000000;
+ y->tv_usec += 1000000 * nsec;
+ y->tv_sec -= nsec;
+ }
+ /* Compute the time remaining to wait.
+ tv_usec is certainly positive. */
+ result->tv_sec = x->tv_sec - y->tv_sec;
+ result->tv_usec = x->tv_usec - y->tv_usec;
+ /* Return 1 if result is negative. */
+ return x->tv_sec < y->tv_sec;
+}
+
+struct str_item {
+ size_t len;
+ char* str;
+};
+
+static char* get_absolute_path(const char *path)
+{
+ if (*path == '/') {
+ return strdup(path);
+ } else {
+ size_t len = curdir_len + 1 + strlen(path) + 1;
+ char* result = (char*)malloc(len);
+ if (!strcmp(curdir, "/")) {
+ snprintf(result, len, "/%s", path);
+ } else {
+ snprintf(result, len, "%s/%s", curdir, path);
+ }
+ return result;
+ }
+}
+
+static char* get_realpath(const char* path)
+{
+ if (!path) return NULL;
+
+ int is_absolute = 0;
+ if (*path == '/') {
+ is_absolute = 1;
+ }
+
+ const char* p = path;
+ if (is_absolute) {
+ while (*p == '/') p++;
+ }
+ if (*p == '\0') {
+ return strdup("/");
+ }
+
+ int c_count = 1;
+ const char* start = p;
+ const char* end = p;
+ struct str_item* comps = NULL;
+
+ while (*p) {
+ if (*p == '/') {
+ p++;
+ end = p-1;
+ while (*p == '/') p++;
+ if (*p == '\0') break;
+ struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
+ if (!newcomps) {
+ free(comps);
+ printf("%s: out of memory?!\n", __func__);
+ return NULL;
+ }
+ comps = newcomps;
+ char *comp = (char*)malloc(end-start+1);
+ strncpy(comp, start, end-start);
+ comp[end-start] = '\0';
+ comps[c_count-1].len = end-start;
+ comps[c_count-1].str = comp;
+ c_count++;
+ start = p;
+ end = p;
+ }
+ p++;
+ }
+ if (p > start) {
+ if (start == end) {
+ end = p;
+ }
+ struct str_item* newcomps = (struct str_item*)realloc(comps, sizeof(struct str_item)*c_count);
+ if (!newcomps) {
+ free(comps);
+ printf("%s: out of memory?!\n", __func__);
+ return NULL;
+ }
+ comps = newcomps;
+ char *comp = (char*)malloc(end-start+1);
+ strncpy(comp, start, end-start);
+ comp[end-start] = '\0';
+ comps[c_count-1].len = end-start;
+ comps[c_count-1].str = comp;
+ }
+
+ struct str_item* comps_final = (struct str_item*)malloc(sizeof(struct str_item)*(c_count+1));
+ int o = 1;
+ if (is_absolute) {
+ comps_final[0].len = 1;
+ comps_final[0].str = (char*)"/";
+ } else {
+ comps_final[0].len = curdir_len;
+ comps_final[0].str = curdir;
+ }
+ size_t o_len = comps_final[0].len;
+
+ for (int i = 0; i < c_count; i++) {
+ if (!strcmp(comps[i].str, "..")) {
+ o--;
+ continue;
+ } else if (!strcmp(comps[i].str, ".")) {
+ continue;
+ }
+ o_len += comps[i].len;
+ comps_final[o].str = comps[i].str;
+ comps_final[o].len = comps[i].len;
+ o++;
+ }
+
+ o_len += o;
+ char* result = (char*)malloc(o_len);
+ char* presult = result;
+ for (int i = 0; i < o; i++) {
+ if (i > 0 && strcmp(comps_final[i-1].str, "/") != 0) {
+ *presult = '/';
+ presult++;
+ }
+ strncpy(presult, comps_final[i].str, comps_final[i].len);
+ presult+=comps_final[i].len;
+ *presult = '\0';
+ }
+ if (presult == result) {
+ *presult = '/';
+ presult++;
+ *presult = 0;
+ }
+
+ for (int i = 0; i < c_count; i++) {
+ free(comps[i].str);
+ }
+ free(comps);
+ free(comps_final);
+
+ return result;
+}
+
+static void handle_devinfo(afc_client_t afc, int argc, char** argv)
+{
+ char **info = NULL;
+ afc_error_t err = afc_get_device_info(afc, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ printf("%s: %s\n", info[i], info[i+1]);
+ }
+ } else {
+ printf("Error: Failed to get device info: %s (%d)\n", afc_strerror(err), err);
+ }
+ afc_dictionary_free(info);
+}
+
+static int get_file_info_stat(afc_client_t afc, const char* path, struct afc_file_stat *stbuf)
+{
+ char **info = NULL;
+ afc_error_t ret = afc_get_file_info(afc, path, &info);
+ memset(stbuf, 0, sizeof(struct afc_file_stat));
+ if (ret != AFC_E_SUCCESS) {
+ return -1;
+ } else if (!info) {
+ return -1;
+ } else {
+ // get file attributes from info list
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ if (!strcmp(info[i], "st_size")) {
+ stbuf->st_size = atoll(info[i+1]);
+ } else if (!strcmp(info[i], "st_blocks")) {
+ stbuf->st_blocks = atoi(info[i+1]);
+ } else if (!strcmp(info[i], "st_ifmt")) {
+ if (!strcmp(info[i+1], "S_IFREG")) {
+ stbuf->st_mode = S_IFREG;
+ } else if (!strcmp(info[i+1], "S_IFDIR")) {
+ stbuf->st_mode = S_IFDIR;
+ } else if (!strcmp(info[i+1], "S_IFLNK")) {
+ stbuf->st_mode = S_IFLNK;
+ } else if (!strcmp(info[i+1], "S_IFBLK")) {
+ stbuf->st_mode = S_IFBLK;
+ } else if (!strcmp(info[i+1], "S_IFCHR")) {
+ stbuf->st_mode = S_IFCHR;
+ } else if (!strcmp(info[i+1], "S_IFIFO")) {
+ stbuf->st_mode = S_IFIFO;
+ } else if (!strcmp(info[i+1], "S_IFSOCK")) {
+ stbuf->st_mode = S_IFSOCK;
+ }
+ } else if (!strcmp(info[i], "st_nlink")) {
+ stbuf->st_nlink = atoi(info[i+1]);
+ } else if (!strcmp(info[i], "st_mtime")) {
+ stbuf->st_mtime = (time_t)(atoll(info[i+1]) / 1000000000);
+ } else if (!strcmp(info[i], "st_birthtime")) { /* available on iOS 7+ */
+ stbuf->st_birthtime = (time_t)(atoll(info[i+1]) / 1000000000);
+ }
+ }
+ afc_dictionary_free(info);
+ }
+ return 0;
+}
+
+static void handle_file_info(afc_client_t afc, int argc, char** argv)
+{
+ if (argc < 1) {
+ printf("Error: Missing PATH.\n");
+ return;
+ }
+
+ char **info = NULL;
+ char* abspath = get_absolute_path(argv[0]);
+ if (!abspath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_get_file_info(afc, abspath, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ printf("%s: %s\n", info[i], info[i+1]);
+ }
+ } else {
+ printf("Error: Failed to get file info for %s: %s (%d)\n", argv[0], afc_strerror(err), err);
+ }
+ afc_dictionary_free(info);
+ free(abspath);
+}
+
+static void print_file_info(afc_client_t afc, const char* path, int list_verbose)
+{
+ struct afc_file_stat st;
+ get_file_info_stat(afc, path, &st);
+ if (list_verbose) {
+ char timebuf[64];
+ time_t t = st.st_mtime;
+ if (S_ISDIR(st.st_mode)) {
+ printf("drwxr-xr-x");
+ } else if (S_ISLNK(st.st_mode)) {
+ printf("lrwxrwxrwx");
+ } else {
+ if (S_ISFIFO(st.st_mode)) {
+ printf("f");
+ } else if (S_ISBLK(st.st_mode)) {
+ printf("b");
+ } else if (S_ISCHR(st.st_mode)) {
+ printf("c");
+ } else if (S_ISSOCK(st.st_mode)) {
+ printf("s");
+ } else {
+ printf("-");
+ }
+ printf("rw-r--r--");
+ }
+ printf(" ");
+ printf("%4d", st.st_nlink);
+ printf(" ");
+ printf("mobile");
+ printf(" ");
+ printf("mobile");
+ printf(" ");
+ printf("%10lld", (long long)st.st_size);
+ printf(" ");
+#ifdef WIN32
+ strftime(timebuf, 64, "%d %b %Y %H:%M:%S", localtime(&t));
+#else
+ strftime(timebuf, 64, "%d %h %Y %H:%M:%S", localtime(&t));
+#endif
+ printf("%s", timebuf);
+ printf(" ");
+ }
+ if (S_ISDIR(st.st_mode)) {
+ cprintf(FG_CYAN);
+ } else if (S_ISLNK(st.st_mode)) {
+ cprintf(FG_MAGENTA);
+ } else if (S_ISREG(st.st_mode)) {
+ cprintf(FG_DEFAULT);
+ } else {
+ cprintf(FG_YELLOW);
+ }
+ cprintf("%s" COLOR_RESET "\n", path_get_basename(path));
+}
+
+static void handle_list(afc_client_t afc, int argc, char** argv)
+{
+ const char* path = NULL;
+ int list_verbose = 0;
+ if (argc < 1) {
+ path = curdir;
+ } else {
+ if (!strcmp(argv[0], "-l")) {
+ list_verbose = 1;
+ if (argc == 2) {
+ path = argv[1];
+ } else {
+ path = curdir;
+ }
+ } else {
+ path = argv[0];
+ }
+ }
+ char* abspath = get_absolute_path(path);
+ if (!abspath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ int abspath_is_root = strcmp(abspath, "/") == 0;
+ size_t abspath_len = (abspath_is_root) ? 0 : strlen(abspath);
+ char** entries = NULL;
+ afc_error_t err = afc_read_directory(afc, abspath, &entries);
+ if (err == AFC_E_READ_ERROR) {
+ print_file_info(afc, abspath, list_verbose);
+ return;
+ } else if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to list '%s': %s (%d)\n", path, afc_strerror(err), err);
+ free(abspath);
+ return;
+ }
+
+ char** p = entries;
+ while (p && *p) {
+ if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
+ p++;
+ continue;
+ }
+ size_t len = abspath_len + 1 + strlen(*p) + 1;
+ char* testpath = (char*)malloc(len);
+ if (abspath_is_root) {
+ snprintf(testpath, len, "/%s", *p);
+ } else {
+ snprintf(testpath, len, "%s/%s", abspath, *p);
+ }
+ print_file_info(afc, testpath, list_verbose);
+ free(testpath);
+ p++;
+ }
+ afc_dictionary_free(entries);
+ free(abspath);
+}
+
+static void handle_rename(afc_client_t afc, int argc, char** argv)
+{
+ if (argc != 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char* srcpath = get_absolute_path(argv[0]);
+ if (!srcpath) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ char* dstpath = get_absolute_path(argv[1]);
+ if (!dstpath) {
+ free(srcpath);
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_rename_path(afc, srcpath, dstpath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to rename '%s' -> '%s': %s (%d)\n", argv[0], argv[1], afc_strerror(err), err);
+ }
+ free(srcpath);
+ free(dstpath);
+}
+
+static void handle_mkdir(afc_client_t afc, int argc, char** argv)
+{
+ for (int i = 0; i < argc; i++) {
+ char* abspath = get_absolute_path(argv[i]);
+ if (!abspath) {
+ printf("Error: Invalid argument '%s'\n", argv[i]);
+ continue;
+ }
+ afc_error_t err = afc_make_directory(afc, abspath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to create directory '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
+ }
+ free(abspath);
+ }
+}
+
+static void handle_link(afc_client_t afc, int argc, char** argv)
+{
+ if (argc < 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ afc_link_type_t link_type = AFC_HARDLINK;
+ if (!strcmp(argv[0], "-s")) {
+ argc--;
+ argv++;
+ link_type = AFC_SYMLINK;
+ }
+ if (argc < 1 || argc > 2) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ const char *link_name = (argc == 1) ? path_get_basename(argv[0]) : argv[1];
+ char* abs_link_name = get_absolute_path(link_name);
+ if (!abs_link_name) {
+ printf("Error: Invalid argument\n");
+ return;
+ }
+ afc_error_t err = afc_make_link(afc, link_type, argv[0], link_name);
+ if (err != AFC_E_SUCCESS) {
+ 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);
+ }
+}
+
+static int ask_yesno(const char* prompt)
+{
+ int ret = 0;
+#ifdef HAVE_READLINE
+ char* result = readline(prompt);
+ if (result && result[0] == 'y') {
+ ret = 1;
+ }
+#else
+ char cmdbuf[2] = {0, };
+ printf("%s", prompt);
+ fflush(stdout);
+ get_input(cmdbuf, sizeof(cmdbuf));
+ if (cmdbuf[0] == 'y') {
+ ret = 1;
+ }
+#endif
+#ifdef HAVE_READLINE
+ free(result);
+#endif
+ return ret;
+}
+
+static void handle_remove(afc_client_t afc, int argc, char** argv)
+{
+ int recursive = 0;
+ int force = 0;
+ int i = 0;
+ for (i = 0; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive = 1;
+ force = 1;
+ } else {
+ break;
+ }
+ }
+ if (recursive && !force) {
+ if (!ask_yesno("WARNING: This operation will remove all contents of the given path(s). Continue? [y/N] ")) {
+ printf("Aborted.\n");
+ return;
+ }
+ }
+ for ( ; i < argc; i++) {
+ char* abspath = get_absolute_path(argv[i]);
+ if (!abspath) {
+ printf("Error: Invalid argument '%s'\n", argv[i]);
+ continue;
+ }
+ afc_error_t err;
+ if (recursive) {
+ err = afc_remove_path_and_contents(afc, abspath);
+ } else {
+ err = afc_remove_path(afc, abspath);
+ }
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to remove '%s': %s (%d)\n", argv[i], afc_strerror(err), err);
+ }
+ free(abspath);
+ }
+}
+
+static uint8_t get_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint64_t file_size, uint8_t force_overwrite)
+{
+ uint64_t fh = 0;
+ afc_error_t err = afc_file_open(afc, srcpath, AFC_FOPEN_RDONLY, &fh);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to open file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ if (file_exists(dstpath) && !force_overwrite) {
+ printf("Error: Failed to overwrite existing file without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ FILE *f = fopen(dstpath, "wb");
+ if (!f) {
+ printf("Error: Failed to open local file '%s': %s\n", dstpath, strerror(errno));
+ return 0;
+ }
+ struct timeval t1;
+ struct timeval t2;
+ struct timeval tdiff;
+ size_t bufsize = 0x100000;
+ char *buf = malloc(bufsize);
+ size_t total = 0;
+ int progress = 0;
+ int lastprog = 0;
+ if (file_size > 0x400000) {
+ progress = 1;
+ gettimeofday(&t1, NULL);
+ }
+ uint8_t succeed = 1;
+ while (err == AFC_E_SUCCESS) {
+ uint32_t bytes_read = 0;
+ size_t chunk = 0;
+ err = afc_file_read(afc, fh, buf, bufsize, &bytes_read);
+ if (bytes_read == 0) {
+ break;
+ }
+ while (chunk < bytes_read) {
+ size_t wr = fwrite(buf + chunk, 1, bytes_read - chunk, f);
+ if (wr == 0) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to write to local file\n");
+ succeed = 0;
+ break;
+ }
+ chunk += wr;
+ }
+ total += chunk;
+ if (progress) {
+ int prog = (int) ((double) total / (double) file_size * 100.0f);
+ if (prog > lastprog) {
+ gettimeofday(&t2, NULL);
+ timeval_subtract(&tdiff, &t2, &t1);
+ double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
+ printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
+ fflush(stdout);
+ lastprog = prog;
+ }
+ }
+ }
+ if (progress) {
+ printf("\n");
+ }
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ succeed = 0;
+ }
+ free(buf);
+ fclose(f);
+ afc_file_close(afc, fh);
+ return succeed;
+}
+
+static int __mkdir(const char* path)
+{
+#ifdef WIN32
+ return mkdir(path);
+#else
+ return mkdir(path, 0755);
+#endif
+}
+
+static uint8_t get_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_get)
+{
+ char **info = NULL;
+ uint64_t file_size = 0;
+ afc_error_t err = afc_get_file_info(afc, srcpath, &info);
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ printf("Error: Failed to read from file '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_size")) {
+ p++;
+ file_size = (uint64_t) strtoull(*p, NULL, 10);
+ }
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ uint8_t succeed = 1;
+ if (is_dir) {
+ if (!recursive_get) {
+ printf("Error: Failed to get a directory without '-r' option: %s\n", srcpath);
+ return 0;
+ }
+ char **entries = NULL;
+ err = afc_read_directory(afc, srcpath, &entries);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to list '%s': %s (%d)\n", srcpath, afc_strerror(err), err);
+ return 0;
+ }
+ char **p = entries;
+ size_t srcpath_len = strlen(srcpath);
+ uint8_t srcpath_is_root = strcmp(srcpath, "/") == 0;
+ // if directory exists, check force_overwrite flag
+ if (is_directory(dstpath)) {
+ if (!force_overwrite) {
+ printf("Error: Failed to write into existing directory without '-f': %s\n", dstpath);
+ return 0;
+ }
+ } else if (__mkdir(dstpath) != 0) {
+ printf("Error: Failed to create directory '%s': %s\n", dstpath, strerror(errno));
+ afc_dictionary_free(entries);
+ return 0;
+ }
+ while (p && *p) {
+ if (strcmp(".", *p) == 0 || strcmp("..", *p) == 0) {
+ p++;
+ continue;
+ }
+ size_t len = srcpath_is_root ? strlen(*p) + 1 : srcpath_len + 1 + strlen(*p) + 1;
+ char *testpath = (char *) malloc(len);
+ if (srcpath_is_root) {
+ snprintf(testpath, len, "/%s", *p);
+ } else {
+ snprintf(testpath, len, "%s/%s", srcpath, *p);
+ }
+ uint8_t dst_is_root = strcmp(srcpath, "/") == 0;
+ size_t dst_len = dst_is_root ? strlen(*p) + 1 : strlen(dstpath) + 1 + strlen(*p) + 1;
+ char *newdst = (char *) malloc(dst_len);
+ if (dst_is_root) {
+ snprintf(newdst, dst_len, "/%s", *p);
+ } else {
+ snprintf(newdst, dst_len, "%s/%s", dstpath, *p);
+ }
+ if (!get_file(afc, testpath, newdst, force_overwrite, recursive_get)) {
+ succeed = 0;
+ break;
+ }
+ free(testpath);
+ free(newdst);
+ p++;
+ }
+ afc_dictionary_free(entries);
+ } else {
+ succeed = get_single_file(afc, srcpath, dstpath, file_size, force_overwrite);
+ }
+ return succeed;
+}
+
+static void handle_get(afc_client_t afc, int argc, char **argv)
+{
+ if (argc < 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ uint8_t force_overwrite = 0, recursive_get = 0;
+ char *srcpath = NULL;
+ char *dstpath = NULL;
+ int i = 0;
+ for ( ; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive_get = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force_overwrite = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive_get = 1;
+ force_overwrite = 1;
+ } else {
+ break;
+ }
+ }
+ if (argc - i == 1) {
+ char *tmp = strdup(argv[i]);
+ size_t src_len = strlen(tmp);
+ if (src_len > 1 && tmp[src_len - 1] == '/') {
+ tmp[src_len - 1] = '\0';
+ }
+ srcpath = get_absolute_path(tmp);
+ dstpath = strdup(path_get_basename(tmp));
+ free(tmp);
+ } else if (argc - i == 2) {
+ char *tmp = strdup(argv[i]);
+ size_t src_len = strlen(tmp);
+ if (src_len > 1 && tmp[src_len - 1] == '/') {
+ tmp[src_len - 1] = '\0';
+ }
+ srcpath = get_absolute_path(tmp);
+ dstpath = strdup(argv[i + 1]);
+ size_t dst_len = strlen(dstpath);
+ if (dst_len > 1 && dstpath[dst_len - 1] == '/') {
+ dstpath[dst_len - 1] = '\0';
+ }
+ free(tmp);
+ } else {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+
+ // target is a directory, put file under this target
+ if (is_directory(dstpath)) {
+ const char *basen = path_get_basename(argv[0]);
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? (strlen(basen) + 1) : (strlen(dstpath) + 1 + strlen(basen) + 1);
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", basen);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, basen);
+ }
+ get_file(afc, srcpath, newdst, force_overwrite, recursive_get);
+ free(srcpath);
+ free(newdst);
+ free(dstpath);
+ } else {
+ // target is not a dir or does not exist, just try to create or rewrite it
+ get_file(afc, srcpath, dstpath, force_overwrite, recursive_get);
+ free(srcpath);
+ free(dstpath);
+ }
+}
+
+static uint8_t put_single_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite)
+{
+ char **info = NULL;
+ afc_error_t ret = afc_get_file_info(afc, dstpath, &info);
+ // file exists, only overwrite with '-f' option was set
+ if (ret == AFC_E_SUCCESS && info) {
+ afc_dictionary_free(info);
+ if (!force_overwrite) {
+ printf("Error: Failed to write into existing file without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ }
+ FILE *f = fopen(srcpath, "rb");
+ if (!f) {
+ printf("Error: Failed to open local file '%s': %s\n", srcpath, strerror(errno));
+ return 0;
+ }
+ struct timeval t1;
+ struct timeval t2;
+ struct timeval tdiff;
+ struct stat fst;
+ int progress = 0;
+ size_t bufsize = 0x100000;
+ char *buf = malloc(bufsize);
+
+ fstat(fileno(f), &fst);
+ if (fst.st_size >= 0x400000) {
+ progress = 1;
+ gettimeofday(&t1, NULL);
+ }
+ size_t total = 0;
+ int lastprog = 0;
+ uint64_t fh = 0;
+ afc_error_t err = afc_file_open(afc, dstpath, AFC_FOPEN_RW, &fh);
+ uint8_t succeed = 1;
+ while (err == AFC_E_SUCCESS) {
+ uint32_t bytes_read = fread(buf, 1, bufsize, f);
+ if (bytes_read == 0) {
+ if (!feof(f)) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to read from local file\n");
+ succeed = 0;
+ }
+ break;
+ }
+ uint32_t chunk = 0;
+ while (chunk < bytes_read) {
+ uint32_t bytes_written = 0;
+ err = afc_file_write(afc, fh, buf + chunk, bytes_read - chunk, &bytes_written);
+ if (err != AFC_E_SUCCESS) {
+ if (progress) {
+ printf("\n");
+ }
+ printf("Error: Failed to write to device file\n");
+ succeed = 0;
+ break;
+ }
+ chunk += bytes_written;
+ }
+ total += chunk;
+ if (progress) {
+ int prog = (int) ((double) total / (double) fst.st_size * 100.0f);
+ if (prog > lastprog) {
+ gettimeofday(&t2, NULL);
+ timeval_subtract(&tdiff, &t2, &t1);
+ double time_in_sec = (double) tdiff.tv_sec + (double) tdiff.tv_usec / 1000000;
+ printf("\r%d%% (%0.1f MB/s) ", prog, (double) total / 1048576.0f / time_in_sec);
+ fflush(stdout);
+ lastprog = prog;
+ }
+ }
+ }
+ free(buf);
+ afc_file_close(afc, fh);
+ fclose(f);
+ return succeed;
+}
+
+static uint8_t put_file(afc_client_t afc, const char *srcpath, const char *dstpath, uint8_t force_overwrite, uint8_t recursive_put)
+{
+ if (is_directory(srcpath)) {
+ if (!recursive_put) {
+ printf("Error: Failed to put directory without '-r' option: %s\n", srcpath);
+ return 0;
+ }
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, dstpath, &info);
+ //create if target directory does not exist
+ afc_dictionary_free(info);
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ err = afc_make_directory(afc, dstpath);
+ if (err != AFC_E_SUCCESS) {
+ printf("Error: Failed to create directory '%s': %s (%d)\n", dstpath, afc_strerror(err), err);
+ return 0;
+ }
+ } else if (!force_overwrite) {
+ printf("Error: Failed to put existing directory without '-f' option: %s\n", dstpath);
+ return 0;
+ }
+ afc_get_file_info(afc, dstpath, &info);
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ break;
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ if (!is_dir) {
+ printf("Error: Failed to create or access directory: '%s'\n", dstpath);
+ return 0;
+ }
+
+ // walk dir recursively to put files
+ DIR *cur_dir = opendir(srcpath);
+ if (cur_dir) {
+ struct dirent *ep;
+ while ((ep = readdir(cur_dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *fpath = string_build_path(srcpath, ep->d_name, NULL);
+ if (fpath) {
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? strlen(ep->d_name) + 1 : strlen(dstpath) + 1 + strlen(ep->d_name) + 1;
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", ep->d_name);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, ep->d_name);
+ }
+ if (!put_file(afc, fpath, newdst, force_overwrite, recursive_put)) {
+ free(newdst);
+ free(fpath);
+ return 0;
+ }
+ free(newdst);
+ free(fpath);
+ }
+ }
+ closedir(cur_dir);
+ } else {
+ printf("Error: Failed to visit directory: '%s': %s\n", srcpath, strerror(errno));
+ return 0;
+ }
+ } else {
+ return put_single_file(afc, srcpath, dstpath, force_overwrite);
+ }
+ return 1;
+}
+
+static void handle_put(afc_client_t afc, int argc, char **argv)
+{
+ if (argc < 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ int i = 0;
+ uint8_t force_overwrite = 0, recursive_put = 0;
+ for ( ; i < argc; i++) {
+ if (!strcmp(argv[i], "--")) {
+ i++;
+ break;
+ } else if (!strcmp(argv[i], "-r")) {
+ recursive_put = 1;
+ } else if (!strcmp(argv[i], "-f")) {
+ force_overwrite = 1;
+ } else if (!strcmp(argv[i], "-rf") || !strcmp(argv[i], "-fr")) {
+ recursive_put = 1;
+ force_overwrite = 1;
+ } else {
+ break;
+ }
+ }
+ if (i >= argc) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char *srcpath = strdup(argv[i]);
+ size_t src_len = strlen(srcpath);
+ if (src_len > 1 && srcpath[src_len - 1] == '/') {
+ srcpath[src_len - 1] = '\0';
+ }
+ char *dstpath = NULL;
+ if (argc - i == 1) {
+ dstpath = get_absolute_path(path_get_basename(srcpath));
+ } else if (argc - i == 2) {
+ char *tmp = strdup(argv[i + 1]);
+ size_t dst_len = strlen(tmp);
+ if (dst_len > 1 && tmp[dst_len - 1] == '/') {
+ tmp[dst_len - 1] = '\0';
+ }
+ dstpath = get_absolute_path(tmp);
+ free(tmp);
+ } else {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, dstpath, &info);
+ // target does not exist, put directly
+ if (err == AFC_E_OBJECT_NOT_FOUND) {
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ free(srcpath);
+ free(dstpath);
+ } else {
+ uint8_t is_dir = 0;
+ if (info) {
+ char **p = info;
+ while (p && *p) {
+ if (!strcmp(*p, "st_ifmt")) {
+ p++;
+ is_dir = !strcmp(*p, "S_IFDIR");
+ break;
+ }
+ p++;
+ }
+ afc_dictionary_free(info);
+ }
+ // target is a directory, try to put under this directory
+ if (is_dir) {
+ const char *basen = path_get_basename(srcpath);
+ uint8_t dst_is_root = strcmp(dstpath, "/") == 0;
+ size_t len = dst_is_root ? strlen(basen) + 1 : strlen(dstpath) + 1 + strlen(basen) + 1;
+ char *newdst = (char *) malloc(len);
+ if (dst_is_root) {
+ snprintf(newdst, len, "/%s", basen);
+ } else {
+ snprintf(newdst, len, "%s/%s", dstpath, basen);
+ }
+ free(dstpath);
+ dstpath = get_absolute_path(newdst);
+ free(newdst);
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ } else {
+ //target is common file, rewrite it
+ put_file(afc, srcpath, dstpath, force_overwrite, recursive_put);
+ }
+ free(srcpath);
+ free(dstpath);
+ }
+}
+
+static void handle_pwd(afc_client_t afc, int argc, char** argv)
+{
+ printf("%s\n", curdir);
+}
+
+static void handle_cd(afc_client_t afc, int argc, char** argv)
+{
+ if (argc != 1) {
+ printf("Error: Invalid number of arguments\n");
+ return;
+ }
+
+ if (!strcmp(argv[0], ".")) {
+ return;
+ }
+
+ if (!strcmp(argv[0], "..")) {
+ if (!strcmp(curdir, "/")) {
+ return;
+ }
+ char *p = strrchr(curdir, '/');
+ if (!p) {
+ strcpy(curdir, "/");
+ return;
+ }
+ if (p == curdir) {
+ *(p+1) = '\0';
+ } else {
+ *p = '\0';
+ }
+ return;
+ }
+
+ char* path = get_realpath(argv[0]);
+ int is_dir = 0;
+ char **info = NULL;
+ afc_error_t err = afc_get_file_info(afc, path, &info);
+ if (err == AFC_E_SUCCESS && info) {
+ int i;
+ for (i = 0; info[i]; i += 2) {
+ if (!strcmp(info[i], "st_ifmt")) {
+ if (!strcmp(info[i+1], "S_IFDIR")) {
+ is_dir = 1;
+ }
+ break;
+ }
+ }
+ afc_dictionary_free(info);
+ } else {
+ printf("Error: Failed to get file info for %s: %s (%d)\n", path, afc_strerror(err), err);
+ free(path);
+ return;
+ }
+
+ if (!is_dir) {
+ printf("Error: '%s' is not a valid directory\n", path);
+ free(path);
+ return;
+ }
+
+ free(curdir);
+ curdir = path;
+ curdir_len = strlen(curdir);
+}
+
+static void parse_cmdline(int* p_argc, char*** p_argv, const char* cmdline)
+{
+ char **argv = NULL;
+ int argc = 0;
+ size_t maxlen = strlen(cmdline);
+ const char* pos = cmdline;
+ const char* qpos = NULL;
+ char *tmpbuf = NULL;
+ int tmplen = 0;
+ int is_error = 0;
+
+ /* skip initial whitespace */
+ while (isspace(*pos)) pos++;
+ maxlen -= (pos - cmdline);
+
+ tmpbuf = (char*)malloc(maxlen+1);
+
+ while (!is_error) {
+ if (*pos == '\\') {
+ pos++;
+ switch (*pos) {
+ case '"':
+ case '\'':
+ case '\\':
+ case ' ':
+ tmpbuf[tmplen++] = *pos;
+ pos++;
+ break;
+ default:
+ printf("Error: Invalid escape sequence\n");
+ is_error++;
+ break;
+ }
+ } else if (*pos == '"' || *pos == '\'') {
+ if (!qpos) {
+ qpos = pos;
+ } else {
+ qpos = NULL;
+ }
+ pos++;
+ } else if (*pos == '\0' || (!qpos && isspace(*pos))) {
+ tmpbuf[tmplen] = '\0';
+ if (*pos == '\0' && qpos) {
+ printf("Error: Unmatched `%c`\n", *qpos);
+ is_error++;
+ break;
+ }
+ char** new_argv = (char**)realloc(argv, (argc+1)*sizeof(char*));
+ if (new_argv == NULL) {
+ printf("Error: Out of memory?!\n");
+ is_error++;
+ break;
+ }
+ argv = new_argv;
+ /* shrink buffer to actual argument size */
+ argv[argc] = (char*)realloc(tmpbuf, tmplen+1);
+ if (!argv[argc]) {
+ printf("Error: Out of memory?!\n");
+ is_error++;
+ break;
+ }
+ argc++;
+ tmpbuf = NULL;
+ if (*pos == '\0') {
+ break;
+ }
+ maxlen -= tmplen;
+ tmpbuf = (char*)malloc(maxlen+1);
+ tmplen = 0;
+ while (isspace(*pos)) pos++;
+ } else {
+ tmpbuf[tmplen++] = *pos;
+ pos++;
+ }
+ }
+ if (tmpbuf) {
+ free(tmpbuf);
+ }
+ if (is_error) {
+ int i;
+ for (i = 0; argv && i < argc; i++) free(argv[i]);
+ free(argv);
+ return;
+ }
+
+ *p_argv = argv;
+ *p_argc = argc;
+}
+
+static int process_args(afc_client_t afc, int argc, char** argv)
+{
+ if (!strcmp(argv[0], "q") || !strcmp(argv[0], "quit") || !strcmp(argv[0], "exit")) {
+ return -1;
+ }
+ else if (!strcmp(argv[0], "help")) {
+ handle_help(afc, argc, argv);
+ }
+ else if (!strcmp(argv[0], "devinfo") || !strcmp(argv[0], "deviceinfo")) {
+ handle_devinfo(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "info")) {
+ handle_file_info(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "ls") || !strcmp(argv[0], "list")) {
+ handle_list(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "mv") || !strcmp(argv[0], "rename")) {
+ handle_rename(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "mkdir")) {
+ handle_mkdir(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "ln")) {
+ handle_link(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "rm") || !strcmp(argv[0], "remove")) {
+ handle_remove(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "get")) {
+ handle_get(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "put")) {
+ handle_put(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "pwd")) {
+ handle_pwd(afc, argc-1, argv+1);
+ }
+ else if (!strcmp(argv[0], "cd")) {
+ handle_cd(afc, argc-1, argv+1);
+ }
+ else {
+ printf("Unknown command '%s'. Type 'help' to get a list of available commands.\n", argv[0]);
+ }
+ return 0;
+}
+
+static void start_cmdline(afc_client_t afc)
+{
+ while (!stop_requested) {
+ int argc = 0;
+ char **argv = NULL;
+ char prompt[128];
+ int plen = curdir_len;
+ char *ppath = curdir;
+ int plim = (int)(sizeof(prompt)/2)-8;
+ if (plen > plim) {
+ ppath = curdir + (plen - plim);
+ plen = plim;
+ }
+ snprintf(prompt, 128, FG_BLACK BG_LIGHT_GRAY "afc:" COLOR_RESET FG_BRIGHT_YELLOW BG_BLUE "%.*s" COLOR_RESET " > ", plen, ppath);
+#ifdef HAVE_READLINE
+ char* cmd = readline(prompt);
+ if (!cmd || !*cmd) {
+ free(cmd);
+ continue;
+ }
+ add_history(cmd);
+ parse_cmdline(&argc, &argv, cmd);
+#else
+ char cmdbuf[4096];
+ printf("%s", prompt);
+ fflush(stdout);
+ get_input(cmdbuf, sizeof(cmdbuf));
+ parse_cmdline(&argc, &argv, cmdbuf);
+#endif
+#ifdef HAVE_READLINE
+ free(cmd);
+#endif
+ /* process arguments */
+ if (argv && argv[0]) {
+ if (process_args(afc, argc, argv) < 0) {
+ break;
+ }
+ }
+ }
+}
+
+static void device_event_cb(const idevice_event_t* event, void* userdata)
+{
+ if (use_network && event->conn_type != CONNECTION_NETWORK) {
+ return;
+ } else if (!use_network && event->conn_type != CONNECTION_USBMUXD) {
+ return;
+ }
+ if (event->event == IDEVICE_DEVICE_ADD) {
+ if (!udid) {
+ udid = strdup(event->udid);
+ }
+ if (strcmp(udid, event->udid) == 0) {
+ connected = 1;
+ }
+ } else if (event->event == IDEVICE_DEVICE_REMOVE) {
+ if (strcmp(udid, event->udid) == 0) {
+ connected = 0;
+ printf("\n[disconnected]\n");
+ handle_signal(SIGINT);
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ const char* appid = NULL;
+ int ret = 0;
+ idevice_t device = NULL;
+ lockdownd_client_t lockdown = NULL;
+ lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
+ lockdownd_service_descriptor_t service = NULL;
+ afc_client_t afc = NULL;
+ house_arrest_client_t house_arrest = NULL;
+ const char* service_name = AFC_SERVICE_NAME;
+ int use_container = 0;
+
+ int c = 0;
+ const struct option longopts[] = {
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "help", no_argument, NULL, 'h' },
+ { "debug", no_argument, NULL, 'd' },
+ { "version", no_argument, NULL, 'v' },
+ { "documents", required_argument, NULL, OPT_DOCUMENTS },
+ { "container", required_argument, NULL, OPT_CONTAINER },
+ { NULL, 0, NULL, 0}
+ };
+
+ signal(SIGTERM, handle_signal);
+#ifndef WIN32
+ signal(SIGQUIT, handle_signal);
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
+ while ((c = getopt_long(argc, argv, "du:nhv", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ idevice_set_debug_level(1);
+ break;
+ case 'u':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ udid = strdup(optarg);
+ break;
+ case 'n':
+ use_network = 1;
+ break;
+ case 'h':
+ print_usage(argc, argv, 0);
+ return 0;
+ case 'v':
+ printf("%s %s", TOOL_NAME, PACKAGE_VERSION);
+#ifdef HAVE_READLINE
+ printf(" (readline)");
+#endif
+ printf("\n");
+ return 0;
+ case OPT_DOCUMENTS:
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: '--documents' requires a non-empty app ID!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ appid = optarg;
+ use_container = 0;
+ break;
+ case OPT_CONTAINER:
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: '--container' requires a not-empty app ID!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ appid = optarg;
+ use_container = 1;
+ break;
+ default:
+ print_usage(argc, argv, 1);
+ return 2;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ int num = 0;
+ idevice_info_t *devices = NULL;
+ idevice_get_device_list_extended(&devices, &num);
+ int count = 0;
+ for (int i = 0; i < num; i++) {
+ if (devices[i]->conn_type == CONNECTION_NETWORK && use_network) {
+ count++;
+ } else if (devices[i]->conn_type == CONNECTION_USBMUXD) {
+ count++;
+ }
+ }
+ idevice_device_list_extended_free(devices);
+ if (count == 0) {
+ fprintf(stderr, "No device found. Plug in a device or pass UDID with -u to wait for device to be available.\n");
+ return 1;
+ }
+
+ idevice_events_subscribe(&context, device_event_cb, NULL);
+
+ while (!connected && !stop_requested) {
+#ifdef WIN32
+ Sleep(100);
+#else
+ usleep(100000);
+#endif
+ }
+ if (stop_requested) {
+ return 0;
+ }
+
+ ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX);
+ if (ret != IDEVICE_E_SUCCESS) {
+ if (udid) {
+ fprintf(stderr, "ERROR: Device %s not found!\n", udid);
+ } else {
+ fprintf(stderr, "ERROR: No device found!\n");
+ }
+ return 1;
+ }
+
+ do {
+ if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) {
+ fprintf(stderr, "ERROR: Could not connect to lockdownd: %s (%d)\n", lockdownd_strerror(ldret), ldret);
+ ret = 1;
+ break;
+ }
+
+ if (appid) {
+ service_name = HOUSE_ARREST_SERVICE_NAME;
+ }
+
+ ldret = lockdownd_start_service(lockdown, service_name, &service);
+ if (ldret != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to start service %s: %s (%d)\n", service_name, lockdownd_strerror(ldret), ldret);
+ ret = 1;
+ break;
+ }
+
+ if (appid) {
+ house_arrest_client_new(device, service, &house_arrest);
+ if (!house_arrest) {
+ fprintf(stderr, "Could not start document sharing service!\n");
+ ret = 1;
+ break;
+ }
+
+ if (house_arrest_send_command(house_arrest, use_container ? "VendContainer": "VendDocuments", appid) != HOUSE_ARREST_E_SUCCESS) {
+ fprintf(stderr, "Could not send house_arrest command!\n");
+ ret = 1;
+ break;
+ }
+
+ plist_t dict = NULL;
+ if (house_arrest_get_result(house_arrest, &dict) != HOUSE_ARREST_E_SUCCESS) {
+ fprintf(stderr, "Could not get result from document sharing service!\n");
+ break;
+ }
+ plist_t node = plist_dict_get_item(dict, "Error");
+ if (node) {
+ char *str = NULL;
+ plist_get_string_val(node, &str);
+ fprintf(stderr, "ERROR: %s\n", str);
+ if (str && !strcmp(str, "InstallationLookupFailed")) {
+ 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);
+ }
+ free(str);
+ plist_free(dict);
+ break;
+ }
+ plist_free(dict);
+ afc_client_new_from_house_arrest_client(house_arrest, &afc);
+ } else {
+ afc_client_new(device, service, &afc);
+ }
+ lockdownd_service_descriptor_free(service);
+ lockdownd_client_free(lockdown);
+ lockdown = NULL;
+
+ curdir = strdup("/");
+ curdir_len = 1;
+
+ if (argc > 0) {
+ // command line mode
+ process_args(afc, argc, argv);
+ } else {
+ // interactive mode
+ start_cmdline(afc);
+ }
+
+ } while (0);
+
+ if (afc) {
+ afc_client_free(afc);
+ }
+ if (lockdown) {
+ lockdownd_client_free(lockdown);
+ }
+ idevice_free(device);
+
+ return ret;
+}
diff --git a/tools/idevicebackup.c b/tools/idevicebackup.c
index 5694c12..c0537b8 100644
--- a/tools/idevicebackup.c
+++ b/tools/idevicebackup.c
@@ -32,24 +32,6 @@
#include <stdlib.h>
#include <signal.h>
#include <getopt.h>
-#if defined(HAVE_OPENSSL)
-#include <openssl/sha.h>
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
-#include <openssl/evp.h>
-#endif
-#elif defined(HAVE_GNUTLS)
-#include <gcrypt.h>
-#elif defined(HAVE_MBEDTLS)
-#include <mbedtls/sha1.h>
-#if MBEDTLS_VERSION_NUMBER < 0x03000000
-#define mbedtls_sha1 mbedtls_sha1_ret
-#define mbedtls_sha1_starts mbedtls_sha1_starts_ret
-#define mbedtls_sha1_update mbedtls_sha1_update_ret
-#define mbedtls_sha1_finish mbedtls_sha1_finish_ret
-#endif
-#else
-#error No supported crypto library enabled
-#endif
#include <unistd.h>
#include <ctype.h>
#include <time.h>
@@ -59,6 +41,7 @@
#include <libimobiledevice/mobilebackup.h>
#include <libimobiledevice/notification_proxy.h>
#include <libimobiledevice/afc.h>
+#include <libimobiledevice-glue/sha.h>
#include <libimobiledevice-glue/utils.h>
#include <plist/plist.h>
@@ -91,17 +74,6 @@ enum device_link_file_status_t {
DEVICE_LINK_FILE_STATUS_LAST_HUNK
};
-static void sha1_of_data(const char *input, uint32_t size, unsigned char *hash_out)
-{
-#if defined(HAVE_OPENSSL)
- SHA1((const unsigned char*)input, size, hash_out);
-#elif defined(HAVE_GNUTLS)
- gcry_md_hash_buffer(GCRY_MD_SHA1, hash_out, input, size);
-#elif defined(HAVE_MBEDTLS)
- mbedtls_sha1((unsigned char*)input, size, hash_out);
-#endif
-}
-
static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, int hash_len)
{
int i;
@@ -113,104 +85,49 @@ static int compare_hash(const unsigned char *hash1, const unsigned char *hash2,
return 1;
}
-static void _sha1_update(void* context, const char* data, size_t len)
-{
-#if defined(HAVE_OPENSSL)
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- EVP_DigestUpdate(context, data, len);
-#else
- SHA1_Update(context, data, len);
-#endif
-#elif defined(HAVE_GNUTLS)
- gcry_md_write(context, data, len);
-#elif defined(HAVE_MBEDTLS)
- mbedtls_sha1_update(context, (const unsigned char*)data, len);
-#endif
-}
-
static void compute_datahash(const char *path, const char *destpath, uint8_t greylist, const char *domain, const char *appid, const char *version, unsigned char *hash_out)
{
-#if defined(HAVE_OPENSSL)
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- EVP_MD_CTX* sha1 = EVP_MD_CTX_new();
- EVP_DigestInit(sha1, EVP_sha1());
- void* psha1 = sha1;
-#else
- SHA_CTX sha1;
- SHA1_Init(&sha1);
- void* psha1 = &sha1;
-#endif
-#elif defined(HAVE_GNUTLS)
- gcry_md_hd_t hd = NULL;
- gcry_md_open(&hd, GCRY_MD_SHA1, 0);
- if (!hd) {
- printf("ERROR: Could not initialize libgcrypt/SHA1\n");
- return;
- }
- gcry_md_reset(hd);
- void* psha1 = hd;
-#elif defined(HAVE_MBEDTLS)
- mbedtls_sha1_context sha1;
- mbedtls_sha1_init(&sha1);
- mbedtls_sha1_starts(&sha1);
- void* psha1 = &sha1;
-#endif
+ sha1_context sha1;
+ sha1_init(&sha1);
FILE *f = fopen(path, "rb");
if (f) {
unsigned char buf[16384];
size_t len;
while ((len = fread(buf, 1, 16384, f)) > 0) {
- _sha1_update(psha1, (const char*)buf, len);
+ sha1_update(&sha1, buf, len);
}
fclose(f);
- _sha1_update(psha1, destpath, strlen(destpath));
- _sha1_update(psha1, ";", 1);
+ sha1_update(&sha1, destpath, strlen(destpath));
+ sha1_update(&sha1, ";", 1);
if (greylist == 1) {
- _sha1_update(psha1, "true", 4);
+ sha1_update(&sha1, "true", 4);
} else {
- _sha1_update(psha1, "false", 5);
+ sha1_update(&sha1, "false", 5);
}
- _sha1_update(psha1, ";", 1);
+ sha1_update(&sha1, ";", 1);
if (domain) {
- _sha1_update(psha1, domain, strlen(domain));
+ sha1_update(&sha1, domain, strlen(domain));
} else {
- _sha1_update(psha1, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
- _sha1_update(psha1, ";", 1);
+ sha1_update(&sha1, ";", 1);
if (appid) {
- _sha1_update(psha1, appid, strlen(appid));
+ sha1_update(&sha1, appid, strlen(appid));
} else {
- _sha1_update(psha1, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
- _sha1_update(psha1, ";", 1);
+ sha1_update(&sha1, ";", 1);
if (version) {
- _sha1_update(psha1, version, strlen(version));
+ sha1_update(&sha1, version, strlen(version));
} else {
- _sha1_update(psha1, "(null)", 6);
+ sha1_update(&sha1, "(null)", 6);
}
-#if defined(HAVE_OPENSSL)
-#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- EVP_DigestFinal(sha1, hash_out, NULL);
- EVP_MD_CTX_destroy(sha1);
-#else
- SHA1_Final(hash_out, &sha1);
-#endif
-#elif defined(HAVE_GNUTLS)
- unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1);
- memcpy(hash_out, newhash, 20);
-#elif defined(HAVE_MBEDTLS)
- mbedtls_sha1_finish(&sha1, hash_out);
-#endif
+ sha1_final(&sha1, hash_out);
}
-#if defined(HAVE_GNUTLS)
- gcry_md_close(hd);
-#elif defined(HAVE_MBEDTLS)
- mbedtls_sha1_free(&sha1);
-#endif
}
static void print_hash(const unsigned char *hash, int len)
@@ -547,7 +464,7 @@ static int mobilebackup_check_file_integrity(const char *backup_directory, const
unsigned char fnhash[20];
char fnamehash[41];
char *p = fnamehash;
- sha1_of_data(fnstr, strlen(fnstr), fnhash);
+ sha1((const unsigned char*)fnstr, strlen(fnstr), fnhash);
free(fnstr);
int i;
for ( i = 0; i < 20; i++, p += 2 ) {
@@ -1285,14 +1202,14 @@ files_out:
}
printf("Verifying backup integrity, please wait.\n");
- char *bin = NULL;
+ unsigned char *bin = NULL;
uint64_t binsize = 0;
node = plist_dict_get_item(manifest_plist, "Data");
if (!node || (plist_get_node_type(node) != PLIST_DATA)) {
printf("Could not read Data key from Manifest.plist!\n");
break;
}
- plist_get_data_val(node, &bin, &binsize);
+ plist_get_data_val(node, (char**)&bin, &binsize);
plist_t backup_data = NULL;
if (bin) {
char *auth_ver = NULL;
@@ -1309,7 +1226,7 @@ files_out:
if (auth_sig && (auth_sig_len == 20)) {
/* calculate the sha1, then compare */
unsigned char data_sha1[20];
- sha1_of_data(bin, binsize, data_sha1);
+ sha1(bin, binsize, data_sha1);
if (compare_hash(auth_sig, data_sha1, 20)) {
printf("AuthSignature is valid\n");
} else {
@@ -1322,7 +1239,7 @@ files_out:
} else if (auth_ver) {
printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver);
}
- plist_from_bin(bin, (uint32_t)binsize, &backup_data);
+ plist_from_bin((char*)bin, (uint32_t)binsize, &backup_data);
free(bin);
}
if (!backup_data) {
diff --git a/tools/idevicecrashreport.c b/tools/idevicecrashreport.c
index 484212e..09bd537 100644
--- a/tools/idevicecrashreport.c
+++ b/tools/idevicecrashreport.c
@@ -163,7 +163,7 @@ static int afc_client_copy_and_remove_crash_reports(afc_client_t afc, const char
char* p = strrchr(list[k], '.');
if (p != NULL && !strncmp(p, ".synced", 7)) {
/* make sure to strip ".synced" extension as seen on iOS 5 */
- int newlen = strlen(list[k]) - 7;
+ size_t newlen = p - list[k];
strncpy(((char*)target_filename) + host_directory_length, list[k], newlen);
target_filename[host_directory_length + newlen] = '\0';
} else {
diff --git a/tools/idevicedevmodectl.c b/tools/idevicedevmodectl.c
index ba9b935..bd1de6a 100644
--- a/tools/idevicedevmodectl.c
+++ b/tools/idevicedevmodectl.c
@@ -41,6 +41,7 @@
#define __usleep(x) Sleep(x/1000)
#else
#include <arpa/inet.h>
+#include <unistd.h>
#define __usleep(x) usleep(x)
#endif
@@ -145,7 +146,6 @@ static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t ms
plist_t reply = NULL;
perr = property_list_service_receive_plist(amfi, &reply);
if (perr == PROPERTY_LIST_SERVICE_E_SUCCESS) {
- uint8_t success = 0;
plist_t val = plist_dict_get_item(reply, "Error");
if (val) {
const char* err = plist_get_string_ptr(val, NULL);
@@ -156,15 +156,7 @@ static int amfi_service_send_msg(property_list_service_client_t amfi, plist_t ms
res = 1;
}
} else {
- val = plist_dict_get_item(reply, "success");
- if (val) {
- plist_get_bool_val(val, &success);
- }
- if (success) {
- res = 0;
- } else {
- res = 1;
- }
+ res = plist_dict_get_item(reply, "success") ? 0 : 1;
}
} else {
fprintf(stderr, "Could not receive reply from device: %d\n", perr);
@@ -397,6 +389,7 @@ int main(int argc, char *argv[])
if ((op == OP_ENABLE || op == OP_ARM) && dev_mode_status) {
if (dev_mode_status) {
+ idevice_free(device);
printf("DeveloperMode is already enabled.\n");
return 0;
}
@@ -410,13 +403,30 @@ int main(int argc, char *argv[])
} else {
printf("%s: Developer Mode armed, waiting for reboot...\n", udid);
- // waiting for device to disconnect...
- WAIT_FOR(!device_connected, 20);
-
- // waiting for device to reconnect...
- WAIT_FOR(device_connected, 60);
-
- res = amfi_send_action(device, DEV_MODE_ENABLE);
+ do {
+ // waiting for device to disconnect...
+ idevice_free(device);
+ device = NULL;
+ WAIT_FOR(!device_connected, 40);
+ if (device_connected) {
+ printf("%s: ERROR: Device didn't reboot?!\n", udid);
+ res = 2;
+ break;
+ }
+ printf("disconnected\n");
+
+ // waiting for device to reconnect...
+ WAIT_FOR(device_connected, 60);
+ if (!device_connected) {
+ printf("%s: ERROR: Device didn't re-connect?!\n", udid);
+ res = 2;
+ break;
+ }
+ printf("connected\n");
+
+ idevice_new(&device, udid);
+ res = amfi_send_action(device, DEV_MODE_ENABLE);
+ } while (0);
if (res == 0) {
printf("%s: Developer Mode successfully enabled.\n", udid);
} else {
diff --git a/tools/ideviceimagemounter.c b/tools/ideviceimagemounter.c
index f551b6c..511583e 100644
--- a/tools/ideviceimagemounter.c
+++ b/tools/ideviceimagemounter.c
@@ -45,8 +45,11 @@
#include <libimobiledevice/afc.h>
#include <libimobiledevice/notification_proxy.h>
#include <libimobiledevice/mobile_image_mounter.h>
+#include <libimobiledevice-glue/sha.h>
+#include <libimobiledevice-glue/utils.h>
#include <asprintf.h>
#include <plist/plist.h>
+#include <libtatsu/tss.h>
static int list_mode = 0;
static int use_network = 0;
@@ -62,18 +65,38 @@ typedef enum {
DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE
} disk_image_upload_type_t;
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_MOUNT,
+ CMD_UNMOUNT,
+ CMD_LIST,
+ CMD_DEVMODESTATUS
+};
+
+int cmd = CMD_NONE;
+
static void print_usage(int argc, char **argv, int is_error)
{
char *name = strrchr(argv[0], '/');
- fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] IMAGE_FILE IMAGE_SIGNATURE_FILE\n", (name ? name + 1: argv[0]));
+ fprintf(is_error ? stderr : stdout, "Usage: %s [OPTIONS] COMMAND [COMMAND OPTIONS...]\n", (name ? name + 1: argv[0]));
fprintf(is_error ? stderr : stdout,
"\n"
- "Mounts the specified disk image on the device.\n"
+ "Mount, list, or unmount a disk image on the device.\n"
+ "\n"
+ "COMMANDS:\n"
+ " mount PATH Mount the developer disk image at PATH.\n"
+ " For iOS 17+, PATH is a directory containing a .dmg image,\n"
+ " a BuildManifest.plist, and a Firmware sub-directory;\n"
+ " for older versions PATH is a .dmg filename with a"
+ " .dmg.signature in the same directory, or with another\n"
+ " parameter pointing to a file elsewhere.\n"
+ " list List mounted disk images.\n"
+ " unmount PATH Unmount the image mounted at PATH.\n"
+ " devmodestatus Query the developer mode status (iOS 16+)\n"
"\n"
"OPTIONS:\n"
" -u, --udid UDID target specific device by UDID\n"
" -n, --network connect to network device\n"
- " -l, --list List mount information\n"
" -t, --imagetype TYPE Image type to use, default is 'Developer'\n"
" -x, --xml Use XML output\n"
" -d, --debug enable communication debugging\n"
@@ -87,11 +110,11 @@ static void print_usage(int argc, char **argv, int is_error)
static void parse_opts(int argc, char **argv)
{
+ int debug_level = 0;
static struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "udid", required_argument, NULL, 'u' },
{ "network", no_argument, NULL, 'n' },
- { "list", no_argument, NULL, 'l' },
{ "imagetype", required_argument, NULL, 't' },
{ "xml", no_argument, NULL, 'x' },
{ "debug", no_argument, NULL, 'd' },
@@ -101,7 +124,7 @@ static void parse_opts(int argc, char **argv)
int c;
while (1) {
- c = getopt_long(argc, argv, "hu:lt:xdnv", longopts, NULL);
+ c = getopt_long(argc, argv, "hu:t:xdnv", longopts, NULL);
if (c == -1) {
break;
}
@@ -121,9 +144,6 @@ static void parse_opts(int argc, char **argv)
case 'n':
use_network = 1;
break;
- case 'l':
- list_mode = 1;
- break;
case 't':
imagetype = optarg;
break;
@@ -131,7 +151,7 @@ static void parse_opts(int argc, char **argv)
xml_mode = 1;
break;
case 'd':
- idevice_set_debug_level(1);
+ debug_level++;
break;
case 'v':
printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
@@ -141,6 +161,8 @@ static void parse_opts(int argc, char **argv)
exit(2);
}
}
+ idevice_set_debug_level(debug_level);
+ tss_set_debug_level(debug_level);
}
static ssize_t mim_upload_cb(void* buf, size_t size, void* userdata)
@@ -169,29 +191,75 @@ int main(int argc, char **argv)
argc -= optind;
argv += optind;
- if (!list_mode) {
- if (argc < 1) {
- printf("ERROR: No IMAGE_FILE has been given!\n");
- return -1;
- }
- image_path = strdup(argv[0]);
- if (argc >= 2) {
- image_sig_path = strdup(argv[1]);
+ if (argc == 0) {
+ fprintf(stderr, "ERROR: Missing command.\n\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+
+ char* cmdstr = argv[0];
+
+ int optind2 = 0;
+ if (!strcmp(cmdstr, "mount")) {
+ cmd = CMD_MOUNT;
+ optind2++;
+ } else if (!strcmp(cmdstr, "list")) {
+ cmd = CMD_LIST;
+ optind2++;
+ } else if (!strcmp(cmdstr, "umount") || !strcmp(cmdstr, "unmount")) {
+ cmd = CMD_UNMOUNT;
+ optind2++;
+ } else if (!strcmp(cmdstr, "devmodestatus")) {
+ cmd = CMD_DEVMODESTATUS;
+ optind2++;
+ } else {
+ // assume mount command, unless -l / --list was specified
+ if (list_mode) {
+ cmd = CMD_LIST;
} else {
- if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) {
- printf("Out of memory?!\n");
- return -1;
- }
+ cmd = CMD_MOUNT;
}
}
+ argc -= optind2;
+ argv += optind2;
+ optind += optind2;
+
+ switch (cmd) {
+ case CMD_MOUNT:
+ if (argc < 1) {
+ fprintf(stderr, "ERROR: Missing IMAGE_FILE for mount command\n");
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ image_path = strdup(argv[0]);
+ if (argc >= 2) {
+ image_sig_path = strdup(argv[1]);
+ } else {
+ if (asprintf(&image_sig_path, "%s.signature", image_path) < 0) {
+ printf("Out of memory?!\n");
+ return 1;
+ }
+ }
+ break;
+ case CMD_UNMOUNT:
+ if (argc != 1) {
+ fprintf(stderr, "ERROR: Missing mount path (argc = %d)\n", argc);
+ print_usage(argc+optind, argv-optind, 1);
+ return 2;
+ }
+ break;
+ default:
+ break;
+ }
+
if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
if (udid) {
printf("No device found with udid %s.\n", udid);
} else {
printf("No device found.\n");
}
- return -1;
+ return 1;
}
if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lckd, TOOL_NAME))) {
@@ -215,7 +283,7 @@ int main(int argc, char **argv)
}
}
- if (product_version_major == 16) {
+ if (product_version_major >= 16) {
uint8_t dev_mode_status = 0;
plist_t val = NULL;
ldret = lockdownd_get_value(lckd, "com.apple.security.mac.amfi", "DeveloperModeStatus", &val);
@@ -246,7 +314,7 @@ int main(int argc, char **argv)
service = NULL;
}
- if (!list_mode) {
+ if (cmd == CMD_MOUNT) {
struct stat fst;
if (disk_image_upload_type == DISK_IMAGE_UPLOAD_TYPE_AFC) {
if ((lockdownd_start_service(lckd, "com.apple.afc", &service) !=
@@ -268,7 +336,7 @@ int main(int argc, char **argv)
goto leave;
}
image_size = fst.st_size;
- if (stat(image_sig_path, &fst) != 0) {
+ if (product_version_major < 17 && stat(image_sig_path, &fst) != 0) {
fprintf(stderr, "ERROR: stat: %s: %s\n", image_sig_path, strerror(errno));
goto leave;
}
@@ -280,10 +348,14 @@ int main(int argc, char **argv)
mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
plist_t result = NULL;
- if (list_mode) {
+ if (cmd == CMD_LIST) {
/* list mounts mode */
if (!imagetype) {
- imagetype = "Developer";
+ if (product_version_major < 17) {
+ imagetype = "Developer";
+ } else {
+ imagetype = "Personalized";
+ }
}
err = mobile_image_mounter_lookup_image(mim, imagetype, &result);
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
@@ -292,25 +364,217 @@ int main(int argc, char **argv)
} else {
printf("Error: lookup_image returned %d\n", err);
}
- } else {
- char sig[8192];
+ } else if (cmd == CMD_MOUNT) {
+ unsigned char *sig = NULL;
size_t sig_length = 0;
- FILE *f = fopen(image_sig_path, "rb");
- if (!f) {
- fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno));
- goto leave;
- }
- sig_length = fread(sig, 1, sizeof(sig), f);
- fclose(f);
- if (sig_length == 0) {
- fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path);
- goto leave;
- }
+ FILE *f;
+ struct stat fst;
+ plist_t mount_options = NULL;
- f = fopen(image_path, "rb");
- if (!f) {
- fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
- goto leave;
+ if (product_version_major < 17) {
+ f = fopen(image_sig_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening signature file '%s': %s\n", image_sig_path, strerror(errno));
+ goto leave;
+ }
+ if (fstat(fileno(f), &fst) != 0) {
+ fprintf(stderr, "Error: fstat: %s\n", strerror(errno));
+ goto leave;
+ }
+ sig = malloc(fst.st_size);
+ sig_length = fread(sig, 1, fst.st_size, f);
+ fclose(f);
+ if (sig_length == 0) {
+ fprintf(stderr, "Could not read signature from file '%s'\n", image_sig_path);
+ goto leave;
+ }
+
+ f = fopen(image_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+ } else {
+ if (stat(image_path, &fst) != 0) {
+ fprintf(stderr, "Error: stat: '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+ if (!S_ISDIR(fst.st_mode)) {
+ fprintf(stderr, "Error: Personalized Disk Image mount expects a directory as image path.\n");
+ goto leave;
+ }
+ char* build_manifest_path = string_build_path(image_path, "BuildManifest.plist", NULL);
+ plist_t build_manifest = NULL;
+ if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) != 0) {
+ free(build_manifest_path);
+ build_manifest_path = string_build_path(image_path, "Restore", "BuildManifest.plist", NULL);
+ if (plist_read_from_file(build_manifest_path, &build_manifest, NULL) == 0) {
+ char* image_path_new = string_build_path(image_path, "Restore", NULL);
+ free(image_path);
+ image_path = image_path_new;
+ }
+ }
+ if (!build_manifest) {
+ fprintf(stderr, "Error: Could not locate BuildManifest.plist inside given disk image path!\n");
+ goto leave;
+ }
+
+ plist_t identifiers = NULL;
+ mobile_image_mounter_error_t merr = mobile_image_mounter_query_personalization_identifiers(mim, NULL, &identifiers);
+ if (merr != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ fprintf(stderr, "Failed to query personalization identifiers: %d\n", merr);
+ goto error_out;
+ }
+
+ unsigned int board_id = plist_dict_get_uint(identifiers, "BoardId");
+ unsigned int chip_id = plist_dict_get_uint(identifiers, "ChipID");
+
+ plist_t build_identities = plist_dict_get_item(build_manifest, "BuildIdentities");
+ plist_array_iter iter;
+ plist_array_new_iter(build_identities, &iter);
+ plist_t item = NULL;
+ plist_t build_identity = NULL;
+ do {
+ plist_array_next_item(build_identities, iter, &item);
+ if (!item) {
+ break;
+ }
+ unsigned int bi_board_id = (unsigned int)plist_dict_get_uint(item, "ApBoardID");
+ unsigned int bi_chip_id = (unsigned int)plist_dict_get_uint(item, "ApChipID");
+ if (bi_chip_id == chip_id && bi_board_id == board_id) {
+ build_identity = item;
+ break;
+ }
+ } while (item);
+ plist_mem_free(iter);
+ if (!build_identity) {
+ fprintf(stderr, "Error: The given disk image is not compatible with the current device.\n");
+ goto leave;
+ }
+ plist_t p_tc_path = plist_access_path(build_identity, 4, "Manifest", "LoadableTrustCache", "Info", "Path");
+ if (!p_tc_path) {
+ fprintf(stderr, "Error: Could not determine path for trust cache!\n");
+ goto leave;
+ }
+ plist_t p_dmg_path = plist_access_path(build_identity, 4, "Manifest", "PersonalizedDMG", "Info", "Path");
+ if (!p_dmg_path) {
+ fprintf(stderr, "Error: Could not determine path for disk image!\n");
+ goto leave;
+ }
+ char *tc_path = string_build_path(image_path, plist_get_string_ptr(p_tc_path, NULL), NULL);
+ unsigned char* trust_cache = NULL;
+ uint64_t trust_cache_size = 0;
+ if (!buffer_read_from_filename(tc_path, (char**)&trust_cache, &trust_cache_size)) {
+ fprintf(stderr, "Error: Trust cache does not exist at '%s'!\n", tc_path);
+ goto leave;
+ }
+ mount_options = plist_new_dict();
+ plist_dict_set_item(mount_options, "ImageTrustCache", plist_new_data((char*)trust_cache, trust_cache_size));
+ free(trust_cache);
+ char *dmg_path = string_build_path(image_path, plist_get_string_ptr(p_dmg_path, NULL), NULL);
+ free(image_path);
+ image_path = dmg_path;
+ f = fopen(image_path, "rb");
+ if (!f) {
+ fprintf(stderr, "Error opening image file '%s': %s\n", image_path, strerror(errno));
+ goto leave;
+ }
+
+ unsigned char buf[8192];
+ unsigned char sha384_digest[48];
+ sha384_context ctx;
+ sha384_init(&ctx);
+ fstat(fileno(f), &fst);
+ image_size = fst.st_size;
+ while (!feof(f)) {
+ ssize_t fr = fread(buf, 1, sizeof(buf), f);
+ if (fr <= 0) {
+ break;
+ }
+ sha384_update(&ctx, buf, fr);
+ }
+ rewind(f);
+ sha384_final(&ctx, sha384_digest);
+ unsigned char* manifest = NULL;
+ unsigned int manifest_size = 0;
+ /* check if the device already has a personalization manifest for this image */
+ if (mobile_image_mounter_query_personalization_manifest(mim, "DeveloperDiskImage", sha384_digest, sizeof(sha384_digest), &manifest, &manifest_size) == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ printf("Using existing personalization manifest from device.\n");
+ } else {
+ /* we need to re-connect in this case */
+ mobile_image_mounter_free(mim);
+ mim = NULL;
+ if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ goto error_out;
+ }
+ printf("No personalization manifest, requesting from TSS...\n");
+ unsigned char* nonce = NULL;
+ unsigned int nonce_size = 0;
+
+ /* create new TSS request and fill parameters */
+ plist_t request = tss_request_new(NULL);
+ plist_t params = plist_new_dict();
+ tss_parameters_add_from_manifest(params, build_identity, 1);
+
+ /* copy all `Ap,*` items from identifiers */
+ plist_dict_iter di = NULL;
+ plist_dict_new_iter(identifiers, &di);
+ plist_t node = NULL;
+ do {
+ char* key = NULL;
+ plist_dict_next_item(identifiers, di, &key, &node);
+ if (node) {
+ if (!strncmp(key, "Ap,", 3)) {
+ plist_dict_set_item(request, key, plist_copy(node));
+ }
+ }
+ free(key);
+ } while (node);
+ plist_mem_free(di);
+
+ plist_dict_copy_uint(params, identifiers, "ApECID", "UniqueChipID");
+ plist_dict_set_item(params, "ApProductionMode", plist_new_bool(1));
+ plist_dict_set_item(params, "ApSecurityMode", plist_new_bool(1));
+ plist_dict_set_item(params, "ApSupportsImg4", plist_new_bool(1));
+
+ /* query nonce from image mounter service */
+ merr = mobile_image_mounter_query_nonce(mim, "DeveloperDiskImage", &nonce, &nonce_size);
+ if (merr == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ plist_dict_set_item(params, "ApNonce", plist_new_data((char*)nonce, nonce_size));
+ } else {
+ fprintf(stderr, "ERROR: Failed to query nonce for developer disk image: %d\n", merr);
+ goto error_out;
+ }
+ mobile_image_mounter_free(mim);
+ mim = NULL;
+
+ plist_dict_set_item(params, "ApSepNonce", plist_new_data("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 20));
+ plist_dict_set_item(params, "UID_MODE", plist_new_bool(0));
+ tss_request_add_ap_tags(request, params, NULL);
+ tss_request_add_common_tags(request, params, NULL);
+ tss_request_add_ap_img4_tags(request, params);
+ plist_free(params);
+
+ /* request IM4M from TSS */
+ plist_t response = tss_request_send(request, NULL);
+ plist_free(request);
+
+ plist_t p_manifest = plist_dict_get_item(response, "ApImg4Ticket");
+ if (!PLIST_IS_DATA(p_manifest)) {
+ fprintf(stderr, "Failed to get Img4Ticket\n");
+ goto error_out;
+ }
+
+ uint64_t m4m_len = 0;
+ plist_get_data_val(p_manifest, (char**)&manifest, &m4m_len);
+ manifest_size = m4m_len;
+ plist_free(response);
+ printf("Done.\n");
+ }
+ sig = manifest;
+ sig_length = manifest_size;
+
+ imagetype = "Personalized";
}
char *targetname = NULL;
@@ -324,11 +588,16 @@ int main(int argc, char **argv)
goto leave;
}
-
if (!imagetype) {
imagetype = "Developer";
}
+ if (!mim) {
+ if (mobile_image_mounter_start_service(device, &mim, TOOL_NAME) != MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ goto error_out;
+ }
+ }
+
switch(disk_image_upload_type) {
case DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE:
printf("Uploading %s\n", image_path);
@@ -403,7 +672,7 @@ int main(int argc, char **argv)
printf("done.\n");
printf("Mounting...\n");
- err = mobile_image_mounter_mount_image(mim, mountname, sig, sig_length, imagetype, &result);
+ err = mobile_image_mounter_mount_image_with_options(mim, mountname, sig, sig_length, imagetype, mount_options, &result);
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
if (result) {
plist_t node = plist_dict_get_item(result, "Status");
@@ -435,7 +704,10 @@ int main(int argc, char **argv)
printf("unexpected result:\n");
plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
-
+ node = plist_dict_get_item(result, "DetailedError");
+ if (node) {
+ printf("DetailedError: %s\n", plist_get_string_ptr(node, NULL));
+ }
} else {
plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
}
@@ -444,6 +716,37 @@ int main(int argc, char **argv)
printf("Error: mount_image returned %d\n", err);
}
+ } else if (cmd == CMD_UNMOUNT) {
+ err = mobile_image_mounter_unmount_image(mim, argv[0]);
+ switch (err) {
+ case MOBILE_IMAGE_MOUNTER_E_SUCCESS:
+ printf("Success\n");
+ res = 0;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED:
+ printf("Error: '%s' is not mounted\n", argv[0]);
+ res = 1;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED:
+ printf("Error: 'unmount' is not supported on this device\n");
+ res = 1;
+ break;
+ case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED:
+ printf("Error: device is locked\n");
+ res = 1;
+ break;
+ default:
+ printf("Error: unmount returned %d\n", err);
+ break;
+ }
+ } else if (cmd == CMD_DEVMODESTATUS) {
+ err = mobile_image_mounter_query_developer_mode_status(mim, &result);
+ if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
+ res = 0;
+ plist_write_to_stream(result, stdout, (xml_mode) ? PLIST_FORMAT_XML : PLIST_FORMAT_LIMD, 0);
+ } else {
+ printf("Error: query_developer_mode_status returned %d\n", err);
+ }
}
if (result) {
@@ -466,7 +769,7 @@ leave:
idevice_free(device);
if (image_path)
- free(image_path);
+ free(image_path);
if (image_sig_path)
free(image_sig_path);
diff --git a/tools/idevicesyslog.c b/tools/idevicesyslog.c
index 5600be2..a0e641d 100644
--- a/tools/idevicesyslog.c
+++ b/tools/idevicesyslog.c
@@ -480,7 +480,10 @@ static void print_usage(int argc, char **argv, int is_error)
" -h, --help prints usage information\n"
" -d, --debug enable communication debugging\n"
" -v, --version prints version information\n"
- " --no-colors disable colored output\n"
+ " --no-colors disable colored output\n"
+ " -o, --output FILE write to FILE instead of stdout\n"
+ " (existing FILE will be overwritten)\n"
+ " --colors force writing colored output, e.g. for --output\n"
"\n"
"FILTER OPTIONS:\n"
" -m, --match STRING only print messages that contain STRING\n"
@@ -508,6 +511,7 @@ int main(int argc, char *argv[])
int exclude_filter = 0;
int include_kernel = 0;
int exclude_kernel = 0;
+ int force_colors = 0;
int c = 0;
const struct option longopts[] = {
{ "debug", no_argument, NULL, 'd' },
@@ -525,6 +529,8 @@ int main(int argc, char *argv[])
{ "no-kernel", no_argument, NULL, 'K' },
{ "quiet-list", no_argument, NULL, 1 },
{ "no-colors", no_argument, NULL, 2 },
+ { "colors", no_argument, NULL, 3 },
+ { "output", required_argument, NULL, 'o' },
{ "version", no_argument, NULL, 'v' },
{ NULL, 0, NULL, 0}
};
@@ -536,7 +542,7 @@ int main(int argc, char *argv[])
signal(SIGPIPE, SIG_IGN);
#endif
- while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:e:p:qkKv", longopts, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "dhu:nxt:T:m:e:p:qkKo:v", longopts, NULL)) != -1) {
switch (c) {
case 'd':
idevice_set_debug_level(1);
@@ -638,6 +644,22 @@ int main(int argc, char *argv[])
case 2:
term_colors_set_enabled(0);
break;
+ case 3:
+ force_colors = 1;
+ break;
+ case 'o':
+ if (!*optarg) {
+ fprintf(stderr, "ERROR: --output option requires an argument!\n");
+ print_usage(argc, argv, 1);
+ return 2;
+ } else {
+ if (freopen(optarg, "w", stdout) == NULL) {
+ fprintf(stderr, "ERROR: Failed to open output file '%s' for writing: %s\n", optarg, strerror(errno));
+ return 1;
+ }
+ term_colors_set_enabled(0);
+ }
+ break;
case 'v':
printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION);
return 0;
@@ -647,6 +669,10 @@ int main(int argc, char *argv[])
}
}
+ if (force_colors) {
+ term_colors_set_enabled(1);
+ }
+
if (include_kernel > 0 && exclude_kernel > 0) {
fprintf(stderr, "ERROR: -k and -K cannot be used together.\n");
print_usage(argc, argv, 1);