diff --git a/.gitignore b/.gitignore index 88c26815..5e502cb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -demo -demo.app -fruitstrap +build/* +node_modules/* +/.DS_Store +*~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..3b4bd41d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +## Contributing to ios-deploy + +Github url: + + https://github.com/phonegap/ios-deploy + +Git clone url: + + https://github.com/phonegap/ios-deploy.git + +## Filing an issue + +Please run the commands below in your Terminal.app and include it in the issue: + +``` +1. sw_vers -productVersion +2. ios-deploy -V +3. xcodebuild -version +4. xcode-select --print-path +5. gcc --version +6. lldb --version + +``` +Also include **command line arguments** you used for ios-deploy. + +Don't forget to check out the [El Capitan](https://github.com/phonegap/ios-deploy/blob/master/README.md#os-x-1011-el-capitan) section of the [README](https://github.com/phonegap/ios-deploy/blob/master/README.md) before filing that issue! + + +## Sending a Pull Request + +Please **create a topic branch** for your issue before submitting your pull request. You will be asked to re-submit if your pull request contains unrelated commits. + +Please elaborate regarding the problem the pull request is supposed to solve, and perhaps also link to any relevant issues the pull request is trying to fix. \ No newline at end of file diff --git a/LICENSE b/LICENSE index e03ea70f..f8e017ae 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,8 @@ -fruitstrap is available under the provisions of the GNU General Public License, -version 3 (or later), available here: http://www.gnu.org/licenses/gpl-3.0.html \ No newline at end of file +ios-deploy is available under the provisions of the GNU General Public License, +version 3 (or later), available here: http://www.gnu.org/licenses/gpl-3.0.html + + +Error codes used for error messages were taken from SDMMobileDevice framework, +originally reverse engineered by Sam Marshall. SDMMobileDevice is distributed +under BSD 3-Clause license and is available here: +https://github.com/samdmarshall/SDMMobileDevice diff --git a/Makefile b/Makefile deleted file mode 100644 index bd6beb41..00000000 --- a/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -IOS_CC = /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc - -all: demo.app fruitstrap - -demo.app: demo Info.plist - mkdir -p demo.app - cp demo demo.app/ - cp Info.plist ResourceRules.plist demo.app/ - codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app - -demo: demo.c - $(IOS_CC) -arch armv7 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk -framework CoreFoundation -o demo demo.c - -fruitstrap: fruitstrap.c - gcc -o fruitstrap -framework CoreFoundation -framework MobileDevice -F/System/Library/PrivateFrameworks fruitstrap.c - -install: all - ./fruitstrap demo.app - -debug: all - ./fruitstrap -d demo.app - -clean: - rm -rf *.app demo fruitstrap \ No newline at end of file diff --git a/README.md b/README.md index f087557c..63a1ce41 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,146 @@ -## This project is no longer maintained. - -fruitstrap +ios-deploy ========== -Install and debug iPhone apps without using Xcode. Designed to work on unjailbroken devices. +Install and debug iOS apps without using Xcode. Designed to work on un-jailbroken devices. ## Requirements -* Mac OS X. Tested on Snow Leopard only. -* You need to have a valid iPhone development certificate installed. -* Xcode must be installed, along with the SDK for your iOS version. +* Mac OS X. Tested on 10.11 El Capitan and iOS 9.0 +* You need to have a valid iOS Development certificate installed. +* Xcode 6.4 or greater should be installed + +## Roadmap + +See our [milestones](https://github.com/phonegap/ios-deploy/milestones). + +Significant changes: + + 1.8.0 will use an Xcode project instead of a Makefile (to prepare for 2.0.0) (1.x branch) + 2.0.0 will break out the commands into their own files, and create ios-deploy-lib for node.js use (master branch) + +## Development + +The legacy `1.x` version is under the `1.x` branch. Bug fixes for the `1.x` series will occur under there. +The 'master' branch now contains the `2.x` series, and is the development branch. + +## Installation +======= + +ios-deploy installation is made simple using the node.js package manager. If you use [Homebrew](http://brew.sh/), install [node.js](https://nodejs.org): + +``` +brew install node +``` + +Now install ios-deploy with the [node.js](https://nodejs.org) package manager: + +``` +npm install -g ios-deploy +``` + +To build from source: + +``` +xcodebuild +``` + +This will build `ios-deploy` into the `build/Release` folder. + +## Testing + +Run: + +``` +npm install && npm test +``` + +### OS X 10.11 El Capitan + +If you are *not* using a node version manager like [nvm](https://github.com/creationix/nvm) or [n](https://github.com/tj/n), you may have to do either of these three things below when under El Capitan: + +1. Add the `--unsafe-perm=true` flag when installing ios-deploy +2. Add the `--allow-root` flag when installing ios-deploy +3. Ensure the `nobody` user has write access to `/usr/local/lib/node_modules/ios-deploy/ios-deploy` ## Usage -* `fruitstrap [-d] -b [device_id]` -* Optional `-d` flag launches a remote GDB session after the app has been installed. -* `` must be an iPhone application bundle, *not* an IPA. -* Optional `device_id`; useful when you have more than one iPhone/iPad connected. + Usage: ios-deploy [OPTION]... + -d, --debug launch the app in lldb after installation + -i, --id the id of the device to connect to + -c, --detect only detect if the device is connected + -b, --bundle the path to the app bundle to be installed + -a, --args command line arguments to pass to the app when launching it + -t, --timeout number of seconds to wait for a device to be connected + -u, --unbuffered don't buffer stdout + -n, --nostart do not start the app when debugging + -I, --noninteractive start in non interactive mode (quit when app crashes or exits) + -L, --justlaunch just launch the app and exit lldb + -v, --verbose enable verbose output + -m, --noinstall directly start debugging without app install (-d not required) + -p, --port port used for device, default: dynamic + -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) + -9, --uninstall_only uninstall the app ONLY. Use only with -1 + -1, --bundle_id specify bundle id for list and upload + -l, --list list files + -o, --upload upload file + -w, --download download app tree + -2, --to use together with up/download file/tree. specify target + -D, --mkdir make directory on device + -R, --rm remove file or directory on device (directories must be empty) + -V, --version print the executable version + -e, --exists check if the app with given bundle_id is installed or not + -B, --list_bundle_id list bundle_id + -W, --no-wifi ignore wifi devices + +## Examples + +The commands below assume that you have an app called `my.app` with bundle id `bundle.id`. Substitute where necessary. + + // deploy and debug your app to a connected device + ios-deploy --debug --bundle my.app + + // deploy and debug your app to a connected device, skipping any wi-fi connection (use USB) + ios-deploy --debug --bundle my.app --no-wifi + + // deploy and launch your app to a connected device, but quit the debugger after + ios-deploy --justlaunch --debug --bundle my.app + + // deploy and launch your app to a connected device, quit when app crashes or exits + ios-deploy --noninteractive --debug --bundle my.app + + // Upload a file to your app's Documents folder + ios-deploy --bundle_id 'bundle.id' --upload test.txt --to Documents/test.txt + + // Download your app's Documents, Library and tmp folders + ios-deploy --bundle_id 'bundle.id' --download --to MyDestinationFolder + + // List the contents of your app's Documents, Library and tmp folders + ios-deploy --bundle_id 'bundle.id' --list + + // deploy and debug your app to a connected device, uninstall the app first + ios-deploy --uninstall --debug --bundle my.app + + // check whether an app by bundle id exists on the device (check return code `echo $?`) + ios-deploy --exists --bundle_id com.apple.mobilemail + + // Download the Documents directory of the app *only* + ios-deploy --download=/Documents --bundle_id my.app.id --to ./my_download_location + + // List ids and names of connected devices + ios-deploy -c + + // Uninstall an app + ios-deploy --uninstall_only --bundle_id my.bundle.id + + // list all bundle ids of all apps on your device + ios-deploy --list_bundle_id ## Demo -* The included demo.app represents the minimum required to get code running on iOS. -* `make install` will install demo.app to the device. -* `make debug` will install demo.app and launch a GDB session. +The included demo.app represents the minimum required to get code running on iOS. + +* `make demo.app` will generate the demo.app executable. If it doesn't compile, modify `IOS_SDK_VERSION` in the Makefile. +* `make debug` will install demo.app and launch a LLDB session. ## Notes -* With some modifications, it may be possible to use this without Xcode installed; however, you would need a copy of the relevant DeveloperDiskImage.dmg (included with Xcode). GDB would also run slower as symbols would be downloaded from the device on-the-fly. +* With some modifications, it may be possible to use this without Xcode installed; however, you would need a copy of the relevant DeveloperDiskImage.dmg (included with Xcode). lldb would also run slower as symbols would be downloaded from the device on-the-fly. diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 00000000..c2ef55db --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,5 @@ +demo +demo.app +demo.dSYM +/.DS_Store +*~ diff --git a/Entitlements.plist b/demo/Entitlements.plist similarity index 100% rename from Entitlements.plist rename to demo/Entitlements.plist diff --git a/Info.plist b/demo/Info.plist similarity index 92% rename from Info.plist rename to demo/Info.plist index 13168c43..c2ade143 100644 --- a/Info.plist +++ b/demo/Info.plist @@ -10,6 +10,8 @@ CFBundleExecutable demo + CFBundleVersion + 1.0 CFBundleIdentifier demo CFBundleResourceSpecification diff --git a/demo/Makefile b/demo/Makefile new file mode 100644 index 00000000..319e9f88 --- /dev/null +++ b/demo/Makefile @@ -0,0 +1,24 @@ +IOS_SDK_VERSION = 9.1 + +IOS_CC = gcc -ObjC +IOS_SDK = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK_VERSION).sdk + +all: clean demo.app + +demo.app: demo Info.plist + mkdir -p demo.app + cp demo demo.app/ + cp Info.plist ResourceRules.plist demo.app/ + codesign -f -s "iPhone Developer" --entitlements Entitlements.plist demo.app + +demo: demo.c + $(IOS_CC) -g -arch armv7 -isysroot $(IOS_SDK) -framework CoreFoundation -o demo demo.c + +debug: all ios-deploy + @../build/Release/ios-deploy --debug --bundle demo.app + +clean: + @rm -rf *.app demo demo.dSYM + +ios-deploy: + @xcodebuild -project ../ios-deploy.xcodeproj diff --git a/ResourceRules.plist b/demo/ResourceRules.plist similarity index 100% rename from ResourceRules.plist rename to demo/ResourceRules.plist diff --git a/demo.c b/demo/demo.c similarity index 98% rename from demo.c rename to demo/demo.c index 583f43b6..b366b145 100644 --- a/demo.c +++ b/demo/demo.c @@ -6,4 +6,4 @@ int main(int argc, const char* argv[]) { printf("argv[%d] = %s\n", i, argv[i]); } return 0; -} +} \ No newline at end of file diff --git a/fruitstrap.c b/fruitstrap.c deleted file mode 100644 index 6693f515..00000000 --- a/fruitstrap.c +++ /dev/null @@ -1,565 +0,0 @@ -//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow - -#import -#include -#include -#include -#include -#include -#include -#include "MobileDevice.h" - -#define FDVENDOR_PATH "/tmp/fruitstrap-remote-debugserver" -#define PREP_CMDS_PATH "/tmp/fruitstrap-gdb-prep-cmds" -#define GDB_SHELL "/Developer/Platforms/iPhoneOS.platform/Developer/usr/libexec/gdb/gdb-arm-apple-darwin --arch armv7 -q -x " PREP_CMDS_PATH - -// approximation of what Xcode does: -#define GDB_PREP_CMDS CFSTR("set mi-show-protections off\n\ - set auto-raise-load-levels 1\n\ - set shlib-path-substitutions /usr \"{ds_path}/Symbols/usr\" /System \"{ds_path}/Symbols/System\" \"{device_container}\" \"{disk_container}\" \"/private{device_container}\" \"{disk_container}\" /Developer \"{ds_path}/Symbols/Developer\"\n\ - set remote max-packet-size 1024\n\ - set sharedlibrary check-uuids on\n\ - set env NSUnbufferedIO YES\n\ - set minimal-signal-handling 1\n\ - set sharedlibrary load-rules \\\".*\\\" \\\".*\\\" container\n\ - set inferior-auto-start-dyld 0\n\ - file \"{disk_app}\"\n\ - set remote executable-directory {device_app}\n\ - set remote noack-mode 1\n\ - set trust-readonly-sections 1\n\ - target remote-mobile " FDVENDOR_PATH "\n\ - mem 0x1000 0x3fffffff cache\n\ - mem 0x40000000 0xffffffff none\n\ - mem 0x00000000 0x0fff none\n\ - run {args}\n\ - set minimal-signal-handling 0\n\ - set inferior-auto-start-cfm off\n\ - set sharedLibrary load-rules dyld \".*libobjc.*\" all dyld \".*CoreFoundation.*\" all dyld \".*Foundation.*\" all dyld \".*libSystem.*\" all dyld \".*AppKit.*\" all dyld \".*PBGDBIntrospectionSupport.*\" all dyld \".*/usr/lib/dyld.*\" all dyld \".*CarbonDataFormatters.*\" all dyld \".*libauto.*\" all dyld \".*CFDataFormatters.*\" all dyld \"/System/Library/Frameworks\\\\\\\\|/System/Library/PrivateFrameworks\\\\\\\\|/usr/lib\" extern dyld \".*\" all exec \".*\" all\n\ - sharedlibrary apply-load-rules all\n\ - set inferior-auto-start-dyld 1") - -typedef struct am_device * AMDeviceRef; -int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); -int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); -int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); -int AMDeviceLookupApplications(AMDeviceRef device, int zero, CFDictionaryRef* result); - -bool found_device = false, debug = false, verbose = false; -char *app_path = NULL; -char *device_id = NULL; -char *args = NULL; -int timeout = 0; -CFStringRef last_path = NULL; -service_conn_t gdbfd; - -Boolean path_exists(CFTypeRef path) { - if (CFGetTypeID(path) == CFStringGetTypeID()) { - CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, true); - Boolean result = CFURLResourceIsReachable(url, NULL); - CFRelease(url); - return result; - } else if (CFGetTypeID(path) == CFURLGetTypeID()) { - return CFURLResourceIsReachable(path, NULL); - } else { - return false; - } -} - -CFStringRef copy_device_support_path(AMDeviceRef device) { - CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); - CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char* home = getenv("HOME"); - CFStringRef path; - bool found = false; - - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)"), home, version, build); - found = path_exists(path); - - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@)"), version, build); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@"), home, version); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); - found = path_exists(path); - } - if (!found) - { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@"), version); - found = path_exists(path); - } - - CFRelease(version); - CFRelease(build); - - if (!found) - { - CFRelease(path); - printf("[ !! ] Unable to locate DeviceSupport directory.\n"); - exit(1); - } - - return path; -} - -CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { - CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); - CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); - const char *home = getenv("HOME"); - CFStringRef path; - bool found = false; - - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/%@ (%@)/DeveloperDiskImage.dmg"), home, version, build); - found = path_exists(path); - - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%@ (%@/DeveloperDiskImage.dmg)"), version, build); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/@%/DeveloperDiskImage.dmg"), home, version); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/@%/DeveloperDiskImage.dmg"), version); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/iOS DeviceSupport/Latest/DeveloperDiskImage.dmg"), home); - found = path_exists(path); - } - if (!found) { - path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Developer/Platforms/iPhoneOS.platform/DeviceSupport/Latest/DeveloperDiskImage.dmg")); - found = path_exists(path); - } - - CFRelease(version); - CFRelease(build); - - if (!found) { - CFRelease(path); - printf("[ !! ] Unable to locate DeviceSupport directory containing DeveloperDiskImage.dmg.\n"); - exit(1); - } - - return path; -} - -void mount_callback(CFDictionaryRef dict, int arg) { - CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); - - if (CFEqual(status, CFSTR("LookingUpImage"))) { - printf("[ 0%%] Looking up developer disk image\n"); - } else if (CFEqual(status, CFSTR("CopyingImage"))) { - printf("[ 30%%] Copying DeveloperDiskImage.dmg to device\n"); - } else if (CFEqual(status, CFSTR("MountingImage"))) { - printf("[ 90%%] Mounting developer disk image\n"); - } -} - -void mount_developer_image(AMDeviceRef device) { - CFStringRef ds_path = copy_device_support_path(device); - CFStringRef image_path = copy_developer_disk_image_path(device); - CFStringRef sig_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@.signature"), image_path); - CFRelease(ds_path); - - if (verbose) { - printf("Device support path: "); - fflush(stdout); - CFShow(ds_path); - printf("Developer disk image: "); - fflush(stdout); - CFShow(image_path); - } - - FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); - void *sig_buf = malloc(128); - assert(fread(sig_buf, 1, 128, sig) == 128); - fclose(sig); - CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); - CFRelease(sig_path); - - CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; - CFTypeRef values[] = { sig_data, CFSTR("Developer") }; - CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFRelease(sig_data); - - int result = AMDeviceMountImage(device, image_path, options, &mount_callback, 0); - if (result == 0) { - printf("[ 95%%] Developer disk image mounted successfully\n"); - } else if (result == 0xe8000076 /* already mounted */) { - printf("[ 95%%] Developer disk image already mounted\n"); - } else { - printf("[ !! ] Unable to mount developer disk image. (%x)\n", result); - exit(1); - } - - CFRelease(image_path); - CFRelease(options); -} - -void transfer_callback(CFDictionaryRef dict, int arg) { - int percent; - CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); - CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); - - if (CFEqual(status, CFSTR("CopyingFile"))) { - CFStringRef path = CFDictionaryGetValue(dict, CFSTR("Path")); - - if ((last_path == NULL || !CFEqual(path, last_path)) && !CFStringHasSuffix(path, CFSTR(".ipa"))) { - printf("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); - } - - if (last_path != NULL) { - CFRelease(last_path); - } - last_path = CFStringCreateCopy(NULL, path); - } -} - -void install_callback(CFDictionaryRef dict, int arg) { - int percent; - CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); - CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); - - printf("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); -} - -void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { - CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); - - struct msghdr message; - struct iovec iov[1]; - struct cmsghdr *control_message = NULL; - char ctrl_buf[CMSG_SPACE(sizeof(int))]; - char dummy_data[1]; - - memset(&message, 0, sizeof(struct msghdr)); - memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); - - dummy_data[0] = ' '; - iov[0].iov_base = dummy_data; - iov[0].iov_len = sizeof(dummy_data); - - message.msg_name = NULL; - message.msg_namelen = 0; - message.msg_iov = iov; - message.msg_iovlen = 1; - message.msg_controllen = CMSG_SPACE(sizeof(int)); - message.msg_control = ctrl_buf; - - control_message = CMSG_FIRSTHDR(&message); - control_message->cmsg_level = SOL_SOCKET; - control_message->cmsg_type = SCM_RIGHTS; - control_message->cmsg_len = CMSG_LEN(sizeof(int)); - - *((int *) CMSG_DATA(control_message)) = gdbfd; - - sendmsg(socket, &message, 0); - CFSocketInvalidate(s); - CFRelease(s); -} - -CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { - CFDictionaryRef result; - assert(AMDeviceLookupApplications(device, 0, &result) == 0); - - CFDictionaryRef app_dict = CFDictionaryGetValue(result, identifier); - assert(app_dict != NULL); - - CFStringRef app_path = CFDictionaryGetValue(app_dict, CFSTR("Path")); - assert(app_path != NULL); - - CFURLRef url = CFURLCreateWithFileSystemPath(NULL, app_path, kCFURLPOSIXPathStyle, true); - CFRelease(result); - return url; -} - -CFStringRef copy_disk_app_identifier(CFURLRef disk_app_url) { - CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent(NULL, disk_app_url, CFSTR("Info.plist"), false); - CFReadStreamRef plist_stream = CFReadStreamCreateWithFile(NULL, plist_url); - CFReadStreamOpen(plist_stream); - CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, plist_stream, 0, kCFPropertyListImmutable, NULL, NULL); - CFStringRef bundle_identifier = CFRetain(CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"))); - CFReadStreamClose(plist_stream); - - CFRelease(plist_url); - CFRelease(plist_stream); - CFRelease(plist); - - return bundle_identifier; -} - -void write_gdb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { - CFMutableStringRef cmds = CFStringCreateMutableCopy(NULL, 0, GDB_PREP_CMDS); - CFRange range = { 0, CFStringGetLength(cmds) }; - - CFStringRef ds_path = copy_device_support_path(device); - CFStringFindAndReplace(cmds, CFSTR("{ds_path}"), ds_path, range, 0); - range.length = CFStringGetLength(cmds); - - if (args) { - CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingASCII); - CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0); - CFRelease(cf_args); - } else { - CFStringFindAndReplace(cmds, CFSTR(" {args}"), CFSTR(""), range, 0); - } - range.length = CFStringGetLength(cmds); - - CFStringRef bundle_identifier = copy_disk_app_identifier(disk_app_url); - CFURLRef device_app_url = copy_device_app_url(device, bundle_identifier); - CFStringRef device_app_path = CFURLCopyFileSystemPath(device_app_url, kCFURLPOSIXPathStyle); - CFStringFindAndReplace(cmds, CFSTR("{device_app}"), device_app_path, range, 0); - range.length = CFStringGetLength(cmds); - - CFStringRef disk_app_path = CFURLCopyFileSystemPath(disk_app_url, kCFURLPOSIXPathStyle); - CFStringFindAndReplace(cmds, CFSTR("{disk_app}"), disk_app_path, range, 0); - range.length = CFStringGetLength(cmds); - - CFURLRef device_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, device_app_url); - CFStringRef device_container_path = CFURLCopyFileSystemPath(device_container_url, kCFURLPOSIXPathStyle); - CFMutableStringRef dcp_noprivate = CFStringCreateMutableCopy(NULL, 0, device_container_path); - range.length = CFStringGetLength(dcp_noprivate); - CFStringFindAndReplace(dcp_noprivate, CFSTR("/private/var/"), CFSTR("/var/"), range, 0); - range.length = CFStringGetLength(cmds); - CFStringFindAndReplace(cmds, CFSTR("{device_container}"), dcp_noprivate, range, 0); - range.length = CFStringGetLength(cmds); - - CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url); - CFStringRef disk_container_path = CFURLCopyFileSystemPath(disk_container_url, kCFURLPOSIXPathStyle); - CFStringFindAndReplace(cmds, CFSTR("{disk_container}"), disk_container_path, range, 0); - - CFDataRef cmds_data = CFStringCreateExternalRepresentation(NULL, cmds, kCFStringEncodingASCII, 0); - FILE *out = fopen(PREP_CMDS_PATH, "w"); - fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out); - fclose(out); - - CFRelease(cmds); - if (ds_path != NULL) CFRelease(ds_path); - CFRelease(bundle_identifier); - CFRelease(device_app_url); - CFRelease(device_app_path); - CFRelease(disk_app_path); - CFRelease(device_container_url); - CFRelease(device_container_path); - CFRelease(dcp_noprivate); - CFRelease(disk_container_url); - CFRelease(disk_container_path); - CFRelease(cmds_data); -} - -void start_remote_debug_server(AMDeviceRef device) { - assert(AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL) == 0); - - CFSocketRef fdvendor = CFSocketCreate(NULL, AF_UNIX, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); - - int yes = 1; - setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - struct sockaddr_un address; - memset(&address, 0, sizeof(address)); - address.sun_family = AF_UNIX; - strcpy(address.sun_path, FDVENDOR_PATH); - address.sun_len = SUN_LEN(&address); - CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&address, sizeof(address)); - - unlink(FDVENDOR_PATH); - - CFSocketSetAddress(fdvendor, address_data); - CFRelease(address_data); - CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); -} - -void gdb_ready_handler(int signum) -{ - _exit(0); -} - -void handle_device(AMDeviceRef device) { - if (found_device) return; // handle one device only - - CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device); - - if (device_id != NULL) { - if(strcmp(device_id, CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())) == 0) { - found_device = true; - } else { - return; - } - } else { - found_device = true; - } - - CFRetain(device); // don't know if this is necessary? - - printf("[ 0%%] Found device (%s), beginning install\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); - - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - assert(AMDeviceValidatePairing(device) == 0); - assert(AMDeviceStartSession(device) == 0); - - CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); - CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); - CFURLRef url = CFURLCopyAbsoluteURL(relative_url); - - CFRelease(relative_url); - - int afcFd; - assert(AMDeviceStartService(device, CFSTR("com.apple.afc"), &afcFd, NULL) == 0); - assert(AMDeviceStopSession(device) == 0); - assert(AMDeviceDisconnect(device) == 0); - assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0); - - close(afcFd); - - CFStringRef keys[] = { CFSTR("PackageType") }; - CFStringRef values[] = { CFSTR("Developer") }; - CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - assert(AMDeviceValidatePairing(device) == 0); - assert(AMDeviceStartSession(device) == 0); - - int installFd; - assert(AMDeviceStartService(device, CFSTR("com.apple.mobile.installation_proxy"), &installFd, NULL) == 0); - - assert(AMDeviceStopSession(device) == 0); - assert(AMDeviceDisconnect(device) == 0); - - mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL); - if (result != 0) - { - printf("AMDeviceInstallApplication failed: %d\n", result); - exit(1); - } - - close(installFd); - - CFRelease(path); - CFRelease(options); - - printf("[100%%] Installed package %s\n", app_path); - - if (!debug) exit(0); // no debug phase - - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - assert(AMDeviceValidatePairing(device) == 0); - assert(AMDeviceStartSession(device) == 0); - - printf("------ Debug phase ------\n"); - - mount_developer_image(device); // put debugserver on the device - start_remote_debug_server(device); // start debugserver - write_gdb_prep_cmds(device, url); // dump the necessary gdb commands into a file - - CFRelease(url); - - printf("[100%%] Connecting to remote debug server\n"); - printf("-------------------------\n"); - - signal(SIGHUP, gdb_ready_handler); - - pid_t parent = getpid(); - int pid = fork(); - if (pid == 0) { - system(GDB_SHELL); // launch gdb - kill(parent, SIGHUP); // "No. I am your father." - _exit(0); - } -} - -void device_callback(struct am_device_notification_callback_info *info, void *arg) { - switch (info->msg) { - case ADNCI_MSG_CONNECTED: - handle_device(info->dev); - default: - break; - } -} - -void timeout_callback(CFRunLoopTimerRef timer, void *info) { - if (!found_device) { - printf("Timed out waiting for device.\n"); - exit(1); - } -} - -void usage(const char* app) { - printf("usage: %s [-d/--debug] [-i/--id device_id] -b/--bundle bundle.app [-a/--args arguments] [-t/--timeout timeout(seconds)]\n", app); -} - -int main(int argc, char *argv[]) { - static struct option longopts[] = { - { "debug", no_argument, NULL, 'd' }, - { "id", required_argument, NULL, 'i' }, - { "bundle", required_argument, NULL, 'b' }, - { "args", required_argument, NULL, 'a' }, - { "verbose", no_argument, NULL, 'v' }, - { "timeout", required_argument, NULL, 't' }, - { NULL, 0, NULL, 0 }, - }; - char ch; - - while ((ch = getopt_long(argc, argv, "dvi:b:a:t:", longopts, NULL)) != -1) - { - switch (ch) { - case 'd': - debug = 1; - break; - case 'i': - device_id = optarg; - break; - case 'b': - app_path = optarg; - break; - case 'a': - args = optarg; - break; - case 'v': - verbose = 1; - break; - case 't': - timeout = atoi(optarg); - break; - default: - usage(argv[0]); - return 1; - } - } - - if (!app_path) { - usage(argv[0]); - exit(0); - } - - printf("------ Install phase ------\n"); - - assert(access(app_path, F_OK) == 0); - - AMDSetLogLevel(5); // otherwise syslog gets flooded with crap - if (timeout > 0) - { - CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + timeout, 0, 0, 0, timeout_callback, NULL); - CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); - printf("[....] Waiting up to %d seconds for iOS device to be connected\n", timeout); - } - else - { - printf("[....] Waiting for iOS device to be connected\n"); - } - - struct am_device_notification *notify; - AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); - CFRunLoopRun(); -} diff --git a/ios-deploy.xcodeproj/project.pbxproj b/ios-deploy.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0e95458a --- /dev/null +++ b/ios-deploy.xcodeproj/project.pbxproj @@ -0,0 +1,537 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 7E70899E1B587F29004D23AA /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E70899D1B587F29004D23AA /* CoreFoundation.framework */; }; + 7E7089A01B58801E004D23AA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E70899F1B58801E004D23AA /* Foundation.framework */; }; + 7E8E3A861C45D4CE0017F6C1 /* ios_deploy_tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E8E3A851C45D4CE0017F6C1 /* ios_deploy_tests.m */; }; + 7E8E3A921C45D5380017F6C1 /* libios_deploy.h in Headers */ = {isa = PBXBuildFile; fileRef = 7E8E3A911C45D5380017F6C1 /* libios_deploy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7E8E3A941C45D5380017F6C1 /* libios_deploy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E8E3A931C45D5380017F6C1 /* libios_deploy.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 7E8E3A9B1C45D5970017F6C1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E70899F1B58801E004D23AA /* Foundation.framework */; }; + 7E8E3A9D1C45D6290017F6C1 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E70899D1B587F29004D23AA /* CoreFoundation.framework */; }; + 7EDCC3CD1C45DC94002F9851 /* ios-deploy.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E7089991B587DE4004D23AA /* ios-deploy.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 7E8E3A991C45D5850017F6C1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7E7089861B587BF3004D23AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7E8E3A8E1C45D5380017F6C1; + remoteInfo = "ios-deploy-lib"; + }; + 7EDCC3CA1C45D933002F9851 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7E7089861B587BF3004D23AA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7E8E3A8E1C45D5380017F6C1; + remoteInfo = "ios-deploy-lib"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 7E1C00CC1C3C93AF00D686B5 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; + 7E1C00CF1C3C9ABB00D686B5 /* lldb.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; name = lldb.py; path = src/scripts/lldb.py; sourceTree = SOURCE_ROOT; }; + 7E1C00D11C3C9CB000D686B5 /* lldb.py.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lldb.py.h; sourceTree = ""; }; + 7E70898E1B587BF3004D23AA /* ios-deploy */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "ios-deploy"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E7089991B587DE4004D23AA /* ios-deploy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ios-deploy.m"; sourceTree = ""; }; + 7E70899A1B587DE4004D23AA /* errors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = errors.h; sourceTree = ""; }; + 7E70899B1B587DE4004D23AA /* MobileDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MobileDevice.h; sourceTree = ""; }; + 7E70899D1B587F29004D23AA /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 7E70899F1B58801E004D23AA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 7E8E3A831C45D4CE0017F6C1 /* ios-deploy-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ios-deploy-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E8E3A851C45D4CE0017F6C1 /* ios_deploy_tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ios_deploy_tests.m; sourceTree = ""; }; + 7E8E3A871C45D4CE0017F6C1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7E8E3A8F1C45D5380017F6C1 /* libios-deploy.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libios-deploy.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E8E3A911C45D5380017F6C1 /* libios_deploy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libios_deploy.h; sourceTree = ""; }; + 7E8E3A931C45D5380017F6C1 /* libios_deploy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = libios_deploy.m; sourceTree = ""; }; + 7EDCC3CE1C45DFF0002F9851 /* check_reqs.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = check_reqs.js; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7E70898B1B587BF3004D23AA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E7089A01B58801E004D23AA /* Foundation.framework in Frameworks */, + 7E70899E1B587F29004D23AA /* CoreFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7E8E3A801C45D4CE0017F6C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7E8E3A8C1C45D5380017F6C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E8E3A9D1C45D6290017F6C1 /* CoreFoundation.framework in Frameworks */, + 7E8E3A9B1C45D5970017F6C1 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7E1C00CE1C3C9A7700D686B5 /* scripts */ = { + isa = PBXGroup; + children = ( + 7EDCC3CE1C45DFF0002F9851 /* check_reqs.js */, + 7E1C00CF1C3C9ABB00D686B5 /* lldb.py */, + ); + name = scripts; + path = src/scripts; + sourceTree = ""; + }; + 7E7089851B587BF3004D23AA = { + isa = PBXGroup; + children = ( + 7E1C00CE1C3C9A7700D686B5 /* scripts */, + 7E7089901B587BF3004D23AA /* ios-deploy */, + 7E8E3A841C45D4CE0017F6C1 /* ios-deploy-tests */, + 7E8E3A901C45D5380017F6C1 /* ios-deploy-lib */, + 7E7089A21B588219004D23AA /* Frameworks */, + 7E70898F1B587BF3004D23AA /* Products */, + ); + sourceTree = ""; + }; + 7E70898F1B587BF3004D23AA /* Products */ = { + isa = PBXGroup; + children = ( + 7E70898E1B587BF3004D23AA /* ios-deploy */, + 7E8E3A831C45D4CE0017F6C1 /* ios-deploy-tests.xctest */, + 7E8E3A8F1C45D5380017F6C1 /* libios-deploy.a */, + ); + name = Products; + sourceTree = ""; + }; + 7E7089901B587BF3004D23AA /* ios-deploy */ = { + isa = PBXGroup; + children = ( + 7E1C00D11C3C9CB000D686B5 /* lldb.py.h */, + 7E1C00CC1C3C93AF00D686B5 /* version.h */, + 7E7089991B587DE4004D23AA /* ios-deploy.m */, + 7E70899A1B587DE4004D23AA /* errors.h */, + 7E70899B1B587DE4004D23AA /* MobileDevice.h */, + ); + name = "ios-deploy"; + path = "src/ios-deploy"; + sourceTree = ""; + }; + 7E7089A21B588219004D23AA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7E70899F1B58801E004D23AA /* Foundation.framework */, + 7E70899D1B587F29004D23AA /* CoreFoundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 7E8E3A841C45D4CE0017F6C1 /* ios-deploy-tests */ = { + isa = PBXGroup; + children = ( + 7E8E3A851C45D4CE0017F6C1 /* ios_deploy_tests.m */, + 7E8E3A871C45D4CE0017F6C1 /* Info.plist */, + ); + name = "ios-deploy-tests"; + path = "src/ios-deploy-tests"; + sourceTree = ""; + }; + 7E8E3A901C45D5380017F6C1 /* ios-deploy-lib */ = { + isa = PBXGroup; + children = ( + 7E8E3A911C45D5380017F6C1 /* libios_deploy.h */, + 7E8E3A931C45D5380017F6C1 /* libios_deploy.m */, + ); + name = "ios-deploy-lib"; + path = "src/ios-deploy-lib"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 7E8E3A8D1C45D5380017F6C1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E8E3A921C45D5380017F6C1 /* libios_deploy.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 7E70898D1B587BF3004D23AA /* ios-deploy */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7E7089951B587BF3004D23AA /* Build configuration list for PBXNativeTarget "ios-deploy" */; + buildPhases = ( + 7EDCC3CF1C45E03B002F9851 /* ShellScript */, + 7E70898B1B587BF3004D23AA /* Frameworks */, + 7EDCC3CC1C45DC89002F9851 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + 7E8E3A9A1C45D5850017F6C1 /* PBXTargetDependency */, + ); + name = "ios-deploy"; + productName = "ios-deploy"; + productReference = 7E70898E1B587BF3004D23AA /* ios-deploy */; + productType = "com.apple.product-type.tool"; + }; + 7E8E3A821C45D4CE0017F6C1 /* ios-deploy-tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7E8E3A8A1C45D4CE0017F6C1 /* Build configuration list for PBXNativeTarget "ios-deploy-tests" */; + buildPhases = ( + 7E8E3A7F1C45D4CE0017F6C1 /* Sources */, + 7E8E3A801C45D4CE0017F6C1 /* Frameworks */, + 7E8E3A811C45D4CE0017F6C1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7EDCC3CB1C45D933002F9851 /* PBXTargetDependency */, + ); + name = "ios-deploy-tests"; + productName = "ios-deploy-tests"; + productReference = 7E8E3A831C45D4CE0017F6C1 /* ios-deploy-tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 7E8E3A8E1C45D5380017F6C1 /* ios-deploy-lib */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7E8E3A951C45D5380017F6C1 /* Build configuration list for PBXNativeTarget "ios-deploy-lib" */; + buildPhases = ( + 7E8E3A8B1C45D5380017F6C1 /* Sources */, + 7E8E3A8C1C45D5380017F6C1 /* Frameworks */, + 7E8E3A8D1C45D5380017F6C1 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ios-deploy-lib"; + productName = "ios-deploy-lib"; + productReference = 7E8E3A8F1C45D5380017F6C1 /* libios-deploy.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7E7089861B587BF3004D23AA /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0710; + ORGANIZATIONNAME = PhoneGap; + TargetAttributes = { + 7E70898D1B587BF3004D23AA = { + CreatedOnToolsVersion = 6.4; + }; + 7E8E3A821C45D4CE0017F6C1 = { + CreatedOnToolsVersion = 7.2; + }; + 7E8E3A8E1C45D5380017F6C1 = { + CreatedOnToolsVersion = 7.2; + }; + }; + }; + buildConfigurationList = 7E7089891B587BF3004D23AA /* Build configuration list for PBXProject "ios-deploy" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7E7089851B587BF3004D23AA; + productRefGroup = 7E70898F1B587BF3004D23AA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7E70898D1B587BF3004D23AA /* ios-deploy */, + 7E8E3A821C45D4CE0017F6C1 /* ios-deploy-tests */, + 7E8E3A8E1C45D5380017F6C1 /* ios-deploy-lib */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7E8E3A811C45D4CE0017F6C1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 7EDCC3CF1C45E03B002F9851 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"\\\"# AUTO-GENERATED - DO NOT MODIFY\\n\\\"\" > src/ios-deploy/lldb.py.h\nawk '{ print \"\\\"\"$0\"\\\\n\\\"\"}' src/scripts/lldb.py >> src/ios-deploy/lldb.py.h"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7E8E3A7F1C45D4CE0017F6C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E8E3A861C45D4CE0017F6C1 /* ios_deploy_tests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7E8E3A8B1C45D5380017F6C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E8E3A941C45D5380017F6C1 /* libios_deploy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7EDCC3CC1C45DC89002F9851 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7EDCC3CD1C45DC94002F9851 /* ios-deploy.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 7E8E3A9A1C45D5850017F6C1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7E8E3A8E1C45D5380017F6C1 /* ios-deploy-lib */; + targetProxy = 7E8E3A991C45D5850017F6C1 /* PBXContainerItemProxy */; + }; + 7EDCC3CB1C45D933002F9851 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7E8E3A8E1C45D5380017F6C1 /* ios-deploy-lib */; + targetProxy = 7EDCC3CA1C45D933002F9851 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 7E7089931B587BF3004D23AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-framework", + MobileDevice, + "-F/System/Library/PrivateFrameworks", + ); + SDKROOT = macosx; + }; + name = Debug; + }; + 7E7089941B587BF3004D23AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "-framework", + MobileDevice, + "-F/System/Library/PrivateFrameworks", + ); + SDKROOT = macosx; + }; + name = Release; + }; + 7E7089961B587BF3004D23AA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 7E7089971B587BF3004D23AA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 7E8E3A881C45D4CE0017F6C1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "src/ios-deploy-tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.phonegap.ios-deploy-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 7E8E3A891C45D4CE0017F6C1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "src/ios-deploy-tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.phonegap.ios-deploy-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 7E8E3A961C45D5380017F6C1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + EXECUTABLE_PREFIX = lib; + INSTALL_PATH = ""; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_LDFLAGS = ( + "-framework", + MobileDevice, + "-F/System/Library/PrivateFrameworks", + ); + PRODUCT_NAME = "ios-deploy"; + PUBLIC_HEADERS_FOLDER_PATH = ""; + }; + name = Debug; + }; + 7E8E3A971C45D5380017F6C1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + EXECUTABLE_PREFIX = lib; + INSTALL_PATH = ""; + MACOSX_DEPLOYMENT_TARGET = 10.11; + OTHER_LDFLAGS = ( + "-framework", + MobileDevice, + "-F/System/Library/PrivateFrameworks", + ); + PRODUCT_NAME = "ios-deploy"; + PUBLIC_HEADERS_FOLDER_PATH = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7E7089891B587BF3004D23AA /* Build configuration list for PBXProject "ios-deploy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E7089931B587BF3004D23AA /* Debug */, + 7E7089941B587BF3004D23AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7E7089951B587BF3004D23AA /* Build configuration list for PBXNativeTarget "ios-deploy" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E7089961B587BF3004D23AA /* Debug */, + 7E7089971B587BF3004D23AA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7E8E3A8A1C45D4CE0017F6C1 /* Build configuration list for PBXNativeTarget "ios-deploy-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E8E3A881C45D4CE0017F6C1 /* Debug */, + 7E8E3A891C45D4CE0017F6C1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7E8E3A951C45D5380017F6C1 /* Build configuration list for PBXNativeTarget "ios-deploy-lib" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7E8E3A961C45D5380017F6C1 /* Debug */, + 7E8E3A971C45D5380017F6C1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7E7089861B587BF3004D23AA /* Project object */; +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..8aa0b049 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "ios-deploy", + "version": "2.0.0", + "os": [ + "darwin" + ], + "description": "launch iOS apps iOS devices from the command line (Xcode 7)", + "main": "ios-deploy", + "scripts": { + }, + "bin": "./build/Release/ios-deploy", + "repository": { + "type": "git", + "url": "https://github.com/phonegap/ios-deploy" + }, + "devDependencies": { + "jshint": "2.5.8" + }, + "scripts": { + "preinstall": "./src/scripts/check_reqs.js && xcodebuild", + "test": "npm run pycompile && npm run jshint && xcodebuild -scheme ios-deploy-lib && xcodebuild test -scheme ios-deploy-tests", + "jshint": "node node_modules/jshint/bin/jshint src/scripts/*.js", + "pycompile": "python -m py_compile src/scripts/*.py" + }, + "keywords": [ + "ios-deploy", + "deploy to iOS device" + ], + "bugs": { + "url": "https://github.com/phonegap/ios-deploy/issues" + }, + "author": "Greg Hughes", + "license": "GPLv3" +} diff --git a/resources/.DS_Store b/resources/.DS_Store new file mode 100644 index 00000000..584b44b0 Binary files /dev/null and b/resources/.DS_Store differ diff --git a/src/ios-deploy-lib/libios_deploy.h b/src/ios-deploy-lib/libios_deploy.h new file mode 100644 index 00000000..78323bc9 --- /dev/null +++ b/src/ios-deploy-lib/libios_deploy.h @@ -0,0 +1,5 @@ +#import + +@interface ios_deploy_lib : NSObject + +@end diff --git a/src/ios-deploy-lib/libios_deploy.m b/src/ios-deploy-lib/libios_deploy.m new file mode 100644 index 00000000..5993a9b5 --- /dev/null +++ b/src/ios-deploy-lib/libios_deploy.m @@ -0,0 +1,5 @@ +#import "libios_deploy.h" + +@implementation ios_deploy_lib + +@end diff --git a/src/ios-deploy-tests/Info.plist b/src/ios-deploy-tests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/src/ios-deploy-tests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/src/ios-deploy-tests/ios_deploy_tests.m b/src/ios-deploy-tests/ios_deploy_tests.m new file mode 100644 index 00000000..f58e9493 --- /dev/null +++ b/src/ios-deploy-tests/ios_deploy_tests.m @@ -0,0 +1,31 @@ +#import + +@interface ios_deploy_tests : XCTestCase + +@end + +@implementation ios_deploy_tests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/MobileDevice.h b/src/ios-deploy/MobileDevice.h similarity index 96% rename from MobileDevice.h rename to src/ios-deploy/MobileDevice.h index 1a39b098..1862bc19 100644 --- a/MobileDevice.h +++ b/src/ios-deploy/MobileDevice.h @@ -195,6 +195,7 @@ mach_error_t AMDeviceNotificationSubscribe(am_device_notification_callback callback, unsigned int unused0, unsigned int unused1, void* //unsigned int dn_unknown3, struct am_device_notification **notification); + /* Connects to the iPhone. Pass in the am_device structure that the * notification callback will give to you. * @@ -396,11 +397,11 @@ afc_error_t AFCFileRefOpen(afc_connection *conn, const char *path, afc_error_t AFCFileRefSeek(afc_connection *conn, afc_file_ref ref, unsigned long long offset1, unsigned long long offset2); afc_error_t AFCFileRefRead(afc_connection *conn, afc_file_ref ref, - void *buf, unsigned int *len); + void *buf, size_t *len); afc_error_t AFCFileRefSetFileSize(afc_connection *conn, afc_file_ref ref, unsigned long long offset); afc_error_t AFCFileRefWrite(afc_connection *conn, afc_file_ref ref, - const void *buf, unsigned int len); + const void *buf, size_t len); afc_error_t AFCFileRefClose(afc_connection *conn, afc_file_ref ref); afc_error_t AFCFileInfoOpen(afc_connection *conn, const char *path, struct @@ -448,6 +449,13 @@ void AMDAddLogFileDescriptor(int fd); //kern_return_t AMDeviceSendMessage(service_conn_t socket, void *unused, CFPropertyListRef plist); //kern_return_t AMDeviceReceiveMessage(service_conn_t socket, CFDictionaryRef options, CFPropertyListRef * result); +typedef int (*am_device_install_application_callback)(CFDictionaryRef, int); + +mach_error_t AMDeviceInstallApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callback, void *user); +mach_error_t AMDeviceTransferApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callbackj, void *user); + +int AMDeviceSecureUninstallApplication(int unknown0, struct am_device *device, CFStringRef bundle_id, int unknown1, void *callback, int callback_arg); + /* ---------------------------------------------------------------------------- * Semi-private routines * ------------------------------------------------------------------------- */ @@ -486,4 +494,4 @@ typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev, } #endif -#endif \ No newline at end of file +#endif diff --git a/src/ios-deploy/errors.h b/src/ios-deploy/errors.h new file mode 100644 index 00000000..868c880c --- /dev/null +++ b/src/ios-deploy/errors.h @@ -0,0 +1,386 @@ + +typedef struct errorcode_to_id { + unsigned int error; + const char* id; +} errorcode_to_id_t; + +typedef struct error_id_to_message { + const char* id; + const char* message; +} error_id_to_message_t; + +// Parts of error code to localization id map is taken from SDMMobileDevice framework. Associated license is bellow. +// https://github.com/samdmarshall/SDMMobileDevice/blob/master/Framework/MobileDevice/Error/SDMMD_Error.h +// +// Copyright (c) 2014, Sam Marshall +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of Sam Marshall nor the names of its contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +static errorcode_to_id_t errorcode_to_id[] = { + { 0xe8000001, "kAMDUndefinedError" }, + { 0xe8000002, "kAMDBadHeaderError" }, + { 0xe8000003, "kAMDNoResourcesError" }, + { 0xe8000004, "kAMDReadError" }, + { 0xe8000005, "kAMDWriteError" }, + { 0xe8000006, "kAMDUnknownPacketError" }, + { 0xe8000007, "kAMDInvalidArgumentError" }, + { 0xe8000008, "kAMDNotFoundError" }, + { 0xe8000009, "kAMDIsDirectoryError" }, + { 0xe800000a, "kAMDPermissionError" }, + { 0xe800000b, "kAMDNotConnectedError" }, + { 0xe800000c, "kAMDTimeOutError" }, + { 0xe800000d, "kAMDOverrunError" }, + { 0xe800000e, "kAMDEOFError" }, + { 0xe800000f, "kAMDUnsupportedError" }, + { 0xe8000010, "kAMDFileExistsError" }, + { 0xe8000011, "kAMDBusyError" }, + { 0xe8000012, "kAMDCryptoError" }, + { 0xe8000013, "kAMDInvalidResponseError" }, + { 0xe8000014, "kAMDMissingKeyError" }, + { 0xe8000015, "kAMDMissingValueError" }, + { 0xe8000016, "kAMDGetProhibitedError" }, + { 0xe8000017, "kAMDSetProhibitedError" }, + { 0xe8000018, "kAMDRemoveProhibitedError" }, + { 0xe8000019, "kAMDImmutableValueError" }, + { 0xe800001a, "kAMDPasswordProtectedError" }, + { 0xe800001b, "kAMDMissingHostIDError" }, + { 0xe800001c, "kAMDInvalidHostIDError" }, + { 0xe800001d, "kAMDSessionActiveError" }, + { 0xe800001e, "kAMDSessionInactiveError" }, + { 0xe800001f, "kAMDMissingSessionIDError" }, + { 0xe8000020, "kAMDInvalidSessionIDError" }, + { 0xe8000021, "kAMDMissingServiceError" }, + { 0xe8000022, "kAMDInvalidServiceError" }, + { 0xe8000023, "kAMDInvalidCheckinError" }, + { 0xe8000024, "kAMDCheckinTimeoutError" }, + { 0xe8000025, "kAMDMissingPairRecordError" }, + { 0xe8000026, "kAMDInvalidActivationRecordError" }, + { 0xe8000027, "kAMDMissingActivationRecordError" }, + { 0xe8000028, "kAMDWrongDroidError" }, + { 0xe8000029, "kAMDSUVerificationError" }, + { 0xe800002a, "kAMDSUPatchError" }, + { 0xe800002b, "kAMDSUFirmwareError" }, + { 0xe800002c, "kAMDProvisioningProfileNotValid" }, + { 0xe800002d, "kAMDSendMessageError" }, + { 0xe800002e, "kAMDReceiveMessageError" }, + { 0xe800002f, "kAMDMissingOptionsError" }, + { 0xe8000030, "kAMDMissingImageTypeError" }, + { 0xe8000031, "kAMDDigestFailedError" }, + { 0xe8000032, "kAMDStartServiceError" }, + { 0xe8000033, "kAMDInvalidDiskImageError" }, + { 0xe8000034, "kAMDMissingDigestError" }, + { 0xe8000035, "kAMDMuxError" }, + { 0xe8000036, "kAMDApplicationAlreadyInstalledError" }, + { 0xe8000037, "kAMDApplicationMoveFailedError" }, + { 0xe8000038, "kAMDApplicationSINFCaptureFailedError" }, + { 0xe8000039, "kAMDApplicationSandboxFailedError" }, + { 0xe800003a, "kAMDApplicationVerificationFailedError" }, + { 0xe800003b, "kAMDArchiveDestructionFailedError" }, + { 0xe800003c, "kAMDBundleVerificationFailedError" }, + { 0xe800003d, "kAMDCarrierBundleCopyFailedError" }, + { 0xe800003e, "kAMDCarrierBundleDirectoryCreationFailedError" }, + { 0xe800003f, "kAMDCarrierBundleMissingSupportedSIMsError" }, + { 0xe8000040, "kAMDCommCenterNotificationFailedError" }, + { 0xe8000041, "kAMDContainerCreationFailedError" }, + { 0xe8000042, "kAMDContainerP0wnFailedError" }, + { 0xe8000043, "kAMDContainerRemovalFailedError" }, + { 0xe8000044, "kAMDEmbeddedProfileInstallFailedError" }, + { 0xe8000045, "kAMDErrorError" }, + { 0xe8000046, "kAMDExecutableTwiddleFailedError" }, + { 0xe8000047, "kAMDExistenceCheckFailedError" }, + { 0xe8000048, "kAMDInstallMapUpdateFailedError" }, + { 0xe8000049, "kAMDManifestCaptureFailedError" }, + { 0xe800004a, "kAMDMapGenerationFailedError" }, + { 0xe800004b, "kAMDMissingBundleExecutableError" }, + { 0xe800004c, "kAMDMissingBundleIdentifierError" }, + { 0xe800004d, "kAMDMissingBundlePathError" }, + { 0xe800004e, "kAMDMissingContainerError" }, + { 0xe800004f, "kAMDNotificationFailedError" }, + { 0xe8000050, "kAMDPackageExtractionFailedError" }, + { 0xe8000051, "kAMDPackageInspectionFailedError" }, + { 0xe8000052, "kAMDPackageMoveFailedError" }, + { 0xe8000053, "kAMDPathConversionFailedError" }, + { 0xe8000054, "kAMDRestoreContainerFailedError" }, + { 0xe8000055, "kAMDSeatbeltProfileRemovalFailedError" }, + { 0xe8000056, "kAMDStageCreationFailedError" }, + { 0xe8000057, "kAMDSymlinkFailedError" }, + { 0xe8000058, "kAMDiTunesArtworkCaptureFailedError" }, + { 0xe8000059, "kAMDiTunesMetadataCaptureFailedError" }, + { 0xe800005a, "kAMDAlreadyArchivedError" }, + { 0xe800005b, "kAMDServiceLimitError" }, + { 0xe800005c, "kAMDInvalidPairRecordError" }, + { 0xe800005d, "kAMDServiceProhibitedError" }, + { 0xe800005e, "kAMDCheckinSetupFailedError" }, + { 0xe800005f, "kAMDCheckinConnectionFailedError" }, + { 0xe8000060, "kAMDCheckinReceiveFailedError" }, + { 0xe8000061, "kAMDCheckinResponseFailedError" }, + { 0xe8000062, "kAMDCheckinSendFailedError" }, + { 0xe8000063, "kAMDMuxCreateListenerError" }, + { 0xe8000064, "kAMDMuxGetListenerError" }, + { 0xe8000065, "kAMDMuxConnectError" }, + { 0xe8000066, "kAMDUnknownCommandError" }, + { 0xe8000067, "kAMDAPIInternalError" }, + { 0xe8000068, "kAMDSavePairRecordFailedError" }, + { 0xe8000069, "kAMDCheckinOutOfMemoryError" }, + { 0xe800006a, "kAMDDeviceTooNewError" }, + { 0xe800006b, "kAMDDeviceRefNoGood" }, + { 0xe800006c, "kAMDCannotTranslateError" }, + { 0xe800006d, "kAMDMobileImageMounterMissingImageSignature" }, + { 0xe800006e, "kAMDMobileImageMounterResponseCreationFailed" }, + { 0xe800006f, "kAMDMobileImageMounterMissingImageType" }, + { 0xe8000070, "kAMDMobileImageMounterMissingImagePath" }, + { 0xe8000071, "kAMDMobileImageMounterImageMapLoadFailed" }, + { 0xe8000072, "kAMDMobileImageMounterAlreadyMounted" }, + { 0xe8000073, "kAMDMobileImageMounterImageMoveFailed" }, + { 0xe8000074, "kAMDMobileImageMounterMountPathMissing" }, + { 0xe8000075, "kAMDMobileImageMounterMountPathNotEmpty" }, + { 0xe8000076, "kAMDMobileImageMounterImageMountFailed" }, + { 0xe8000077, "kAMDMobileImageMounterTrustCacheLoadFailed" }, + { 0xe8000078, "kAMDMobileImageMounterDigestFailed" }, + { 0xe8000079, "kAMDMobileImageMounterDigestCreationFailed" }, + { 0xe800007a, "kAMDMobileImageMounterImageVerificationFailed" }, + { 0xe800007b, "kAMDMobileImageMounterImageInfoCreationFailed" }, + { 0xe800007c, "kAMDMobileImageMounterImageMapStoreFailed" }, + { 0xe800007d, "kAMDBonjourSetupError" }, + { 0xe800007e, "kAMDDeviceOSVersionTooLow" }, + { 0xe800007f, "kAMDNoWifiSyncSupportError" }, + { 0xe8000080, "kAMDDeviceFamilyNotSupported" }, + { 0xe8000081, "kAMDEscrowLockedError" }, + { 0xe8000082, "kAMDPairingProhibitedError" }, + { 0xe8000083, "kAMDProhibitedBySupervision" }, + { 0xe8000084, "kAMDDeviceDisconnectedError" }, + { 0xe8000085, "kAMDTooBigError" }, + { 0xe8000086, "kAMDPackagePatchFailedError" }, + { 0xe8000087, "kAMDIncorrectArchitectureError" }, + { 0xe8000088, "kAMDPluginCopyFailedError" }, + { 0xe8000089, "kAMDBreadcrumbFailedError" }, + { 0xe800008a, "kAMDBreadcrumbUnlockError" }, + { 0xe800008b, "kAMDGeoJSONCaptureFailedError" }, + { 0xe800008c, "kAMDNewsstandArtworkCaptureFailedError" }, + { 0xe800008d, "kAMDMissingCommandError" }, + { 0xe800008e, "kAMDNotEntitledError" }, + { 0xe800008f, "kAMDMissingPackagePathError" }, + { 0xe8000090, "kAMDMissingContainerPathError" }, + { 0xe8000091, "kAMDMissingApplicationIdentifierError" }, + { 0xe8000092, "kAMDMissingAttributeValueError" }, + { 0xe8000093, "kAMDLookupFailedError" }, + { 0xe8000094, "kAMDDictCreationFailedError" }, + { 0xe8000095, "kAMDUserDeniedPairingError" }, + { 0xe8000096, "kAMDPairingDialogResponsePendingError" }, + { 0xe8000097, "kAMDInstallProhibitedError" }, + { 0xe8000098, "kAMDUninstallProhibitedError" }, + { 0xe8000099, "kAMDFMiPProtectedError" }, + { 0xe800009a, "kAMDMCProtected" }, + { 0xe800009b, "kAMDMCChallengeRequired" }, + { 0xe800009c, "kAMDMissingBundleVersionError" }, + + // Errors without id->string mapping. + { 0xe8008015, "A valid provisioning profile for this executable was not found." }, + { 0xe8008016, "The entitlements specified in your application’s Code Signing Entitlements file do not match those specified in your provisioning profile." }, + { 0xe8008017, "A signed resource has been added, modified, or deleted." }, + { 0xe8008018, "The identity used to sign the executable is no longer valid. Please verify that your device’s clock is properly set, and that your signing certificate is not expired." }, + { 0xe8008019, "The application does not have a valid signature." }, + { 0xe800801c, "No code signature found." }, +}; + +const int errorcode_to_id_count = sizeof(errorcode_to_id) / sizeof(errorcode_to_id_t); + +// Taken from /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/en_GB.lproj/Localizable.strings +error_id_to_message_t error_id_to_message[] = { + { "kAMDAPIInternalError", "There was an internal API error." }, + { "kAMDAlreadyArchivedError", "The application is already archived." }, + { "kAMDAppBlacklistedError", "This app is not allowed to be installed on this device." }, + { "kAMDApplicationAlreadyInstalledError", "A system application with the given bundle identifier is already installed on the device and cannot be replaced." }, + { "kAMDApplicationMoveFailedError", "The application could not be moved into place on the device." }, + { "kAMDApplicationSandboxFailedError", "The application could not be sandboxed." }, + { "kAMDApplicationVerificationFailedError", "The application could not be verified." }, + { "kAMDArchiveDestructionFailedError", "Could not remove the application archive." }, + { "kAMDBadHeaderError", "Could not transfer file." }, + { "kAMDBreadcrumbFailedError", "Could not write installation breadcrumb." }, + { "kAMDBreadcrumbUnlockError", "Could not update installation breadcrumb." }, + { "kAMDBundleVerificationFailedError", "The carrier bundle could not be verified." }, + { "kAMDBusyError", "The device is busy." }, + { "kAMDCannotTranslateError", "Could not translate messages from device" }, + { "kAMDCarrierBundleCopyFailedError", "Could not install the carrier bundle." }, + { "kAMDCarrierBundleDirectoryCreationFailedError", "Could not create the carrier bundle directory." }, + { "kAMDCarrierBundleMissingSupportedSIMsError", "There are no supported SIMs for this carrier bundle." }, + { "kAMDCheckinConnectionFailedError", "The service did not start properly on the device." }, + { "kAMDCheckinOutOfMemoryError", "The service did not start properly on the device." }, + { "kAMDCheckinReceiveFailedError", "The service did not start properly on the device." }, + { "kAMDCheckinResponseFailedError", "The service did not start properly on the device." }, + { "kAMDCheckinSendFailedError", "The service did not start properly on the device." }, + { "kAMDCheckinSetupFailedError", "Could not start service on device" }, + { "kAMDCheckinTimeoutError", "The service did not start properly on the device." }, + { "kAMDCommCenterNotificationFailedError", "Could not listen for notification from the baseband." }, + { "kAMDContainerCreationFailedError", "Could not create application container." }, + { "kAMDContainerP0wnFailedError", "Could not repair permissions on application container." }, + { "kAMDContainerRemovalFailedError", "Could not remove the application container." }, + { "kAMDCryptoError", "Could not establish a secure connection to the device." }, + { "kAMDDeviceDisconnectedError", "This device is no longer connected." }, + { "kAMDDeviceFamilyNotSupported", "This application does not support this kind of device." }, + { "kAMDDeviceOSVersionTooLow", "The device OS version is too low." }, + { "kAMDDeviceRefNoGood", "This device is no longer connected." }, + { "kAMDDeviceTooNewError", "This application needs to be updated." }, + { "kAMDDictCreationFailedError", "Could not extract capabilities from the request." }, + { "kAMDDigestFailedError", "Could not read disk image." }, + { "kAMDEOFError", "End of file." }, + { "kAMDEmbeddedProfileInstallFailedError", "Could not install the embedded provisioning profile." }, + { "kAMDErrorError", "An error occurred." }, + { "kAMDEscrowLockedError", "Device is not available until first unlock after boot." }, + { "kAMDExecutableTwiddleFailedError", "Could not change executable permissions on the application." }, + { "kAMDExistenceCheckFailedError", "Could not check to see if the application already exists." }, + { "kAMDFMiPProtectedError", "The device is in lost mode." }, + { "kAMDFileExistsError", "The file already exists." }, + { "kAMDGeoJSONCaptureFailedError", "Could not save the GeoJSON data." }, + { "kAMDGetProhibitedError", "Cannot retrieve value from the passcode-locked device." }, + { "kAMDImmutableValueError", "This value cannot be changed." }, + { "kAMDIncorrectArchitectureError", "This application does not support this device's CPU type." }, + { "kAMDInstallMapUpdateFailedError", "Could not update the installed applications list." }, + { "kAMDInstallProhibitedError", "Installation of apps is prohibited by a policy on the device." }, + { "kAMDInvalidActivationRecordError", "The activation record is not valid." }, + { "kAMDInvalidArgumentError", "The argument is invalid." }, + { "kAMDInvalidCheckinError", "Could not start service on device" }, + { "kAMDInvalidDiskImageError", "The disk image is invalid." }, + { "kAMDInvalidHostIDError", "The device does not recognise this host." }, + { "kAMDInvalidPairRecordError", "The host is no longer paired with the device." }, + { "kAMDInvalidResponseError", "Received an unexpected response from the device." }, + { "kAMDInvalidServiceError", "The service is invalid." }, + { "kAMDInvalidSessionIDError", "The session ID is invalid." }, + { "kAMDIsDirectoryError", "The path is a directory." }, + { "kAMDLookupFailedError", "Could not list installed applications." }, + { "kAMDMCChallengeRequired", "A policy on the device requires secure pairing." }, + { "kAMDMCProtected", "Pairing is prohibited by a policy on the device." }, + { "kAMDManifestCaptureFailedError", "Could not save the application manifest." }, + { "kAMDMapGenerationFailedError", "Could not generate the map." }, + { "kAMDMissingActivationRecordError", "The activation record could not be found." }, + { "kAMDMissingApplicationIdentifierError", "Request was missing the application identifier." }, + { "kAMDMissingAttributeValueError", "Request was missing a required value." }, + { "kAMDMissingBundleExecutableError", "The application bundle does not contain an executable." }, + { "kAMDMissingBundleIdentifierError", "The application bundle does not contain a valid identifier." }, + { "kAMDMissingBundlePathError", "Could not determine the application bundle path." }, + { "kAMDMissingBundleVersionError", "The bundle's Info.plist does not contain a CFBundleVersion key or its value is not a string." }, + { "kAMDMissingCommandError", "The request did not contain a command." }, + { "kAMDMissingContainerError", "Could not find the container for the installed application." }, + { "kAMDMissingContainerPathError", "Request was missing the container path." }, + { "kAMDMissingDigestError", "The digest is missing." }, + { "kAMDMissingHostIDError", "The device does not recognise this host." }, + { "kAMDMissingImageTypeError", "The image is missing." }, + { "kAMDMissingKeyError", "The key is missing." }, + { "kAMDMissingOptionsError", "The options are missing." }, + { "kAMDMissingPackagePathError", "Request was missing the package path." }, + { "kAMDMissingPairRecordError", "The host is not paired with the device." }, + { "kAMDMissingServiceError", "The service is missing." }, + { "kAMDMissingSessionIDError", "The session ID is missing." }, + { "kAMDMissingValueError", "The value is missing." }, + { "kAMDMobileImageMounterAlreadyMounted", "Could not support development." }, + { "kAMDMobileImageMounterDigestCreationFailed", "Could not support development." }, + { "kAMDMobileImageMounterDigestFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageInfoCreationFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageMapLoadFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageMapStoreFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageMountFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageMoveFailed", "Could not support development." }, + { "kAMDMobileImageMounterImageVerificationFailed", "Could not support development." }, + { "kAMDMobileImageMounterMissingImagePath", "Could not support development." }, + { "kAMDMobileImageMounterMissingImageSignature", "Could not support development." }, + { "kAMDMobileImageMounterMissingImageType", "Could not support development." }, + { "kAMDMobileImageMounterMountPathMissing", "Could not support development." }, + { "kAMDMobileImageMounterMountPathNotEmpty", "Could not support development." }, + { "kAMDMobileImageMounterResponseCreationFailed", "Could not support development." }, + { "kAMDMobileImageMounterTrustCacheLoadFailed", "Could not support development." }, + { "kAMDMuxConnectError", "Could not connect to the device." }, + { "kAMDMuxCreateListenerError", "Could not listen for USB devices." }, + { "kAMDMuxError", "There was an error with the USB device multiplexor." }, + { "kAMDMuxGetListenerError", "Could not get the USB listener." }, + { "kAMDNewsstandArtworkCaptureFailedError", "Could not save the Newsstand artwork." }, + { "kAMDNoResourcesError", "Could not allocate a resource." }, + { "kAMDNoWifiSyncSupportError", "Device doesn't support wireless sync." }, + { "kAMDNotConnectedError", "Not connected to the device." }, + { "kAMDNotEntitledError", "The requesting application is not allowed to make this request." }, + { "kAMDNotFoundError", "The file could not be found." }, + { "kAMDNotificationFailedError", "Could not post a notification." }, + { "kAMDOverrunError", "There was a buffer overrun." }, + { "kAMDPackageExtractionFailedError", "Could not open the application package." }, + { "kAMDPackageInspectionFailedError", "Could not inspect the application package." }, + { "kAMDPackageMoveFailedError", "Could not move the application package into the staging location." }, + { "kAMDPackagePatchFailedError", "Could not apply patch update to application." }, + { "kAMDPairingDialogResponsePendingError", "The user has not yet responded to the pairing request." }, + { "kAMDPairingProhibitedError", "Pairing only allowed over USB." }, + { "kAMDPasswordProtectedError", "The device is passcode protected." }, + { "kAMDPathConversionFailedError", "Could not convert the path." }, + { "kAMDPermissionError", "You do not have permission." }, + { "kAMDPluginCopyFailedError", "Could not copy VPN Plug-in into app container." }, + { "kAMDProhibitedBySupervision", "Operation prohibited on supervised devices." }, + { "kAMDProvisioningProfileNotValid", "The provisioning profile is not valid." }, + { "kAMDReadError", "Could not read from the device." }, + { "kAMDReceiveMessageError", "Could not receive a message from the device." }, + { "kAMDRemoveProhibitedError", "Cannot remove value on device." }, + { "kAMDRestoreContainerFailedError", "Could not restore the application container." }, + { "kAMDSUFirmwareError", "Could not flash the firmware." }, + { "kAMDSUPatchError", "Could not patch the file." }, + { "kAMDSUVerificationError", "The software update package could not be verified." }, + { "kAMDSavePairRecordFailedError", "Could not save the pairing record." }, + { "kAMDSeatbeltProfileRemovalFailedError", "Could not remove the application seatbelt profile." }, + { "kAMDSendMessageError", "Could not send a message to the device." }, + { "kAMDServiceLimitError", "Too many instances of this service are already running." }, + { "kAMDServiceProhibitedError", "The service could not be started on the device." }, + { "kAMDSessionActiveError", "The session is active." }, + { "kAMDSessionInactiveError", "The session is inactive." }, + { "kAMDSetProhibitedError", "Cannot set value on device." }, + { "kAMDStageCreationFailedError", "Could not create the staging directory." }, + { "kAMDStartServiceError", "The service could not be started." }, + { "kAMDSuccess", "There was no error." }, + { "kAMDSymlinkFailedError", "Could not create the symlink." }, + { "kAMDTimeOutError", "The operation timed out." }, + { "kAMDTooBigError", "The message is too big." }, + { "kAMDUndefinedError", "An unknown error occurred." }, + { "kAMDUninstallProhibitedError", "Uninstallation of apps is prohibited by a policy on the device." }, + { "kAMDUnknownCommandError", "The device does not recognise the command." }, + { "kAMDUnknownPacketError", "The packet is unknown." }, + { "kAMDUnsupportedError", "This operation is unsupported." }, + { "kAMDUserDeniedPairingError", "The device rejected the pairing attempt." }, + { "kAMDWriteError", "Could not write to the device." }, + { "kAMDWrongDroidError", "The device is in recovery mode." }, + { "kAMDiTunesArtworkCaptureFailedError", "Could not save the iTunes artwork." }, + { "kAMDiTunesMetadataCaptureFailedError", "Could not save the iTunes metadata." }, +}; + +const int error_id_to_message_count = sizeof(error_id_to_message) / sizeof(error_id_to_message_t); + +const char* get_error_message(unsigned int error) { + const char* id = NULL; + + // Lookup error localization id + for (int i = 0; i < errorcode_to_id_count; i++) { + if (errorcode_to_id[i].error == error) { + id = errorcode_to_id[i].id; + break; + } + } + + // Lookup error message + if (id) { + for (int i = 0; i < error_id_to_message_count; i++) + if (strcmp(error_id_to_message[i].id, id) == 0) + return error_id_to_message[i].message; + } + + // If message is not found, then at least return id if it was found, otherwise NULL + return id; +}; diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m new file mode 100644 index 00000000..e314c101 --- /dev/null +++ b/src/ios-deploy/ios-deploy.m @@ -0,0 +1,1913 @@ +//TODO: don't copy/mount DeveloperDiskImage.dmg if it's already done - Xcode checks this somehow + +#import +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MobileDevice.h" +#include "errors.h" + + +#define PREP_CMDS_PATH @"/tmp/%@/fruitstrap-lldb-prep-cmds-" +#define LLDB_SHELL @"lldb -s %@" +/* + * Startup script passed to lldb. + * To see how xcode interacts with lldb, put this into .lldbinit: + * log enable -v -f /Users/vargaz/lldb.log lldb all + * log enable -v -f /Users/vargaz/gdb-remote.log gdb-remote all + */ +#define LLDB_PREP_CMDS CFSTR("\ + platform select remote-ios --sysroot {symbols_path}\n\ + target create \"{disk_app}\"\n\ + script fruitstrap_device_app=\"{device_app}\"\n\ + script fruitstrap_connect_url=\"connect://127.0.0.1:{device_port}\"\n\ + command script import \"{python_file_path}\"\n\ + command script add -f {python_command}.connect_command connect\n\ + command script add -s asynchronous -f {python_command}.run_command run\n\ + command script add -s asynchronous -f {python_command}.autoexit_command autoexit\n\ + command script add -s asynchronous -f {python_command}.safequit_command safequit\n\ + connect\n\ +") + +const char* lldb_prep_no_cmds = ""; + +const char* lldb_prep_interactive_cmds = "\ + run\n\ +"; + +const char* lldb_prep_noninteractive_justlaunch_cmds = "\ + run\n\ + safequit\n\ +"; + +const char* lldb_prep_noninteractive_cmds = "\ + run\n\ + autoexit\n\ +"; + +/* + * Some things do not seem to work when using the normal commands like process connect/launch, so we invoke them + * through the python interface. Also, Launch () doesn't seem to work when ran from init_module (), so we add + * a command which can be used by the user to run it. + */ +NSString* LLDB_FRUITSTRAP_MODULE = @ + #include "lldb.py.h" +; + + +typedef struct am_device * AMDeviceRef; +mach_error_t AMDeviceSecureStartService(struct am_device *device, CFStringRef service_name, unsigned int *unknown, service_conn_t *handle); +int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); +int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); +mach_error_t AMDeviceLookupApplications(AMDeviceRef device, CFDictionaryRef options, CFDictionaryRef *result); +int AMDeviceGetInterfaceType(struct am_device *device); + +bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, detect_only = false, install = true, uninstall = false, no_wifi = false; +bool command_only = false; +char *command = NULL; +char *target_filename = NULL; +char *upload_pathname = NULL; +char *bundle_id = NULL; +bool interactive = true; +bool justlaunch = false; +char *app_path = NULL; +char *device_id = NULL; +char *args = NULL; +char *list_root = NULL; +int _timeout = 0; +int port = 0; // 0 means "dynamically assigned" +CFStringRef last_path = NULL; +service_conn_t gdbfd; +pid_t parent = 0; +// PID of child process running lldb +pid_t child = 0; +// Signal sent from child to parent process when LLDB finishes. +const int SIGLLDB = SIGUSR1; +AMDeviceRef best_device_match = NULL; +NSString* tmpUUID; +struct am_device_notification *notify; + +// Error codes we report on different failures, so scripts can distinguish between user app exit +// codes and our exit codes. For non app errors we use codes in reserved 128-255 range. +const int exitcode_error = 253; +const int exitcode_app_crash = 254; + +// Checks for MobileDevice.framework errors, tries to print them and exits. +#define check_error(call) \ + do { \ + unsigned int err = (unsigned int)call; \ + if (err != 0) \ + { \ + const char* msg = get_error_message(err); \ + /*on_error("Error 0x%x: %s " #call, err, msg ? msg : "unknown.");*/ \ + on_error(@"Error 0x%x: %@ " #call, err, msg ? [NSString stringWithUTF8String:msg] : @"unknown."); \ + } \ + } while (false); + +void on_error(NSString* format, ...) +{ + va_list valist; + va_start(valist, format); + NSString* str = [[[NSString alloc] initWithFormat:format arguments:valist] autorelease]; + va_end(valist); + + NSLog(@"[ !! ] %@", str); + + exit(exitcode_error); +} + +// Print error message getting last errno and exit +void on_sys_error(NSString* format, ...) { + const char* errstr = strerror(errno); + + va_list valist; + va_start(valist, format); + NSString* str = [[[NSString alloc] initWithFormat:format arguments:valist] autorelease]; + va_end(valist); + + on_error(@"%@ : %@", str, [NSString stringWithUTF8String:errstr]); +} + +void __NSLogOut(NSString* format, va_list valist) { + NSString* str = [[[NSString alloc] initWithFormat:format arguments:valist] autorelease]; + [[str stringByAppendingString:@"\n"] writeToFile:@"/dev/stdout" atomically:NO encoding:NSUTF8StringEncoding error:nil]; +} + +void NSLogOut(NSString* format, ...) { + va_list valist; + va_start(valist, format); + __NSLogOut(format, valist); + va_end(valist); +} + +void NSLogVerbose(NSString* format, ...) { + if (verbose) { + va_list valist; + va_start(valist, format); + __NSLogOut(format, valist); + va_end(valist); + } +} + + +BOOL mkdirp(NSString* path) { + NSError* error = nil; + BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:path + withIntermediateDirectories:YES + attributes:nil + error:&error]; + return success; +} + +Boolean path_exists(CFTypeRef path) { + if (CFGetTypeID(path) == CFStringGetTypeID()) { + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, true); + Boolean result = CFURLResourceIsReachable(url, NULL); + CFRelease(url); + return result; + } else if (CFGetTypeID(path) == CFURLGetTypeID()) { + return CFURLResourceIsReachable(path, NULL); + } else { + return false; + } +} + +CFStringRef find_path(CFStringRef rootPath, CFStringRef namePattern, CFStringRef expression) { + FILE *fpipe = NULL; + CFStringRef quotedRootPath = rootPath; + CFStringRef cf_command; + CFRange slashLocation; + + if (CFStringGetCharacterAtIndex(rootPath, 0) != '`') { + quotedRootPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@'"), rootPath); + } + + slashLocation = CFStringFind(namePattern, CFSTR("/"), 0); + if (slashLocation.location == kCFNotFound) { + cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -name '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); + } else { + cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -path '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); + } + + if (quotedRootPath != rootPath) { + CFRelease(quotedRootPath); + } + + char command[1024] = { '\0' }; + CFStringGetCString(cf_command, command, sizeof(command), kCFStringEncodingUTF8); + CFRelease(cf_command); + + if (!(fpipe = (FILE *)popen(command, "r"))) + on_sys_error(@"Error encountered while opening pipe"); + + char buffer[256] = { '\0' }; + + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); + + strtok(buffer, "\n"); + return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); +} + +CFStringRef copy_long_shot_disk_image_path() { + return find_path(CFSTR("`xcode-select --print-path`"), CFSTR("DeveloperDiskImage.dmg"), CFSTR("")); +} + +CFStringRef copy_xcode_dev_path() { + static char xcode_dev_path[256] = { '\0' }; + if (strlen(xcode_dev_path) == 0) { + FILE *fpipe = NULL; + char *command = "xcode-select -print-path"; + + if (!(fpipe = (FILE *)popen(command, "r"))) + on_sys_error(@"Error encountered while opening pipe"); + + char buffer[256] = { '\0' }; + + fgets(buffer, sizeof(buffer), fpipe); + pclose(fpipe); + + strtok(buffer, "\n"); + strcpy(xcode_dev_path, buffer); + } + return CFStringCreateWithCString(NULL, xcode_dev_path, kCFStringEncodingUTF8); +} + +const char *get_home() { + const char* home = getenv("HOME"); + if (!home) { + struct passwd *pwd = getpwuid(getuid()); + home = pwd->pw_dir; + } + return home; +} + +CFStringRef copy_xcode_path_for(CFStringRef subPath, CFStringRef search) { + CFStringRef xcodeDevPath = copy_xcode_dev_path(); + CFStringRef path = NULL; + bool found = false; + const char* home = get_home(); + CFRange slashLocation; + + + // Try using xcode-select --print-path + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@/%@"), xcodeDevPath, subPath, search); + found = path_exists(path); + } + // Try find `xcode-select --print-path` with search as a name pattern + if (!found) { + slashLocation = CFStringFind(search, CFSTR("/"), 0); + if (slashLocation.location == kCFNotFound) { + path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("-maxdepth 1")); + } else { + path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("")); + } + found = CFStringGetLength(path) > 0 && path_exists(path); + } + // If not look in the default xcode location (xcode-select is sometimes wrong) + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/%@&%@"), subPath, search); + found = path_exists(path); + } + // If not look in the users home directory, Xcode can store device support stuff there + if (!found) { + path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/%@/%@"), home, subPath, search); + found = path_exists(path); + } + + CFRelease(xcodeDevPath); + + if (found) { + return path; + } else { + CFRelease(path); + return NULL; + } +} + +#define GET_FRIENDLY_MODEL_NAME(VALUE, INTERNAL_NAME, FRIENDLY_NAME) if (kCFCompareEqualTo == CFStringCompare(VALUE, CFSTR(INTERNAL_NAME), kCFCompareNonliteral)) { return CFSTR( FRIENDLY_NAME); }; + + +// Please ensure that device is connected or the name will be unknown +const CFStringRef get_device_hardware_name(const AMDeviceRef device) { + CFStringRef model = AMDeviceCopyValue(device, 0, CFSTR("HardwareModel")); + + if (model == NULL) { + return CFSTR("Unknown Device"); + } + + // iPod Touch + + GET_FRIENDLY_MODEL_NAME(model, "N45AP", "iPod Touch") + GET_FRIENDLY_MODEL_NAME(model, "N72AP", "iPod Touch 2G") + GET_FRIENDLY_MODEL_NAME(model, "N18AP", "iPod Touch 3G") + GET_FRIENDLY_MODEL_NAME(model, "N81AP", "iPod Touch 4G") + GET_FRIENDLY_MODEL_NAME(model, "N78AP", "iPod Touch 5G") + GET_FRIENDLY_MODEL_NAME(model, "N78AAP", "iPod Touch 5G") + + // iPad + + GET_FRIENDLY_MODEL_NAME(model, "K48AP", "iPad") + GET_FRIENDLY_MODEL_NAME(model, "K93AP", "iPad 2") + GET_FRIENDLY_MODEL_NAME(model, "K94AP", "iPad 2 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "K95AP", "iPad 2 (CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "K93AAP", "iPad 2 (Wi-Fi, revision A)") + GET_FRIENDLY_MODEL_NAME(model, "J1AP", "iPad 3") + GET_FRIENDLY_MODEL_NAME(model, "J2AP", "iPad 3 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "J2AAP", "iPad 3 (CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "P101AP", "iPad 4") + GET_FRIENDLY_MODEL_NAME(model, "P102AP", "iPad 4 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "P103AP", "iPad 4 (CDMA)") + + // iPad Mini + + GET_FRIENDLY_MODEL_NAME(model, "P105AP", "iPad mini") + GET_FRIENDLY_MODEL_NAME(model, "P106AP", "iPad mini (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "P107AP", "iPad mini (CDMA)") + + // Apple TV + + GET_FRIENDLY_MODEL_NAME(model, "K66AP", "Apple TV 2G") + GET_FRIENDLY_MODEL_NAME(model, "J33AP", "Apple TV 3G") + GET_FRIENDLY_MODEL_NAME(model, "J33IAP", "Apple TV 3.1G") + + // iPhone + + GET_FRIENDLY_MODEL_NAME(model, "M68AP", "iPhone") + GET_FRIENDLY_MODEL_NAME(model, "N82AP", "iPhone 3G") + GET_FRIENDLY_MODEL_NAME(model, "N88AP", "iPhone 3GS") + GET_FRIENDLY_MODEL_NAME(model, "N90AP", "iPhone 4 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "N92AP", "iPhone 4 (CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "N90BAP", "iPhone 4 (GSM, revision A)") + GET_FRIENDLY_MODEL_NAME(model, "N94AP", "iPhone 4S") + GET_FRIENDLY_MODEL_NAME(model, "N41AP", "iPhone 5 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "N42AP", "iPhone 5 (Global/CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "N48AP", "iPhone 5c (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "N49AP", "iPhone 5c (Global/CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "N51AP", "iPhone 5s (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "N53AP", "iPhone 5s (Global/CDMA)") + GET_FRIENDLY_MODEL_NAME(model, "N61AP", "iPhone 6 (GSM)") + GET_FRIENDLY_MODEL_NAME(model, "N56AP", "iPhone 6 Plus") + + GET_FRIENDLY_MODEL_NAME(model, "N71mAP", "iPhone 6s") + GET_FRIENDLY_MODEL_NAME(model, "N71AP", "iPhone 6s") + GET_FRIENDLY_MODEL_NAME(model, "N66AP", "iPhone 6s Plus") + GET_FRIENDLY_MODEL_NAME(model, "N66mAP", "iPhone 6s Plus") + + return model; +} + +char * MYCFStringCopyUTF8String(CFStringRef aString) { + if (aString == NULL) { + return NULL; + } + + CFIndex length = CFStringGetLength(aString); + CFIndex maxSize = + CFStringGetMaximumSizeForEncoding(length, + kCFStringEncodingUTF8); + char *buffer = (char *)malloc(maxSize); + if (CFStringGetCString(aString, buffer, maxSize, + kCFStringEncodingUTF8)) { + return buffer; + } + return NULL; +} + +CFStringRef get_device_full_name(const AMDeviceRef device) { + CFStringRef full_name = NULL, + device_udid = AMDeviceCopyDeviceIdentifier(device), + device_name = NULL, + model_name = NULL; + + AMDeviceConnect(device); + + device_name = AMDeviceCopyValue(device, 0, CFSTR("DeviceName")), + model_name = get_device_hardware_name(device); + + NSLogVerbose(@"Device Name: %@", device_name); + NSLogVerbose(@"Model Name: %@", model_name); + + if(device_name != NULL && model_name != NULL) + { + full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ '%@' (%@)"), model_name, device_name, device_udid); + } + else + { + full_name = CFStringCreateWithFormat(NULL, NULL, CFSTR("(%@ss)"), device_udid); + } + + AMDeviceDisconnect(device); + + if(device_udid != NULL) + CFRelease(device_udid); + if(device_name != NULL) + CFRelease(device_name); + if(model_name != NULL) + CFRelease(model_name); + + return full_name; +} + +CFStringRef get_device_interface_name(const AMDeviceRef device) { + // AMDeviceGetInterfaceType(device) 0=Unknown, 1 = Direct/USB, 2 = Indirect/WIFI + switch(AMDeviceGetInterfaceType(device)) { + case 1: + return CFSTR("USB"); + case 2: + return CFSTR("WIFI"); + default: + return CFSTR("Unknown Connection"); + } +} + +CFMutableArrayRef get_device_product_version_parts(AMDeviceRef device) { + CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); + CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, version, CFSTR(".")); + CFMutableArrayRef result = CFArrayCreateMutableCopy(NULL, CFArrayGetCount(parts), parts); + CFRelease(version); + CFRelease(parts); + return result; +} + +CFStringRef copy_device_support_path(AMDeviceRef device) { + CFStringRef version = NULL; + CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); + CFStringRef path = NULL; + CFMutableArrayRef version_parts = get_device_product_version_parts(device); + + while (CFArrayGetCount(version_parts) > 0) { + version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (*)"), version)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), version); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("")); + } + CFRelease(version); + if (path != NULL) { + break; + } + CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); + } + + CFRelease(version_parts); + CFRelease(build); + + if (path == NULL) + on_error(@"Unable to locate DeviceSupport directory. This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!"); + + return path; +} + +CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { + CFStringRef version = NULL; + CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); + CFStringRef path = NULL; + CFMutableArrayRef version_parts = get_device_product_version_parts(device); + + while (CFArrayGetCount(version_parts) > 0) { + version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("*/%@ (*)/DeveloperDiskImage.dmg"), version)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/DeveloperDiskImage.dmg"), version)); + } + if (path == NULL) { + path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("DeveloperDiskImage.dmg")); + } + CFRelease(version); + if (path != NULL) { + break; + } + CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); + } + + CFRelease(version_parts); + CFRelease(build); + if (path == NULL) + on_error(@"Unable to locate DeveloperDiskImage.dmg. This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!"); + + return path; +} + +void mount_callback(CFDictionaryRef dict, int arg) { + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + + if (CFEqual(status, CFSTR("LookingUpImage"))) { + NSLogOut(@"[ 0%%] Looking up developer disk image"); + } else if (CFEqual(status, CFSTR("CopyingImage"))) { + NSLogOut(@"[ 30%%] Copying DeveloperDiskImage.dmg to device"); + } else if (CFEqual(status, CFSTR("MountingImage"))) { + NSLogOut(@"[ 90%%] Mounting developer disk image"); + } +} + +void mount_developer_image(AMDeviceRef device) { + CFStringRef ds_path = copy_device_support_path(device); + CFStringRef image_path = copy_developer_disk_image_path(device); + CFStringRef sig_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@.signature"), image_path); + + NSLogVerbose(@"Device support path: %@", ds_path); + NSLogVerbose(@"Developer disk image: %@", image_path); + CFRelease(ds_path); + + FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); + void *sig_buf = malloc(128); + assert(fread(sig_buf, 1, 128, sig) == 128); + fclose(sig); + CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); + CFRelease(sig_path); + + CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; + CFTypeRef values[] = { sig_data, CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease(sig_data); + + int result = AMDeviceMountImage(device, image_path, options, &mount_callback, 0); + if (result == 0) { + NSLogOut(@"[ 95%%] Developer disk image mounted successfully"); + } else if (result == 0xe8000076 /* already mounted */) { + NSLogOut(@"[ 95%%] Developer disk image already mounted"); + } else { + on_error(@"Unable to mount developer disk image. (%x)", result); + } + + CFRelease(image_path); + CFRelease(options); +} + +mach_error_t transfer_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + + if (CFEqual(status, CFSTR("CopyingFile"))) { + CFStringRef path = CFDictionaryGetValue(dict, CFSTR("Path")); + + if ((last_path == NULL || !CFEqual(path, last_path)) && !CFStringHasSuffix(path, CFSTR(".ipa"))) { + NSLogOut(@"[%3d%%] Copying %@ to device", percent / 2, path); + } + + if (last_path != NULL) { + CFRelease(last_path); + } + last_path = CFStringCreateCopy(NULL, path); + } + + return 0; +} + +mach_error_t install_callback(CFDictionaryRef dict, int arg) { + int percent; + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + + NSLogOut(@"[%3d%%] %@", (percent / 2) + 50, status); + return 0; +} + +CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { + CFDictionaryRef result = nil; + + NSArray *a = [NSArray arrayWithObjects: + @"CFBundleIdentifier", // absolute must + @"ApplicationDSID", + @"ApplicationType", + @"CFBundleExecutable", + @"CFBundleDisplayName", + @"CFBundleIconFile", + @"CFBundleName", + @"CFBundleShortVersionString", + @"CFBundleSupportedPlatforms", + @"CFBundleURLTypes", + @"CodeInfoIdentifier", + @"Container", + @"Entitlements", + @"HasSettingsBundle", + @"IsUpgradeable", + @"MinimumOSVersion", + @"Path", + @"SignerIdentity", + @"UIDeviceFamily", + @"UIFileSharingEnabled", + @"UIStatusBarHidden", + @"UISupportedInterfaceOrientations", + nil]; + + NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:a forKey:@"ReturnAttributes"]; + CFDictionaryRef options = (CFDictionaryRef)optionsDict; + + check_error(AMDeviceLookupApplications(device, options, &result)); + + CFDictionaryRef app_dict = CFDictionaryGetValue(result, identifier); + assert(app_dict != NULL); + + CFStringRef app_path = CFDictionaryGetValue(app_dict, CFSTR("Path")); + assert(app_path != NULL); + + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, app_path, kCFURLPOSIXPathStyle, true); + CFRelease(result); + return url; +} + +CFStringRef copy_disk_app_identifier(CFURLRef disk_app_url) { + CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent(NULL, disk_app_url, CFSTR("Info.plist"), false); + CFReadStreamRef plist_stream = CFReadStreamCreateWithFile(NULL, plist_url); + if (!CFReadStreamOpen(plist_stream)) { + on_error(@"Cannot read Info.plist file: %@", plist_url); + } + + CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, plist_stream, 0, kCFPropertyListImmutable, NULL, NULL); + CFStringRef bundle_identifier = CFRetain(CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"))); + CFReadStreamClose(plist_stream); + + CFRelease(plist_url); + CFRelease(plist_stream); + CFRelease(plist); + + return bundle_identifier; +} + +void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { + CFStringRef ds_path = copy_device_support_path(device); + CFStringRef symbols_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@/Symbols'"), ds_path); + + CFMutableStringRef cmds = CFStringCreateMutableCopy(NULL, 0, LLDB_PREP_CMDS); + CFRange range = { 0, CFStringGetLength(cmds) }; + + CFStringFindAndReplace(cmds, CFSTR("{symbols_path}"), symbols_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFStringFindAndReplace(cmds, CFSTR("{ds_path}"), ds_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFMutableStringRef pmodule = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)LLDB_FRUITSTRAP_MODULE); + + CFRange rangeLLDB = { 0, CFStringGetLength(pmodule) }; + CFStringRef exitcode_app_crash_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), exitcode_app_crash); + CFStringFindAndReplace(pmodule, CFSTR("{exitcode_app_crash}"), exitcode_app_crash_str, rangeLLDB, 0); + rangeLLDB.length = CFStringGetLength(pmodule); + + if (args) { + CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingUTF8); + CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0); + rangeLLDB.length = CFStringGetLength(pmodule); + CFStringFindAndReplace(pmodule, CFSTR("{args}"), cf_args, rangeLLDB, 0); + + //printf("write_lldb_prep_cmds:args: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), + // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); + CFRelease(cf_args); + } else { + CFStringFindAndReplace(cmds, CFSTR("{args}"), CFSTR(""), range, 0); + CFStringFindAndReplace(pmodule, CFSTR("{args}"), CFSTR(""), rangeLLDB, 0); + //printf("write_lldb_prep_cmds: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), + // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); + } + range.length = CFStringGetLength(cmds); + + CFStringRef bundle_identifier = copy_disk_app_identifier(disk_app_url); + CFURLRef device_app_url = copy_device_app_url(device, bundle_identifier); + CFStringRef device_app_path = CFURLCopyFileSystemPath(device_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{device_app}"), device_app_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFStringRef disk_app_path = CFURLCopyFileSystemPath(disk_app_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_app}"), disk_app_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFStringRef device_port = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), port); + CFStringFindAndReplace(cmds, CFSTR("{device_port}"), device_port, range, 0); + range.length = CFStringGetLength(cmds); + + CFURLRef device_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, device_app_url); + CFStringRef device_container_path = CFURLCopyFileSystemPath(device_container_url, kCFURLPOSIXPathStyle); + CFMutableStringRef dcp_noprivate = CFStringCreateMutableCopy(NULL, 0, device_container_path); + range.length = CFStringGetLength(dcp_noprivate); + CFStringFindAndReplace(dcp_noprivate, CFSTR("/private/var/"), CFSTR("/var/"), range, 0); + range.length = CFStringGetLength(cmds); + CFStringFindAndReplace(cmds, CFSTR("{device_container}"), dcp_noprivate, range, 0); + range.length = CFStringGetLength(cmds); + + CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url); + CFStringRef disk_container_path = CFURLCopyFileSystemPath(disk_container_url, kCFURLPOSIXPathStyle); + CFStringFindAndReplace(cmds, CFSTR("{disk_container}"), disk_container_path, range, 0); + + NSString* python_file_path = [NSString stringWithFormat:@"/tmp/%@/fruitstrap_", tmpUUID]; + mkdirp(python_file_path); + + NSString* python_command = @"fruitstrap_"; + if(device_id != NULL) { + python_file_path = [python_file_path stringByAppendingString:[NSString stringWithUTF8String:device_id]]; + python_command = [python_command stringByAppendingString:[NSString stringWithUTF8String:device_id]]; + } + python_file_path = [python_file_path stringByAppendingString:@".py"]; + + CFStringFindAndReplace(cmds, CFSTR("{python_command}"), (CFStringRef)python_command, range, 0); + range.length = CFStringGetLength(cmds); + CFStringFindAndReplace(cmds, CFSTR("{python_file_path}"), (CFStringRef)python_file_path, range, 0); + range.length = CFStringGetLength(cmds); + + CFDataRef cmds_data = CFStringCreateExternalRepresentation(NULL, cmds, kCFStringEncodingUTF8, 0); + NSString* prep_cmds_path = [NSString stringWithFormat:PREP_CMDS_PATH, tmpUUID]; + if(device_id != NULL) { + prep_cmds_path = [prep_cmds_path stringByAppendingString:[NSString stringWithUTF8String:device_id]]; + } + FILE *out = fopen([prep_cmds_path UTF8String], "w"); + fwrite(CFDataGetBytePtr(cmds_data), CFDataGetLength(cmds_data), 1, out); + // Write additional commands based on mode we're running in + const char* extra_cmds; + if (!interactive) + { + if (justlaunch) + extra_cmds = lldb_prep_noninteractive_justlaunch_cmds; + else + extra_cmds = lldb_prep_noninteractive_cmds; + } + else if (nostart) + extra_cmds = lldb_prep_no_cmds; + else + extra_cmds = lldb_prep_interactive_cmds; + fwrite(extra_cmds, strlen(extra_cmds), 1, out); + fclose(out); + + CFDataRef pmodule_data = CFStringCreateExternalRepresentation(NULL, pmodule, kCFStringEncodingUTF8, 0); + + out = fopen([python_file_path UTF8String], "w"); + fwrite(CFDataGetBytePtr(pmodule_data), CFDataGetLength(pmodule_data), 1, out); + fclose(out); + + CFRelease(cmds); + if (ds_path != NULL) CFRelease(ds_path); + CFRelease(bundle_identifier); + CFRelease(device_app_url); + CFRelease(device_app_path); + CFRelease(disk_app_path); + CFRelease(device_container_url); + CFRelease(device_container_path); + CFRelease(dcp_noprivate); + CFRelease(disk_container_url); + CFRelease(disk_container_path); + CFRelease(cmds_data); +} + +CFSocketRef server_socket; +CFSocketRef lldb_socket; +CFWriteStreamRef serverWriteStream = NULL; +CFWriteStreamRef lldbWriteStream = NULL; + +int kill_ptree(pid_t root, int signum); +void +server_callback (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) +{ + ssize_t res; + + if (CFDataGetLength (data) == 0) { + // close the socket on which we've got end-of-file, the server_socket. + CFSocketInvalidate(s); + CFRelease(s); + return; + } + res = write (CFSocketGetNative (lldb_socket), CFDataGetBytePtr (data), CFDataGetLength (data)); +} + +void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) +{ + //printf ("lldb: %s\n", CFDataGetBytePtr (data)); + + if (CFDataGetLength (data) == 0) { + // close the socket on which we've got end-of-file, the lldb_socket. + CFSocketInvalidate(s); + CFRelease(s); + return; + } + write (gdbfd, CFDataGetBytePtr (data), CFDataGetLength (data)); +} + +void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { + CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); + + assert (callbackType == kCFSocketAcceptCallBack); + //PRINT ("callback!\n"); + + lldb_socket = CFSocketCreateWithNative(NULL, socket, kCFSocketDataCallBack, &lldb_callback, NULL); + int flag = 1; + int res = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(flag)); + assert(res == 0); + CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, lldb_socket, 0), kCFRunLoopCommonModes); + + CFSocketInvalidate(s); + CFRelease(s); +} + +void start_remote_debug_server(AMDeviceRef device) { + + check_error(AMDeviceStartService(device, CFSTR("com.apple.debugserver"), &gdbfd, NULL)); + assert(gdbfd > 0); + + /* + * The debugserver connection is through a fd handle, while lldb requires a host/port to connect, so create an intermediate + * socket to transfer data. + */ + server_socket = CFSocketCreateWithNative (NULL, gdbfd, kCFSocketDataCallBack, &server_callback, NULL); + CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, server_socket, 0), kCFRunLoopCommonModes); + + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + CFSocketRef fdvendor = CFSocketCreate(NULL, PF_INET, 0, 0, kCFSocketAcceptCallBack, &fdvendor_callback, NULL); + + if (port) { + int yes = 1; + setsockopt(CFSocketGetNative(fdvendor), SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + } + + CFDataRef address_data = CFDataCreate(NULL, (const UInt8 *)&addr4, sizeof(addr4)); + + CFSocketSetAddress(fdvendor, address_data); + CFRelease(address_data); + socklen_t addrlen = sizeof(addr4); + int res = getsockname(CFSocketGetNative(fdvendor),(struct sockaddr *)&addr4,&addrlen); + assert(res == 0); + port = ntohs(addr4.sin_port); + + CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, fdvendor, 0), kCFRunLoopCommonModes); +} + +void kill_ptree_inner(pid_t root, int signum, struct kinfo_proc *kp, int kp_len) { + int i; + for (i = 0; i < kp_len; i++) { + if (kp[i].kp_eproc.e_ppid == root) { + kill_ptree_inner(kp[i].kp_proc.p_pid, signum, kp, kp_len); + } + } + if (root != getpid()) { + kill(root, signum); + } +} + +int kill_ptree(pid_t root, int signum) { + int mib[3]; + size_t len; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ALL; + if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1) { + return -1; + } + + struct kinfo_proc *kp = calloc(1, len); + if (!kp) { + return -1; + } + + if (sysctl(mib, 3, kp, &len, NULL, 0) == -1) { + free(kp); + return -1; + } + + kill_ptree_inner(root, signum, kp, (int)(len / sizeof(struct kinfo_proc))); + + free(kp); + return 0; +} + +void killed(int signum) { + // SIGKILL needed to kill lldb, probably a better way to do this. + kill(0, SIGKILL); + _exit(0); +} + +void lldb_finished_handler(int signum) +{ + int status = 0; + if (waitpid(child, &status, 0) == -1) + perror("waitpid failed"); + _exit(WEXITSTATUS(status)); +} + +void bring_process_to_foreground() { + if (setpgid(0, 0) == -1) + perror("setpgid failed"); + + signal(SIGTTOU, SIG_IGN); + if (tcsetpgrp(STDIN_FILENO, getpid()) == -1) + perror("tcsetpgrp failed"); + signal(SIGTTOU, SIG_DFL); +} + +void setup_dummy_pipe_on_stdin(int pfd[2]) { + if (pipe(pfd) == -1) + perror("pipe failed"); + if (dup2(pfd[0], STDIN_FILENO) == -1) + perror("dup2 failed"); +} + +void setup_lldb(AMDeviceRef device, CFURLRef url) { + CFStringRef device_full_name = get_device_full_name(device), + device_interface_name = get_device_interface_name(device); + + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + NSLogOut(@"------ Debug phase ------"); + + if(AMDeviceGetInterfaceType(device) == 2) + { + NSLogOut(@"Cannot debug %@ over %@.", device_full_name, device_interface_name); + exit(0); + } + + NSLogOut(@"Starting debug of %@ connected through %@...", device_full_name, device_interface_name); + + mount_developer_image(device); // put debugserver on the device + start_remote_debug_server(device); // start debugserver + write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file + + CFRelease(url); + + NSLogOut(@"[100%%] Connecting to remote debug server"); + NSLogOut(@"-------------------------"); + + setpgid(getpid(), 0); + signal(SIGHUP, killed); + signal(SIGINT, killed); + signal(SIGTERM, killed); + // Need this before fork to avoid race conditions. For child process we remove this right after fork. + signal(SIGLLDB, lldb_finished_handler); + + parent = getpid(); +} + +void launch_debugger(AMDeviceRef device, CFURLRef url) { + setup_lldb(device, url); + int pid = fork(); + if (pid == 0) { + signal(SIGHUP, SIG_DFL); + signal(SIGLLDB, SIG_DFL); + child = getpid(); + + int pfd[2] = {-1, -1}; + if (isatty(STDIN_FILENO)) + // If we are running on a terminal, then we need to bring process to foreground for input + // to work correctly on lldb's end. + bring_process_to_foreground(); + else + // If lldb is running in a non terminal environment, then it freaks out spamming "^D" and + // "quit". It seems this is caused by read() on stdin returning EOF in lldb. To hack around + // this we setup a dummy pipe on stdin, so read() would block expecting "user's" input. + setup_dummy_pipe_on_stdin(pfd); + + NSString* lldb_shell; + NSString* prep_cmds = [NSString stringWithFormat:PREP_CMDS_PATH, tmpUUID]; + lldb_shell = [NSString stringWithFormat:LLDB_SHELL, prep_cmds]; + + if(device_id != NULL) { + lldb_shell = [lldb_shell stringByAppendingString: [NSString stringWithUTF8String:device_id]]; + } + + int status = system([lldb_shell UTF8String]); // launch lldb + if (status == -1) + perror("failed launching lldb"); + + close(pfd[0]); + close(pfd[1]); + + // Notify parent we're exiting + kill(parent, SIGLLDB); + // Pass lldb exit code + _exit(WEXITSTATUS(status)); + } else if (pid > 0) { + child = pid; + } else { + on_sys_error(@"Fork failed"); + } +} + +void launch_debugger_and_exit(AMDeviceRef device, CFURLRef url) { + setup_lldb(device,url); + int pfd[2] = {-1, -1}; + if (pipe(pfd) == -1) + perror("Pipe failed"); + int pid = fork(); + if (pid == 0) { + signal(SIGHUP, SIG_DFL); + signal(SIGLLDB, SIG_DFL); + child = getpid(); + + if (dup2(pfd[0],STDIN_FILENO) == -1) + perror("dup2 failed"); + + + NSString* prep_cmds = [NSString stringWithFormat:PREP_CMDS_PATH, tmpUUID]; + NSString* lldb_shell = [NSString stringWithFormat:LLDB_SHELL, prep_cmds]; + if(device_id != NULL) { + lldb_shell = [lldb_shell stringByAppendingString:[NSString stringWithUTF8String:device_id]]; + } + + int status = system([lldb_shell UTF8String]); // launch lldb + if (status == -1) + perror("failed launching lldb"); + + close(pfd[0]); + + // Notify parent we're exiting + kill(parent, SIGLLDB); + // Pass lldb exit code + _exit(WEXITSTATUS(status)); + } else if (pid > 0) { + child = pid; + NSLogVerbose(@"Waiting for child [Child: %d][Parent: %d]\n", child, parent); + } else { + on_sys_error(@"Fork failed"); + } +} + +CFStringRef get_bundle_id(CFURLRef app_url) +{ + if (app_url == NULL) + return NULL; + + CFURLRef url = CFURLCreateCopyAppendingPathComponent(NULL, app_url, CFSTR("Info.plist"), false); + + if (url == NULL) + return NULL; + + CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, url); + CFRelease(url); + + if (stream == NULL) + return NULL; + + CFPropertyListRef plist = NULL; + if (CFReadStreamOpen(stream) == TRUE) { + plist = CFPropertyListCreateWithStream(NULL, stream, 0, + kCFPropertyListImmutable, NULL, NULL); + } + CFReadStreamClose(stream); + CFRelease(stream); + + if (plist == NULL) + return NULL; + + const void *value = CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier")); + CFStringRef bundle_id = NULL; + if (value != NULL) + bundle_id = CFRetain(value); + + CFRelease(plist); + return bundle_id; +} + + +void read_dir(service_conn_t afcFd, afc_connection* afc_conn_p, const char* dir, + void(*callback)(afc_connection *conn,const char *dir,int file)) +{ + char *dir_ent; + + afc_connection afc_conn; + if (!afc_conn_p) { + afc_conn_p = &afc_conn; + AFCConnectionOpen(afcFd, 0, &afc_conn_p); + } + + afc_dictionary* afc_dict_p; + char *key, *val; + int not_dir = 0; + + unsigned int code = AFCFileInfoOpen(afc_conn_p, dir, &afc_dict_p); + if (code != 0) { + // there was a problem reading or opening the file to get info on it, abort + return; + } + + while((AFCKeyValueRead(afc_dict_p,&key,&val) == 0) && key && val) { + if (strcmp(key,"st_ifmt")==0) { + not_dir = strcmp(val,"S_IFDIR"); + break; + } + } + AFCKeyValueClose(afc_dict_p); + + if (not_dir) { + NSLogOut(@"%@", [NSString stringWithUTF8String:dir]); + } else { + NSLogOut(@"%@/", [NSString stringWithUTF8String:dir]); + } + + if (not_dir) { + if (callback) (*callback)(afc_conn_p, dir, not_dir); + return; + } + + afc_directory* afc_dir_p; + afc_error_t err = AFCDirectoryOpen(afc_conn_p, dir, &afc_dir_p); + + if (err != 0) { + // Couldn't open dir - was probably a file + return; + } else { + if (callback) (*callback)(afc_conn_p, dir, not_dir); + } + + while(true) { + err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent); + + if (err != 0 || !dir_ent) + break; + + if (strcmp(dir_ent, ".") == 0 || strcmp(dir_ent, "..") == 0) + continue; + + char* dir_joined = malloc(strlen(dir) + strlen(dir_ent) + 2); + strcpy(dir_joined, dir); + if (dir_joined[strlen(dir)-1] != '/') + strcat(dir_joined, "/"); + strcat(dir_joined, dir_ent); + read_dir(afcFd, afc_conn_p, dir_joined, callback); + free(dir_joined); + } + + AFCDirectoryClose(afc_conn_p, afc_dir_p); +} + + +// Used to send files to app-specific sandbox (Documents dir) +service_conn_t start_house_arrest_service(AMDeviceRef device) { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + service_conn_t houseFd; + + if (bundle_id == NULL) { + on_error(@"Bundle id is not specified"); + } + + CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingUTF8); + if (AMDeviceStartHouseArrestService(device, cf_bundle_id, 0, &houseFd, 0) != 0) + { + on_error(@"Unable to find bundle with id: %@", [NSString stringWithUTF8String:bundle_id]); + } + + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + CFRelease(cf_bundle_id); + + return houseFd; +} + +char* get_filename_from_path(char* path) +{ + char *ptr = path + strlen(path); + while (ptr > path) + { + if (*ptr == '/') + break; + --ptr; + } + if (ptr+1 >= path+strlen(path)) + return NULL; + if (ptr == path) + return ptr; + return ptr+1; +} + +void* read_file_to_memory(char * path, size_t* file_size) +{ + struct stat buf; + int err = stat(path, &buf); + if (err < 0) + { + return NULL; + } + + *file_size = buf.st_size; + FILE* fd = fopen(path, "r"); + char* content = malloc(*file_size); + if (*file_size != 0 && fread(content, *file_size, 1, fd) != 1) + { + fclose(fd); + return NULL; + } + fclose(fd); + return content; +} + +void list_files(AMDeviceRef device) +{ + service_conn_t houseFd = start_house_arrest_service(device); + + afc_connection* afc_conn_p; + if (AFCConnectionOpen(houseFd, 0, &afc_conn_p) == 0) { + read_dir(houseFd, afc_conn_p, list_root?list_root:"/", NULL); + AFCConnectionClose(afc_conn_p); + } +} + +int app_exists(AMDeviceRef device) +{ + if (bundle_id == NULL) { + NSLogOut(@"Bundle id is not specified."); + return 1; + } + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingUTF8); + + NSArray *a = [NSArray arrayWithObjects:@"CFBundleIdentifier", nil]; + NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:a forKey:@"ReturnAttributes"]; + CFDictionaryRef options = (CFDictionaryRef)optionsDict; + CFDictionaryRef result = nil; + check_error(AMDeviceLookupApplications(device, options, &result)); + + bool appExists = CFDictionaryContainsKey(result, cf_bundle_id); + NSLogOut(@"%@", appExists ? @"true" : @"false"); + CFRelease(cf_bundle_id); + + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + if (appExists) + return 0; + return -1; +} + +void list_bundle_id(AMDeviceRef device) +{ + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + NSArray *a = [NSArray arrayWithObjects:@"CFBundleIdentifier", nil]; + NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:a forKey:@"ReturnAttributes"]; + CFDictionaryRef options = (CFDictionaryRef)optionsDict; + CFDictionaryRef result = nil; + check_error(AMDeviceLookupApplications(device, options, &result)); + + CFIndex count; + count = CFDictionaryGetCount(result); + const void *keys[count]; + CFDictionaryGetKeysAndValues(result, keys, NULL); + for(int i = 0; i < count; ++i) { + NSLogOut(@"%@", (CFStringRef)keys[i]); + } + + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); +} + +void copy_file_callback(afc_connection* afc_conn_p, const char *name,int file) +{ + const char *local_name=name; + + if (*local_name=='/') local_name++; + + if (*local_name=='\0') return; + + if (file) { + afc_file_ref fref; + int err = AFCFileRefOpen(afc_conn_p,name,1,&fref); + + if (err) { + fprintf(stderr,"AFCFileRefOpen(\"%s\") failed: %d\n",name,err); + return; + } + + FILE *fp = fopen(local_name,"w"); + + if (fp==NULL) { + fprintf(stderr,"fopen(\"%s\",\"w\") failer: %s\n",local_name,strerror(errno)); + AFCFileRefClose(afc_conn_p,fref); + return; + } + + char buf[4096]; + size_t sz=sizeof(buf); + + while (AFCFileRefRead(afc_conn_p,fref,buf,&sz)==0 && sz) { + fwrite(buf,sz,1,fp); + sz = sizeof(buf); + } + + AFCFileRefClose(afc_conn_p,fref); + fclose(fp); + } else { + if (mkdir(local_name,0777) && errno!=EEXIST) + fprintf(stderr,"mkdir(\"%s\") failed: %s\n",local_name,strerror(errno)); + } +} + +void download_tree(AMDeviceRef device) +{ + service_conn_t houseFd = start_house_arrest_service(device); + afc_connection* afc_conn_p = NULL; + char *dirname = NULL; + + list_root = list_root? list_root : "/"; + target_filename = target_filename? target_filename : "."; + + NSString* targetPath = [NSString pathWithComponents:@[ @(target_filename), @(list_root)] ]; + mkdirp([targetPath stringByDeletingLastPathComponent]); + + if (AFCConnectionOpen(houseFd, 0, &afc_conn_p) == 0) do { + + if (target_filename) { + dirname = strdup(target_filename); + mkdirp(@(dirname)); + if (mkdir(dirname,0777) && errno!=EEXIST) { + fprintf(stderr,"mkdir(\"%s\") failed: %s\n",dirname,strerror(errno)); + break; + } + if (chdir(dirname)) { + fprintf(stderr,"chdir(\"%s\") failed: %s\n",dirname,strerror(errno)); + break; + } + } + + read_dir(houseFd, afc_conn_p, list_root, copy_file_callback); + + } while(0); + + if (dirname) free(dirname); + if (afc_conn_p) AFCConnectionClose(afc_conn_p); +} + +void upload_file(AMDeviceRef device) { + service_conn_t houseFd = start_house_arrest_service(device); + + afc_file_ref file_ref; + + afc_connection afc_conn; + afc_connection* afc_conn_p = &afc_conn; + AFCConnectionOpen(houseFd, 0, &afc_conn_p); + + // read_dir(houseFd, NULL, "/", NULL); + + if (!target_filename) + { + target_filename = get_filename_from_path(upload_pathname); + } + + size_t file_size; + void* file_content = read_file_to_memory(upload_pathname, &file_size); + + if (!file_content) + { + on_error(@"Could not open file: %@", [NSString stringWithUTF8String:upload_pathname]); + } + + // Make sure the directory was created + { + char *dirpath = strdup(target_filename); + char *c = dirpath, *lastSlash = dirpath; + while(*c) { + if(*c == '/') { + lastSlash = c; + } + c++; + } + *lastSlash = '\0'; + check_error(AFCDirectoryCreate(afc_conn_p, dirpath)); + } + + + int ret = AFCFileRefOpen(afc_conn_p, target_filename, 3, &file_ref); + if (ret == 0x000a) { + on_error(@"Cannot write to %@. Permission error.", [NSString stringWithUTF8String:target_filename]); + } + if (ret == 0x0009) { + on_error(@"Target %@ is a directory.", [NSString stringWithUTF8String:target_filename]); + } + assert(ret == 0); + assert(AFCFileRefWrite(afc_conn_p, file_ref, file_content, file_size) == 0); + assert(AFCFileRefClose(afc_conn_p, file_ref) == 0); + assert(AFCConnectionClose(afc_conn_p) == 0); + + free(file_content); +} + +void make_directory(AMDeviceRef device) { + service_conn_t houseFd = start_house_arrest_service(device); + + afc_connection afc_conn; + afc_connection* afc_conn_p = &afc_conn; + AFCConnectionOpen(houseFd, 0, &afc_conn_p); + + assert(AFCDirectoryCreate(afc_conn_p, target_filename) == 0); + assert(AFCConnectionClose(afc_conn_p) == 0); +} + +void remove_path(AMDeviceRef device) { + service_conn_t houseFd = start_house_arrest_service(device); + + afc_connection afc_conn; + afc_connection* afc_conn_p = &afc_conn; + AFCConnectionOpen(houseFd, 0, &afc_conn_p); + + + assert(AFCRemovePath(afc_conn_p, target_filename) == 0); + assert(AFCConnectionClose(afc_conn_p) == 0); +} + +void uninstall_app(AMDeviceRef device) { + CFRetain(device); // don't know if this is necessary? + + NSLogOut(@"------ Uninstall phase ------"); + + //Do we already have the bundle_id passed in via the command line? if so, use it. + CFStringRef cf_uninstall_bundle_id = NULL; + if (bundle_id != NULL) + { + cf_uninstall_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingUTF8); + } else { + on_error(@"Error: you need to pass in the bundle id, (i.e. --bundle_id com.my.app)"); + } + + if (cf_uninstall_bundle_id == NULL) { + on_error(@"Error: Unable to get bundle id from user command or package %@.\nUninstall failed.", [NSString stringWithUTF8String:app_path]); + } else { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); + if (code == 0) { + NSLogOut(@"[ OK ] Uninstalled package with bundle id %@", cf_uninstall_bundle_id); + } else { + on_error(@"[ ERROR ] Could not uninstall package with bundle id %@", cf_uninstall_bundle_id); + } + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + } +} + +void handle_device(AMDeviceRef device) { + NSLogVerbose(@"Already found device? %d", found_device); + + CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device), + device_full_name = get_device_full_name(device), + device_interface_name = get_device_interface_name(device); + + if (detect_only) { + + NSLogOut(@"[....] Found %@ connected through %@.", device_full_name, device_interface_name); + found_device = true; + return; + } + if (device_id != NULL) { + CFStringRef deviceCFSTR = CFStringCreateWithCString(NULL, device_id, kCFStringEncodingUTF8); + if (CFStringCompare(deviceCFSTR, found_device_id, kCFCompareCaseInsensitive) == kCFCompareEqualTo) { + found_device = true; + CFRelease(deviceCFSTR); + } else { + + NSLogOut(@"Skipping %@.", device_full_name); + return; + } + } else { + device_id = MYCFStringCopyUTF8String(found_device_id); + found_device = true; + } + + + NSLogOut(@"[....] Using %@.", device_full_name); + + if (command_only) { + if (strcmp("list", command) == 0) { + list_files(device); + } else if (strcmp("upload", command) == 0) { + upload_file(device); + } else if (strcmp("download", command) == 0) { + download_tree(device); + } else if (strcmp("mkdir", command) == 0) { + make_directory(device); + } else if (strcmp("rm", command) == 0) { + remove_path(device); + } else if (strcmp("exists", command) == 0) { + exit(app_exists(device)); + } else if (strcmp("uninstall_only", command) == 0) { + uninstall_app(device); + } else if (strcmp("list_bundle_id", command) == 0) { + list_bundle_id(device); + } + exit(0); + } + + + CFRetain(device); // don't know if this is necessary? + + CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingUTF8); + CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); + CFURLRef url = CFURLCopyAbsoluteURL(relative_url); + + CFRelease(relative_url); + + if (uninstall) { + NSLogOut(@"------ Uninstall phase ------"); + + //Do we already have the bundle_id passed in via the command line? if so, use it. + CFStringRef cf_uninstall_bundle_id = NULL; + if (bundle_id != NULL) + { + cf_uninstall_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingUTF8); + } else { + cf_uninstall_bundle_id = get_bundle_id(url); + } + + if (cf_uninstall_bundle_id == NULL) { + on_error(@"Error: Unable to get bundle id from user command or package %@.\nUninstall failed.", [NSString stringWithUTF8String:app_path]); + } else { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); + if (code == 0) { + NSLogOut(@"[ OK ] Uninstalled package with bundle id %@", cf_uninstall_bundle_id); + } else { + on_error(@"[ ERROR ] Could not uninstall package with bundle id %@", cf_uninstall_bundle_id); + } + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + } + } + + if(install) { + + NSLogOut(@"------ Install phase ------"); + NSLogOut(@"[ 0%%] Found %@ connected through %@, beginning install", device_full_name, device_interface_name); + + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + + // NOTE: the secure version doesn't seem to require us to start the AFC service + service_conn_t afcFd; + check_error(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd)); + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + + CFStringRef keys[] = { CFSTR("PackageType") }; + CFStringRef values[] = { CFSTR("Developer") }; + CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + //assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0); + check_error(AMDeviceSecureTransferPath(0, device, url, options, transfer_callback, 0)); + + close(afcFd); + + + + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); + + // // NOTE: the secure version doesn't seem to require us to start the installation_proxy service + // // Although I can't find it right now, I in some code that the first param of AMDeviceSecureInstallApplication was a "dontStartInstallProxy" + // // implying this is done for us by iOS already + + //service_conn_t installFd; + //assert(AMDeviceSecureStartService(device, CFSTR("com.apple.mobile.installation_proxy"), NULL, &installFd) == 0); + + //mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL); + check_error(AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0)); + + // close(installFd); + + check_error(AMDeviceStopSession(device)); + check_error(AMDeviceDisconnect(device)); + + CFRelease(path); + CFRelease(options); + + NSLogOut(@"[100%%] Installed package %@", [NSString stringWithUTF8String:app_path]); + } + + if (!debug) + exit(0); // no debug phase + + if (justlaunch) + launch_debugger_and_exit(device, url); + else + launch_debugger(device, url); +} + +void device_callback(struct am_device_notification_callback_info *info, void *arg) { + switch (info->msg) { + case ADNCI_MSG_CONNECTED: + if(device_id != NULL || !debug || AMDeviceGetInterfaceType(info->dev) != 2) { + if (no_wifi && AMDeviceGetInterfaceType(info->dev) == 2) + { + NSLogVerbose(@"Skipping wifi device (type: %d)", AMDeviceGetInterfaceType(info->dev)); + } + else + { + NSLogVerbose(@"Handling device type: %d", AMDeviceGetInterfaceType(info->dev)); + handle_device(info->dev); + } + } else if(best_device_match == NULL) { + NSLogVerbose(@"Best device match: %d", AMDeviceGetInterfaceType(info->dev)); + best_device_match = info->dev; + CFRetain(best_device_match); + } + default: + break; + } +} + +void timeout_callback(CFRunLoopTimerRef timer, void *info) { + if ((!found_device) && (!detect_only)) { + if(best_device_match != NULL) { + NSLogVerbose(@"Handling best device match."); + handle_device(best_device_match); + + CFRelease(best_device_match); + best_device_match = NULL; + } + + if(!found_device) + on_error(@"Timed out waiting for device."); + } + else + { + if (!debug) { + NSLogOut(@"[....] No more devices found."); + } + + if (detect_only && !found_device) { + exit(exitcode_error); + return; + } else { + int mypid = getpid(); + if ((parent != 0) && (parent == mypid) && (child != 0)) + { + NSLogVerbose(@"Timeout. Killing child (%d) tree.", child); + kill_ptree(child, SIGHUP); + } + } + exit(0); + } +} + +void usage(const char* app) { + NSLog( + @"Usage: %@ [OPTION]...\n" + @" -d, --debug launch the app in lldb after installation\n" + @" -i, --id the id of the device to connect to\n" + @" -c, --detect only detect if the device is connected\n" + @" -b, --bundle the path to the app bundle to be installed\n" + @" -a, --args command line arguments to pass to the app when launching it\n" + @" -t, --timeout number of seconds to wait for a device to be connected\n" + @" -u, --unbuffered don't buffer stdout\n" + @" -n, --nostart do not start the app when debugging\n" + @" -I, --noninteractive start in non interactive mode (quit when app crashes or exits)\n" + @" -L, --justlaunch just launch the app and exit lldb\n" + @" -v, --verbose enable verbose output\n" + @" -m, --noinstall directly start debugging without app install (-d not required)\n" + @" -p, --port port used for device, default: dynamic\n" + @" -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) \n" + @" -9, --uninstall_only uninstall the app ONLY. Use only with -1 \n" + @" -1, --bundle_id specify bundle id for list and upload\n" + @" -l, --list list files\n" + @" -o, --upload upload file\n" + @" -w, --download download app tree\n" + @" -2, --to use together with up/download file/tree. specify target\n" + @" -D, --mkdir make directory on device\n" + @" -R, --rm remove file or directory on device (directories must be empty)\n" + @" -V, --version print the executable version \n" + @" -e, --exists check if the app with given bundle_id is installed or not \n" + @" -B, --list_bundle_id list bundle_id \n" + @" -W, --no-wifi ignore wifi devices\n", + [NSString stringWithUTF8String:app]); +} + +void show_version() { + NSLogOut(@"%@", @ +#include "version.h" + ); +} + +int main(int argc, char *argv[]) { + + // create a UUID for tmp purposes + CFUUIDRef uuid = CFUUIDCreate(NULL); + CFStringRef str = CFUUIDCreateString(NULL, uuid); + CFRelease(uuid); + tmpUUID = [(NSString*)str autorelease]; + + static struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "id", required_argument, NULL, 'i' }, + { "bundle", required_argument, NULL, 'b' }, + { "args", required_argument, NULL, 'a' }, + { "verbose", no_argument, NULL, 'v' }, + { "timeout", required_argument, NULL, 't' }, + { "unbuffered", no_argument, NULL, 'u' }, + { "nostart", no_argument, NULL, 'n' }, + { "noninteractive", no_argument, NULL, 'I' }, + { "justlaunch", no_argument, NULL, 'L' }, + { "detect", no_argument, NULL, 'c' }, + { "version", no_argument, NULL, 'V' }, + { "noinstall", no_argument, NULL, 'm' }, + { "port", required_argument, NULL, 'p' }, + { "uninstall", no_argument, NULL, 'r' }, + { "uninstall_only", no_argument, NULL, '9'}, + { "list", optional_argument, NULL, 'l' }, + { "bundle_id", required_argument, NULL, '1'}, + { "upload", required_argument, NULL, 'o'}, + { "download", optional_argument, NULL, 'w'}, + { "to", required_argument, NULL, '2'}, + { "mkdir", required_argument, NULL, 'D'}, + { "rm", required_argument, NULL, 'R'}, + { "exists", no_argument, NULL, 'e'}, + { "list_bundle_id", no_argument, NULL, 'B'}, + { "no-wifi", no_argument, NULL, 'W'}, + { NULL, 0, NULL, 0 }, + }; + char ch; + + while ((ch = getopt_long(argc, argv, "VmcdvunrILeD:R:i:b:a:t:g:x:p:1:2:o:l::w::9::B::W", longopts, NULL)) != -1) + { + switch (ch) { + case 'm': + install = 0; + debug = 1; + break; + case 'd': + debug = 1; + break; + case 'i': + device_id = optarg; + break; + case 'b': + app_path = optarg; + break; + case 'a': + args = optarg; + break; + case 'v': + verbose = 1; + break; + case 't': + _timeout = atoi(optarg); + break; + case 'u': + unbuffered = 1; + break; + case 'n': + nostart = 1; + break; + case 'I': + interactive = false; + debug = 1; + break; + case 'L': + interactive = false; + justlaunch = true; + debug = 1; + break; + case 'c': + detect_only = true; + debug = 1; + break; + case 'V': + show_version(); + return 0; + case 'p': + port = atoi(optarg); + break; + case 'r': + uninstall = 1; + break; + case '9': + command_only = true; + command = "uninstall_only"; + break; + case '1': + bundle_id = optarg; + break; + case '2': + target_filename = optarg; + break; + case 'o': + command_only = true; + upload_pathname = optarg; + command = "upload"; + break; + case 'l': + command_only = true; + command = "list"; + list_root = optarg; + break; + case 'w': + command_only = true; + command = "download"; + list_root = optarg; + break; + case 'D': + command_only = true; + target_filename = optarg; + command = "mkdir"; + break; + case 'R': + command_only = true; + target_filename = optarg; + command = "rm"; + break; + case 'e': + command_only = true; + command = "exists"; + break; + case 'B': + command_only = true; + command = "list_bundle_id"; + break; + case 'W': + no_wifi = true; + break; + default: + usage(argv[0]); + return exitcode_error; + } + } + + if (!app_path && !detect_only && !command_only) { + usage(argv[0]); + on_error(@"One of -[b|c|o|l|w|D|R|e|9] is required to proceed!"); + } + + if (unbuffered) { + setbuf(stdout, NULL); + setbuf(stderr, NULL); + } + + if (detect_only && _timeout == 0) { + _timeout = 5; + } + + if (app_path) { + if (access(app_path, F_OK) != 0) { + on_sys_error(@"Can't access app path '%@'", [NSString stringWithUTF8String:app_path]); + } + } + + AMDSetLogLevel(5); // otherwise syslog gets flooded with crap + if (_timeout > 0) + { + CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + _timeout, 0, 0, 0, timeout_callback, NULL); + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); + NSLogOut(@"[....] Waiting up to %d seconds for iOS device to be connected", _timeout); + } + else + { + NSLogOut(@"[....] Waiting for iOS device to be connected"); + } + + AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); + CFRunLoopRun(); +} diff --git a/src/ios-deploy/lldb.py.h b/src/ios-deploy/lldb.py.h new file mode 100644 index 00000000..ef800838 --- /dev/null +++ b/src/ios-deploy/lldb.py.h @@ -0,0 +1,86 @@ +"# AUTO-GENERATED - DO NOT MODIFY\n" +"import lldb\n" +"import os\n" +"import sys\n" +"import shlex\n" +"\n" +"def connect_command(debugger, command, result, internal_dict):\n" +" # These two are passed in by the script which loads us\n" +" connect_url = internal_dict['fruitstrap_connect_url']\n" +" error = lldb.SBError()\n" +"\n" +" process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error)\n" +"\n" +" # Wait for connection to succeed\n" +" listener = lldb.target.GetDebugger().GetListener()\n" +" listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged)\n" +" events = []\n" +" state = (process.GetState() or lldb.eStateInvalid)\n" +" while state != lldb.eStateConnected:\n" +" event = lldb.SBEvent()\n" +" if listener.WaitForEvent(1, event):\n" +" state = process.GetStateFromEvent(event)\n" +" events.append(event)\n" +" else:\n" +" state = lldb.eStateInvalid\n" +"\n" +" # Add events back to queue, otherwise lldb freezes\n" +" for event in events:\n" +" listener.AddEvent(event)\n" +"\n" +"def run_command(debugger, command, result, internal_dict):\n" +" device_app = internal_dict['fruitstrap_device_app']\n" +" args = command.split('--',1)\n" +" error = lldb.SBError()\n" +" lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n" +" lldb.target.Launch(lldb.SBLaunchInfo(shlex.split(args[1] and args[1] or '{args}')), error)\n" +" lockedstr = ': Locked'\n" +" if lockedstr in str(error):\n" +" print('\\nDevice Locked\\n')\n" +" os._exit(254)\n" +" else:\n" +" print(str(error))\n" +"\n" +"def safequit_command(debugger, command, result, internal_dict):\n" +" process = lldb.target.process\n" +" listener = debugger.GetListener()\n" +" listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n" +" event = lldb.SBEvent()\n" +" while True:\n" +" if listener.WaitForEvent(1, event) and lldb.SBProcess.EventIsProcessEvent(event):\n" +" state = lldb.SBProcess.GetStateFromEvent(event)\n" +" else:\n" +" state = process.GetState()\n" +"\n" +" if state == lldb.eStateRunning:\n" +" process.Detach()\n" +" os._exit(0)\n" +" elif state > lldb.eStateRunning:\n" +" os._exit(state)\n" +"\n" +"def autoexit_command(debugger, command, result, internal_dict):\n" +" process = lldb.target.process\n" +" listener = debugger.GetListener()\n" +" listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n" +" event = lldb.SBEvent()\n" +" while True:\n" +" if listener.WaitForEvent(1, event) and lldb.SBProcess.EventIsProcessEvent(event):\n" +" state = lldb.SBProcess.GetStateFromEvent(event)\n" +" else:\n" +" state = process.GetState()\n" +"\n" +" if state == lldb.eStateExited:\n" +" os._exit(process.GetExitStatus())\n" +" elif state == lldb.eStateStopped:\n" +" debugger.HandleCommand('bt')\n" +" os._exit({exitcode_app_crash})\n" +"\n" +" stdout = process.GetSTDOUT(1024)\n" +" while stdout:\n" +" sys.stdout.write(stdout)\n" +" stdout = process.GetSTDOUT(1024)\n" +"\n" +" stderr = process.GetSTDERR(1024)\n" +" while stderr:\n" +" sys.stdout.write(stderr)\n" +" stderr = process.GetSTDERR(1024)\n" diff --git a/src/ios-deploy/version.h b/src/ios-deploy/version.h new file mode 100644 index 00000000..27c13522 --- /dev/null +++ b/src/ios-deploy/version.h @@ -0,0 +1 @@ +"2.0.0" \ No newline at end of file diff --git a/src/scripts/check_reqs.js b/src/scripts/check_reqs.js new file mode 100755 index 00000000..b6b5ee58 --- /dev/null +++ b/src/scripts/check_reqs.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node + +var util = require('util'); +var os = require('os'); +var child_process = require('child_process'); + +var XCODEBUILD_MIN_VERSION = '6.0'; +var XCODEBUILD_NOT_FOUND_MESSAGE = util.format('Please install Xcode version %s or greater from the Mac App Store.', XCODEBUILD_MIN_VERSION); +var TOOL = 'xcodebuild'; + +var xcode_version = child_process.spawn(TOOL, ['-version']), + version_string = ''; + +xcode_version.stdout.on('data', function (data) { + version_string += data; +}); + +xcode_version.stderr.on('data', function (data) { + console.log('stderr: ' + data); +}); + +xcode_version.on('error', function (err) { + console.log(util.format('Tool %s was not found. %s', TOOL, XCODEBUILD_NOT_FOUND_MESSAGE)); +}); + +xcode_version.on('close', function (code) { + if (code === 0) { + var arr = version_string.match(/^Xcode (\d+\.\d+)/); + var ver = arr[1]; + + if (os.release() >= '15.0.0' && ver < '7.0') { + console.log(util.format('You need at least Xcode 7.0 when you are on OS X 10.11 El Capitan (you have version %s)', ver)); + process.exit(1); + } + + if (ver < XCODEBUILD_MIN_VERSION) { + console.log(util.format('%s : %s. (you have version %s)', TOOL, XCODEBUILD_NOT_FOUND_MESSAGE, ver)); + } + } + process.exit(code); +}); + + + + diff --git a/src/scripts/lldb.py b/src/scripts/lldb.py new file mode 100644 index 00000000..ec02219e --- /dev/null +++ b/src/scripts/lldb.py @@ -0,0 +1,85 @@ +import lldb +import os +import sys +import shlex + +def connect_command(debugger, command, result, internal_dict): + # These two are passed in by the script which loads us + connect_url = internal_dict['fruitstrap_connect_url'] + error = lldb.SBError() + + process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error) + + # Wait for connection to succeed + listener = lldb.target.GetDebugger().GetListener() + listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged) + events = [] + state = (process.GetState() or lldb.eStateInvalid) + while state != lldb.eStateConnected: + event = lldb.SBEvent() + if listener.WaitForEvent(1, event): + state = process.GetStateFromEvent(event) + events.append(event) + else: + state = lldb.eStateInvalid + + # Add events back to queue, otherwise lldb freezes + for event in events: + listener.AddEvent(event) + +def run_command(debugger, command, result, internal_dict): + device_app = internal_dict['fruitstrap_device_app'] + args = command.split('--',1) + error = lldb.SBError() + lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app)) + lldb.target.Launch(lldb.SBLaunchInfo(shlex.split(args[1] and args[1] or '{args}')), error) + lockedstr = ': Locked' + if lockedstr in str(error): + print('\\nDevice Locked\\n') + os._exit(254) + else: + print(str(error)) + +def safequit_command(debugger, command, result, internal_dict): + process = lldb.target.process + listener = debugger.GetListener() + listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR) + event = lldb.SBEvent() + while True: + if listener.WaitForEvent(1, event) and lldb.SBProcess.EventIsProcessEvent(event): + state = lldb.SBProcess.GetStateFromEvent(event) + else: + state = process.GetState() + + if state == lldb.eStateRunning: + process.Detach() + os._exit(0) + elif state > lldb.eStateRunning: + os._exit(state) + +def autoexit_command(debugger, command, result, internal_dict): + process = lldb.target.process + listener = debugger.GetListener() + listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR) + event = lldb.SBEvent() + while True: + if listener.WaitForEvent(1, event) and lldb.SBProcess.EventIsProcessEvent(event): + state = lldb.SBProcess.GetStateFromEvent(event) + else: + state = process.GetState() + + if state == lldb.eStateExited: + os._exit(process.GetExitStatus()) + elif state == lldb.eStateStopped: + debugger.HandleCommand('bt') + os._exit({exitcode_app_crash}) + + stdout = process.GetSTDOUT(1024) + while stdout: + sys.stdout.write(stdout) + stdout = process.GetSTDOUT(1024) + + stderr = process.GetSTDERR(1024) + while stderr: + sys.stdout.write(stderr) + stderr = process.GetSTDERR(1024)