//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" #define APP_VERSION "1.5.0" #define PREP_CMDS_PATH "/tmp/fruitstrap-lldb-prep-cmds-" #define LLDB_SHELL "lldb -s " PREP_CMDS_PATH /* * 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. */ #define LLDB_FRUITSTRAP_MODULE CFSTR("\ 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\ ") 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; 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; // 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; 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"))) { perror("Error encountered while opening pipe"); exit(exitcode_error); } 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"))) { perror("Error encountered while opening pipe"); exit(exitcode_error); } 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; 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") 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); if (verbose) { char *devName = MYCFStringCopyUTF8String(device_name); printf("Device Name:[%s]\n",devName); CFShow(device_name); printf("\n"); free(devName); char *mdlName = MYCFStringCopyUTF8String(model_name); printf("Model Name:[%s]\n",mdlName); printf("MM: [%s]\n",CFStringGetCStringPtr(model_name, kCFStringEncodingUTF8)); CFShow(model_name); printf("\n"); free(mdlName); } 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"); } } 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"))) { printf("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); } 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); printf("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); return 0; } CFSocketRef server_socket; CFSocketRef lldb_socket; CFWriteStreamRef serverWriteStream = NULL; CFWriteStreamRef lldbWriteStream = NULL; int kill_ptree(pid_t root, int signum); 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, len / sizeof(struct kinfo_proc)); free(kp); return 0; } int app_exists(AMDeviceRef device) { if (bundle_id == NULL) { printf("Bundle id is not specified\n"); return false; } AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII); NSArray *a = [NSArray arrayWithObjects:@"CFBundleIdentifier", nil]; NSDictionary *optionsDict = [NSDictionary dictionaryWithObject:a forKey:@"ReturnAttributes"]; CFDictionaryRef options = (CFDictionaryRef)optionsDict; CFDictionaryRef result = nil; afc_error_t resultStatus = AMDeviceLookupApplications(device, options, &result); assert(resultStatus == 0); CFDictionaryRef app_dict = CFDictionaryGetValue(result, cf_bundle_id); int appExists = (app_dict == NULL) ? -1 : 0; CFRelease(cf_bundle_id); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); if (appExists==0) {printf("the app exists\n");} else {printf("couldn't find app\n");} return appExists; } void uninstall_only(AMDeviceRef device) { CFRetain(device); // don't know if this is necessary? printf("------ Uninstall phase ------\n"); //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 { printf("Error: you need to pass in the bundle id, (i.e. --bundle_id com.my.app)\n"); } if (cf_uninstall_bundle_id == NULL) { printf("Error: Unable to get bundle id from user command or package.\nUninstall failed.\n"); } else { AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); AMDeviceValidatePairing(device); AMDeviceStartSession(device); int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); if (code == 0) { printf("[ OK ] Uninstalled package with bundle id\n"); } else { printf("[ ERROR ] Could not uninstall package with bundle id\n"); } AMDeviceStopSession(device); AMDeviceDisconnect(device); } } void handle_device(AMDeviceRef device) { //if (found_device) // return; // handle one device only CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(device), device_full_name = get_device_full_name(device), device_interface_name = get_device_interface_name(device); if (device_id != NULL) { if(strcmp(device_id, CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())) == 0) { found_device = true; } else { printf("Skipping %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding())); return; } } else { device_id = MYCFStringCopyUTF8String(found_device_id); found_device = true; } printf("[....] Using %s (%s).\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding())); if (command_only) { if (strcmp("exists", command) == 0) { exit(app_exists(device)); } else if (strcmp("uninstall", command) == 0) { uninstall_only(device); exit(0); } else if (strcmp("reinstall", command) == 0) { uninstall_only(device); } else { exit(0); } } CFRetain(device); // don't know if this is necessary? CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); CFURLRef url = CFURLCopyAbsoluteURL(relative_url); CFRelease(relative_url); //never used, but it has to be there because install fails otherwise.... if (uninstall) { CFRetain(device); // don't know if this is necessary? printf("------ Uninstall phase ------\n"); //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 { printf("Error: you need to pass in the bundle id, (i.e. --bundle_id com.my.app)\n"); } if (cf_uninstall_bundle_id == NULL) { printf("Error: Unable to get bundle id from user command or package.\nUninstall failed.\n"); } else { AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); AMDeviceValidatePairing(device); AMDeviceStartSession(device); int code = AMDeviceSecureUninstallApplication(0, device, cf_uninstall_bundle_id, 0, NULL, 0); if (code == 0) { printf("[ OK ] Uninstalled package with bundle id\n"); } else { printf("[ ERROR ] Could not uninstall package with bundle id\n"); } AMDeviceStopSession(device); AMDeviceDisconnect(device); } } if(install) { printf("------ Install phase ------\n"); printf("[ 0%%] Found %s connected through %s, beginning install\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); // NOTE: the secure version doesn't seem to require us to start the AFC service service_conn_t afcFd; assert(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd) == 0); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); 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); assert(AMDeviceSecureTransferPath(0, device, url, options, transfer_callback, 0)==0); close(afcFd); AMDeviceConnect(device); assert(AMDeviceIsPaired(device)); assert(AMDeviceValidatePairing(device) == 0); assert(AMDeviceStartSession(device) == 0); // // 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); mach_error_t result = AMDeviceSecureInstallApplication(0, device, url, options, install_callback, 0); if (result != 0) { char* error = "Unknown error."; if (result == 0xe8008015) error = "Your application failed code-signing checks. Check your certificates, provisioning profiles, and bundle ids."; printf("AMDeviceInstallApplication failed: 0x%X: %s\n", result, error); exit(exitcode_error); } // close(installFd); assert(AMDeviceStopSession(device) == 0); assert(AMDeviceDisconnect(device) == 0); CFRelease(path); CFRelease(options); printf("[100%%] Installed package %s\n", app_path); } exit(0); } 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) { handle_device(info->dev); } else if(best_device_match == NULL) { 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) { handle_device(best_device_match); CFRelease(best_device_match); best_device_match = NULL; } if(!found_device) { printf("[....] Timed out waiting for device.\n"); exit(exitcode_error); } } else { if (!debug) { printf("[....] No more devices found.\n"); } if (detect_only && !found_device) { exit(exitcode_error); return; } else { int mypid = getpid(); if ((parent != 0) && (parent == mypid) && (child != 0)) { if (verbose) { printf("Timeout. Killing child (%d) tree\n", child); } kill_ptree(child, SIGHUP); } } exit(0); } } void usage(const char* app) { printf( "Usage: %s [OPTION]...\n" " -u, --uninstall uninstall an app (requires -b)\n" " -r, --reinstall uninstall an app and then install an app (requires -b for uninstall and -p for install) \n" " -i, --isinstalled check if the app is installed or not (requires -b)\n", " -d, --device the id of the device to connect to (use all devices if not set?)\n" " -p, --path the path to the app bundle (.ipa) which should be installed\n" " -b, --bundle specify bundle id \n" " use only -p or -p and -d to install an app\n" ); } void show_version() { printf("%s\n", APP_VERSION); } int main(int argc, char *argv[]) { static struct option longopts[] = { { "device", required_argument, NULL, 'd' }, { "path", required_argument, NULL, 'p' }, { "reinstall", no_argument, NULL, 'r' }, { "uninstall", no_argument, NULL, 'u' }, { "bundle", required_argument, NULL, 'b'}, { "isinstalled", no_argument, NULL, 'i'}, { NULL, 0, NULL, 0 }, }; char ch; while ((ch = getopt_long(argc, argv, "ruid:p:b:", longopts, NULL)) != -1) { switch (ch) { case 'd': device_id = optarg; break; case 'p': app_path = optarg; break; case 'r': command_only = true; command = "reinstall"; break; case 'u': command_only = true; command = "uninstall"; install = false; break; case 'b': bundle_id = optarg; break; case 'i': install = false; command_only = true; command = "exists"; break; default: usage(argv[0]); return exitcode_error; } } if (!app_path && !bundle_id && !command_only) { usage(argv[0]); exit(exitcode_error); } if (unbuffered) { setbuf(stdout, NULL); setbuf(stderr, NULL); } if (detect_only && timeout == 0) { timeout = 5; } if (app_path) { 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(); }