diff options
| -rw-r--r-- | .github/FUNDING.yml | 3 | ||||
| -rw-r--r-- | .github/workflows/build.yml | 272 | ||||
| -rw-r--r-- | AUTHORS | 3 | ||||
| -rw-r--r-- | Makefile.am | 7 | ||||
| -rw-r--r-- | NEWS | 80 | ||||
| -rw-r--r-- | README | 56 | ||||
| -rw-r--r-- | README.md | 127 | ||||
| -rwxr-xr-x | autogen.sh | 33 | ||||
| -rw-r--r-- | configure.ac | 62 | ||||
| -rwxr-xr-x | git-version-gen | 20 | ||||
| -rw-r--r-- | m4/as-compiler-flag.m4 | 4 | ||||
| -rw-r--r-- | man/ideviceinstaller.1 | 156 | ||||
| -rw-r--r-- | src/ideviceinstaller.c | 1756 |
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 @@ -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 @@ -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 ~~~~~~~~~~~~~ @@ -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.* + + + +## 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 @@ -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, ¤t_index, ¤t_amount, ¤t_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; |
