summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml17
-rw-r--r--Makefile.am6
-rw-r--r--README.md12
-rw-r--r--configure.ac23
-rwxr-xr-xgit-version-gen20
-rw-r--r--m4/as-compiler-flag.m44
-rw-r--r--man/ideviceinstaller.1151
-rw-r--r--src/ideviceinstaller.c870
8 files changed, 652 insertions, 451 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ffff6db..9c0508a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,9 @@
name: build
-on: [push]
+on:
+ push:
+ schedule:
+ - cron: '0 0 1 * *'
jobs:
build-linux-ubuntu:
@@ -50,7 +53,7 @@ jobs:
rm -rf extract/lib
sudo cp -r extract/* /
sudo ldconfig
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
- name: autogen
@@ -65,7 +68,7 @@ jobs:
DESTDIR=`pwd`/dest make install
tar -C dest -cf ideviceinstaller.tar usr
- name: publish artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ideviceinstaller-latest_${{env.target_triplet}}
path: ideviceinstaller.tar
@@ -115,7 +118,7 @@ jobs:
tar -C extract -xvf $I
done
sudo cp -r extract/* /
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: install additional requirements
run: |
SDKDIR=`xcrun --sdk macosx --show-sdk-path 2>/dev/null`
@@ -166,7 +169,7 @@ jobs:
DESTDIR=`pwd`/dest make install
tar -C dest -cf ideviceinstaller.tar usr
- name: publish artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ideviceinstaller-latest_macOS
path: ideviceinstaller.tar
@@ -237,7 +240,7 @@ jobs:
tar -C extract -xvf $I
done
cp -r extract/* /
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: install additional requirements
run: |
FILENAME="libzip-1.7.1-static.tar.bz2"
@@ -259,7 +262,7 @@ jobs:
DESTDIR=`pwd`/dest make install
tar -C dest -cf ideviceinstaller.tar ${{ env.dest }}
- name: publish artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ideviceinstaller-latest_${{ matrix.arch }}-${{ env.dest }}
path: ideviceinstaller.tar
diff --git a/Makefile.am b/Makefile.am
index e6a4e94..5888709 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,4 +3,8 @@ ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src man
EXTRA_DIST = \
- README.md
+ README.md \
+ git-version-gen
+
+dist-hook:
+ echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/README.md b/README.md
index c3d4f34..84d4f38 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,10 @@ communication with iOS devices.
Some key features are:
-- **Status:** Install, upgrade, uninstall, archive, restore and enumerate apps
+- **Status:** Install, upgrade, uninstall, and enumerate apps
- **Browse**: Allows to retrieve a list of installed apps with filter options
- **Install**: Supports app package, carrier bundle and developer .app directory
-- **Format**: Allows command output in plist format
+- **Format**: Allows command output in plist, XML, or JSON format
- **Compatibility**: Supports latest device firmware releases
- **Cross-Platform:** Tested on Linux, macOS, Windows and Android platforms
@@ -60,7 +60,7 @@ First of all attach your device to your machine.
Then simply run:
```shell
-ideviceinstaller --list-apps
+ideviceinstaller list
```
This will print a list of `<appid>` identifiers (bundle identifiers) for use
@@ -68,12 +68,12 @@ with other commands (see further below).
To install an app from a package file use:
```shell
-ideviceinstaller --install <file>
+ideviceinstaller install <file>
```
To uninstall an app with the `<appid>` from the device use:
```shell
-ideviceinstaller --uninstall <appid>
+ideviceinstaller uninstall <appid>
```
Please consult the usage information or manual page for a full documentation of
@@ -124,4 +124,4 @@ iPadOS, tvOS, watchOS, and macOS are trademarks of Apple Inc.
ideviceinstaller is an independent software application and has not been
authorized, sponsored or otherwise approved by Apple Inc.
-README Updated on: 2022-04-04
+README Updated on: 2023-07-20
diff --git a/configure.ac b/configure.ac
index 39e78f5..be124b9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,26 +1,30 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.64)
-AC_INIT([ideviceinstaller], [1.1.2], [https://github.com/libimobiledevice/ideviceinstaller/issues],, [https://libimobiledevice.org])
+AC_PREREQ([2.68])
+AC_INIT([ideviceinstaller], [m4_esyscmd(./git-version-gen $RELEASE_VERSION)], [https://github.com/libimobiledevice/ideviceinstaller/issues], [], [https://libimobiledevice.org])
AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip check-news])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES])
AC_CONFIG_SRCDIR([src/])
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIR([m4])
+# Check if we have a version defined
+if test -z $PACKAGE_VERSION; then
+ AC_MSG_ERROR([PACKAGE_VERSION is not defined. Make sure to configure a source tree checked out from git or that .tarball-version is present.])
+fi
+
# Checks for programs.
AC_PROG_CC
AM_PROG_CC_C_O
-AC_PROG_LIBTOOL
+LT_INIT
# Checks for libraries.
PKG_CHECK_MODULES(libimobiledevice, libimobiledevice-1.0 >= 1.3.0)
-PKG_CHECK_MODULES(libplist, libplist-2.0 >= 2.2.0)
+PKG_CHECK_MODULES(libplist, libplist-2.0 >= 2.3.0)
PKG_CHECK_MODULES(libzip, libzip >= 0.10)
# Checks for header files.
-AC_HEADER_STDC
AC_CHECK_HEADERS([stdint.h stdlib.h string.h])
# Checks for typedefs, structures, and compiler characteristics.
@@ -39,16 +43,16 @@ AC_CHECK_FUNCS([strdup strerror asprintf vasprintf])
# Check for lstat
AC_MSG_CHECKING([whether lstat is available])
-AC_TRY_LINK([
+AC_LINK_IFELSE([AC_LANG_PROGRAM([[
#include <sys/types.h>
#include <sys/stat.h>
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
-],[
+]],[[
struct stat st;
lstat("/tmp", &st);
-], [have_lstat="yes"], [have_lstat="no"])
+]])], [have_lstat="yes"], [have_lstat="no"])
AC_MSG_RESULT([${have_lstat}])
if test "x${have_lstat}" = "xyes" ; then
@@ -60,11 +64,12 @@ AC_SUBST(GLOBAL_CFLAGS)
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
-AC_OUTPUT([
+AC_CONFIG_FILES([
Makefile
src/Makefile
man/Makefile
])
+AC_OUTPUT
echo "
Configuration for $PACKAGE $VERSION:
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..d868952
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,20 @@
+#!/bin/sh
+SRCDIR=`dirname $0`
+if test -n "$1"; then
+ VER=$1
+else
+ if test -r "${SRCDIR}/.git" && test -x "`which git`" ; then
+ git update-index -q --refresh
+ if ! VER=`git describe --tags --dirty 2>/dev/null`; then
+ COMMIT=`git rev-parse --short HEAD`
+ DIRTY=`git diff --quiet HEAD || echo "-dirty"`
+ VER=`sed -n '1,/RE/s/Version \(.*\)/\1/p' ${SRCDIR}/NEWS`-git-${COMMIT}${DIRTY}
+ fi
+ else
+ if test -f "${SRCDIR}/.tarball-version"; then
+ VER=`cat "${SRCDIR}/.tarball-version"`
+ fi
+ fi
+fi
+VER=`printf %s "$VER" | head -n1`
+printf %s "$VER"
diff --git a/m4/as-compiler-flag.m4 b/m4/as-compiler-flag.m4
index 0f660cf..baab5d9 100644
--- a/m4/as-compiler-flag.m4
+++ b/m4/as-compiler-flag.m4
@@ -18,7 +18,7 @@ AC_DEFUN([AS_COMPILER_FLAG],
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $1"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
@@ -44,7 +44,7 @@ AC_DEFUN([AS_COMPILER_FLAGS],
do
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $each"
- AC_TRY_COMPILE([ ], [], [flag_ok=yes], [flag_ok=no])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])], [flag_ok=yes], [flag_ok=no])
CFLAGS="$save_CFLAGS"
if test "X$flag_ok" = Xyes ; then
diff --git a/man/ideviceinstaller.1 b/man/ideviceinstaller.1
index 8d2a01d..0cd0bee 100644
--- a/man/ideviceinstaller.1
+++ b/man/ideviceinstaller.1
@@ -7,102 +7,131 @@ ideviceinstaller \- Manage apps on iOS devices.
.SH DESCRIPTION
-Allows to install, upgrade, uninstall, archive, restore and enumerate installed
-or archived apps on iOS devices.
+Allows to enumerate, install, upgrade, and uninstall apps on iOS devices.
-.SH OPTIONS
-
-.SS General options:
+.SH COMMANDS
.TP
-.B \-d, \-\-debug
-enable communication debugging.
+.B list
+List installed apps on the device. Options:
+.RS
.TP
-.B \-u, \-\-udid UDID
-target specific device by its 40-digit device UDID.
+.B \-\-user
+List user apps only (apps installed by the user).
+.B This is the default.
.TP
-.B \-h, \-\-help
-prints usage information
-
-.SS Commands:
+.B \-\-system
+List system apps only (apps available from the system firmware).
.TP
-.B \-l, \-\-list-apps
-list apps installed on the device.
-
+.B \-\-all
+List all types of apps.
+.TP
+.B \-\-xml
+Print output as XML Property List.
+.TP
+.B \-a, \-\-attribute ATTR
+Specify attribute to return. This argument can be passed multiple times. If omitted and \f[B]\-\-xml\f[] is *not* specified, the default attributes \f[B]CFBundleIdentifier\f[], \f[B]CFBundleShortVersionString\f[], and \f[B]CFBundleDisplayName\f[] will be used. The attributes can be found in the app's Info.plist, but also some extra attributes exist. Some examples:
.RS
-.B Additional options:
.TP
-\-o list_user
-list user apps only (apps installed by the user)
-.B This is the default.
+\f[B]StaticDiskUsage\f[] disk usage of installed app
.TP
-\-o list_system
-list system apps only (apps available from the system firmware)
+\f[B]DynamicDiskUsage\f[] app user data disk usage
.TP
-\-o list_all
-list all types of apps
+\f[B]Path\f[] app installation location
.TP
-\-o xml
-print output in xml format (PList)
+\f[B]SignerIdentity\f[] code signing identity
+.TP
+NOTE: It is suggested to always add CFBundleIdentifier to allow unique identification of the apps.
.RE
-
.TP
-.B \-i, \-\-install ARCHIVE
-install app from a package file specified by ARCHIVE. ARCHIVE can also be a
-.ipcc file for carrier bundle installation or a .app directory for developer
+.B \-b, \-\-bundle\-identifier BUNDLEID
+Only query given bundle identifier. This argument can be passed multiple times.
+.RE
+.TP
+.B install PATH
+Install app from a package file specified by PATH. PATH can also be a .ipcc
+file for carrier bundle installation or a .app directory for developer
app installation.
+.RS
+.TP
+.B \-s, \-\-sinf PATH
+Pass an external SINF file located at PATH.
+.TP
+.B \-m, \-\-metadata PATH
+Pass an external iTunesMetadata file located at PATH.
+.RE
.TP
-.B \-U, \-\-uninstall APPID
-uninstall app specified by APPID.
+.B uninstall BUNDLEID
+Uninstall app specified by BUNDLEID.
.TP
-.B \-g, \-\-upgrade ARCHIVE
-upgrade app from a package file specified by ARCHIVE.
+.B upgrade PATH
+Upgrade app from a package file specified by PATH.
+.SH LEGACY COMMANDS
+The following commands are non-functional with iOS 7 or later.
+.TP
+.B archive BUNDLEID
+Archive app specified by BUNDLEID. Options:
+.RS
+.TP
+.B \-\-uninstall
+Uninstall the package after making an archive
.TP
-.B \-r, \-\-restore APPID
-restore archived app specified by APPID.
+.B \-\-app_only
+Archive application data only
+.TP
+.B \-\-docs_only
+Archive documents (user data) only
+.TP
+.B \-\-copy=PATH
+Copy the app archive to directory PATH when done
+.TP
+.B \-\-remove
+Only valid when copy=PATH is used: remove after copy
+.RE
.TP
-.B \-L, \-\-list-archives
-list archived applications on the device.
+.B restore BUNDLEID
+Restore archived app specified by BUNDLEID.
+.TP
+.B list-archives
+List archived apps on the device. Options:
.RS
-.B Additional options:
.TP
-\-o xml
-print output in xml format (PList)
+.B \-\-xml
+Print output as XML Property List.
.RE
.TP
-.B \-a, \-\-archive APPID
-archive app specified by APPID.
+.B remove-archive BUNDLEID
+Remove app archive specified by BUNDLEID.
-.RS
-.B Additional options:
+.SH OPTIONS
.TP
-\-o uninstall
-uninstall the package after making an archive
+.B \-u, \-\-udid UDID
+Target specific device by UDID.
.TP
-\-o app_only
-archive application data only
+.B \-n, \-\-network
+Connect to network device.
.TP
-\-o docs_only
-archive documents (user data) only
+.B \-w, \-\-notify-wait
+Wait for app installed/uninstalled notification before reporting success of operation.
.TP
-\-o copy=PATH
-copy the app archive to directory PATH when done
+.B \-h, \-\-help
+Print usage information.
.TP
-\-o remove
-only valid when copy=PATH is used: remove after copy
-.RE
-
+.B \-d, \-\-debug
+Enable communication debugging.
.TP
-.B \-R, \-\-remove-archive APPID
-remove app archive specified by APPID.
+.B \-v, \-\-version
+Print version information.
+
+.SH AUTHORS
+Nikias Bassen
-.SH AUTHOR
-This manual page was written by Martin Szulecki.
+Martin Szulecki
.SH ON THE WEB
https://libimobiledevice.org
diff --git a/src/ideviceinstaller.c b/src/ideviceinstaller.c
index e45bb12..c50bacf 100644
--- a/src/ideviceinstaller.c
+++ b/src/ideviceinstaller.c
@@ -1,7 +1,7 @@
/*
* ideviceinstaller - Manage apps on iOS devices.
*
- * Copyright (C) 2010-2019 Nikias Bassen <nikias@gmx.li>
+ * Copyright (C) 2010-2023 Nikias Bassen <nikias@gmx.li>
* Copyright (C) 2010-2015 Martin Szulecki <m.szulecki@libimobiledevice.org>
*
* Licensed under the GNU General Public License Version 2
@@ -93,8 +93,9 @@ const char PKG_PATH[] = "PublicStaging";
const char APPARCH_PATH[] = "ApplicationArchives";
char *udid = NULL;
-char *options = NULL;
-char *appid = NULL;
+char *cmdarg = NULL;
+char *extsinf = NULL;
+char *extmeta = NULL;
enum cmd_mode {
CMD_NONE = 0,
@@ -117,58 +118,83 @@ int use_notifier = 0;
int notification_expected = 0;
int is_device_connected = 0;
int command_completed = 0;
+int ignore_events = 0;
int err_occurred = 0;
int notified = 0;
+plist_t bundle_ids = NULL;
+plist_t return_attrs = NULL;
+#define FORMAT_XML 1
+#define FORMAT_JSON 2
+int output_format = 0;
+int opt_list_user = 0;
+int opt_list_system = 0;
+char *copy_path = NULL;
+int remove_after_copy = 0;
+int skip_uninstall = 1;
+int app_only = 0;
+int docs_only = 0;
static void print_apps_header()
{
- /* output app details header */
- printf("%s", "CFBundleIdentifier");
- printf(", %s", "CFBundleVersion");
- printf(", %s", "CFBundleDisplayName");
+ if (!return_attrs) {
+ return;
+ }
+ uint32_t i = 0;
+ for (i = 0; i < plist_array_get_size(return_attrs); i++) {
+ plist_t node = plist_array_get_item(return_attrs, i);
+ if (i > 0) {
+ printf(", ");
+ }
+ printf("%s", plist_get_string_ptr(node, NULL));
+ }
printf("\n");
}
static void print_apps(plist_t apps)
{
+ if (!return_attrs) {
+ return;
+ }
uint32_t i = 0;
for (i = 0; i < plist_array_get_size(apps); i++) {
plist_t app = plist_array_get_item(apps, i);
- plist_t p_bundle_identifier = plist_dict_get_item(app, "CFBundleIdentifier");
- char *s_bundle_identifier = NULL;
- char *s_display_name = NULL;
- char *s_version = NULL;
- plist_t display_name = plist_dict_get_item(app, "CFBundleDisplayName");
- plist_t version = plist_dict_get_item(app, "CFBundleVersion");
-
- if (p_bundle_identifier) {
- plist_get_string_val(p_bundle_identifier, &s_bundle_identifier);
- }
- if (!s_bundle_identifier) {
- fprintf(stderr, "ERROR: Failed to get APPID!\n");
- break;
- }
-
- if (version) {
- plist_get_string_val(version, &s_version);
- }
- if (display_name) {
- plist_get_string_val(display_name, &s_display_name);
- }
- if (!s_display_name) {
- s_display_name = strdup(s_bundle_identifier);
- }
-
- /* output app details */
- printf("%s", s_bundle_identifier);
- if (s_version) {
- printf(", \"%s\"", s_version);
- free(s_version);
+ uint32_t j = 0;
+ for (j = 0; j < plist_array_get_size(return_attrs); j++) {
+ plist_t node = plist_array_get_item(return_attrs, j);
+ if (j > 0) {
+ printf(", ");
+ }
+ const char* key = plist_get_string_ptr(node, NULL);
+ node = plist_dict_get_item(app, key);
+ if (node) {
+ if (!strcmp(key, "CFBundleIdentifier")) {
+ printf("%s", plist_get_string_ptr(node, NULL));
+ } else {
+ uint64_t uval = 0;
+ switch (plist_get_node_type(node)) {
+ case PLIST_STRING:
+ printf("\"%s\"", plist_get_string_ptr(node, NULL));
+ break;
+ case PLIST_INT:
+ plist_get_uint_val(node, &uval);
+ printf("%" PRIu64, uval);
+ break;
+ case PLIST_BOOLEAN:
+ printf("%s", plist_bool_val_is_true(node) ? "true" : "false");
+ break;
+ case PLIST_ARRAY:
+ printf("(array)");
+ break;
+ case PLIST_DICT:
+ printf("(dict)");
+ break;
+ default:
+ break;
+ }
+ }
+ }
}
- printf(", \"%s\"", s_display_name);
printf("\n");
- free(s_display_name);
- free(s_bundle_identifier);
}
}
@@ -298,8 +324,8 @@ static int zip_get_contents(struct zip *zf, const char *filename, int locate_fla
static int zip_get_app_directory(struct zip* zf, char** path)
{
- int i = 0;
- int c = zip_get_num_files(zf);
+ zip_int64_t i = 0;
+ zip_int64_t c = (zip_int64_t)zip_get_num_entries(zf, 0);
int len = 0;
const char* name = NULL;
@@ -329,6 +355,12 @@ static int zip_get_app_directory(struct zip* zf, char** path)
len = p - name + 1;
+ /* make sure app directory endwith .app */
+ if (len < 12 || strncmp(p - 4, ".app", 4))
+ {
+ continue;
+ }
+
if (path != NULL) {
free(*path);
*path = NULL;
@@ -346,11 +378,18 @@ static int zip_get_app_directory(struct zip* zf, char** path)
}
} while(i < c);
+ if (*path == NULL) {
+ return -1;
+ }
+
return 0;
}
static void idevice_event_callback(const idevice_event_t* event, void* userdata)
{
+ if (ignore_events) {
+ return;
+ }
if (event->event == IDEVICE_DEVICE_REMOVE) {
if (!strcmp(udid, event->udid)) {
fprintf(stderr, "ideviceinstaller: Device removed\n");
@@ -362,6 +401,7 @@ static void idevice_event_callback(const idevice_event_t* event, void* userdata)
static void idevice_wait_for_command_to_complete()
{
is_device_connected = 1;
+ ignore_events = 0;
/* subscribe to make sure to exit on device removal */
idevice_event_subscribe(idevice_event_callback, NULL);
@@ -377,109 +417,115 @@ static void idevice_wait_for_command_to_complete()
wait_ms(50);
}
+ ignore_events = 1;
idevice_event_unsubscribe();
}
-static void print_usage(int argc, char **argv)
+static void print_usage(int argc, char **argv, int is_error)
{
- char *name = NULL;
-
- name = strrchr(argv[0], '/');
- printf("Usage: %s OPTIONS\n", (name ? name + 1 : argv[0]));
- printf("\n");
- printf("Manage apps on iOS devices.\n");
- printf("\n");
- printf(
- "OPTIONS:\n"
- " -u, --udid UDID\tTarget specific device by UDID.\n"
- " -n, --network\t\tConnect to network device.\n"
- " -l, --list-apps\tList apps, possible options:\n"
- " -o list_user\t- list user apps only (this is the default)\n"
- " -o list_system\t- list system apps only\n"
- " -o list_all\t- list all types of apps\n"
- " -o xml\t\t- print full output as xml plist\n"
- " -i, --install ARCHIVE\tInstall app from package file specified by ARCHIVE.\n"
- " \tARCHIVE can also be a .ipcc file for carrier bundles.\n"
- " -U, --uninstall APPID\tUninstall app specified by APPID.\n"
- " -g, --upgrade ARCHIVE\tUpgrade app from package file specified by ARCHIVE.\n"
- " -L, --list-archives\tList archived applications, possible options:\n"
- " -o xml\t\t- print full output as xml plist\n"
- " -a, --archive APPID\tArchive app specified by APPID, possible options:\n"
- " -o uninstall\t- uninstall the package after making an archive\n"
- " -o app_only\t- archive application data only\n"
- " -o docs_only\t- archive documents (user data) only\n"
- " -o copy=PATH\t- copy the app archive to directory PATH when done\n"
- " -o remove\t- only valid when copy=PATH is used: remove after copy\n"
- " -r, --restore APPID\tRestore archived app specified by APPID\n"
- " -R, --remove-archive APPID Remove app archive specified by APPID\n"
- " -o, --options\t\tPass additional options to the specified command.\n"
- " -w, --notify-wait\t\tWait for app installed/uninstalled notification\n"
- " \t\tto before reporting success of operation\n"
- " -h, --help\t\tprints usage information\n"
- " -d, --debug\t\tenable communication debugging\n"
- " -v, --version\t\tprint version information\n"
- "\n"
+ 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"
+ "Manage apps on iOS devices.\n"
+ "\n"
+ "COMMANDS:\n"
+ " list List installed apps. Options:\n"
+ " --user List user apps only (this is the default)\n"
+ " --system List system apps only\n"
+ " --all List all types of apps\n"
+ " --xml Print output as XML Property List\n"
+ " -a, --attribute ATTR Specify attribute to return - see man page\n"
+ " (can be passed multiple times)\n"
+ " -b, --bundle-identifier BUNDLEID Only query given bundle identifier\n"
+ " (can be passed multiple times)\n"
+ " install PATH Install app from package file specified by PATH.\n"
+ " PATH can also be a .ipcc file for carrier bundles.\n"
+ " -s, --sinf PATH Pass an external SINF file\n"
+ " -m, --metadata PATH Pass an external iTunesMetadata file\n"
+ " uninstall BUNDLEID Uninstall app specified by BUNDLEID.\n"
+ " upgrade PATH Upgrade app from package file specified by PATH.\n"
+ "\n"
+ "LEGACY COMMANDS (non-functional with iOS 7 or later):\n"
+ " archive BUNDLEID Archive app specified by BUNDLEID. Options:\n"
+ " --uninstall Uninstall the package after making an archive\n"
+ " --app-only Archive application data only\n"
+ " --docs-only Archive documents (user data) only\n"
+ " --copy=PATH Copy the app archive to directory PATH when done\n"
+ " --remove Only valid when copy=PATH is used: remove after copy\n"
+ " restore BUNDLEID Restore archived app specified by BUNDLEID\n"
+ " list-archives List archived apps. Options:\n"
+ " --xml Print output as XML Property List\n"
+ " remove-archive BUNDLEID Remove app archive specified by BUNDLEID\n"
+ "\n"
+ "OPTIONS:\n"
+ " -u, --udid UDID Target specific device by UDID\n"
+ " -n, --network Connect to network device\n"
+ " -w, --notify-wait Wait for app installed/uninstalled notification\n"
+ " before reporting success of operation\n"
+ " -h, --help Print usage information\n"
+ " -d, --debug Enable communication debugging\n"
+ " -v, --version Print version information\n"
+ "\n"
+ "Homepage: <" PACKAGE_URL ">\n"
+ "Bug Reports: <" PACKAGE_BUGREPORT ">\n"
);
- printf("Homepage: <" PACKAGE_URL ">\n");
- printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n");
}
+enum numerical_opts {
+ LIST_USER = 1,
+ LIST_SYSTEM,
+ LIST_ALL,
+ ARCHIVE_UNINSTALL,
+ ARCHIVE_APP_ONLY,
+ ARCHIVE_DOCS_ONLY,
+ ARCHIVE_COPY_PATH,
+ ARCHIVE_COPY_REMOVE,
+ OUTPUT_XML,
+ OUTPUT_JSON
+};
+
static void parse_opts(int argc, char **argv)
{
static struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
{ "udid", required_argument, NULL, 'u' },
{ "network", no_argument, NULL, 'n' },
- { "list-apps", no_argument, NULL, 'l' },
- { "install", required_argument, NULL, 'i' },
- { "uninstall", required_argument, NULL, 'U' },
- { "upgrade", required_argument, NULL, 'g' },
- { "list-archives", no_argument, NULL, 'L' },
- { "archive", required_argument, NULL, 'a' },
- { "restore", required_argument, NULL, 'r' },
- { "remove-archive", required_argument, NULL, 'R' },
- { "options", required_argument, NULL, 'o' },
{ "notify-wait", no_argument, NULL, 'w' },
{ "debug", no_argument, NULL, 'd' },
{ "version", no_argument, NULL, 'v' },
+ { "bundle-identifier", required_argument, NULL, 'b' },
+ { "attribute", required_argument, NULL, 'a' },
+ { "user", no_argument, NULL, LIST_USER },
+ { "system", no_argument, NULL, LIST_SYSTEM },
+ { "all", no_argument, NULL, LIST_ALL },
+ { "xml", no_argument, NULL, OUTPUT_XML },
+ { "json", no_argument, NULL, OUTPUT_JSON },
+ { "sinf", required_argument, NULL, 's' },
+ { "metadata", required_argument, NULL, 'm' },
+ { "uninstall", no_argument, NULL, ARCHIVE_UNINSTALL },
+ { "app-only", no_argument, NULL, ARCHIVE_APP_ONLY },
+ { "docs-only", no_argument, NULL, ARCHIVE_DOCS_ONLY },
+ { "copy", required_argument, NULL, ARCHIVE_COPY_PATH },
+ { "remove", no_argument, NULL, ARCHIVE_COPY_REMOVE },
{ NULL, 0, NULL, 0 }
};
int c;
while (1) {
- c = getopt_long(argc, argv, "hU:li:u:g:La:r:R:o:nwdv", longopts,
- (int *) 0);
+ c = getopt_long(argc, argv, "hu:nwdvb:a:s:m:", longopts, (int*)0);
if (c == -1) {
break;
}
- /* verify if multiple modes have been supplied */
- switch (c) {
- case 'l':
- case 'i':
- case 'g':
- case 'L':
- case 'a':
- case 'r':
- case 'R':
- if (cmd != CMD_NONE) {
- printf("ERROR: A mode has already been supplied. Multiple modes are not supported.\n");
- print_usage(argc, argv);
- exit(2);
- }
- break;
- default:
- break;
- }
-
switch (c) {
case 'h':
- print_usage(argc, argv);
+ print_usage(argc, argv, 0);
exit(0);
case 'u':
if (!*optarg) {
printf("ERROR: UDID must not be empty!\n");
- print_usage(argc, argv);
+ print_usage(argc, argv, 1);
exit(2);
}
udid = strdup(optarg);
@@ -487,47 +533,43 @@ static void parse_opts(int argc, char **argv)
case 'n':
use_network = 1;
break;
- case 'l':
- cmd = CMD_LIST_APPS;
- break;
- case 'i':
- cmd = CMD_INSTALL;
- appid = strdup(optarg);
- break;
- case 'U':
- cmd = CMD_UNINSTALL;
- appid = strdup(optarg);
- break;
- case 'g':
- cmd = CMD_UPGRADE;
- appid = strdup(optarg);
- break;
- case 'L':
- cmd = CMD_LIST_ARCHIVES;
- break;
case 'a':
- cmd = CMD_ARCHIVE;
- appid = strdup(optarg);
+ if (!*optarg) {
+ printf("ERROR: attribute must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ if (return_attrs == NULL) {
+ return_attrs = plist_new_array();
+ }
+ plist_array_append_item(return_attrs, plist_new_string(optarg));
break;
- case 'r':
- cmd = CMD_RESTORE;
- appid = strdup(optarg);
+ case 'b':
+ if (!*optarg) {
+ printf("ERROR: bundle identifier must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ if (bundle_ids == NULL) {
+ bundle_ids = plist_new_array();
+ }
+ plist_array_append_item(bundle_ids, plist_new_string(optarg));
break;
- case 'R':
- cmd = CMD_REMOVE_ARCHIVE;
- appid = strdup(optarg);
+ case 's':
+ if (!*optarg) {
+ printf("ERROR: path for --sinf must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
+ }
+ extsinf = strdup(optarg);
break;
- case 'o':
- if (!options) {
- options = strdup(optarg);
- } else {
- char *newopts = malloc(strlen(options) + strlen(optarg) + 2);
- strcpy(newopts, options);
- free(options);
- strcat(newopts, ",");
- strcat(newopts, optarg);
- options = newopts;
+ case 'm':
+ if (!*optarg) {
+ printf("ERROR: path for --metadata must not be empty!\n");
+ print_usage(argc, argv, 1);
+ exit(2);
}
+ extmeta = strdup(optarg);
break;
case 'w':
use_notifier = 1;
@@ -538,20 +580,103 @@ static void parse_opts(int argc, char **argv)
case 'v':
printf("%s %s\n", PACKAGE_NAME, PACKAGE_VERSION);
exit(0);
+ case LIST_USER:
+ opt_list_user = 1;
+ break;
+ case LIST_SYSTEM:
+ opt_list_system = 1;
+ break;
+ case LIST_ALL:
+ opt_list_user = 1;
+ opt_list_system = 1;
+ break;
+ case OUTPUT_XML:
+ output_format = FORMAT_XML;
+ break;
+ case OUTPUT_JSON:
+ output_format = FORMAT_JSON;
+ break;
+ case ARCHIVE_UNINSTALL:
+ skip_uninstall = 0;
+ break;
+ case ARCHIVE_APP_ONLY:
+ app_only = 1;
+ docs_only = 0;
+ break;
+ case ARCHIVE_DOCS_ONLY:
+ docs_only = 1;
+ app_only = 0;
+ break;
+ case ARCHIVE_COPY_PATH:
+ copy_path = strdup(optarg);
+ break;
+ case ARCHIVE_COPY_REMOVE:
+ remove_after_copy = 1;
+ break;
default:
- print_usage(argc, argv);
+ print_usage(argc, argv, 1);
exit(2);
}
}
- if (cmd == CMD_NONE) {
- printf("ERROR: No mode/command was supplied.\n");
- }
+ argv += optind;
+ argc -= optind;
- if (cmd == CMD_NONE || optind <= 1 || (argc - optind > 0)) {
- print_usage(argc, argv);
+ if (argc == 0) {
+ fprintf(stderr, "ERROR: Missing command.\n\n");
+ print_usage(argc+optind, argv-optind, 1);
exit(2);
}
+
+ char *cmdstr = argv[0];
+
+ if (!strcmp(cmdstr, "list")) {
+ cmd = CMD_LIST_APPS;
+ } else if (!strcmp(cmdstr, "install")) {
+ cmd = CMD_INSTALL;
+ } else if (!strcmp(cmdstr, "upgrade")) {
+ cmd = CMD_UPGRADE;
+ } else if (!strcmp(cmdstr, "uninstall") || !strcmp(cmdstr, "remove")) {
+ cmd = CMD_UNINSTALL;
+ } else if (!strcmp(cmdstr, "archives") || !strcmp(cmdstr, "list-archives")) {
+ cmd = CMD_LIST_ARCHIVES;
+ } else if (!strcmp(cmdstr, "archive")) {
+ cmd = CMD_ARCHIVE;
+ } else if (!strcmp(cmdstr, "restore")) {
+ cmd = CMD_RESTORE;
+ } else if (!strcmp(cmdstr, "remove-archive")) {
+ cmd = CMD_REMOVE_ARCHIVE;
+ }
+
+ switch (cmd) {
+ case CMD_LIST_APPS:
+ case CMD_LIST_ARCHIVES:
+ break;
+ case CMD_INSTALL:
+ case CMD_UPGRADE:
+ if (argc < 2) {
+ fprintf(stderr, "ERROR: Missing filename for '%s' command.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
+ cmdarg = argv[1];
+ break;
+ case CMD_UNINSTALL:
+ case CMD_ARCHIVE:
+ case CMD_RESTORE:
+ case CMD_REMOVE_ARCHIVE:
+ if (argc < 2) {
+ fprintf(stderr, "ERROR: Missing bundle ID for '%s' command.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
+ cmdarg = argv[1];
+ break;
+ default:
+ fprintf(stderr, "ERROR: Invalid command '%s'.\n\n", cmdstr);
+ print_usage(argc+optind, argv-optind, 1);
+ exit(2);
+ }
}
static int afc_upload_file(afc_client_t afc, const char* filename, const char* dstfn)
@@ -562,7 +687,7 @@ static int afc_upload_file(afc_client_t afc, const char* filename, const char* d
f = fopen(filename, "rb");
if (!f) {
- fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
+ fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno));
return -1;
}
@@ -649,6 +774,37 @@ static void afc_upload_dir(afc_client_t afc, const char* path, const char* afcpa
}
}
+static char *buf_from_file(const char *filename, size_t *size)
+{
+ struct stat st;
+ FILE *fp = NULL;
+
+ if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) {
+ return NULL;
+ }
+ size_t filesize = st.st_size;
+ if (filesize == 0) {
+ return NULL;
+ }
+ char *ibuf = malloc(filesize * sizeof(char));
+ if (ibuf == NULL) {
+ return NULL;
+ }
+ size_t amount = fread(ibuf, 1, filesize, fp);
+ if (amount != filesize) {
+ fprintf(stderr, "ERROR: could not read %" PRIu64 " bytes from %s\n", (uint64_t)filesize, filename);
+ free(ibuf);
+ return NULL;
+ }
+ fclose(fp);
+
+ if (size) {
+ *size = filesize;
+ }
+
+ return ibuf;
+}
+
int main(int argc, char **argv)
{
idevice_t device = NULL;
@@ -658,7 +814,7 @@ int main(int argc, char **argv)
np_client_t np = NULL;
afc_client_t afc = NULL;
lockdownd_service_descriptor_t service = NULL;
- int res = 0;
+ int res = EXIT_FAILURE;
char *bundleidentifier = NULL;
#ifndef WIN32
@@ -675,26 +831,23 @@ int main(int argc, char **argv)
} else {
fprintf(stderr, "No device found.\n");
}
- return -1;
+ return EXIT_FAILURE;
}
if (!udid) {
idevice_get_udid(device, &udid);
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) {
- fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
- res = -1;
+ lockdownd_error_t lerr = lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller");
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to lockdownd: %s. Exiting.\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
if (use_notifier) {
- if ((lockdownd_start_service
- (client, "com.apple.mobile.notification_proxy",
- &service) != LOCKDOWN_E_SUCCESS) || !service) {
- fprintf(stderr,
- "Could not start com.apple.mobile.notification_proxy!\n");
- res = -1;
+ lerr =lockdownd_start_service(client, "com.apple.mobile.notification_proxy", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.mobile.notification_proxy: %s\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
@@ -707,7 +860,6 @@ int main(int argc, char **argv)
if (nperr != NP_E_SUCCESS) {
fprintf(stderr, "Could not connect to notification_proxy!\n");
- res = -1;
goto leave_cleanup;
}
@@ -724,11 +876,9 @@ run_again:
}
service = NULL;
- if ((lockdownd_start_service(client, "com.apple.mobile.installation_proxy",
- &service) != LOCKDOWN_E_SUCCESS) || !service) {
- fprintf(stderr,
- "Could not start com.apple.mobile.installation_proxy!\n");
- res = -1;
+ lerr = lockdownd_start_service(client, "com.apple.mobile.installation_proxy", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.mobile.installation_proxy: %s\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
@@ -741,7 +891,6 @@ run_again:
if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "Could not connect to installation_proxy!\n");
- res = -1;
goto leave_cleanup;
}
@@ -753,60 +902,89 @@ run_again:
notification_expected = 0;
if (cmd == CMD_LIST_APPS) {
- int xml_mode = 0;
plist_t client_opts = instproxy_client_options_new();
instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
plist_t apps = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "list_system")) {
- instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
- } else if (!strcmp(elem, "list_all")) {
- plist_dict_remove_item(client_opts, "ApplicationType");
- } else if (!strcmp(elem, "list_user")) {
- /* do nothing, we're already set */
- } else if (!strcmp(elem, "xml")) {
- xml_mode = 1;
- }
- elem = strtok(NULL, ",");
- }
- free(opts);
+ if (opt_list_system && opt_list_user) {
+ plist_dict_remove_item(client_opts, "ApplicationType");
+ } else if (opt_list_system) {
+ instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
+ } else if (opt_list_user) {
+ instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
}
- if (!xml_mode) {
- instproxy_client_options_set_return_attributes(client_opts,
- "CFBundleIdentifier",
- "CFBundleDisplayName",
- "CFBundleVersion",
- "StaticDiskUsage",
- "DynamicDiskUsage",
- NULL
- );
+ if (bundle_ids) {
+ plist_dict_set_item(client_opts, "BundleIDs", plist_copy(bundle_ids));
}
- if (xml_mode) {
+ if (!output_format && !return_attrs) {
+ return_attrs = plist_new_array();
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleIdentifier"));
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleShortVersionString"));
+ plist_array_append_item(return_attrs, plist_new_string("CFBundleDisplayName"));
+ }
+
+ if (return_attrs) {
+ instproxy_client_options_add(client_opts, "ReturnAttributes", return_attrs, NULL);
+ }
+
+ if (output_format) {
err = instproxy_browse(ipc, client_opts, &apps);
if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
- fprintf(stderr,
- "ERROR: instproxy_browse returnd an invalid plist!\n");
- res = -1;
+ fprintf(stderr, "ERROR: instproxy_browse returnd an invalid plist!\n");
goto leave_cleanup;
}
-
- char *xml = NULL;
+ char *buf = NULL;
uint32_t len = 0;
-
- plist_to_xml(apps, &xml, &len);
- if (xml) {
- puts(xml);
- free(xml);
+ if (output_format == FORMAT_XML) {
+ plist_err_t perr = plist_to_xml(apps, &buf, &len);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr);
+ }
+ } else if (output_format == FORMAT_JSON) {
+ /* for JSON, we need to convert some stuff since it doesn't support PLIST_DATA nodes */
+ plist_array_iter aiter = NULL;
+ plist_array_new_iter(apps, &aiter);
+ plist_t entry = NULL;
+ do {
+ plist_array_next_item(apps, aiter, &entry);
+ if (!entry) break;
+ plist_t items = plist_dict_get_item(entry, "UIApplicationShortcutItems");
+ plist_array_iter inner = NULL;
+ plist_array_new_iter(items, &inner);
+ plist_t item = NULL;
+ do {
+ plist_array_next_item(items, inner, &item);
+ if (!item) break;
+ plist_t userinfo = plist_dict_get_item(item, "UIApplicationShortcutItemUserInfo");
+ if (userinfo) {
+ plist_t data_node = plist_dict_get_item(userinfo, "data");
+
+ if (data_node) {
+ char *strbuf = NULL;
+ uint32_t buflen = 0;
+ plist_write_to_string(data_node, &strbuf, &buflen, PLIST_FORMAT_LIMD, PLIST_OPT_NO_NEWLINE);
+ plist_set_string_val(data_node, strbuf);
+ free(strbuf);
+ }
+ }
+ } while (item);
+ free(inner);
+ } while (entry);
+ free(aiter);
+ plist_err_t perr = plist_to_json(apps, &buf, &len, 1);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr);
+ }
+ }
+ if (buf) {
+ puts(buf);
+ free(buf);
}
plist_free(apps);
+ res = 0;
goto leave_cleanup;
}
@@ -820,7 +998,6 @@ run_again:
instproxy_client_options_free(client_opts);
if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
- res = -1;
goto leave_cleanup;
}
@@ -837,10 +1014,9 @@ run_again:
lockdownd_service_descriptor_free(service);
service = NULL;
- if ((lockdownd_start_service(client, "com.apple.afc", &service) !=
- LOCKDOWN_E_SUCCESS) || !service) {
- fprintf(stderr, "Could not start com.apple.afc!\n");
- res = -1;
+ lerr = lockdownd_start_service(client, "com.apple.afc", &service);
+ if (lerr != LOCKDOWN_E_SUCCESS) {
+ fprintf(stderr, "Could not start com.apple.afc: %s\n", lockdownd_strerror(lerr));
goto leave_cleanup;
}
@@ -849,13 +1025,11 @@ run_again:
if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
- res = -1;
goto leave_cleanup;
}
- if (stat(appid, &fst) != 0) {
- fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
- res = -1;
+ if (stat(cmdarg, &fst) != 0) {
+ fprintf(stderr, "ERROR: stat: %s: %s\n", cmdarg, strerror(errno));
goto leave_cleanup;
}
@@ -880,15 +1054,14 @@ run_again:
int errp = 0;
struct zip *zf = NULL;
- if ((strlen(appid) > 5) && (strcmp(&appid[strlen(appid)-5], ".ipcc") == 0)) {
- zf = zip_open(appid, 0, &errp);
+ if ((strlen(cmdarg) > 5) && (strcmp(&cmdarg[strlen(cmdarg)-5], ".ipcc") == 0)) {
+ zf = zip_open(cmdarg, 0, &errp);
if (!zf) {
- fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
- res = -1;
+ fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp);
goto leave_cleanup;
}
- char* ipcc = strdup(appid);
+ char* ipcc = strdup(cmdarg);
if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) {
afc_make_directory(afc, pkgname);
}
@@ -896,8 +1069,8 @@ run_again:
printf("Uploading %s package contents... ", basename(ipcc));
/* extract the contents of the .ipcc file to PublicStaging/<name>.ipcc directory */
- zip_uint64_t numzf = zip_get_num_entries(zf, 0);
- zip_uint64_t i = 0;
+ zip_int64_t numzf = (zip_int64_t)zip_get_num_entries(zf, 0);
+ zip_int64_t i = 0;
for (i = 0; numzf > 0 && i < numzf; i++) {
const char* zname = zip_get_name(zf, i, 0);
char* dstpath = NULL;
@@ -957,7 +1130,6 @@ run_again:
afc_file_close(afc, af);
zip_fclose(zfile);
free(dstpath);
- res = -1;
goto leave_cleanup;
}
}
@@ -979,21 +1151,20 @@ run_again:
/* upload developer app directory */
instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL);
- if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(cmdarg)) < 0) {
fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n");
- res = -1;
goto leave_cleanup;
}
- printf("Uploading %s package contents... ", basename(appid));
- afc_upload_dir(afc, appid, pkgname);
+ printf("Uploading %s package contents... ", basename(cmdarg));
+ afc_upload_dir(afc, cmdarg, pkgname);
printf("DONE.\n");
/* extract the CFBundleIdentifier from the package */
/* construct full filename to Info.plist */
- char *filename = (char*)malloc(strlen(appid)+11+1);
- strcpy(filename, appid);
+ char *filename = (char*)malloc(strlen(cmdarg)+11+1);
+ strcpy(filename, cmdarg);
strcat(filename, "/Info.plist");
struct stat st;
@@ -1002,7 +1173,6 @@ run_again:
if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) {
fprintf(stderr, "ERROR: could not locate %s in app!\n", filename);
free(filename);
- res = -1;
goto leave_cleanup;
}
size_t filesize = st.st_size;
@@ -1011,23 +1181,17 @@ run_again:
if (amount != filesize) {
fprintf(stderr, "ERROR: could not read %u bytes from %s\n", (uint32_t)filesize, filename);
free(filename);
- res = -1;
goto leave_cleanup;
}
fclose(fp);
free(filename);
plist_t info = NULL;
- if (memcmp(ibuf, "bplist00", 8) == 0) {
- plist_from_bin(ibuf, filesize, &info);
- } else {
- plist_from_xml(ibuf, filesize, &info);
- }
+ plist_from_memory(ibuf, filesize, &info, NULL);
free(ibuf);
if (!info) {
fprintf(stderr, "ERROR: could not parse Info.plist!\n");
- res = -1;
goto leave_cleanup;
}
@@ -1038,28 +1202,45 @@ run_again:
plist_free(info);
info = NULL;
} else {
- zf = zip_open(appid, 0, &errp);
+ zf = zip_open(cmdarg, 0, &errp);
if (!zf) {
- fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
- res = -1;
+ fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp);
goto leave_cleanup;
}
- /* extract iTunesMetadata.plist from package */
char *zbuf = NULL;
uint32_t len = 0;
plist_t meta_dict = NULL;
- if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) {
- meta = plist_new_data(zbuf, len);
- if (memcmp(zbuf, "bplist00", 8) == 0) {
- plist_from_bin(zbuf, len, &meta_dict);
- } else {
- plist_from_xml(zbuf, len, &meta_dict);
+
+ if (extmeta) {
+ size_t flen = 0;
+ zbuf = buf_from_file(extmeta, &flen);
+ if (zbuf && flen) {
+ meta = plist_new_data(zbuf, flen);
+ plist_from_memory(zbuf, flen, &meta_dict, NULL);
+ free(zbuf);
}
- } else {
- fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME);
+ if (!meta_dict) {
+ plist_free(meta);
+ meta = NULL;
+ fprintf(stderr, "WARNING: could not load external iTunesMetadata %s!\n", extmeta);
+ }
+ zbuf = NULL;
+ }
+
+ if (!meta && !meta_dict) {
+ /* extract iTunesMetadata.plist from package */
+ if (zip_get_contents(zf, ITUNES_METADATA_PLIST_FILENAME, 0, &zbuf, &len) == 0) {
+ meta = plist_new_data(zbuf, len);
+ plist_from_memory(zbuf, len, &meta_dict, NULL);
+ }
+ if (!meta_dict) {
+ plist_free(meta);
+ meta = NULL;
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", ITUNES_METADATA_PLIST_FILENAME);
+ }
+ free(zbuf);
}
- free(zbuf);
/* determine .app directory in archive */
zbuf = NULL;
@@ -1069,8 +1250,7 @@ run_again:
char* app_directory_name = NULL;
if (zip_get_app_directory(zf, &app_directory_name)) {
- fprintf(stderr, "Unable to locate app directory in archive!\n");
- res = -1;
+ fprintf(stderr, "ERROR: Unable to locate .app directory in archive. Make sure it is inside a 'Payload' directory.\n");
goto leave_cleanup;
}
@@ -1086,22 +1266,16 @@ run_again:
free(filename);
zip_unchange_all(zf);
zip_close(zf);
- res = -1;
goto leave_cleanup;
}
free(filename);
- if (memcmp(zbuf, "bplist00", 8) == 0) {
- plist_from_bin(zbuf, len, &info);
- } else {
- plist_from_xml(zbuf, len, &info);
- }
+ plist_from_memory(zbuf, len, &info, NULL);
free(zbuf);
if (!info) {
fprintf(stderr, "Could not parse Info.plist!\n");
zip_unchange_all(zf);
zip_close(zf);
- res = -1;
goto leave_cleanup;
}
@@ -1123,40 +1297,52 @@ run_again:
fprintf(stderr, "Could not determine value for CFBundleExecutable!\n");
zip_unchange_all(zf);
zip_close(zf);
- res = -1;
goto leave_cleanup;
}
- char *sinfname = NULL;
- if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) {
- fprintf(stderr, "Out of memory!?\n");
- res = -1;
- goto leave_cleanup;
+ if (extsinf) {
+ size_t flen = 0;
+ zbuf = buf_from_file(extsinf, &flen);
+ if (zbuf && flen) {
+ sinf = plist_new_data(zbuf, flen);
+ free(zbuf);
+ } else {
+ fprintf(stderr, "WARNING: could not load external SINF %s!\n", extsinf);
+ }
+ zbuf = NULL;
}
- free(bundleexecutable);
- /* extract .sinf from package */
- zbuf = NULL;
- len = 0;
- if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
- sinf = plist_new_data(zbuf, len);
- } else {
- fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname);
+ if (!sinf) {
+ char *sinfname = NULL;
+ if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundleexecutable, bundleexecutable) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
+ free(bundleexecutable);
+
+ /* extract .sinf from package */
+ zbuf = NULL;
+ len = 0;
+ if (zip_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
+ sinf = plist_new_data(zbuf, len);
+ } else {
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", sinfname);
+ }
+ free(sinfname);
+ free(zbuf);
}
- free(sinfname);
- free(zbuf);
/* copy archive to device */
pkgname = NULL;
if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) {
fprintf(stderr, "Out of memory!?\n");
- res = -1;
goto leave_cleanup;
}
- printf("Copying '%s' to device... ", appid);
+ printf("Copying '%s' to device... ", cmdarg);
- if (afc_upload_file(afc, appid, pkgname) < 0) {
+ if (afc_upload_file(afc, cmdarg, pkgname) < 0) {
+ printf("FAILED\n");
free(pkgname);
goto leave_cleanup;
}
@@ -1191,48 +1377,41 @@ run_again:
wait_for_command_complete = 1;
notification_expected = 1;
} else if (cmd == CMD_UNINSTALL) {
- printf("Uninstalling '%s'\n", appid);
- instproxy_uninstall(ipc, appid, NULL, status_cb, NULL);
+ printf("Uninstalling '%s'\n", cmdarg);
+ instproxy_uninstall(ipc, cmdarg, NULL, status_cb, NULL);
wait_for_command_complete = 1;
notification_expected = 0;
} else if (cmd == CMD_LIST_ARCHIVES) {
- int xml_mode = 0;
plist_t dict = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "xml")) {
- xml_mode = 1;
- }
- elem = strtok(NULL, ",");
- }
- }
-
err = instproxy_lookup_archives(ipc, NULL, &dict);
if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "ERROR: lookup_archives returned %d\n", err);
- res = -1;
goto leave_cleanup;
}
if (!dict) {
- fprintf(stderr,
- "ERROR: lookup_archives did not return a plist!?\n");
- res = -1;
+ fprintf(stderr, "ERROR: lookup_archives did not return a plist!?\n");
goto leave_cleanup;
}
- if (xml_mode) {
- char *xml = NULL;
+ if (output_format) {
+ char *buf = NULL;
uint32_t len = 0;
-
- plist_to_xml(dict, &xml, &len);
- if (xml) {
- puts(xml);
- free(xml);
+ if (output_format == FORMAT_XML) {
+ plist_err_t perr = plist_to_xml(dict, &buf, &len);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to XML format (%d).\n", perr);
+ }
+ } else if (output_format == FORMAT_JSON) {
+ plist_err_t perr = plist_to_json(dict, &buf, &len, 1);
+ if (perr != PLIST_ERR_SUCCESS) {
+ fprintf(stderr, "ERROR: Failed to convert data to JSON format (%d).\n", perr);
+ }
+ }
+ if (buf) {
+ puts(buf);
+ free(buf);
}
plist_free(dict);
goto leave_cleanup;
@@ -1258,7 +1437,7 @@ run_again:
plist_t dispName =
plist_dict_get_item(node, "CFBundleDisplayName");
plist_t version =
- plist_dict_get_item(node, "CFBundleVersion");
+ plist_dict_get_item(node, "CFBundleShortVersionString");
if (dispName) {
plist_get_string_val(dispName, &s_dispName);
}
@@ -1281,35 +1460,8 @@ run_again:
while (node);
plist_free(dict);
} else if (cmd == CMD_ARCHIVE) {
- char *copy_path = NULL;
- int remove_after_copy = 0;
- int skip_uninstall = 1;
- int app_only = 0;
- int docs_only = 0;
plist_t client_opts = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "uninstall")) {
- skip_uninstall = 0;
- } else if (!strcmp(elem, "app_only")) {
- app_only = 1;
- docs_only = 0;
- } else if (!strcmp(elem, "docs_only")) {
- docs_only = 1;
- app_only = 0;
- } else if ((strlen(elem) > 5) && !strncmp(elem, "copy=", 5)) {
- copy_path = strdup(elem+5);
- } else if (!strcmp(elem, "remove")) {
- remove_after_copy = 1;
- }
- elem = strtok(NULL, ",");
- }
- }
-
if (skip_uninstall || app_only || docs_only) {
client_opts = instproxy_client_options_new();
if (skip_uninstall) {
@@ -1326,15 +1478,11 @@ run_again:
struct stat fst;
if (stat(copy_path, &fst) != 0) {
fprintf(stderr, "ERROR: stat: %s: %s\n", copy_path, strerror(errno));
- free(copy_path);
- res = -1;
goto leave_cleanup;
}
if (!S_ISDIR(fst.st_mode)) {
fprintf(stderr, "ERROR: '%s' is not a directory as expected.\n", copy_path);
- free(copy_path);
- res = -1;
goto leave_cleanup;
}
@@ -1345,8 +1493,6 @@ run_again:
if ((lockdownd_start_service(client, "com.apple.afc", &service) != LOCKDOWN_E_SUCCESS) || !service) {
fprintf(stderr, "Could not start com.apple.afc!\n");
- free(copy_path);
- res = -1;
goto leave_cleanup;
}
@@ -1355,12 +1501,11 @@ run_again:
if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
- res = -1;
goto leave_cleanup;
}
}
- instproxy_archive(ipc, appid, client_opts, status_cb, NULL);
+ instproxy_archive(ipc, cmdarg, client_opts, status_cb, NULL);
instproxy_client_options_free(client_opts);
wait_for_command_complete = 1;
@@ -1376,19 +1521,16 @@ run_again:
if (err_occurred) {
afc_client_free(afc);
afc = NULL;
- res = -1;
goto leave_cleanup;
}
FILE *f = NULL;
uint64_t af = 0;
/* local filename */
char *localfile = NULL;
- if (asprintf(&localfile, "%s/%s.ipa", copy_path, appid) < 0) {
+ if (asprintf(&localfile, "%s/%s.ipa", copy_path, cmdarg) < 0) {
fprintf(stderr, "Out of memory!?\n");
- res = -1;
goto leave_cleanup;
}
- free(copy_path);
f = fopen(localfile, "wb");
if (!f) {
@@ -1399,7 +1541,7 @@ run_again:
/* remote filename */
char *remotefile = NULL;
- if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, appid) < 0) {
+ if (asprintf(&remotefile, "%s/%s.zip", APPARCH_PATH, cmdarg) < 0) {
fprintf(stderr, "Out of memory!?\n");
goto leave_cleanup;
}
@@ -1411,7 +1553,6 @@ run_again:
fclose(f);
free(remotefile);
free(localfile);
- res = -1;
goto leave_cleanup;
}
@@ -1442,7 +1583,6 @@ run_again:
fprintf(stderr, "ERROR: could not open '%s' on device for reading!\n", remotefile);
free(remotefile);
free(localfile);
- res = -1;
goto leave_cleanup;
}
@@ -1486,13 +1626,10 @@ run_again:
if (remove_after_copy) {
/* remove archive if requested */
- printf("Removing '%s'\n", appid);
+ printf("Removing '%s'\n", cmdarg);
cmd = CMD_REMOVE_ARCHIVE;
- free(options);
- options = NULL;
if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) {
fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
- res = -1;
goto leave_cleanup;
}
goto run_again;
@@ -1500,16 +1637,15 @@ run_again:
}
goto leave_cleanup;
} else if (cmd == CMD_RESTORE) {
- instproxy_restore(ipc, appid, NULL, status_cb, NULL);
+ instproxy_restore(ipc, cmdarg, NULL, status_cb, NULL);
wait_for_command_complete = 1;
notification_expected = 1;
} else if (cmd == CMD_REMOVE_ARCHIVE) {
- instproxy_remove_archive(ipc, appid, NULL, status_cb, NULL);
+ instproxy_remove_archive(ipc, cmdarg, NULL, status_cb, NULL);
wait_for_command_complete = 1;
} else {
- printf
- ("ERROR: no command selected?! This should not be reached!\n");
- res = -2;
+ printf("ERROR: no command selected?! This should not be reached!\n");
+ res = 2;
goto leave_cleanup;
}
@@ -1518,6 +1654,7 @@ run_again:
client = NULL;
idevice_wait_for_command_to_complete();
+ res = 0;
leave_cleanup:
np_client_free(np);
@@ -1527,9 +1664,12 @@ leave_cleanup:
idevice_free(device);
free(udid);
- free(appid);
- free(options);
+ free(copy_path);
+ free(extsinf);
+ free(extmeta);
free(bundleidentifier);
+ plist_free(bundle_ids);
+ plist_free(return_attrs);
if (err_occurred && !res) {
res = 128;