diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3b4bd41d..40a1f05a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,26 +8,8 @@ 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 +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. diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 7b3855aa..160d7d08 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ Include the **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 this issue. +Don't forget to check out the [README](https://github.com/phonegap/ios-deploy/blob/master/README.md) before filing this issue. # Expected behavior @@ -23,6 +23,6 @@ Please run the commands below in your Terminal.app and include it in the issue. - [ ] 4. xcode-select --print-path - [ ] 5. gcc --version - [ ] 6. lldb --version - - - +- [ ] **If using NodeJS/npm** + - [ ] 7. npm -v + - [ ] 8. node -v diff --git a/README.md b/README.md index 31c1c662..3b480e7c 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,16 @@ Install and debug iOS apps from the command line. Designed to work on un-jailbro ## Requirements -* Mac OS X. Tested on 10.11 El Capitan, 10.12 Sierra, iOS 9.0 and iOS 10.0 -* You need to have a valid iOS Development certificate installed. -* Xcode 7 or greater should be installed (**NOT** just Command Line Tools!) +* macOS +* You need to have a valid iOS Development certificate installed +* Xcode (**NOT** just Command Line Tools!) + +#### Tested Configurations +The ios-deploy binary in Homebrew should work on macOS 10.0+ with Xcode7+. It has been most recently tested with the following configurations: + - macOS 10.14 Mojave, 10.15 Catalina and preliminary testing on 11.0b BigSur + - iOS 13.0 and preliminary testing on iOS 14.0b + - Xcode 11.3, 11.6 and preliminary testing on Xcode 12 betas + - x86 and preliminary testing on Arm64e based Apple Macintosh Computers ## Roadmap @@ -43,36 +50,43 @@ python -m py_compile src/scripts/*.py && xcodebuild -target ios-deploy-lib && xc ## Usage 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 - -s, --envs environment variables, space separated key-value pairs, 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) - -A, --app_deltas incremental install. must specify a directory to store app deltas to determine what needs to be installed - -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 all app files or the specified directory - -o, --upload upload file - -w, --download[=] download app tree or the specified file/directory - -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 - -O, --output write stdout and stderr to this file - --detect_deadlocks start printing backtraces for all threads periodically after specific amount of seconds + -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 + -s, --envs environment variables, space separated key-value pairs, 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 + -N, --nolldb start debugserver only. do not run lldb. Can not be used with args or envs options + -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) + -A, --app_deltas incremental install. must specify a directory to store app deltas to determine what needs to be installed + -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 all app files or the specified directory + -o, --upload upload file + -w, --download[=] download app tree or the specified file/directory + -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) + -X, --rmtree remove directory and all contained files recursively on device + -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 + -C, --get_battery_level get battery current capacity + -O, --output write stdout to this file + -E, --error_output write stderr to this file + --detect_deadlocks start printing backtraces for all threads periodically after specific amount of seconds + -f, --file_system specify file system for mkdir / list / upload / download / rm + -F, --non-recursively specify non-recursively walk directory + -j, --json format output as JSON ## Examples @@ -122,6 +136,21 @@ The commands below assume that you have an app called `my.app` with bundle id `b // list all bundle ids of all apps on your device ios-deploy --list_bundle_id + + // list the files in cameral roll, a.k.a /DCIM + ios-deploy -f -l/DCIM + + // download the file in /DCIM + ios-deploy -f -w/DCIM/100APPLE/IMG_001.jpg + + // remove the file /DCIM + ios-deploy -f -R /DCIM/100APPLE/IMG_001.jpg + + // make directoly in /DCIM + ios-deploy -f -D/DCIM/test + + // upload file to /DCIM + ios-deploy -f -o/Users/ryan/Downloads/test.png -2/DCIM/test.png ## Demo diff --git a/package.json b/package.json index e59842bf..2b2b47eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ios-deploy", - "version": "1.10.0", + "version": "1.11.2", "os": [ "darwin" ], diff --git a/src/ios-deploy/MobileDevice.h b/src/ios-deploy/MobileDevice.h index 2646bcd9..637890ba 100644 --- a/src/ios-deploy/MobileDevice.h +++ b/src/ios-deploy/MobileDevice.h @@ -65,7 +65,14 @@ typedef unsigned int mach_error_t; typedef unsigned int afc_error_t; typedef unsigned int usbmux_error_t; -typedef unsigned int service_conn_t; + +typedef struct { + char unknown[0x10]; + int sockfd; + void * sslContext; + // ?? +} service_conn_t; + typedef service_conn_t * ServiceConnRef; struct am_recovery_device; diff --git a/src/ios-deploy/device_db.h b/src/ios-deploy/device_db.h index 8e8c4aa4..4f679300 100644 --- a/src/ios-deploy/device_db.h +++ b/src/ios-deploy/device_db.h @@ -146,6 +146,7 @@ device_desc device_db[] = { ADD_DEVICE("N104AP", "iPhone 11", "iphoneos", "arm64e"), ADD_DEVICE("D421AP", "iPhone 11 Pro", "iphoneos", "arm64e"), ADD_DEVICE("D431AP", "iPhone 11 Pro Max", "iphoneos", "arm64e"), + ADD_DEVICE("D79AP", "iPhone SE 2G", "iphoneos", "arm64e"), // Apple TV diff --git a/src/ios-deploy/ios-deploy.m b/src/ios-deploy/ios-deploy.m index e19c420b..175478bc 100644 --- a/src/ios-deploy/ios-deploy.m +++ b/src/ios-deploy/ios-deploy.m @@ -12,6 +12,8 @@ #include #include #include +#include + #include #include @@ -59,6 +61,8 @@ autoexit\n\ "; +NSMutableString * custom_commands = nil; + /* * 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 @@ -75,6 +79,9 @@ mach_error_t AMDeviceSecureStartService(AMDeviceRef device, CFStringRef service_name, unsigned int *unknown, ServiceConnRef * handle); mach_error_t AMDeviceCreateHouseArrestService(AMDeviceRef device, CFStringRef identifier, CFDictionaryRef options, AFCConnectionRef * handle); CFSocketNativeHandle AMDServiceConnectionGetSocket(ServiceConnRef con); +void AMDServiceConnectionInvalidate(ServiceConnRef con); + +bool AMDeviceIsAtLeastVersionOnPlatform(AMDeviceRef device, CFDictionaryRef vers); 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 AMDeviceSecureInstallApplicationBundle(AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); @@ -82,6 +89,9 @@ mach_error_t AMDeviceLookupApplications(AMDeviceRef device, CFDictionaryRef options, CFDictionaryRef *result); int AMDeviceGetInterfaceType(AMDeviceRef device); +int AMDServiceConnectionSend(ServiceConnRef con, const void * data, size_t size); +int AMDServiceConnectionReceive(ServiceConnRef con, void * data, size_t size); + bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, debugserver_only = false, detect_only = false, install = true, uninstall = false, no_wifi = false; bool command_only = false; char *command = NULL; @@ -98,13 +108,14 @@ char *args = NULL; char *envs = NULL; char *list_root = NULL; +const char * custom_script_path = NULL; int _timeout = 0; int _detectDeadlockTimeout = 0; bool _json_output = false; NSMutableArray *_file_meta_info = nil; int port = 0; // 0 means "dynamically assigned" CFStringRef last_path = NULL; -service_conn_t gdbfd; +ServiceConnRef dbgServiceConnection = NULL; pid_t parent = 0; // PID of child process running lldb pid_t child = 0; @@ -135,6 +146,22 @@ } \ } while (false); + +void disable_ssl(ServiceConnRef con) +{ + // MobileDevice links with SSL, so function will be available; + typedef void (*SSL_free_t)(void*); + static SSL_free_t SSL_free = NULL; + if (SSL_free == NULL) + { + SSL_free = (SSL_free_t)dlsym(RTLD_DEFAULT, "SSL_free"); + } + + SSL_free(con->sslContext); + con->sslContext = NULL; +} + + void on_error(NSString* format, ...) { va_list valist; @@ -596,11 +623,14 @@ void mount_developer_image(AMDeviceRef device) { NSLogVerbose(@"Developer disk image: %@", image_path); FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); - void *sig_buf = malloc(128); - size_t bytes_read = fread(sig_buf, 1, 128, sig); - assert( bytes_read == 128); + size_t buf_size = 128; + void *sig_buf = malloc(buf_size); + size_t bytes_read = fread(sig_buf, 1, buf_size, sig); + if (bytes_read != buf_size) { + on_sys_error(@"fread read %d bytes but expected %d bytes.", bytes_read, buf_size); + } fclose(sig); - CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); + CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, buf_size, NULL); CFRelease(sig_path); CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; @@ -634,6 +664,9 @@ void mount_developer_image(AMDeviceRef device) { } mach_error_t transfer_callback(CFDictionaryRef dict, int arg) { + if (CFDictionaryGetValue(dict, CFSTR("Error"))) { + return 0; + } int percent; CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); @@ -661,6 +694,9 @@ mach_error_t transfer_callback(CFDictionaryRef dict, int arg) { } mach_error_t install_callback(CFDictionaryRef dict, int arg) { + if (CFDictionaryGetValue(dict, CFSTR("Error"))) { + return 0; + } int percent; CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); @@ -681,21 +717,24 @@ mach_error_t install_callback(CFDictionaryRef dict, int arg) { // use this callback to determine which step is occuring and call the proper // callback. mach_error_t incremental_install_callback(CFDictionaryRef dict, int arg) { - CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); - if (CFEqual(status, CFSTR("TransferringPackage"))) { - int percent; - CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); - int overall_percent = (percent / 2); - NSLogOut(@"[%3d%%] %@", overall_percent, status); - NSLogJSON(@{@"Event": @"TransferringPackage", - @"OverallPercent": @(overall_percent), - }); - return 0; - } else if (CFEqual(status, CFSTR("CopyingFile"))) { - return transfer_callback(dict, arg); - } else { - return install_callback(dict, arg); - } + if (CFDictionaryGetValue(dict, CFSTR("Error"))) { + return 0; + } + CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); + if (CFEqual(status, CFSTR("TransferringPackage"))) { + int percent; + CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); + int overall_percent = (percent / 2); + NSLogOut(@"[%3d%%] %@", overall_percent, status); + NSLogJSON(@{@"Event": @"TransferringPackage", + @"OverallPercent": @(overall_percent), + }); + return 0; + } else if (CFEqual(status, CFSTR("CopyingFile"))) { + return transfer_callback(dict, arg); + } else { + return install_callback(dict, arg); + } } CFURLRef copy_device_app_url(AMDeviceRef device, CFStringRef identifier) { @@ -846,7 +885,7 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { range.length = CFStringGetLength(cmds); if (output_path) { - CFStringRef output_path_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), output_path); + CFStringRef output_path_str = CFStringCreateWithCString(NULL, output_path, kCFStringEncodingUTF8); CFStringFindAndReplace(cmds, CFSTR("{output_path}"), output_path_str, range, 0); CFRelease(output_path_str); } else { @@ -854,7 +893,7 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { } range.length = CFStringGetLength(cmds); if (error_path) { - CFStringRef error_path_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), error_path); + CFStringRef error_path_str = CFStringCreateWithCString(NULL, error_path, kCFStringEncodingUTF8); CFStringFindAndReplace(cmds, CFSTR("{error_path}"), error_path_str, range, 0); CFRelease(error_path_str); } else { @@ -925,6 +964,11 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { else extra_cmds = lldb_prep_interactive_cmds; fwrite(extra_cmds, strlen(extra_cmds), 1, out); + if (custom_commands != nil) + { + const char * cmds = [custom_commands UTF8String]; + fwrite(cmds, 1, strlen(cmds), out); + } fclose(out); @@ -932,6 +976,24 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { CFDataRef pmodule_data = CFStringCreateExternalRepresentation(NULL, pmodule, kCFStringEncodingUTF8, 0); fwrite(CFDataGetBytePtr(pmodule_data), CFDataGetLength(pmodule_data), 1, out); CFRelease(pmodule_data); + + if (custom_script_path) + { + FILE * fh = fopen(custom_script_path, "r"); + if (fh == NULL) + { + on_error(@"Failed to open %s", custom_script_path); + } + fwrite("\n", 1, 1, out); + char buffer[0x1000]; + size_t bytesRead; + while ((bytesRead = fread(buffer, 1, sizeof(buffer), fh)) > 0) + { + fwrite(buffer, 1, bytesRead, out); + } + fclose(fh); + } + fclose(out); CFRelease(cmds); @@ -947,13 +1009,24 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) { void server_callback (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { - if (CFDataGetLength (data) == 0) { + char buffer[0x1000]; + int bytesRead = AMDServiceConnectionReceive(dbgServiceConnection, buffer, sizeof(buffer)); + if (bytesRead == 0) + { // close the socket on which we've got end-of-file, the server_socket. CFSocketInvalidate(s); CFRelease(s); return; } - write(CFSocketGetNative(lldb_socket), CFDataGetBytePtr(data), CFDataGetLength(data)); + write(CFSocketGetNative (lldb_socket), buffer, bytesRead); + while (bytesRead == sizeof(buffer)) + { + bytesRead = AMDServiceConnectionReceive(dbgServiceConnection, buffer, sizeof(buffer)); + if (bytesRead > 0) + { + write(CFSocketGetNative (lldb_socket), buffer, bytesRead); + } + } } void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) @@ -966,7 +1039,8 @@ void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef a CFRelease(s); return; } - write (gdbfd, CFDataGetBytePtr (data), CFDataGetLength (data)); + int sent = AMDServiceConnectionSend(dbgServiceConnection, CFDataGetBytePtr(data), CFDataGetLength (data)); + assert (CFDataGetLength (data) == sent); } void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { @@ -978,7 +1052,9 @@ void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataR 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); + if (res == -1) { + on_sys_error(@"Setting socket option failed."); + } if (lldb_socket_runloop) { CFRelease(lldb_socket_runloop); } @@ -989,18 +1065,65 @@ void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataR CFRelease(s); } +void connect_and_start_session(AMDeviceRef device) { + AMDeviceConnect(device); + assert(AMDeviceIsPaired(device)); + check_error(AMDeviceValidatePairing(device)); + check_error(AMDeviceStartSession(device)); +} + void start_remote_debug_server(AMDeviceRef device) { - ServiceConnRef con; + dbgServiceConnection = NULL; + CFStringRef serviceName = CFSTR("com.apple.debugserver"); + CFStringRef keys[] = { CFSTR("MinIPhoneVersion"), CFSTR("MinAppleTVVersion") }; + CFStringRef values[] = { CFSTR("14.0"), CFSTR("14.0")}; + CFDictionaryRef version = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + bool useSecureProxy = AMDeviceIsAtLeastVersionOnPlatform(device, version); + if (useSecureProxy) + { + serviceName = CFSTR("com.apple.debugserver.DVTSecureSocketProxy"); + } + + int start_err = AMDeviceSecureStartService(device, serviceName, NULL, &dbgServiceConnection); + if (start_err != 0) + { + // After we mount the image, iOS needs to scan the image to register new services. + // If we ask to start the service before it is found by ios, we will get 0xe8000022. + // In other cases, it's been observed, that device may loose connection here (0xe800002d). + // Luckly, we can just restart the connection and continue. + // In other cases we just error out. + NSLogOut(@"Failed to start debugserver: %x %s", start_err, get_error_message(start_err)); + switch(start_err) + { + case 0xe8000022: + NSLogOut(@"Waiting for the device to scan mounted image"); + sleep(1); + break; + case 0x800002d: + NSLogOut(@"Reconnecting to device"); + // We dont call AMDeviceStopSession as we cannot send any messages anymore + check_error(AMDeviceDisconnect(device)); + connect_and_start_session(device); + break; + default: + check_error(start_err); + } + check_error(AMDeviceSecureStartService(device, serviceName, NULL, &dbgServiceConnection)); + } + assert(dbgServiceConnection != NULL); + + if (!useSecureProxy) + { + disable_ssl(dbgServiceConnection); + } - check_error(AMDeviceSecureStartService(device, CFSTR("com.apple.debugserver"), NULL, &con)); - assert(con != NULL); - gdbfd = AMDServiceConnectionGetSocket(con); /* * 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); + server_socket = CFSocketCreateWithNative (NULL, AMDServiceConnectionGetSocket(dbgServiceConnection), kCFSocketReadCallBack, &server_callback, NULL); if (server_socket_runloop) { CFRelease(server_socket_runloop); } @@ -1027,7 +1150,9 @@ void start_remote_debug_server(AMDeviceRef device) { CFRelease(address_data); socklen_t addrlen = sizeof(addr4); int res = getsockname(CFSocketGetNative(fdvendor),(struct sockaddr *)&addr4,&addrlen); - assert(res == 0); + if (res == -1) { + on_sys_error(@"Getting socket name failed."); + } port = ntohs(addr4.sin_port); if (fdvendor_runloop) { @@ -1112,10 +1237,7 @@ 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)); + connect_and_start_session(device); NSLogOut(@"------ Debug phase ------"); @@ -1414,10 +1536,7 @@ AFCConnectionRef start_afc_service(AMDeviceRef device) { // Used to send files to app-specific sandbox (Documents dir) AFCConnectionRef start_house_arrest_service(AMDeviceRef device) { - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - check_error(AMDeviceValidatePairing(device)); - check_error(AMDeviceStartSession(device)); + connect_and_start_session(device); AFCConnectionRef conn = NULL; @@ -1575,23 +1694,30 @@ void get_battery_level(AMDeviceRef device) void list_bundle_id(AMDeviceRef device) { - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - check_error(AMDeviceValidatePairing(device)); - check_error(AMDeviceStartSession(device)); + connect_and_start_session(device); - NSArray *a = [NSArray arrayWithObjects:@"CFBundleIdentifier", nil]; + NSArray *a = [NSArray arrayWithObjects: + @"CFBundleIdentifier", + @"CFBundleName", + @"CFBundleDisplayName", + @"CFBundleVersion", + @"CFBundleShortVersionString", 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]); + if (_json_output) { + NSLogJSON(@{@"Event": @"ListBundleId", + @"Apps": (NSDictionary *)result}); + } else { + 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)); @@ -1831,10 +1957,7 @@ void uninstall_app(AMDeviceRef device) { 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)); + connect_and_start_session(device); int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); if (code == 0) { @@ -1931,10 +2054,7 @@ void handle_device(AMDeviceRef device) { 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)); + connect_and_start_session(device); int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); if (code == 0) { @@ -1952,15 +2072,11 @@ void handle_device(AMDeviceRef device) { 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)); - CFDictionaryRef options; if (app_deltas == NULL) { // standard install // NOTE: the secure version doesn't seem to require us to start the AFC service ServiceConnRef afcFd; + connect_and_start_session(device); check_error(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd)); check_error(AMDeviceStopSession(device)); check_error(AMDeviceDisconnect(device)); @@ -1969,17 +2085,13 @@ void handle_device(AMDeviceRef device) { CFStringRef values[] = { CFSTR("Developer") }; options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); check_error(AMDeviceSecureTransferPath(0, device, url, options, transfer_callback, 0)); - close(*afcFd); + AMDServiceConnectionInvalidate(afcFd); - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - check_error(AMDeviceValidatePairing(device)); - check_error(AMDeviceStartSession(device)); + connect_and_start_session(device); check_error(AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0)); - } else { // incremental install check_error(AMDeviceStopSession(device)); check_error(AMDeviceDisconnect(device)); - + } else { // incremental install CFStringRef extracted_bundle_id = NULL; CFStringRef extracted_bundle_id_ref = copy_bundle_id(url); if (bundle_id != NULL) { @@ -2022,10 +2134,7 @@ void handle_device(AMDeviceRef device) { CFIndex size = sizeof(keys)/sizeof(CFStringRef); options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, size, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - AMDeviceConnect(device); - assert(AMDeviceIsPaired(device)); - check_error(AMDeviceValidatePairing(device)); - check_error(AMDeviceStartSession(device)); + // Incremental installs should be done without a session started because of timeouts. check_error(AMDeviceSecureInstallApplicationBundle(device, url, options, incremental_install_callback, 0)); CFRelease(extracted_bundle_id); CFRelease(deltas_path); @@ -2035,9 +2144,6 @@ void handle_device(AMDeviceRef device) { app_deltas = NULL; } - check_error(AMDeviceStopSession(device)); - check_error(AMDeviceDisconnect(device)); - CFRelease(options); NSLogOut(@"[100%%] Installed package %@", [NSString stringWithUTF8String:app_path]); @@ -2160,7 +2266,9 @@ void usage(const char* app) { @" --detect_deadlocks start printing backtraces for all threads periodically after specific amount of seconds\n" @" -f, --file_system specify file system for mkdir / list / upload / download / rm\n" @" -F, --non-recursively specify non-recursively walk directory\n" - @" -j, --json format output as JSON\n", + @" -j, --json format output as JSON\n" + @" --custom-script