summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/FUNDING.yml3
-rw-r--r--.github/workflows/build.yml272
-rw-r--r--AUTHORS3
-rw-r--r--Makefile.am7
-rw-r--r--NEWS80
-rw-r--r--README56
-rw-r--r--README.md127
-rwxr-xr-xautogen.sh33
-rw-r--r--configure.ac62
-rwxr-xr-xgit-version-gen20
-rw-r--r--m4/as-compiler-flag.m44
-rw-r--r--man/ideviceinstaller.1156
-rw-r--r--src/ideviceinstaller.c1756
13 files changed, 1859 insertions, 720 deletions
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..e995b30
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+github: nikias
+patreon: nikias
+custom: ["https://www.paypal.me/NikiasBassen"]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..5484545
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,272 @@
+name: build
+
+on:
+ push:
+ schedule:
+ - cron: '0 0 1 * *'
+
+jobs:
+ build-linux-ubuntu:
+ runs-on: ubuntu-latest
+ steps:
+ - name: install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install libzip-dev
+ - name: prepare environment
+ run: |
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ - name: fetch libplist
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libplist-latest_${{env.target_triplet}}
+ repo: libimobiledevice/libplist
+ - name: fetch libusbmuxd
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libusbmuxd-latest_${{env.target_triplet}}
+ repo: libimobiledevice/libusbmuxd
+ - name: fetch libimobiledevice-glue
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-glue-latest_${{env.target_triplet}}
+ repo: libimobiledevice/libimobiledevice-glue
+ - name: fetch libimobiledevice
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-latest_${{env.target_triplet}}
+ repo: libimobiledevice/libimobiledevice
+ - name: install external dependencies
+ run: |
+ mkdir extract
+ for I in *.tar; do
+ tar -C extract -xvf $I
+ done
+ rm -rf extract/lib
+ sudo cp -r extract/* /
+ sudo ldconfig
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: autogen
+ run: ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
+ - name: make
+ run: make
+ - name: make install
+ run: sudo make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf ideviceinstaller.tar usr
+ - name: publish artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ideviceinstaller-latest_${{env.target_triplet}}
+ path: ideviceinstaller.tar
+ build-macOS:
+ runs-on: macOS-latest
+ steps:
+ - name: install dependencies
+ run: |
+ if test -x "`which port`"; then
+ sudo port install libtool autoconf automake pkgconfig
+ else
+ brew install libtool autoconf automake pkgconfig
+ fi
+ shell: bash
+ - name: fetch libplist
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libplist-latest_macOS
+ repo: libimobiledevice/libplist
+ - name: fetch libusbmuxd
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libusbmuxd-latest_macOS
+ repo: libimobiledevice/libusbmuxd
+ - name: fetch libimobiledevice-glue
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-glue-latest_macOS
+ repo: libimobiledevice/libimobiledevice-glue
+ - name: fetch libimobiledevice
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-latest_macOS
+ repo: libimobiledevice/libimobiledevice
+ - name: install external dependencies
+ run: |
+ mkdir extract
+ for I in *.tar; do
+ tar -C extract -xvf $I
+ done
+ sudo cp -r extract/* /
+ - uses: actions/checkout@v4
+ - name: install additional requirements
+ run: |
+ SDKDIR=`xcrun --sdk macosx --show-sdk-path 2>/dev/null`
+ echo "SDKDIR=$SDKDIR" >> $GITHUB_ENV
+ TESTARCHS="arm64 x86_64"
+ USEARCHS=
+ for ARCH in $TESTARCHS; do
+ if echo "int main(int argc, char **argv) { return 0; }" |clang -arch $ARCH -o /dev/null -isysroot $SDKDIR -x c - 2>/dev/null; then
+ USEARCHS="$USEARCHS -arch $ARCH"
+ fi
+ done
+ export CFLAGS="$USEARCHS -isysroot $SDKDIR"
+ echo "Using CFLAGS: $CFLAGS"
+ echo "BUILD_CFLAGS=$CFLAGS" >> $GITHUB_ENV
+ mkdir -p lib
+ curl -o lib/libcrypto.35.tbd -Ls \
+ https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libcrypto.35.tbd
+ curl -o lib/libssl.35.tbd -Ls \
+ https://gist.github.com/nikias/94c99fd145a75a5104415e5117b0cafa/raw/5209dfbff5a871a14272afe4794e76eb4cf6f062/libssl.35.tbd
+ LIBRESSL_VER=2.2.7
+ FILENAME="libressl-$LIBRESSL_VER.tar.gz"
+ curl -o $FILENAME -Ls "https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/$FILENAME"
+ mkdir -p deps
+ tar -C deps -xzf $FILENAME
+ echo "LIBRESSL_CFLAGS=-I`pwd`/deps/libressl-$LIBRESSL_VER/include" >> $GITHUB_ENV
+ echo "LIBRESSL_LIBS=-Xlinker `pwd`/lib/libssl.35.tbd -Xlinker `pwd`/lib/libcrypto.35.tbd" >> $GITHUB_ENV
+ FILENAME="libzip-static.tar.bz2"
+ curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_macOS.tar.bz2"
+ base64 -D < $FILENAME.b64 > $FILENAME
+ tar -C deps -xjf $FILENAME
+ echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV
+ echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a -Xlinker ${SDKDIR}/usr/lib/libbz2.tbd -Xlinker ${SDKDIR}/usr/lib/liblzma.tbd -lz" >> $GITHUB_ENV
+ - name: autogen
+ run: |
+ export CFLAGS="${{env.BUILD_CFLAGS}} -Wno-nullability-completeness -Wno-expansion-to-defined"
+ echo "Using CFLAGS: $CFLAGS"
+ ./autogen.sh PKG_CONFIG_PATH=/usr/local/lib/pkgconfig \
+ openssl_CFLAGS="$LIBRESSL_CFLAGS" openssl_LIBS="$LIBRESSL_LIBS" \
+ libzip_CFLAGS="$LIBZIP_CFLAGS -I${{env.SDKDIR}}/usr/include" libzip_LIBS="$LIBZIP_LIBS -lz" \
+ libimobiledevice_CFLAGS="-I/usr/local/include ${{env.LIBRESSL_CFLAGS}}" libimobiledevice_LIBS="-L/usr/local/lib -lusbmuxd-2.0 -limobiledevice-glue-1.0 -limobiledevice-1.0 ${{env.LIBRESSL_LIBS}}"
+ - name: make
+ run: make
+ - name: make install
+ run: sudo make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf ideviceinstaller.tar usr
+ - name: publish artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ideviceinstaller-latest_macOS
+ path: ideviceinstaller.tar
+ build-windows:
+ runs-on: windows-latest
+ defaults:
+ run:
+ shell: msys2 {0}
+ strategy:
+ fail-fast: false
+ matrix:
+ include: [
+ { msystem: MINGW64, arch: x86_64 },
+ { msystem: MINGW32, arch: i686 }
+ ]
+ steps:
+ - uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.msystem }}
+ release: false
+ update: false
+ install: >-
+ base-devel
+ git
+ mingw-w64-${{ matrix.arch }}-gcc
+ mingw-w64-${{ matrix.arch }}-pkg-config
+ mingw-w64-${{ matrix.arch }}-openssl
+ mingw-w64-${{ matrix.arch }}-xz
+ mingw-w64-${{ matrix.arch }}-bzip2
+ make
+ libtool
+ autoconf
+ automake-wrapper
+ liblzma
+ - name: prepare environment
+ run: |
+ dest=`echo ${{ matrix.msystem }} |tr [:upper:] [:lower:]`
+ echo "dest=$dest" >> $GITHUB_ENV
+ echo "target_triplet=`gcc -dumpmachine`" >> $GITHUB_ENV
+ - name: fetch libplist
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libplist-latest_${{ matrix.arch }}-${{ env.dest }}
+ repo: libimobiledevice/libplist
+ - name: fetch libusbmuxd
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libusbmuxd-latest_${{ matrix.arch }}-${{ env.dest }}
+ repo: libimobiledevice/libusbmuxd
+ - name: fetch libimobiledevice-glue
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-glue-latest_${{ matrix.arch }}-${{ env.dest }}
+ repo: libimobiledevice/libimobiledevice-glue
+ - name: fetch libimobiledevice
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ github_token: ${{secrets.GITHUB_TOKEN}}
+ workflow: build.yml
+ name: libimobiledevice-latest_${{ matrix.arch }}-${{ env.dest }}
+ repo: libimobiledevice/libimobiledevice
+ - name: install external dependencies
+ run: |
+ mkdir extract
+ for I in *.tar; do
+ tar -C extract -xvf $I
+ done
+ cp -r extract/* /
+ - uses: actions/checkout@v4
+ - name: install additional requirements
+ run: |
+ FILENAME="libzip-1.7.1-static.tar.bz2"
+ curl -o $FILENAME.b64 -Ls "https://gist.github.com/nikias/3da15d03120382f87b44029cd8495a02/raw/99cd8138fed99e8f6530b6f179f787342c698e1f/libzip-1.7.1_static_${{matrix.arch}}-${{env.dest}}.tar.bz2"
+ base64 -d < $FILENAME.b64 > $FILENAME
+ mkdir deps
+ tar -C deps -xjf $FILENAME
+ echo "LIBZIP_CFLAGS=-I`pwd`/deps/include" >> $GITHUB_ENV
+ echo "LIBZIP_LIBS=`pwd`/deps/lib/libzip.a /${{env.dest}}/lib/libbz2.a /${{env.dest}}/lib/liblzma.a " >> $GITHUB_ENV
+ - name: autogen
+ run: ./autogen.sh PKG_CONFIG_PATH=/${{env.dest}}/lib/pkgconfig libzip_CFLAGS="${{env.LIBZIP_CFLAGS}}" libzip_LIBS="${{env.LIBZIP_LIBS}} /${{env.dest}}/lib/libz.a"
+ - name: make
+ run: make
+ - name: make install
+ run: make install
+ - name: prepare artifact
+ run: |
+ mkdir -p dest
+ DESTDIR=`pwd`/dest make install
+ tar -C dest -cf ideviceinstaller.tar ${{ env.dest }}
+ - name: publish artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ideviceinstaller-latest_${{ matrix.arch }}-${{ env.dest }}
+ path: ideviceinstaller.tar
diff --git a/AUTHORS b/AUTHORS
index b818ec5..e347cf3 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,4 @@
+Julien Lavergne
+Keith Gable
+Martin Szulecki
Nikias Bassen
diff --git a/Makefile.am b/Makefile.am
index 7c28996..ca007b7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,3 +2,10 @@ AUTOMAKE_OPTIONS = foreign
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = src man
+EXTRA_DIST = \
+ README.md \
+ git-version-gen
+
+dist-hook:
+ @if ! git diff --quiet; then echo "Uncommitted changes present; not releasing"; exit 1; fi
+ echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/NEWS b/NEWS
index 4ca6291..f7dc77f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,83 @@
+Version 1.2.0
+~~~~~~~~~~~~~
+
+* Breaking changes:
+ - Command line options are now using subcommands like `install`, `list`, etc. instead of the old `-i`, `-l`
+
+* Changes:
+ - Add options for using external sinf and iTunes metadata
+ - Add JSON output capability
+ - Use CFBundleShortVersionString to display the app version
+ - Add -a command line switch to specify return attributes for 'list' command
+ - Add -b, --bundle-identifier command line switch to allow querying for bundle identifier
+
+* Bug Fixes:
+ - Fix picking wrong app directory from zip/ipa when there are other directories
+ - Fix wrong exit code on specific output format (xml or json)
+ - Make sure to always return an error code if something goes wrong
+
+Version 1.1.1
+~~~~~~~~~~~~~
+
+* Changes:
+ - Bump autoconf requirement to 2.64
+ - Bump libzip dependency to 0.10
+ - Ignore .DS_Store and hidden files when parsing ZIP files
+ - Fix device removal detection triggering on any device unplug
+ - Improve excessive progress output
+ - Return non-zero exit status on errors
+ - Remove length check on UDID argument to support newer devices
+ - Fix win32 build
+ - Add "-n" option to make waiting on install/uninstall notification optional
+ - Ignore SIGPIPE signal
+ - Add "--network" and "--version" options to ideviceactivation tool
+ - Bump libimobiledevice dependency to 1.3.0
+ - Bump libplist dependency to 2.2.0
+ - Improve README.md with project description, installation, contributing and
+ usage sections
+
+Version 1.1.0
+~~~~~~~~~~~~~
+
+* Changes:
+ - Fix installation of archives which are missing app directory zip file entry
+ - Return non-zero exit status (128) when device error occurs
+ - Add error checking for readlink use
+ - Add support for installing from directories which contain symlinks
+ - Plug a few memory leaks
+ - Print AFC error code if writing fails
+ - Fix possible buffer overflow with filename argument
+ - Increase transfer buffer size for faster file uploads
+ - Fix building with older libzip versions
+ - Swap "-u" and "-U" arguments and print deprecation warning if still used
+ - Improve error reporting on wrong usage
+ - Replace "iPhone" wording with more general term "iOS device"
+ - Remove waiting timeouts as they appear unreliable with large archives
+ - Don't wait for a notification during uninstall as there is none sometimes
+ - Improve command line output for more clarity and unification
+ - Detect device removal and abort operation in that case
+ - Turn some errors messages into warnings to not confuse users
+ - Support iOS 7 correctly by passing "CFBundleIdentifier" option
+ - Allow installation of developer apps by passing a ".app" directory
+ - Add compatibility for libimobiledevice >= 1.1.5
+ - Fix file operations for WIN32
+ - Fix wrong usage description for upgrade command
+ - Use CFBundleExecutable instead of CFBundleName to construct executable path
+ - Rename "uuid" to correct "udid" abbreviation as used in other tools
+ - Fix various issues with ZIP index and locating files in the archive
+ - Improve detection of Info.plist in application archives
+ - Fix compiler warnings
+ - Allow creating app archives with just the documents/user data
+ - Add support for CarrierBundle installation (.ipcc files)
+
+Version 1.0.1
+~~~~~~~~~~~~~
+
+* Changes:
+ - Fix minor manpage typos
+ - Fix build on OS X
+ - Fix build warnings and make compilers happy
+
Version 1.0.0
~~~~~~~~~~~~~
diff --git a/README b/README
deleted file mode 100644
index 6f857a7..0000000
--- a/README
+++ /dev/null
@@ -1,56 +0,0 @@
-About
-=====
-
-ideviceinstaller is a tool to interact with the installation_proxy
-of an iDevice allowing to install, upgrade, uninstall, archive, restore,
-and enumerate installed or archived apps.
-
-It makes use of the libimobiledevice library that allows communication
-with the devices.
-
-Requirements
-============
-
-Development Packages of:
- libimobiledevice
- libplist
- libzip
-
-Installation
-============
-
-To compile run:
- ./configure
- make
- sudo make install
-
-Notes
-=====
-
-There is a known bug in libimobiledevice which causes transfers of large apps
-to/from the device to abort due to a timeout.
-
-Who/What/Where?
-===============
-
-Home:
- http://www.libimobiledevice.org/
-
-Code:
- git clone git://git.sukimashita.com/ideviceinstaller.git
-
-Tickets:
- http://libiphone.lighthouseapp.com/
-
-Mailing List:
- http://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel
-
-Credits
-=======
-
-Apple, iPhone, iPod, and iPod Touch are trademarks of Apple Inc.
-libimobiledevice is an independent software library and has not been
-authorized, sponsored, or otherwise approved by Apple Inc.
-
-README Updated on:
- 2011-03-19
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..84d4f38
--- /dev/null
+++ b/README.md
@@ -0,0 +1,127 @@
+# ideviceinstaller
+
+*A command-line application to manage apps and app archives on iOS devices.*
+
+![](https://github.com/libimobiledevice/ideviceinstaller/actions/workflows/build.yml/badge.svg)
+
+## Features
+
+The ideviceinstaller application allows interacting with the app installation
+service of an iOS device.
+
+It makes use of the fabulous [libimobiledevice library](https://github.com/libimobiledevice/libimobiledevice) that allows
+communication with iOS devices.
+
+Some key features are:
+
+- **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, XML, or JSON format
+- **Compatibility**: Supports latest device firmware releases
+- **Cross-Platform:** Tested on Linux, macOS, Windows and Android platforms
+
+## Installation / Getting started
+
+### Debian / Ubuntu Linux
+
+First install all required dependencies and build tools:
+```shell
+sudo apt-get install \
+ build-essential \
+ pkg-config \
+ checkinstall \
+ git \
+ autoconf \
+ automake \
+ libtool-bin \
+ libplist-dev \
+ libimobiledevice-dev \
+ libzip-dev \
+ usbmuxd
+```
+
+Continue with cloning the actual project repository:
+```shell
+git clone https://github.com/libimobiledevice/ideviceinstaller.git
+cd ideviceinstaller
+```
+
+Now you can build and install it:
+```shell
+./autogen.sh
+make
+sudo make install
+```
+
+## Usage
+
+First of all attach your device to your machine.
+
+Then simply run:
+```shell
+ideviceinstaller list
+```
+
+This will print a list of `<appid>` identifiers (bundle identifiers) for use
+with other commands (see further below).
+
+To install an app from a package file use:
+```shell
+ideviceinstaller install <file>
+```
+
+To uninstall an app with the `<appid>` from the device use:
+```shell
+ideviceinstaller uninstall <appid>
+```
+
+Please consult the usage information or manual page for a full documentation of
+available command line options:
+```shell
+ideviceinstaller --help
+man ideviceinstaller
+```
+
+## Contributing
+
+We welcome contributions from anyone and are grateful for every pull request!
+
+If you'd like to contribute, please fork the `master` branch, change, commit and
+send a pull request for review. Once approved it can be merged into the main
+code base.
+
+If you plan to contribute larger changes or a major refactoring, please create a
+ticket first to discuss the idea upfront to ensure less effort for everyone.
+
+Please make sure your contribution adheres to:
+* Try to follow the code style of the project
+* Commit messages should describe the change well without being too short
+* Try to split larger changes into individual commits of a common domain
+* Use your real name and a valid email address for your commits
+
+We are still working on the guidelines so bear with us!
+
+## Links
+
+* Homepage: https://libimobiledevice.org/
+* Repository: https://git.libimobiledevice.org/ideviceinstaller.git
+* Repository (Mirror): https://github.com/libimobiledevice/ideviceinstaller.git
+* Issue Tracker: https://github.com/libimobiledevice/ideviceinstaller/issues
+* Mailing List: https://lists.libimobiledevice.org/mailman/listinfo/libimobiledevice-devel
+* Twitter: https://twitter.com/libimobiledev
+
+## License
+
+This software is licensed under the [GNU General Public License v2.0](https://www.gnu.org/licenses/gpl-2.0.en.html),
+also included in the repository in the `COPYING` file.
+
+## Credits
+
+Apple, iPhone, iPad, iPod, iPod Touch, Apple TV, Apple Watch, Mac, iOS,
+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: 2023-07-20
diff --git a/autogen.sh b/autogen.sh
index 3292973..5a0ec43 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,15 +1,26 @@
#!/bin/sh
-gprefix=`which glibtoolize 2>&1 >/dev/null`
-if [ $? -eq 0 ]; then
- glibtoolize --force
-else
- libtoolize --force
-fi
-aclocal -I m4
-autoheader
-automake --add-missing
-autoconf
+
+olddir=`pwd`
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+(
+ cd "$srcdir"
+
+ gprefix=`which glibtoolize 2>&1 >/dev/null`
+ if [ $? -eq 0 ]; then
+ glibtoolize --force
+ else
+ libtoolize --force
+ fi
+ aclocal -I m4
+ autoheader
+ automake --add-missing
+ autoconf
+
+ cd "$olddir"
+)
if [ -z "$NOCONFIGURE" ]; then
- ./configure "$@"
+ $srcdir/configure "$@"
fi
diff --git a/configure.ac b/configure.ac
index 277f966..be124b9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,35 +1,30 @@
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.61)
-AC_INIT(ideviceinstaller, 1.0.0, nospam@nowhere.com)
-AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip])
+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 >= 0.9.7)
-PKG_CHECK_MODULES(libimobiledevice10, libimobiledevice-1.0 >= 1.0.0, libimobiledevice_1_0=yes, libimobiledevice_1_0=no)
-if test x"$libimobiledevice_1_0" = xyes; then
- AC_DEFINE([HAVE_LIBIMOBILEDEVICE_1_0], 1, [Define if libimobiledevice is using 1.0.0 API])
-fi
-PKG_CHECK_MODULES(libimobiledevice11, libimobiledevice-1.0 >= 1.1.0, libimobiledevice_1_1=yes, libimobiledevice_1_1=no)
-if test x"$libimobiledevice_1_1" = xyes; then
- AC_DEFINE([HAVE_LIBIMOBILEDEVICE_1_1], 1, [Define if libimobiledevice is using 1.1.0 API])
-fi
-PKG_CHECK_MODULES(libglib2, glib-2.0 >= 2.14.1)
-PKG_CHECK_MODULES(libplist, libplist >= 0.15)
-PKG_CHECK_MODULES(libzip, libzip >= 0.8)
+PKG_CHECK_MODULES(libimobiledevice, libimobiledevice-1.0 >= 1.3.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.
@@ -43,16 +38,45 @@ AC_TYPE_UINT8_T
# Checks for library functions.
AC_FUNC_MALLOC
AC_FUNC_REALLOC
-AC_CHECK_FUNCS([strcasecmp strdup strerror strndup])
+AC_CHECK_FUNCS([strdup strerror asprintf vasprintf])
+
+# Check for lstat
+
+AC_MSG_CHECKING([whether lstat is available])
+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"])
+AC_MSG_RESULT([${have_lstat}])
+
+if test "x${have_lstat}" = "xyes" ; then
+ AC_DEFINE([HAVE_LSTAT], 1, [Define if lstat syscall is supported])
+fi
AS_COMPILER_FLAGS(GLOBAL_CFLAGS, "-Wall -Wextra -Wmissing-declarations -Wredundant-decls -Wshadow -Wpointer-arith -Wwrite-strings -Wswitch-default -Wno-unused-parameter -Werror -g")
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:
+-------------------------------------------
+
+ Install prefix: .........: $prefix
+ Now type 'make' to build $PACKAGE $VERSION,
+ and then 'make install' for installation.
+"
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 a9d2665..0cd0bee 100644
--- a/man/ideviceinstaller.1
+++ b/man/ideviceinstaller.1
@@ -1,101 +1,139 @@
.TH "ideviceinstaller" 1
.SH NAME
-ideviceinstaller \- Manage iDevice apps
+ideviceinstaller \- Manage apps on iOS devices.
.SH SYNOPSIS
.B ideviceinstaller
[OPTIONS]
.SH DESCRIPTION
-Allows to install, upgrade, uninstall, archive, restore and
-enumerate installed or archived apps on iDevices.
+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, \-\-uuid UUID
-target specific device by its 40-digit device UUID.
+.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
+\f[B]DynamicDiskUsage\f[] app user data disk usage
.TP
-\-o list_system
-list system apps only (apps available from the system firmware)
+\f[B]Path\f[] app installation location
.TP
-\-o list_all
-list all types of apps
+\f[B]SignerIdentity\f[] code signing identity
.TP
-\-o xml
-print output in xml format (PList)
+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.
+.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 APPID
-upgrade app specified by APPID.
+.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 \-\-app_only
+Archive application data only
.TP
-.B \-r, \-\-restore APPID
-restore archived app specified by APPID.
+.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 copy=PATH
-copy the app archive to directory PATH when done
+.B \-w, \-\-notify-wait
+Wait for app installed/uninstalled notification before reporting success of operation.
.TP
-\-o remove
-only valid when copy=PATH is used: remove after copy
-.RE
-
+.B \-h, \-\-help
+Print usage information.
.TP
-.B \-R, \-\-remove-archive APPID
-remove app archive specified by APPID.
+.B \-d, \-\-debug
+Enable communication debugging.
+.TP
+.B \-v, \-\-version
+Print version information.
+
+.SH AUTHORS
+Nikias Bassen
+
+Martin Szulecki
-.SH AUTHOR
-This manual page was written by Martin Szulecki.
+.SH ON THE WEB
+https://libimobiledevice.org
+https://github.com/libimobiledevice/ideviceinstaller
diff --git a/src/ideviceinstaller.c b/src/ideviceinstaller.c
index 3780979..d07cedf 100644
--- a/src/ideviceinstaller.c
+++ b/src/ideviceinstaller.c
@@ -1,7 +1,8 @@
-/**
- * ideviceinstaller -- Manage iPhone/iPod apps
+/*
+ * ideviceinstaller - Manage apps on iOS devices.
*
- * Copyright (C) 2010 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
*
@@ -17,7 +18,7 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifdef HAVE_CONFIG_H
@@ -33,6 +34,15 @@
#include <time.h>
#include <libgen.h>
#include <inttypes.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifndef WIN32
+#include <signal.h>
+#endif
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
@@ -44,93 +54,211 @@
#include <zip.h>
+#ifdef WIN32
+#include <windows.h>
+#define wait_ms(x) Sleep(x)
+#else
+#define wait_ms(x) { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = x * 1000000; nanosleep(&ts, NULL); }
+#endif
+
+#ifndef HAVE_VASPRINTF
+static int vasprintf(char **PTR, const char *TEMPLATE, va_list AP)
+{
+ int res;
+ char buf[16];
+ res = vsnprintf(buf, 16, TEMPLATE, AP);
+ if (res > 0) {
+ *PTR = (char*)malloc(res+1);
+ res = vsnprintf(*PTR, res+1, TEMPLATE, AP);
+ }
+ return res;
+}
+#endif
+
+#ifndef HAVE_ASPRINTF
+static int asprintf(char **PTR, const char *TEMPLATE, ...)
+{
+ int res;
+ va_list AP;
+ va_start(AP, TEMPLATE);
+ res = vasprintf(PTR, TEMPLATE, AP);
+ va_end(AP);
+ return res;
+}
+#endif
+
+#define ITUNES_METADATA_PLIST_FILENAME "iTunesMetadata.plist"
+
const char PKG_PATH[] = "PublicStaging";
const char APPARCH_PATH[] = "ApplicationArchives";
-char *uuid = NULL;
-char *options = NULL;
-char *appid = NULL;
-
-int list_apps_mode = 0;
-int install_mode = 0;
-int uninstall_mode = 0;
-int upgrade_mode = 0;
-int list_archives_mode = 0;
-int archive_mode = 0;
-int restore_mode = 0;
-int remove_archive_mode = 0;
+char *udid = NULL;
+char *cmdarg = NULL;
+char *extsinf = NULL;
+char *extmeta = NULL;
+
+enum cmd_mode {
+ CMD_NONE = 0,
+ CMD_LIST_APPS,
+ CMD_INSTALL,
+ CMD_UNINSTALL,
+ CMD_UPGRADE,
+ CMD_LIST_ARCHIVES,
+ CMD_ARCHIVE,
+ CMD_RESTORE,
+ CMD_REMOVE_ARCHIVE
+};
+
+int cmd = CMD_NONE;
char *last_status = NULL;
-int wait_for_op_complete = 0;
+int wait_for_command_complete = 0;
+int use_network = 0;
+int use_notifier = 0;
int notification_expected = 0;
-int op_completed = 0;
-int err_occured = 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()
+{
+ 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);
+ 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 {
+ plist_write_to_stream(node, stdout, PLIST_FORMAT_PRINT, PLIST_OPT_NO_NEWLINE);
+ }
+ }
+ }
+ printf("\n");
+ }
+}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_0
static void notifier(const char *notification, void *unused)
-#else
-static void notifier(const char *notification)
-#endif
{
- /* printf("notification received: %s\n", notification);*/
notified = 1;
}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
-static void status_cb(const char *operation, plist_t status, void *unused)
-#else
-static void status_cb(const char *operation, plist_t status)
-#endif
+static void status_cb(plist_t command, plist_t status, void *unused)
{
- if (status && operation) {
- plist_t npercent = plist_dict_get_item(status, "PercentComplete");
- plist_t nstatus = plist_dict_get_item(status, "Status");
- plist_t nerror = plist_dict_get_item(status, "Error");
- int percent = 0;
- char *status_msg = NULL;
- if (npercent) {
- uint64_t val = 0;
- plist_get_uint_val(npercent, &val);
- percent = val;
- }
- if (nstatus) {
- plist_get_string_val(nstatus, &status_msg);
- if (!strcmp(status_msg, "Complete")) {
- op_completed = 1;
+ if (command && status) {
+ char* command_name = NULL;
+ instproxy_command_get_name(command, &command_name);
+
+ /* get status */
+ char *status_name = NULL;
+ instproxy_status_get_name(status, &status_name);
+
+ if (status_name) {
+ if (!strcmp(status_name, "Complete")) {
+ command_completed = 1;
}
}
- if (!nerror) {
- if (last_status && (strcmp(last_status, status_msg))) {
- printf("\n");
- }
- if (!npercent) {
- printf("%s - %s\n", operation, status_msg);
- } else {
- printf("%s - %s (%d%%)\r", operation, status_msg, percent);
+ /* get error if any */
+ char* error_name = NULL;
+ char* error_description = NULL;
+ uint64_t error_code = 0;
+ instproxy_status_get_error(status, &error_name, &error_description, &error_code);
+
+ /* output/handling */
+ if (!error_name) {
+ if (!strcmp(command_name, "Browse")) {
+ uint64_t total = 0;
+ uint64_t current_index = 0;
+ uint64_t current_amount = 0;
+ plist_t current_list = NULL;
+ instproxy_status_get_current_list(status, &total, &current_index, &current_amount, &current_list);
+ if (current_list) {
+ print_apps(current_list);
+ plist_free(current_list);
+ }
+ } else if (status_name) {
+ /* get progress if any */
+ int percent = -1;
+ instproxy_status_get_percent_complete(status, &percent);
+
+ if (last_status && (strcmp(last_status, status_name))) {
+ printf("\n");
+ }
+
+ if (percent >= 0) {
+ printf("\r%s: %s (%d%%)", command_name, status_name, percent);
+ } else {
+ printf("\r%s: %s", command_name, status_name);
+ }
+ if (command_completed) {
+ printf("\n");
+ }
}
} else {
- char *err_msg = NULL;
- plist_get_string_val(nerror, &err_msg);
- printf("%s - Error occured: %s\n", operation, err_msg);
- free(err_msg);
- err_occured = 1;
- }
- if (last_status) {
- free(last_status);
- last_status = NULL;
- }
- if (status_msg) {
- last_status = strdup(status_msg);
- free(status_msg);
+ /* report error to the user */
+ if (error_description)
+ fprintf(stderr, "ERROR: %s failed. Got error \"%s\" with code 0x%08"PRIx64": %s\n", command_name, error_name, error_code, error_description ? error_description: "N/A");
+ else
+ fprintf(stderr, "ERROR: %s failed. Got error \"%s\".\n", command_name, error_name);
+ err_occurred = 1;
}
+
+ /* clean up */
+ free(error_name);
+ free(error_description);
+
+ free(last_status);
+ last_status = status_name;
+
+ free(command_name);
+ command_name = NULL;
} else {
- printf("%s: called with invalid data!\n", __func__);
+ fprintf(stderr, "ERROR: %s was called with invalid arguments!\n", __func__);
}
}
-static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
+static int zip_get_contents(struct zip *zf, const char *filename, int locate_flags, char **buffer, uint32_t *len)
{
struct zip_stat zs;
struct zip_file *zfile;
@@ -140,7 +268,6 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
*len = 0;
if (zindex < 0) {
- fprintf(stderr, "ERROR: could not locate %s in archive!\n", filename);
return -1;
}
@@ -163,7 +290,7 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
}
*buffer = malloc(zs.size);
- if (zip_fread(zfile, *buffer, zs.size) != zs.size) {
+ if (zs.size > LLONG_MAX || zip_fread(zfile, *buffer, zs.size) != (zip_int64_t)zs.size) {
fprintf(stderr, "ERROR: zip_fread %" PRIu64 " bytes from '%s'\n", (uint64_t)zs.size, filename);
free(*buffer);
*buffer = NULL;
@@ -175,572 +302,1096 @@ static int zip_f_get_contents(struct zip *zf, const char *filename, int locate_f
return 0;
}
-static void do_wait_when_needed()
+static int zip_get_app_directory(struct zip* zf, char** path)
{
- int i = 0;
- struct timespec ts;
- ts.tv_sec = 0;
- ts.tv_nsec = 500000000;
-
- /* wait for operation to complete */
- while (wait_for_op_complete && !op_completed && !err_occured
- && !notified && (i < 60)) {
- nanosleep(&ts, NULL);
- i++;
+ 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;
+
+ /* look through all filenames in the archive */
+ do {
+ /* get filename at current index */
+ name = zip_get_name(zf, i++, 0);
+ if (name != NULL) {
+ /* check if we have a "Payload/.../" name */
+ len = strlen(name);
+ if (!strncmp(name, "Payload/", 8) && (len > 8)) {
+ /* skip hidden files */
+ if (name[8] == '.')
+ continue;
+
+ /* locate the second directory delimiter */
+ const char* p = name + 8;
+ do {
+ if (*p == '/') {
+ break;
+ }
+ } while(p++ != NULL);
+
+ /* try next entry if not found */
+ if (p == NULL)
+ continue;
+
+ 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;
+ }
+
+ /* allocate and copy filename */
+ *path = (char*)malloc(len + 1);
+ strncpy(*path, name, len);
+
+ /* add terminating null character */
+ char* t = *path + len;
+ *t = '\0';
+ break;
+ }
+ }
+ } 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");
+ is_device_connected = 0;
+ }
+ }
+}
+
+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);
+
+ /* wait for command to complete */
+ while (wait_for_command_complete && !command_completed && !err_occurred
+ && is_device_connected) {
+ wait_ms(50);
}
/* wait some time if a notification is expected */
- while (notification_expected && !notified && !err_occured && (i < 10)) {
- nanosleep(&ts, NULL);
- i++;
+ while (use_notifier && notification_expected && !notified && !err_occurred && is_device_connected) {
+ 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("Manage apps on an iDevice.\n\n");
- printf
- (" -U, --uuid UUID\tTarget specific device by its 40-digit device UUID.\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"
- " -u, --uninstall APPID\tUninstall app specified by APPID.\n"
- " -g, --upgrade APPID\tUpgrade app specified by APPID.\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 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"
- " -h, --help\t\tprints usage information\n"
- " -d, --debug\t\tenable communication debugging\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"
+ );
}
+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", 0, NULL, 'h'},
- {"uuid", 1, NULL, 'U'},
- {"list-apps", 0, NULL, 'l'},
- {"install", 1, NULL, 'i'},
- {"uninstall", 1, NULL, 'u'},
- {"upgrade", 1, NULL, 'g'},
- {"list-archives", 0, NULL, 'L'},
- {"archive", 1, NULL, 'a'},
- {"restore", 1, NULL, 'r'},
- {"remove-archive", 1, NULL, 'R'},
- {"options", 1, NULL, 'o'},
- {"debug", 0, NULL, 'd'},
- {NULL, 0, NULL, 0}
+ { "help", no_argument, NULL, 'h' },
+ { "udid", required_argument, NULL, 'u' },
+ { "network", no_argument, NULL, 'n' },
+ { "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:d", longopts,
- (int *) 0);
+ c = getopt_long(argc, argv, "hu:nwdvb:a:s:m:", longopts, (int*)0);
if (c == -1) {
break;
}
switch (c) {
case 'h':
- print_usage(argc, argv);
+ print_usage(argc, argv, 0);
exit(0);
- case 'U':
- if (strlen(optarg) != 40) {
- printf("%s: invalid UUID specified (length != 40)\n",
- argv[0]);
- print_usage(argc, argv);
+ case 'u':
+ if (!*optarg) {
+ printf("ERROR: UDID must not be empty!\n");
+ print_usage(argc, argv, 1);
exit(2);
}
- uuid = strdup(optarg);
+ udid = strdup(optarg);
break;
- case 'l':
- list_apps_mode = 1;
- break;
- case 'i':
- install_mode = 1;
- appid = strdup(optarg);
- break;
- case 'u':
- uninstall_mode = 1;
- appid = strdup(optarg);
- break;
- case 'g':
- upgrade_mode = 1;
- appid = strdup(optarg);
- break;
- case 'L':
- list_archives_mode = 1;
+ case 'n':
+ use_network = 1;
break;
case 'a':
- archive_mode = 1;
- 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':
- restore_mode = 1;
- 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':
- remove_archive_mode = 1;
- 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;
break;
case 'd':
idevice_set_debug_level(1);
break;
+ 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 (optind <= 1 || (argc - optind > 0)) {
- print_usage(argc, argv);
+ argv += optind;
+ argc -= optind;
+
+ 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)
+{
+ FILE *f = NULL;
+ uint64_t af = 0;
+ char buf[1048576];
+
+ f = fopen(filename, "rb");
+ if (!f) {
+ fprintf(stderr, "fopen: %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ if ((afc_file_open(afc, dstfn, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS) || !af) {
+ fclose(f);
+ fprintf(stderr, "afc_file_open on '%s' failed!\n", dstfn);
+ return -1;
+ }
+
+ size_t amount = 0;
+ do {
+ amount = fread(buf, 1, sizeof(buf), f);
+ if (amount > 0) {
+ uint32_t written, total = 0;
+ while (total < amount) {
+ written = 0;
+ afc_error_t aerr = afc_file_write(afc, af, buf, amount, &written);
+ if (aerr != AFC_E_SUCCESS) {
+ fprintf(stderr, "AFC Write error: %d\n", aerr);
+ break;
+ }
+ total += written;
+ }
+ if (total != amount) {
+ fprintf(stderr, "Error: wrote only %u of %u\n", total, (uint32_t)amount);
+ afc_file_close(afc, af);
+ fclose(f);
+ return -1;
+ }
+ }
+ } while (amount > 0);
+
+ afc_file_close(afc, af);
+ fclose(f);
+
+ return 0;
+}
+
+static void afc_upload_dir(afc_client_t afc, const char* path, const char* afcpath)
+{
+ afc_make_directory(afc, afcpath);
+
+ DIR *dir = opendir(path);
+ if (dir) {
+ struct dirent* ep;
+ while ((ep = readdir(dir))) {
+ if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) {
+ continue;
+ }
+ char *fpath = (char*)malloc(strlen(path)+1+strlen(ep->d_name)+1);
+ char *apath = (char*)malloc(strlen(afcpath)+1+strlen(ep->d_name)+1);
+
+ struct stat st;
+
+ strcpy(fpath, path);
+ strcat(fpath, "/");
+ strcat(fpath, ep->d_name);
+
+ strcpy(apath, afcpath);
+ strcat(apath, "/");
+ strcat(apath, ep->d_name);
+
+#ifdef HAVE_LSTAT
+ if ((lstat(fpath, &st) == 0) && S_ISLNK(st.st_mode)) {
+ char *target = (char *)malloc(st.st_size+1);
+ if (readlink(fpath, target, st.st_size+1) < 0) {
+ fprintf(stderr, "ERROR: readlink: %s (%d)\n", strerror(errno), errno);
+ } else {
+ target[st.st_size] = '\0';
+ afc_make_link(afc, AFC_SYMLINK, target, fpath);
+ }
+ free(target);
+ } else
+#endif
+ if ((stat(fpath, &st) == 0) && S_ISDIR(st.st_mode)) {
+ afc_upload_dir(afc, fpath, apath);
+ } else {
+ afc_upload_file(afc, fpath, apath);
+ }
+ free(fpath);
+ free(apath);
+ }
+ closedir(dir);
+ }
+}
+
+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 phone = NULL;
+ idevice_t device = NULL;
lockdownd_client_t client = NULL;
instproxy_client_t ipc = NULL;
+ instproxy_error_t err;
np_client_t np = NULL;
afc_client_t afc = NULL;
- uint16_t port = 0;
- int res = 0;
+ lockdownd_service_descriptor_t service = NULL;
+ int res = EXIT_FAILURE;
+ char *bundleidentifier = NULL;
+#ifndef WIN32
+ signal(SIGPIPE, SIG_IGN);
+#endif
parse_opts(argc, argv);
argc -= optind;
argv += optind;
- if (IDEVICE_E_SUCCESS != idevice_new(&phone, uuid)) {
- fprintf(stderr, "No iPhone found, is it plugged in?\n");
- return -1;
+ if (IDEVICE_E_SUCCESS != idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX)) {
+ if (udid) {
+ fprintf(stderr, "No device found with udid %s.\n", udid);
+ } else {
+ fprintf(stderr, "No device found.\n");
+ }
+ return EXIT_FAILURE;
}
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
- fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
- goto leave_cleanup;
+ if (!udid) {
+ idevice_get_udid(device, &udid);
}
- if ((lockdownd_start_service
- (client, "com.apple.mobile.notification_proxy",
- &port) != LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr,
- "Could not start com.apple.mobile.notification_proxy!\n");
+ 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 (np_client_new(phone, port, &np) != NP_E_SUCCESS) {
- fprintf(stderr, "Could not connect to notification_proxy!\n");
- goto leave_cleanup;
- }
+ if (use_notifier) {
+ 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;
+ }
-#ifdef HAVE_LIBIMOBILEDEVICE_1_0
- np_set_notify_callback(np, notifier, NULL);
-#else
- np_set_notify_callback(np, notifier);
-#endif
+ np_error_t nperr = np_client_new(device, service, &np);
- const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
- np_observe_notifications(np, noties);
+ if (nperr != NP_E_SUCCESS) {
+ fprintf(stderr, "Could not connect to notification_proxy!\n");
+ goto leave_cleanup;
+ }
+
+ np_set_notify_callback(np, notifier, NULL);
+
+ const char *noties[3] = { NP_APP_INSTALLED, NP_APP_UNINSTALLED, NULL };
+
+ np_observe_notifications(np, noties);
+ }
run_again:
- port = 0;
- if ((lockdownd_start_service
- (client, "com.apple.mobile.installation_proxy",
- &port) != LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr,
- "Could not start com.apple.mobile.installation_proxy!\n");
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
+
+ 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;
}
- if (instproxy_client_new(phone, port, &ipc) != INSTPROXY_E_SUCCESS) {
+ err = instproxy_client_new(device, service, &ipc);
+
+ if (service) {
+ lockdownd_service_descriptor_free(service);
+ }
+ service = NULL;
+
+ if (err != INSTPROXY_E_SUCCESS) {
fprintf(stderr, "Could not connect to installation_proxy!\n");
goto leave_cleanup;
}
setbuf(stdout, NULL);
- if (last_status) {
- free(last_status);
- last_status = NULL;
- }
+ free(last_status);
+ last_status = NULL;
+
notification_expected = 0;
- if (list_apps_mode) {
- int xml_mode = 0;
+ if (cmd == CMD_LIST_APPS) {
plist_t client_opts = instproxy_client_options_new();
instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL);
- instproxy_error_t err;
plist_t apps = NULL;
- /* look for options */
- if (options) {
- char *opts = strdup(options);
- char *elem = strtok(opts, ",");
- while (elem) {
- if (!strcmp(elem, "list_system")) {
- if (!client_opts) {
- client_opts = instproxy_client_options_new();
- }
- instproxy_client_options_add(client_opts, "ApplicationType", "System", NULL);
- } else if (!strcmp(elem, "list_all")) {
- instproxy_client_options_free(client_opts);
- client_opts = NULL;
- } else if (!strcmp(elem, "list_user")) {
- /* do nothing, we're already set */
- } else if (!strcmp(elem, "xml")) {
- xml_mode = 1;
- }
- elem = strtok(NULL, ",");
- }
+ 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);
}
- err = instproxy_browse(ipc, client_opts, &apps);
- instproxy_client_options_free(client_opts);
- if (err != INSTPROXY_E_SUCCESS) {
- fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
- goto leave_cleanup;
+ if (bundle_ids) {
+ plist_dict_set_item(client_opts, "BundleIDs", plist_copy(bundle_ids));
}
- if (!apps || (plist_get_node_type(apps) != PLIST_ARRAY)) {
- fprintf(stderr,
- "ERROR: instproxy_browse returnd an invalid plist!\n");
- goto leave_cleanup;
+
+ 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 (xml_mode) {
- char *xml = NULL;
- uint32_t len = 0;
- plist_to_xml(apps, &xml, &len);
- if (xml) {
- puts(xml);
- free(xml);
+ 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");
+ goto leave_cleanup;
+ }
+ char *buf = NULL;
+ uint32_t len = 0;
+ 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;
}
- printf("Total: %d apps\n", plist_array_get_size(apps));
- 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_appid =
- plist_dict_get_item(app, "CFBundleIdentifier");
- char *s_appid = NULL;
- char *s_dispName = NULL;
- char *s_version = NULL;
- plist_t dispName =
- plist_dict_get_item(app, "CFBundleDisplayName");
- plist_t version = plist_dict_get_item(app, "CFBundleVersion");
-
- if (p_appid) {
- plist_get_string_val(p_appid, &s_appid);
- }
- if (!s_appid) {
- fprintf(stderr, "ERROR: Failed to get APPID!\n");
- break;
- }
- if (dispName) {
- plist_get_string_val(dispName, &s_dispName);
- }
- if (version) {
- plist_get_string_val(version, &s_version);
- }
+ print_apps_header();
- if (!s_dispName) {
- s_dispName = strdup(s_appid);
- }
- if (s_version) {
- printf("%s - %s %s\n", s_appid, s_dispName, s_version);
- free(s_version);
- } else {
- printf("%s - %s\n", s_appid, s_dispName);
- }
- free(s_dispName);
- free(s_appid);
+ err = instproxy_browse_with_callback(ipc, client_opts, status_cb, NULL);
+ if (err == INSTPROXY_E_RECEIVE_TIMEOUT) {
+ fprintf(stderr, "NOTE: timeout waiting for device to browse apps, trying again...\n");
}
- plist_free(apps);
- } else if (install_mode || upgrade_mode) {
+
+ instproxy_client_options_free(client_opts);
+ if (err != INSTPROXY_E_SUCCESS) {
+ fprintf(stderr, "ERROR: instproxy_browse returned %d\n", err);
+ goto leave_cleanup;
+ }
+
+ wait_for_command_complete = 1;
+ notification_expected = 0;
+ } else if (cmd == CMD_INSTALL || cmd == CMD_UPGRADE) {
plist_t sinf = NULL;
plist_t meta = NULL;
char *pkgname = NULL;
struct stat fst;
- FILE *f = NULL;
uint64_t af = 0;
char buf[8192];
- port = 0;
- if ((lockdownd_start_service(client, "com.apple.afc", &port) !=
- LOCKDOWN_E_SUCCESS) || !port) {
- fprintf(stderr, "Could not start com.apple.afc!\n");
+ lockdownd_service_descriptor_free(service);
+ service = NULL;
+
+ 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;
}
lockdownd_client_free(client);
client = NULL;
- if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
+ if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
goto leave_cleanup;
}
- if (stat(appid, &fst) != 0) {
- fprintf(stderr, "ERROR: stat: %s: %s\n", appid, strerror(errno));
+ if (stat(cmdarg, &fst) != 0) {
+ fprintf(stderr, "ERROR: stat: %s: %s\n", cmdarg, strerror(errno));
goto leave_cleanup;
}
+ char **strs = NULL;
+ if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
+ if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
+ fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
+ }
+ }
+ if (strs) {
+ int i = 0;
+ while (strs[i]) {
+ free(strs[i]);
+ i++;
+ }
+ free(strs);
+ }
+
+ plist_t client_opts = instproxy_client_options_new();
+
/* open install package */
int errp = 0;
- struct zip *zf = zip_open(appid, 0, &errp);
- if (!zf) {
- fprintf(stderr, "ERROR: zip_open: %s: %d\n", appid, errp);
- goto leave_cleanup;
- }
+ struct zip *zf = NULL;
- /* extract iTunesMetadata.plist from package */
- char *zbuf = NULL;
- uint32_t len = 0;
- if (zip_f_get_contents(zf, "iTunesMetadata.plist", 0, &zbuf, &len) == 0) {
- meta = plist_new_data(zbuf, len);
- }
- if (zbuf) {
- free(zbuf);
- }
+ 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", cmdarg, errp);
+ goto leave_cleanup;
+ }
- /* we need to get the CFBundleName first */
- plist_t info = NULL;
- zbuf = NULL;
- len = 0;
- if (zip_f_get_contents(zf, "Info.plist", ZIP_FL_NODIR, &zbuf, &len) < 0) {
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
- if (memcmp(zbuf, "bplist00", 8) == 0) {
- plist_from_bin(zbuf, len, &info);
- } else {
- plist_from_xml(zbuf, len, &info);
- }
- free(zbuf);
+ char* ipcc = strdup(cmdarg);
+ if ((asprintf(&pkgname, "%s/%s", PKG_PATH, basename(ipcc)) > 0) && pkgname) {
+ afc_make_directory(afc, pkgname);
+ }
- if (!info) {
- fprintf(stderr, "Could not parse Info.plist!\n");
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
+ printf("Uploading %s package contents... ", basename(ipcc));
+
+ /* extract the contents of the .ipcc file to PublicStaging/<name>.ipcc directory */
+ 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;
+ if (!zname) continue;
+ if (zname[strlen(zname)-1] == '/') {
+ // directory
+ if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) > 0) && dstpath) {
+ afc_make_directory(afc, dstpath); }
+ free(dstpath);
+ dstpath = NULL;
+ } else {
+ // file
+ struct zip_file* zfile = zip_fopen_index(zf, i, 0);
+ if (!zfile) continue;
+
+ if ((asprintf(&dstpath, "%s/%s/%s", PKG_PATH, basename(ipcc), zname) <= 0) || !dstpath || (afc_file_open(afc, dstpath, AFC_FOPEN_WRONLY, &af) != AFC_E_SUCCESS)) {
+ fprintf(stderr, "ERROR: can't open afc://%s for writing\n", dstpath);
+ free(dstpath);
+ dstpath = NULL;
+ zip_fclose(zfile);
+ continue;
+ }
- char *bundlename = NULL;
+ struct zip_stat zs;
+ zip_stat_init(&zs);
+ if (zip_stat_index(zf, i, 0, &zs) != 0) {
+ fprintf(stderr, "ERROR: zip_stat_index %" PRIu64 " failed!\n", i);
+ free(dstpath);
+ dstpath = NULL;
+ zip_fclose(zfile);
+ continue;
+ }
- plist_t bname = plist_dict_get_item(info, "CFBundleName");
- if (bname) {
- plist_get_string_val(bname, &bundlename);
- }
- plist_free(info);
+ free(dstpath);
+ dstpath = NULL;
+
+ zip_uint64_t zfsize = 0;
+ while (zfsize < zs.size) {
+ zip_int64_t amount = zip_fread(zfile, buf, sizeof(buf));
+ if (amount == 0) {
+ break;
+ }
+
+ if (amount > 0) {
+ uint32_t written, total = 0;
+ while (total < amount) {
+ written = 0;
+ if (afc_file_write(afc, af, buf, amount, &written) !=
+ AFC_E_SUCCESS) {
+ fprintf(stderr, "AFC Write error!\n");
+ break;
+ }
+ total += written;
+ }
+ if (total != amount) {
+ fprintf(stderr, "Error: wrote only %d of %" PRIi64 "\n", total, amount);
+ afc_file_close(afc, af);
+ zip_fclose(zfile);
+ free(dstpath);
+ goto leave_cleanup;
+ }
+ }
+
+ zfsize += amount;
+ }
- if (!bundlename) {
- fprintf(stderr, "Could not determine CFBundleName!\n");
- zip_unchange_all(zf);
- zip_close(zf);
- goto leave_cleanup;
- }
+ afc_file_close(afc, af);
+ af = 0;
- char *sinfname = NULL;
- if (asprintf(&sinfname, "Payload/%s.app/SC_Info/%s.sinf", bundlename, bundlename) < 0) {
- fprintf(stderr, "Out of memory!?\n");
- goto leave_cleanup;
- }
- free(bundlename);
+ zip_fclose(zfile);
+ }
+ }
+ free(ipcc);
+ printf("DONE.\n");
- /* extract .sinf from package */
- zbuf = NULL;
- len = 0;
- if (zip_f_get_contents(zf, sinfname, 0, &zbuf, &len) == 0) {
- sinf = plist_new_data(zbuf, len);
- }
- free(sinfname);
- if (zbuf) {
- free(zbuf);
- }
+ instproxy_client_options_add(client_opts, "PackageType", "CarrierBundle", NULL);
+ } else if (S_ISDIR(fst.st_mode)) {
+ /* upload developer app directory */
+ instproxy_client_options_add(client_opts, "PackageType", "Developer", NULL);
- zip_unchange_all(zf);
- zip_close(zf);
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(cmdarg)) < 0) {
+ fprintf(stderr, "ERROR: Out of memory allocating pkgname!?\n");
+ goto leave_cleanup;
+ }
- /* copy archive to device */
- f = fopen(appid, "r");
- if (!f) {
- fprintf(stderr, "fopen: %s: %s\n", appid, strerror(errno));
- goto leave_cleanup;
- }
+ printf("Uploading %s package contents... ", basename(cmdarg));
+ afc_upload_dir(afc, cmdarg, pkgname);
+ printf("DONE.\n");
- pkgname = NULL;
- if (asprintf(&pkgname, "%s/%s", PKG_PATH, basename(appid)) < 0) {
- fprintf(stderr, "Out of memory!?\n");
- goto leave_cleanup;
- }
+ /* extract the CFBundleIdentifier from the package */
- printf("Copying '%s' --> '%s'\n", appid, pkgname);
+ /* construct full filename to Info.plist */
+ char *filename = (char*)malloc(strlen(cmdarg)+11+1);
+ strcpy(filename, cmdarg);
+ strcat(filename, "/Info.plist");
- char **strs = NULL;
- if (afc_get_file_info(afc, PKG_PATH, &strs) != AFC_E_SUCCESS) {
- if (afc_make_directory(afc, PKG_PATH) != AFC_E_SUCCESS) {
- fprintf(stderr, "WARNING: Could not create directory '%s' on device!\n", PKG_PATH);
+ struct stat st;
+ FILE *fp = NULL;
+
+ if (stat(filename, &st) == -1 || (fp = fopen(filename, "r")) == NULL) {
+ fprintf(stderr, "ERROR: could not locate %s in app!\n", filename);
+ free(filename);
+ goto leave_cleanup;
}
- }
- if (strs) {
- int i = 0;
- while (strs[i]) {
- free(strs[i]);
- i++;
+ size_t filesize = st.st_size;
+ char *ibuf = malloc(filesize * sizeof(char));
+ size_t amount = fread(ibuf, 1, filesize, fp);
+ if (amount != filesize) {
+ fprintf(stderr, "ERROR: could not read %u bytes from %s\n", (uint32_t)filesize, filename);
+ free(filename);
+ goto leave_cleanup;
}
- free(strs);
- }
+ fclose(fp);
+ free(filename);
- if ((afc_file_open(afc, pkgname, AFC_FOPEN_WRONLY, &af) !=
- AFC_E_SUCCESS) || !af) {
- fclose(f);
- fprintf(stderr, "afc_file_open on '%s' failed!\n", pkgname);
- free(pkgname);
- goto leave_cleanup;
- }
+ plist_t info = NULL;
+ plist_from_memory(ibuf, filesize, &info, NULL);
+ free(ibuf);
- size_t amount = 0;
- do {
- amount = fread(buf, 1, sizeof(buf), f);
- if (amount > 0) {
- uint32_t written, total = 0;
- while (total < amount) {
- written = 0;
- if (afc_file_write(afc, af, buf, amount, &written) !=
- AFC_E_SUCCESS) {
- fprintf(stderr, "AFC Write error!\n");
- break;
- }
- total += written;
+ if (!info) {
+ fprintf(stderr, "ERROR: could not parse Info.plist!\n");
+ goto leave_cleanup;
+ }
+
+ plist_t bname = plist_dict_get_item(info, "CFBundleIdentifier");
+ if (bname) {
+ plist_get_string_val(bname, &bundleidentifier);
+ }
+ plist_free(info);
+ info = NULL;
+ } else {
+ zf = zip_open(cmdarg, 0, &errp);
+ if (!zf) {
+ fprintf(stderr, "ERROR: zip_open: %s: %d\n", cmdarg, errp);
+ goto leave_cleanup;
+ }
+
+ char *zbuf = NULL;
+ uint32_t len = 0;
+ plist_t meta_dict = NULL;
+
+ 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);
}
- if (total != amount) {
- fprintf(stderr, "Error: wrote only %d of %zu\n", total,
- amount);
- afc_file_close(afc, af);
- fclose(f);
- free(pkgname);
+ 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);
+ }
+
+ /* determine .app directory in archive */
+ zbuf = NULL;
+ len = 0;
+ plist_t info = NULL;
+ char* filename = NULL;
+ char* app_directory_name = NULL;
+
+ if (zip_get_app_directory(zf, &app_directory_name)) {
+ fprintf(stderr, "ERROR: Unable to locate .app directory in archive. Make sure it is inside a 'Payload' directory.\n");
+ goto leave_cleanup;
+ }
+
+ /* construct full filename to Info.plist */
+ filename = (char*)malloc(strlen(app_directory_name)+10+1);
+ strcpy(filename, app_directory_name);
+ free(app_directory_name);
+ app_directory_name = NULL;
+ strcat(filename, "Info.plist");
+
+ if (zip_get_contents(zf, filename, 0, &zbuf, &len) < 0) {
+ fprintf(stderr, "WARNING: could not locate %s in archive!\n", filename);
+ free(filename);
+ zip_unchange_all(zf);
+ zip_close(zf);
+ goto leave_cleanup;
+ }
+ free(filename);
+ 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);
+ goto leave_cleanup;
+ }
+
+ char *bundleexecutable = NULL;
+
+ plist_t bname = plist_dict_get_item(info, "CFBundleExecutable");
+ if (bname) {
+ plist_get_string_val(bname, &bundleexecutable);
+ }
+
+ bname = plist_dict_get_item(info, "CFBundleIdentifier");
+ if (bname) {
+ plist_get_string_val(bname, &bundleidentifier);
+ }
+ plist_free(info);
+ info = NULL;
+
+ if (!bundleexecutable) {
+ fprintf(stderr, "Could not determine value for CFBundleExecutable!\n");
+ zip_unchange_all(zf);
+ zip_close(zf);
+ 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;
+ }
+
+ 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);
}
- }
- while (amount > 0);
- afc_file_close(afc, af);
- fclose(f);
+ /* copy archive to device */
+ pkgname = NULL;
+ if (asprintf(&pkgname, "%s/%s", PKG_PATH, bundleidentifier) < 0) {
+ fprintf(stderr, "Out of memory!?\n");
+ goto leave_cleanup;
+ }
- printf("done.\n");
+ printf("Copying '%s' to device... ", cmdarg);
- /* perform installation or upgrade */
- plist_t client_opts = instproxy_client_options_new();
- if (sinf) {
- instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
+ if (afc_upload_file(afc, cmdarg, pkgname) < 0) {
+ printf("FAILED\n");
+ free(pkgname);
+ goto leave_cleanup;
+ }
+
+ printf("DONE.\n");
+
+ if (bundleidentifier) {
+ instproxy_client_options_add(client_opts, "CFBundleIdentifier", bundleidentifier, NULL);
+ }
+ if (sinf) {
+ instproxy_client_options_add(client_opts, "ApplicationSINF", sinf, NULL);
+ }
+ if (meta) {
+ instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
+ }
}
- if (meta) {
- instproxy_client_options_add(client_opts, "iTunesMetadata", meta, NULL);
+ if (zf) {
+ zip_unchange_all(zf);
+ zip_close(zf);
}
- if (install_mode) {
- printf("Installing '%s'\n", pkgname);
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
+
+ /* perform installation or upgrade */
+ if (cmd == CMD_INSTALL) {
+ printf("Installing '%s'\n", bundleidentifier);
instproxy_install(ipc, pkgname, client_opts, status_cb, NULL);
-#else
- instproxy_install(ipc, pkgname, client_opts, status_cb);
-#endif
} else {
- printf("Upgrading '%s'\n", pkgname);
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
+ printf("Upgrading '%s'\n", bundleidentifier);
instproxy_upgrade(ipc, pkgname, client_opts, status_cb, NULL);
-#else
- instproxy_upgrade(ipc, pkgname, client_opts, status_cb);
-#endif
}
instproxy_client_options_free(client_opts);
free(pkgname);
- wait_for_op_complete = 1;
- notification_expected = 1;
- } else if (uninstall_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_uninstall(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_uninstall(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ wait_for_command_complete = 1;
notification_expected = 1;
- } else if (list_archives_mode) {
- int xml_mode = 0;
+ } else if (cmd == CMD_UNINSTALL) {
+ 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) {
plist_t dict = NULL;
- plist_t lres = NULL;
- instproxy_error_t err;
-
- /* 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);
goto leave_cleanup;
}
- if (!dict) {
- fprintf(stderr,
- "ERROR: lookup_archives did not return a plist!?\n");
- goto leave_cleanup;
- }
- lres = plist_dict_get_item(dict, "LookupResult");
- if (!lres || (plist_get_node_type(lres) != PLIST_DICT)) {
- plist_free(dict);
- fprintf(stderr, "ERROR: Could not get dict 'LookupResult'\n");
+ if (!dict) {
+ 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(lres, &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;
@@ -749,8 +1400,8 @@ run_again:
plist_t node = NULL;
char *key = NULL;
- printf("Total: %d archived apps\n", plist_dict_get_size(lres));
- plist_dict_new_iter(lres, &iter);
+ printf("Total: %d archived apps\n", plist_dict_get_size(dict));
+ plist_dict_new_iter(dict, &iter);
if (!iter) {
plist_free(dict);
fprintf(stderr, "ERROR: Could not create plist_dict_iter!\n");
@@ -759,14 +1410,14 @@ run_again:
do {
key = NULL;
node = NULL;
- plist_dict_next_item(lres, iter, &key, &node);
+ plist_dict_next_item(dict, iter, &key, &node);
if (key && (plist_get_node_type(node) == PLIST_DICT)) {
char *s_dispName = NULL;
char *s_version = NULL;
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);
}
@@ -788,38 +1439,18 @@ run_again:
}
while (node);
plist_free(dict);
- } else if (archive_mode) {
- char *copy_path = NULL;
- int remove_after_copy = 0;
- int skip_uninstall = 1;
- int app_only = 0;
+ } else if (cmd == CMD_ARCHIVE) {
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;
- } 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) {
+ if (skip_uninstall || app_only || docs_only) {
client_opts = instproxy_client_options_new();
if (skip_uninstall) {
instproxy_client_options_add(client_opts, "SkipUninstall", 1, NULL);
}
if (app_only) {
instproxy_client_options_add(client_opts, "ArchiveType", "ApplicationOnly", NULL);
+ } else if (docs_only) {
+ instproxy_client_options_add(client_opts, "ArchiveType", "DocumentsOnly", NULL);
}
}
@@ -827,49 +1458,47 @@ 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);
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);
goto leave_cleanup;
}
- port = 0;
- if ((lockdownd_start_service(client, "com.apple.afc", &port) != LOCKDOWN_E_SUCCESS) || !port) {
+ if (service) {
+ 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");
- free(copy_path);
goto leave_cleanup;
}
lockdownd_client_free(client);
client = NULL;
- if (afc_client_new(phone, port, &afc) != INSTPROXY_E_SUCCESS) {
+ if (afc_client_new(device, service, &afc) != AFC_E_SUCCESS) {
fprintf(stderr, "Could not connect to AFC!\n");
goto leave_cleanup;
}
}
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_archive(ipc, appid, client_opts, status_cb, NULL);
-#else
- instproxy_archive(ipc, appid, client_opts, status_cb);
-#endif
+ instproxy_archive(ipc, cmdarg, client_opts, status_cb, NULL);
+
instproxy_client_options_free(client_opts);
- wait_for_op_complete = 1;
+ wait_for_command_complete = 1;
if (skip_uninstall) {
notification_expected = 0;
} else {
notification_expected = 1;
}
- do_wait_when_needed();
+ idevice_wait_for_command_to_complete();
if (copy_path) {
- if (err_occured) {
+ if (err_occurred) {
afc_client_free(afc);
afc = NULL;
goto leave_cleanup;
@@ -878,13 +1507,12 @@ run_again:
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");
goto leave_cleanup;
}
- free(copy_path);
- f = fopen(localfile, "w");
+ f = fopen(localfile, "wb");
if (!f) {
fprintf(stderr, "ERROR: fopen: %s: %s\n", localfile, strerror(errno));
free(localfile);
@@ -893,7 +1521,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;
}
@@ -939,7 +1567,7 @@ run_again:
}
/* copy file over */
- printf("Copying '%s' --> '%s'\n", remotefile, localfile);
+ printf("Copying '%s' --> '%s'... ", remotefile, localfile);
free(remotefile);
free(localfile);
@@ -966,7 +1594,8 @@ run_again:
afc_file_close(afc, af);
fclose(f);
- printf("done.\n");
+ printf("DONE.\n");
+
if (total != fsize) {
fprintf(stderr, "WARNING: remote and local file sizes don't match (%d != %d)\n", fsize, total);
if (remove_after_copy) {
@@ -977,12 +1606,9 @@ run_again:
if (remove_after_copy) {
/* remove archive if requested */
- printf("Removing '%s'\n", appid);
- archive_mode = 0;
- remove_archive_mode = 1;
- free(options);
- options = NULL;
- if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "ideviceinstaller")) {
+ printf("Removing '%s'\n", cmdarg);
+ cmd = CMD_REMOVE_ARCHIVE;
+ if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &client, "ideviceinstaller")) {
fprintf(stderr, "Could not connect to lockdownd. Exiting.\n");
goto leave_cleanup;
}
@@ -990,59 +1616,43 @@ run_again:
}
}
goto leave_cleanup;
- } else if (restore_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_restore(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_restore(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ } else if (cmd == CMD_RESTORE) {
+ instproxy_restore(ipc, cmdarg, NULL, status_cb, NULL);
+ wait_for_command_complete = 1;
notification_expected = 1;
- } else if (remove_archive_mode) {
-#ifdef HAVE_LIBIMOBILEDEVICE_1_1
- instproxy_remove_archive(ipc, appid, NULL, status_cb, NULL);
-#else
- instproxy_remove_archive(ipc, appid, NULL, status_cb);
-#endif
- wait_for_op_complete = 1;
+ } else if (cmd == CMD_REMOVE_ARCHIVE) {
+ instproxy_remove_archive(ipc, cmdarg, NULL, status_cb, NULL);
+ wait_for_command_complete = 1;
} else {
- printf
- ("ERROR: no operation 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;
}
- if (client) {
- /* not needed anymore */
- lockdownd_client_free(client);
- client = NULL;
- }
-
- do_wait_when_needed();
-
- leave_cleanup:
- if (np) {
- np_client_free(np);
- }
- if (ipc) {
- instproxy_client_free(ipc);
- }
- if (afc) {
- afc_client_free(afc);
- }
- if (client) {
- lockdownd_client_free(client);
- }
- idevice_free(phone);
-
- if (uuid) {
- free(uuid);
- }
- if (appid) {
- free(appid);
- }
- if (options) {
- free(options);
+ /* not needed anymore */
+ lockdownd_client_free(client);
+ client = NULL;
+
+ idevice_wait_for_command_to_complete();
+ res = 0;
+
+leave_cleanup:
+ np_client_free(np);
+ instproxy_client_free(ipc);
+ afc_client_free(afc);
+ lockdownd_client_free(client);
+ idevice_free(device);
+
+ free(udid);
+ free(copy_path);
+ free(extsinf);
+ free(extmeta);
+ free(bundleidentifier);
+ plist_free(bundle_ids);
+ plist_free(return_attrs);
+
+ if (err_occurred && !res) {
+ res = 128;
}
return res;