├── .gitignore ├── CHANGES ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md └── ios-deploy.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | demo 2 | demo.app 3 | ios-deploy 4 | ios-deploy.dSYM 5 | /.DS_Store 6 | *~ 7 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Changes from phonegap/ios-deploy: 2 | 3 | in read_dir(): 4 | - If the call to afc_read_directory fails with error AFC_E_READ_ERROR, 5 | the function assumes that they are actually files, and will invoke 6 | the callback as such. 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to ios-deploy 2 | 3 | Github url: 4 | 5 | https://github.com/phonegap/ios-deploy 6 | 7 | Git clone url: 8 | 9 | https://github.com/phonegap/ios-deploy.git 10 | 11 | ## Filing an issue 12 | 13 | Please run the commands below in your Terminal.app and include it in the issue: 14 | 15 | ``` 16 | 1. sw_vers -productVersion 17 | 2. ios-deploy -V 18 | 3. xcodebuild -version 19 | 4. xcode-select --print-path 20 | 5. gcc --version 21 | 6. lldb --version 22 | 23 | ``` 24 | Also include **command line arguments** you used for ios-deploy. 25 | 26 | 27 | ## Sending a Pull Request 28 | 29 | 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. 30 | 31 | 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. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ios-deploy is available under the provisions of the GNU General Public License, 2 | version 3 (or later), available here: http://www.gnu.org/licenses/gpl-3.0.html 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXX=g++ 3 | CPPFLAGS+= -Wall -Wextra -pedantic 4 | 5 | all: clean ios-deploy 6 | 7 | ios-deploy: clean ios-deploy.cpp 8 | $(CXX) $(CPPFLAGS) -g -o ios-deploy ios-deploy.cpp -limobiledevice -l:libplist.so.3 9 | 10 | install: symlink ios-deploy 11 | mkdir -p $(prefix)/bin 12 | cp ios-deploy $(prefix)/bin 13 | 14 | uninstall: 15 | rm $(prefix)/bin/ios-deploy 16 | 17 | clean: 18 | rm -rf demo ios-deploy 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ios-deploy 2 | ========== 3 | Install and debug iOS apps without using Xcode. Designed to work on un-jailbroken devices. 4 | 5 | This is a modification of phonegap/ios-deploy to use libimobiledevice for iOS device interface, 6 | rather than Apple's library, which is only available on Windows and OS X. 7 | 8 | ## Requirements 9 | 10 | * Currently only tested on Linux Mint 17.1 with new builds of libimobiledevice, libplist, 11 | libusbmuxd, and usbmuxd. 12 | 13 | #### Build 14 | * libimobiledevice-dev (version 1.2.0 recommended) 15 | * libplist 16 | 17 | #### Extras 18 | * ideviceinstaller (optional, used for deploying apps) 19 | * ios-lldb\* (optional, used for debugging apps) 20 | * DeveloperDiskImage.dmg (optional, needed for debugging) 21 | 22 | \*On non-Apple systems, ios-deploy will use 'ios-lldb' as a debugger. This should point to a 23 | build of LLDB that supports the 'remote-ios' platform. 24 | 25 | ## Usage 26 | 27 | Usage: ios-deploy [OPTION]... 28 | -d, --debug launch the app in GDB after installation 29 | -i, --id the id of the device to connect to 30 | -c, --detect only detect if the device is connected 31 | -b, --bundle the path to the app bundle to be installed 32 | -a, --args command line arguments to pass to the app when launching it 33 | -t, --timeout number of seconds to wait for a device to be connected 34 | -u, --unbuffered don't buffer stdout 35 | -n, --nostart do not start the app when debugging 36 | -I, --noninteractive start in non interactive mode (quit when app crashes or exits) 37 | -L, --justlaunch just launch the app and exit lldb 38 | -v, --verbose enable verbose output 39 | -m, --noinstall directly start debugging without app install (-d not required) 40 | -p, --port port used for device, default: 12345 41 | -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) 42 | -1, --bundle_id specify bundle id for list and upload 43 | -l, --list list files 44 | -o, --upload upload file 45 | -w, --download download app tree 46 | -2, --to use together with up/download file/tree. specify target 47 | -V, --version print the executable version 48 | 49 | ## Examples 50 | 51 | The commands below assume that you have an app called `my.app` with bundle id `bundle.id`. Substitute where necessary. 52 | 53 | // deploy and debug your app to a connected device 54 | ios-deploy --debug --bundle my.app 55 | 56 | // deploy and launch your app to a connected device, but quit the debugger after 57 | ios-deploy --justlaunch --debug --bundle my.app 58 | 59 | // deploy and launch your app to a connected device, quit when app crashes or exits 60 | ios-deploy --noninteractive --debug --bundle my.app 61 | 62 | // Upload a file to your app's Documents folder 63 | ios-deploy --bundle_id 'bundle.id' --upload test.txt --to Documents/test.txt 64 | 65 | // Download your app's Documents, Library and tmp folders 66 | ios-deploy --bundle_id 'bundle.id' --download --to MyDestinationFolder 67 | 68 | // List the contents of your app's Documents, Library and tmp folders 69 | ios-deploy --bundle_id 'bundle.id' --list 70 | 71 | // deploy and debug your app to a connected device, uninstall the app first 72 | ios-deploy --uninstall --debug --bundle my.app 73 | -------------------------------------------------------------------------------- /ios-deploy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | using namespace std; 29 | 30 | #define APP_VERSION "1.4.0-Linux" 31 | #define PREP_CMDS_PATH "/tmp/fruitstrap-lldb-prep-cmds-" 32 | #ifndef __APPLE__ 33 | #define LLDB_SHELL "ios-lldb -s " PREP_CMDS_PATH 34 | #else 35 | #define LLDB_SHELL "lldb -s " PREP_CMDS_PATH 36 | #endif 37 | /* 38 | * Startup script passed to lldb. 39 | * To see how xcode interacts with lldb, put this into .lldbinit: 40 | * log enable -v -f /Users/vargaz/lldb.log lldb all 41 | * log enable -v -f /Users/vargaz/gdb-remote.log gdb-remote all 42 | */ 43 | // "platform select remote-ios --sysroot {symbols_path}\n\ " 44 | #define MAIN_LLDB_PREP_CMDS "\ 45 | platform select remote-ios --sysroot /home/david/syms\n\ 46 | target create \"{disk_app}\"\n\ 47 | script fruitstrap_device_app=\"{device_app}\"\n\ 48 | script fruitstrap_connect_url=\"connect://localhost:{device_port}\"\n\ 49 | command script import \"{python_file_path}\"\n\ 50 | command script add -f {python_command}.connect_command connect\n\ 51 | command script add -s asynchronous -f {python_command}.run_command run\n\ 52 | command script add -s asynchronous -f {python_command}.autoexit_command autoexit\n\ 53 | command script add -s asynchronous -f {python_command}.safequit_command safequit\n\ 54 | connect\n\ 55 | " 56 | 57 | #define LLDB_PREP_CMDS MAIN_LLDB_PREP_CMDS 58 | 59 | const char* lldb_prep_no_cmds = ""; 60 | 61 | const char* lldb_prep_interactive_cmds = "\ 62 | run\n\ 63 | "; 64 | 65 | #ifdef __APPLE__ 66 | const char* lldb_prep_noninteractive_justlaunch_cmds = "\ 67 | run\n\ 68 | safequit\n\ 69 | "; 70 | #else 71 | const char* lldb_prep_noninteractive_justlaunch_cmds = "\ 72 | run\n\ 73 | safequit\n\ 74 | quit\n\ 75 | "; 76 | #endif 77 | 78 | const char* lldb_prep_noninteractive_cmds = "\ 79 | run\n\ 80 | autoexit\n\ 81 | "; 82 | 83 | /* 84 | * Some things do not seem to work when using the normal commands like process connect/launch, so we invoke them 85 | * through the python interface. Also, Launch () doesn't seem to work when ran from init_module (), so we add 86 | * a command which can be used by the user to run it. 87 | */ 88 | #define LLDB_FRUITSTRAP_MODULE ("\ 89 | import lldb\n\ 90 | import sys\n\ 91 | import shlex\n\ 92 | \n\ 93 | def connect_command(debugger, command, result, internal_dict):\n\ 94 | # These two are passed in by the script which loads us\n\ 95 | connect_url = internal_dict['fruitstrap_connect_url']\n\ 96 | error = lldb.SBError()\n\ 97 | \n\ 98 | process = lldb.target.ConnectRemote(lldb.target.GetDebugger().GetListener(), connect_url, None, error)\n\ 99 | \n\ 100 | # Wait for connection to succeed\n\ 101 | listener = lldb.target.GetDebugger().GetListener()\n\ 102 | listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged)\n\ 103 | events = []\n\ 104 | state = (process.GetState() or lldb.eStateInvalid)\n\ 105 | while state != lldb.eStateConnected:\n\ 106 | event = lldb.SBEvent()\n\ 107 | if listener.WaitForEvent(1, event):\n\ 108 | state = process.GetStateFromEvent(event)\n\ 109 | events.append(event)\n\ 110 | else:\n\ 111 | state = lldb.eStateInvalid\n\ 112 | \n\ 113 | # Add events back to queue, otherwise lldb freezes\n\ 114 | for event in events:\n\ 115 | listener.AddEvent(event)\n\ 116 | \n\ 117 | def run_command(debugger, command, result, internal_dict):\n\ 118 | device_app = internal_dict['fruitstrap_device_app']\n\ 119 | error = lldb.SBError()\n\ 120 | lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\ 121 | lldb.target.Launch(lldb.SBLaunchInfo(shlex.split('{args}')), error)\n\ 122 | lockedstr = ': Locked'\n\ 123 | if lockedstr in str(error):\n\ 124 | print('\\nDevice Locked\\n')\n\ 125 | sys.exit(254)\n\ 126 | else:\n\ 127 | print(str(error))\n\ 128 | \n\ 129 | def safequit_command(debugger, command, result, internal_dict):\n\ 130 | process = lldb.target.process\n\ 131 | listener = debugger.GetListener()\n\ 132 | listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n\ 133 | event = lldb.SBEvent()\n\ 134 | while True:\n\ 135 | if listener.WaitForEvent(1, event):\n\ 136 | state = process.GetStateFromEvent(event)\n\ 137 | else:\n\ 138 | state = lldb.eStateInvalid\n\ 139 | process.Detach()\n\ 140 | sys.exit(0)\n\ 141 | \n\ 142 | def autoexit_command(debugger, command, result, internal_dict):\n\ 143 | process = lldb.target.process\n\ 144 | listener = debugger.GetListener()\n\ 145 | listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged | lldb.SBProcess.eBroadcastBitSTDOUT | lldb.SBProcess.eBroadcastBitSTDERR)\n\ 146 | event = lldb.SBEvent()\n\ 147 | while True:\n\ 148 | if listener.WaitForEvent(1, event):\n\ 149 | state = process.GetStateFromEvent(event)\n\ 150 | else:\n\ 151 | state = lldb.eStateInvalid\n\ 152 | \n\ 153 | stdout = process.GetSTDOUT(1024)\n\ 154 | while stdout:\n\ 155 | sys.stdout.write(stdout)\n\ 156 | stdout = process.GetSTDOUT(1024)\n\ 157 | \n\ 158 | stderr = process.GetSTDERR(1024)\n\ 159 | while stderr:\n\ 160 | sys.stdout.write(stderr)\n\ 161 | stderr = process.GetSTDERR(1024)\n\ 162 | \n\ 163 | if lldb.SBProcess.EventIsProcessEvent(event):\n\ 164 | if state == lldb.eStateExited:\n\ 165 | sys.exit(process.GetExitStatus())\n\ 166 | if state == lldb.eStateStopped:\n\ 167 | debugger.HandleCommand('frame select')\n\ 168 | debugger.HandleCommand('bt')\n\ 169 | sys.exit({exitcode_app_crash})\n\ 170 | ") 171 | 172 | #if 0 173 | 174 | typedef struct am_device * AMDeviceRef; 175 | mach_error_t AMDeviceSecureStartService(struct am_device *device, CFStringRef service_name, unsigned int *unknown, service_conn_t *handle); 176 | int AMDeviceSecureTransferPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); 177 | int AMDeviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, void *callback, int cbarg); 178 | int AMDeviceMountImage(AMDeviceRef device, CFStringRef image, CFDictionaryRef options, void *callback, int cbarg); 179 | mach_error_t AMDeviceLookupApplications(AMDeviceRef device, CFDictionaryRef options, CFDictionaryRef *result); 180 | int AMDeviceGetInterfaceType(struct am_device *device); 181 | 182 | #endif 183 | 184 | bool found_device = false, debug = false, verbose = false, unbuffered = false, nostart = false, detect_only = false, install = true, uninstall = false; 185 | bool command_only = false; 186 | const char *command = NULL; 187 | char *target_filename = NULL; 188 | char *upload_pathname = NULL; 189 | char *bundle_id = NULL; 190 | bool interactive = true; 191 | bool justlaunch = false; 192 | char *app_path = NULL; 193 | char *device_id = NULL; 194 | char *args = NULL; 195 | char *list_root = NULL; 196 | int timeout = 0; 197 | int port = 12345; 198 | const char *last_path = NULL; 199 | int gdbfd; // TODO:Q: was service_conn_t 200 | pid_t parent = 0; 201 | // PID of child process running lldb 202 | pid_t child = 0; 203 | pid_t debugserver = 0; 204 | // Signal sent from child to parent process when LLDB finishes. 205 | const int SIGLLDB = SIGUSR1; 206 | char * best_device_match = NULL; // type was AMDeviceRef 207 | 208 | // Error codes we report on different failures, so scripts can distinguish between user app exit 209 | // codes and our exit codes. For non app errors we use codes in reserved 128-255 range. 210 | const int exitcode_error = 253; 211 | const int exitcode_app_crash = 254; 212 | 213 | #if 0 214 | 215 | Boolean path_exists(CFTypeRef path) { 216 | if (CFGetTypeID(path) == CFStringGetTypeID()) { 217 | CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, true); 218 | Boolean result = CFURLResourceIsReachable(url, NULL); 219 | CFRelease(url); 220 | return result; 221 | } else if (CFGetTypeID(path) == CFURLGetTypeID()) { 222 | return CFURLResourceIsReachable(path, NULL); 223 | } else { 224 | return false; 225 | } 226 | } 227 | 228 | CFStringRef find_path(CFStringRef rootPath, CFStringRef namePattern, CFStringRef expression) { 229 | FILE *fpipe = NULL; 230 | CFStringRef quotedRootPath = rootPath; 231 | CFStringRef cf_command; 232 | CFRange slashLocation; 233 | 234 | if (CFStringGetCharacterAtIndex(rootPath, 0) != '`') { 235 | quotedRootPath = CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@'"), rootPath); 236 | } 237 | 238 | slashLocation = CFStringFind(namePattern, CFSTR("/"), 0); 239 | if (slashLocation.location == kCFNotFound) { 240 | cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -name '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); 241 | } else { 242 | cf_command = CFStringCreateWithFormat(NULL, NULL, CFSTR("find %@ -path '%@' %@ 2>/dev/null | sort | tail -n 1"), quotedRootPath, namePattern, expression); 243 | } 244 | 245 | if (quotedRootPath != rootPath) { 246 | CFRelease(quotedRootPath); 247 | } 248 | 249 | char command[1024] = { '\0' }; 250 | CFStringGetCString(cf_command, command, sizeof(command), kCFStringEncodingUTF8); 251 | CFRelease(cf_command); 252 | 253 | if (!(fpipe = (FILE *)popen(command, "r"))) 254 | { 255 | perror("Error encountered while opening pipe"); 256 | exit(exitcode_error); 257 | } 258 | 259 | char buffer[256] = { '\0' }; 260 | 261 | fgets(buffer, sizeof(buffer), fpipe); 262 | pclose(fpipe); 263 | 264 | strtok(buffer, "\n"); 265 | return CFStringCreateWithCString(NULL, buffer, kCFStringEncodingUTF8); 266 | } 267 | 268 | CFStringRef copy_long_shot_disk_image_path() { 269 | return find_path(CFSTR("`xcode-select --print-path`"), CFSTR("DeveloperDiskImage.dmg"), CFSTR("")); 270 | } 271 | 272 | CFStringRef copy_xcode_dev_path() { 273 | static char xcode_dev_path[256] = { '\0' }; 274 | if (strlen(xcode_dev_path) == 0) { 275 | FILE *fpipe = NULL; 276 | char *command = "xcode-select -print-path"; 277 | 278 | if (!(fpipe = (FILE *)popen(command, "r"))) 279 | { 280 | perror("Error encountered while opening pipe"); 281 | exit(exitcode_error); 282 | } 283 | 284 | char buffer[256] = { '\0' }; 285 | 286 | fgets(buffer, sizeof(buffer), fpipe); 287 | pclose(fpipe); 288 | 289 | strtok(buffer, "\n"); 290 | strcpy(xcode_dev_path, buffer); 291 | } 292 | return CFStringCreateWithCString(NULL, xcode_dev_path, kCFStringEncodingUTF8); 293 | } 294 | 295 | const char *get_home() { 296 | const char* home = getenv("HOME"); 297 | if (!home) { 298 | struct passwd *pwd = getpwuid(getuid()); 299 | home = pwd->pw_dir; 300 | } 301 | return home; 302 | } 303 | 304 | CFStringRef copy_xcode_path_for(CFStringRef subPath, CFStringRef search) { 305 | CFStringRef xcodeDevPath = copy_xcode_dev_path(); 306 | CFStringRef path; 307 | bool found = false; 308 | const char* home = get_home(); 309 | CFRange slashLocation; 310 | 311 | 312 | // Try using xcode-select --print-path 313 | if (!found) { 314 | path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@/%@"), xcodeDevPath, subPath, search); 315 | found = path_exists(path); 316 | } 317 | // Try find `xcode-select --print-path` with search as a name pattern 318 | if (!found) { 319 | slashLocation = CFStringFind(search, CFSTR("/"), 0); 320 | if (slashLocation.location == kCFNotFound) { 321 | path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("-maxdepth 1")); 322 | } else { 323 | path = find_path(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/%@"), xcodeDevPath, subPath), search, CFSTR("")); 324 | } 325 | found = CFStringGetLength(path) > 0 && path_exists(path); 326 | } 327 | // If not look in the default xcode location (xcode-select is sometimes wrong) 328 | if (!found) { 329 | path = CFStringCreateWithFormat(NULL, NULL, CFSTR("/Applications/Xcode.app/Contents/Developer/%@&%@"), subPath, search); 330 | found = path_exists(path); 331 | } 332 | // If not look in the users home directory, Xcode can store device support stuff there 333 | if (!found) { 334 | path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%s/Library/Developer/Xcode/%@/%@"), home, subPath, search); 335 | found = path_exists(path); 336 | } 337 | 338 | CFRelease(xcodeDevPath); 339 | 340 | if (found) { 341 | return path; 342 | } else { 343 | CFRelease(path); 344 | return NULL; 345 | } 346 | } 347 | 348 | #endif 349 | 350 | #define GET_FRIENDLY_MODEL_NAME(VALUE, INTERNAL_NAME, FRIENDLY_NAME) if (!strcmp(VALUE, INTERNAL_NAME)) { free(model); return FRIENDLY_NAME; }; 351 | 352 | 353 | // Please ensure that device is connected or the name will be unknown 354 | const char *get_device_hardware_name(plist_t deviceinfo) { 355 | 356 | char *get_plist_string_value(plist_t plist, const char *key); 357 | char *model = get_plist_string_value(deviceinfo, "HardwareModel"); 358 | 359 | if (model == NULL) { 360 | return "Unknown Device"; 361 | } 362 | 363 | // iPod Touch 364 | 365 | GET_FRIENDLY_MODEL_NAME(model, "N45AP", "iPod Touch") 366 | GET_FRIENDLY_MODEL_NAME(model, "N72AP", "iPod Touch 2G") 367 | GET_FRIENDLY_MODEL_NAME(model, "N18AP", "iPod Touch 3G") 368 | GET_FRIENDLY_MODEL_NAME(model, "N81AP", "iPod Touch 4G") 369 | GET_FRIENDLY_MODEL_NAME(model, "N78AP", "iPod Touch 5G") 370 | GET_FRIENDLY_MODEL_NAME(model, "N78AAP", "iPod Touch 5G") 371 | 372 | // iPad 373 | 374 | GET_FRIENDLY_MODEL_NAME(model, "K48AP", "iPad") 375 | GET_FRIENDLY_MODEL_NAME(model, "K93AP", "iPad 2") 376 | GET_FRIENDLY_MODEL_NAME(model, "K94AP", "iPad 2 (GSM)") 377 | GET_FRIENDLY_MODEL_NAME(model, "K95AP", "iPad 2 (CDMA)") 378 | GET_FRIENDLY_MODEL_NAME(model, "K93AAP", "iPad 2 (Wi-Fi, revision A)") 379 | GET_FRIENDLY_MODEL_NAME(model, "J1AP", "iPad 3") 380 | GET_FRIENDLY_MODEL_NAME(model, "J2AP", "iPad 3 (GSM)") 381 | GET_FRIENDLY_MODEL_NAME(model, "J2AAP", "iPad 3 (CDMA)") 382 | GET_FRIENDLY_MODEL_NAME(model, "P101AP", "iPad 4") 383 | GET_FRIENDLY_MODEL_NAME(model, "P102AP", "iPad 4 (GSM)") 384 | GET_FRIENDLY_MODEL_NAME(model, "P103AP", "iPad 4 (CDMA)") 385 | 386 | // iPad Mini 387 | 388 | GET_FRIENDLY_MODEL_NAME(model, "P105AP", "iPad mini") 389 | GET_FRIENDLY_MODEL_NAME(model, "P106AP", "iPad mini (GSM)") 390 | GET_FRIENDLY_MODEL_NAME(model, "P107AP", "iPad mini (CDMA)") 391 | 392 | // Apple TV 393 | 394 | GET_FRIENDLY_MODEL_NAME(model, "K66AP", "Apple TV 2G") 395 | GET_FRIENDLY_MODEL_NAME(model, "J33AP", "Apple TV 3G") 396 | GET_FRIENDLY_MODEL_NAME(model, "J33IAP", "Apple TV 3.1G") 397 | 398 | // iPhone 399 | 400 | GET_FRIENDLY_MODEL_NAME(model, "M68AP", "iPhone") 401 | GET_FRIENDLY_MODEL_NAME(model, "N82AP", "iPhone 3G") 402 | GET_FRIENDLY_MODEL_NAME(model, "N88AP", "iPhone 3GS") 403 | GET_FRIENDLY_MODEL_NAME(model, "N90AP", "iPhone 4 (GSM)") 404 | GET_FRIENDLY_MODEL_NAME(model, "N92AP", "iPhone 4 (CDMA)") 405 | GET_FRIENDLY_MODEL_NAME(model, "N90BAP", "iPhone 4 (GSM, revision A)") 406 | GET_FRIENDLY_MODEL_NAME(model, "N94AP", "iPhone 4S") 407 | GET_FRIENDLY_MODEL_NAME(model, "N41AP", "iPhone 5 (GSM)") 408 | GET_FRIENDLY_MODEL_NAME(model, "N42AP", "iPhone 5 (Global/CDMA)") 409 | GET_FRIENDLY_MODEL_NAME(model, "N48AP", "iPhone 5c (GSM)") 410 | GET_FRIENDLY_MODEL_NAME(model, "N49AP", "iPhone 5c (Global/CDMA)") 411 | GET_FRIENDLY_MODEL_NAME(model, "N51AP", "iPhone 5s (GSM)") 412 | GET_FRIENDLY_MODEL_NAME(model, "N53AP", "iPhone 5s (Global/CDMA)") 413 | GET_FRIENDLY_MODEL_NAME(model, "N61AP", "iPhone 6 (GSM)") 414 | GET_FRIENDLY_MODEL_NAME(model, "N56AP", "iPhone 6 Plus") 415 | 416 | return model; 417 | } 418 | 419 | #if 0 420 | 421 | char * MYCFStringCopyUTF8String(CFStringRef aString) { 422 | if (aString == NULL) { 423 | return NULL; 424 | } 425 | 426 | CFIndex length = CFStringGetLength(aString); 427 | CFIndex maxSize = 428 | CFStringGetMaximumSizeForEncoding(length, 429 | kCFStringEncodingUTF8); 430 | char *buffer = (char *)malloc(maxSize); 431 | if (CFStringGetCString(aString, buffer, maxSize, 432 | kCFStringEncodingUTF8)) { 433 | return buffer; 434 | } 435 | return NULL; 436 | } 437 | 438 | #endif 439 | 440 | plist_t get_device_info_plist(idevice_t device) { 441 | // Stuff from ideviceinfo.c 442 | lockdownd_client_t client = NULL; 443 | lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; 444 | //int format = FORMAT_KEY_VALUE; 445 | plist_t node = NULL; 446 | bool simple = true; 447 | 448 | if (LOCKDOWN_E_SUCCESS != (ldret = simple ? 449 | lockdownd_client_new(device, &client, "ideviceinfo"): 450 | lockdownd_client_new_with_handshake(device, &client, "ideviceinfo"))) { 451 | fprintf(stderr, "ERROR: Could not connect to lockdownd, error code %d\n", ldret); 452 | return NULL; 453 | } 454 | 455 | /* run query and output information */ //domain, key, node 456 | if((ldret = lockdownd_get_value(client, NULL, NULL, &node)) != LOCKDOWN_E_SUCCESS) { 457 | fprintf(stderr, "Unable to get lockdown values, error code %d\n", ldret); 458 | } 459 | 460 | lockdownd_client_free(client); 461 | 462 | return node; 463 | } 464 | 465 | char *get_plist_string_value(plist_t plist, const char *key) 466 | { 467 | plist_t val; 468 | char *ret = NULL; 469 | if (!plist) 470 | return NULL; 471 | val = plist_dict_get_item(plist, key); 472 | if (!val) 473 | return NULL; 474 | plist_get_string_val(val, &ret); 475 | return ret; 476 | } 477 | 478 | const char *get_device_full_name(idevice_t device) { 479 | char *full_name = NULL, 480 | *device_udid = NULL, 481 | *device_name = NULL, 482 | *model_name = NULL; 483 | plist_t info; 484 | 485 | idevice_get_udid(device, &device_udid); 486 | info = get_device_info_plist(device); 487 | 488 | device_name = get_plist_string_value(info, "DeviceName"); 489 | model_name = strdup(get_device_hardware_name(info)); 490 | 491 | if (verbose) 492 | { 493 | printf("Device Name:[%s]\n",device_name); 494 | printf("Model Name:[%s]\n",model_name); 495 | } 496 | 497 | if(device_name != NULL && model_name != NULL) 498 | { 499 | asprintf(&full_name, "%s '%s' (%s)", model_name, device_name, device_udid); 500 | } 501 | else 502 | { 503 | asprintf(&full_name, "(%s)", device_udid); 504 | } 505 | 506 | if(device_udid != NULL) 507 | free(device_udid); 508 | if(device_name != NULL) 509 | free(device_name); 510 | if(model_name != NULL) 511 | free(model_name); 512 | 513 | return full_name; 514 | } 515 | 516 | const char *get_device_interface_name(idevice_t device) { 517 | (void)device; 518 | // AMDeviceGetInterfaceType(device) 0=Unknown, 1 = Direct/USB, 2 = Indirect/WIFI 519 | switch(1) { 520 | case 1: 521 | return "USB"; 522 | case 2: 523 | return "WIFI"; 524 | default: 525 | return "Unknown Connection"; 526 | } 527 | } 528 | 529 | #if 0 530 | 531 | CFMutableArrayRef get_device_product_version_parts(AMDeviceRef device) { 532 | CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion")); 533 | CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, version, CFSTR(".")); 534 | CFMutableArrayRef result = CFArrayCreateMutableCopy(NULL, CFArrayGetCount(parts), parts); 535 | CFRelease(version); 536 | CFRelease(parts); 537 | return result; 538 | } 539 | 540 | CFStringRef copy_device_support_path(AMDeviceRef device) { 541 | CFStringRef version = NULL; 542 | CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); 543 | CFStringRef path = NULL; 544 | CFMutableArrayRef version_parts = get_device_product_version_parts(device); 545 | 546 | while (CFArrayGetCount(version_parts) > 0) { 547 | version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); 548 | if (path == NULL) { 549 | path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); 550 | } 551 | if (path == NULL) { 552 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), version, build)); 553 | } 554 | if (path == NULL) { 555 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (*)"), version)); 556 | } 557 | if (path == NULL) { 558 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), version); 559 | } 560 | if (path == NULL) { 561 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("")); 562 | } 563 | CFRelease(version); 564 | if (path != NULL) { 565 | break; 566 | } 567 | CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); 568 | } 569 | 570 | CFRelease(version_parts); 571 | CFRelease(build); 572 | 573 | if (path == NULL) 574 | { 575 | printf("[ !! ] Unable to locate DeviceSupport directory.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); 576 | exit(exitcode_error); 577 | } 578 | 579 | return path; 580 | } 581 | 582 | CFStringRef copy_developer_disk_image_path(AMDeviceRef device) { 583 | CFStringRef version = NULL; 584 | CFStringRef build = AMDeviceCopyValue(device, 0, CFSTR("BuildVersion")); 585 | CFStringRef path = NULL; 586 | CFMutableArrayRef version_parts = get_device_product_version_parts(device); 587 | 588 | while (CFArrayGetCount(version_parts) > 0) { 589 | version = CFStringCreateByCombiningStrings(NULL, version_parts, CFSTR(".")); 590 | if (path == NULL) { 591 | path = copy_xcode_path_for(CFSTR("iOS DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); 592 | } 593 | if (path == NULL) { 594 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)/DeveloperDiskImage.dmg"), version, build)); 595 | } 596 | if (path == NULL) { 597 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("*/%@ (*)/DeveloperDiskImage.dmg"), version)); 598 | } 599 | if (path == NULL) { 600 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport"), CFStringCreateWithFormat(NULL, NULL, CFSTR("%@/DeveloperDiskImage.dmg"), version)); 601 | } 602 | if (path == NULL) { 603 | path = copy_xcode_path_for(CFSTR("Platforms/iPhoneOS.platform/DeviceSupport/Latest"), CFSTR("DeveloperDiskImage.dmg")); 604 | } 605 | CFRelease(version); 606 | if (path != NULL) { 607 | break; 608 | } 609 | CFArrayRemoveValueAtIndex(version_parts, CFArrayGetCount(version_parts) - 1); 610 | } 611 | 612 | CFRelease(version_parts); 613 | CFRelease(build); 614 | if (path == NULL) 615 | { 616 | printf("[ !! ] Unable to locate DeveloperDiskImage.dmg.\n[ !! ] This probably means you don't have Xcode installed, you will need to launch the app manually and logging output will not be shown!\n"); 617 | exit(exitcode_error); 618 | } 619 | 620 | return path; 621 | } 622 | 623 | void mount_callback(CFDictionaryRef dict, int arg) { 624 | CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); 625 | 626 | if (CFEqual(status, CFSTR("LookingUpImage"))) { 627 | printf("[ 0%%] Looking up developer disk image\n"); 628 | } else if (CFEqual(status, CFSTR("CopyingImage"))) { 629 | printf("[ 30%%] Copying DeveloperDiskImage.dmg to device\n"); 630 | } else if (CFEqual(status, CFSTR("MountingImage"))) { 631 | printf("[ 90%%] Mounting developer disk image\n"); 632 | } 633 | } 634 | 635 | #endif 636 | 637 | void mount_developer_image(idevice_t device) { 638 | mobile_image_mounter_client_t mnt = NULL; 639 | plist_t lookup = NULL; 640 | plist_t imagepresent = NULL; 641 | uint8_t has_developer_image = 0; 642 | int ret = 0; 643 | 644 | mobile_image_mounter_start_service(device, &mnt, "ios-deploy"); 645 | mobile_image_mounter_lookup_image(mnt, "Developer", &lookup); 646 | 647 | imagepresent = plist_dict_get_item(lookup, "ImagePresent"); 648 | if (!imagepresent) { 649 | printf("[ !! ] Unable to read image status.\n"); 650 | ret = exitcode_error; 651 | goto exit; 652 | } 653 | 654 | plist_get_bool_val(imagepresent, &has_developer_image); 655 | 656 | if (has_developer_image) { 657 | printf("[ 95%%] Developer disk image already mounted\n"); 658 | goto exit; 659 | } 660 | 661 | printf("[ !! ] Please mount the developer disk image.\n"); 662 | ret = exitcode_error; 663 | goto exit; 664 | 665 | 666 | // CFStringRef ds_path = copy_device_support_path(device); 667 | // CFStringRef image_path = copy_developer_disk_image_path(device); 668 | // CFStringRef sig_path = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@.signature"), image_path); 669 | // 670 | // if (verbose) { 671 | // printf("Device support path: %s\n", CFStringGetCStringPtr(ds_path, CFStringGetSystemEncoding())); 672 | // printf("Developer disk image: %s\n", CFStringGetCStringPtr(image_path, CFStringGetSystemEncoding())); 673 | // } 674 | // CFRelease(ds_path); 675 | // 676 | // FILE* sig = fopen(CFStringGetCStringPtr(sig_path, kCFStringEncodingMacRoman), "rb"); 677 | // void *sig_buf = malloc(128); 678 | // assert(fread(sig_buf, 1, 128, sig) == 128); 679 | // fclose(sig); 680 | // CFDataRef sig_data = CFDataCreateWithBytesNoCopy(NULL, sig_buf, 128, NULL); 681 | // CFRelease(sig_path); 682 | // 683 | // CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") }; 684 | // CFTypeRef values[] = { sig_data, CFSTR("Developer") }; 685 | // CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 686 | // CFRelease(sig_data); 687 | // 688 | // int result = AMDeviceMountImage(device, image_path, options, &mount_callback, 0); 689 | // if (result == 0) { 690 | // printf("[ 95%%] Developer disk image mounted successfully\n"); 691 | // } else if (result == 0xe8000076 /* already mounted */) { 692 | // printf("[ 95%%] Developer disk image already mounted\n"); 693 | // } else { 694 | // printf("[ !! ] Unable to mount developer disk image. (%x)\n", result); 695 | // exit(exitcode_error); 696 | // } 697 | 698 | exit: 699 | if (mnt) { 700 | mobile_image_mounter_hangup(mnt); 701 | mobile_image_mounter_free(mnt); 702 | } 703 | if (ret) 704 | exit(ret); 705 | } 706 | 707 | #if 0 708 | 709 | mach_error_t transfer_callback(CFDictionaryRef dict, int arg) { 710 | int percent; 711 | CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); 712 | CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); 713 | 714 | if (CFEqual(status, CFSTR("CopyingFile"))) { 715 | CFStringRef path = CFDictionaryGetValue(dict, CFSTR("Path")); 716 | 717 | if ((last_path == NULL || !CFEqual(path, last_path)) && !CFStringHasSuffix(path, CFSTR(".ipa"))) { 718 | printf("[%3d%%] Copying %s to device\n", percent / 2, CFStringGetCStringPtr(path, kCFStringEncodingMacRoman)); 719 | } 720 | 721 | if (last_path != NULL) { 722 | CFRelease(last_path); 723 | } 724 | last_path = CFStringCreateCopy(NULL, path); 725 | } 726 | 727 | return 0; 728 | } 729 | 730 | mach_error_t install_callback(CFDictionaryRef dict, int arg) { 731 | int percent; 732 | CFStringRef status = CFDictionaryGetValue(dict, CFSTR("Status")); 733 | CFNumberGetValue(CFDictionaryGetValue(dict, CFSTR("PercentComplete")), kCFNumberSInt32Type, &percent); 734 | 735 | printf("[%3d%%] %s\n", (percent / 2) + 50, CFStringGetCStringPtr(status, kCFStringEncodingMacRoman)); 736 | return 0; 737 | } 738 | 739 | #endif 740 | 741 | // Returns the path to the .app bundle on the device. 742 | char *copy_device_app_url(idevice_t device, const char *identifier) { 743 | char *path = NULL; 744 | instproxy_client_t ipc = NULL; 745 | lockdownd_client_t ldc = NULL; 746 | lockdownd_service_descriptor_t svc = NULL; 747 | 748 | lockdownd_client_new_with_handshake(device, &ldc, "ios-deploy"); 749 | lockdownd_start_service(ldc, "com.apple.mobile.installation_proxy", &svc); 750 | instproxy_client_new(device, svc, &ipc); 751 | instproxy_client_get_path_for_bundle_identifier(ipc, identifier, &path); 752 | 753 | instproxy_client_free(ipc); 754 | lockdownd_service_descriptor_free(svc); 755 | lockdownd_client_free(ldc); 756 | 757 | // get_path_for_bundle_identifier() returns the path to the 758 | // executable, so we need to remove the last path component. 759 | *strrchr(path, '/') = '\0'; 760 | 761 | return path; 762 | } 763 | 764 | 765 | 766 | string copy_disk_app_identifier(const char *disk_app_url) { 767 | extern char *get_bundle_id(const char *); 768 | return string(get_bundle_id(disk_app_url)); // How is this function different from get_bundle_id? 769 | #if 0 770 | CFURLRef plist_url = CFURLCreateCopyAppendingPathComponent(NULL, disk_app_url, CFSTR("Info.plist"), false); 771 | CFReadStreamRef plist_stream = CFReadStreamCreateWithFile(NULL, plist_url); 772 | CFReadStreamOpen(plist_stream); 773 | CFPropertyListRef plist = CFPropertyListCreateWithStream(NULL, plist_stream, 0, kCFPropertyListImmutable, NULL, NULL); 774 | CFStringRef bundle_identifier = CFRetain(CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"))); 775 | CFReadStreamClose(plist_stream); 776 | 777 | CFRelease(plist_url); 778 | CFRelease(plist_stream); 779 | CFRelease(plist); 780 | 781 | return bundle_identifier; 782 | #endif 783 | } 784 | 785 | #ifndef __APPLE__ 786 | string get_disk_app_executable(const string &disk_app_url) { 787 | string infoplistpath = disk_app_url; 788 | string executablepath; 789 | plist_t infoplist = NULL; 790 | char *executable = NULL; 791 | 792 | if (infoplistpath[infoplistpath.size() - 1] != '/') 793 | infoplistpath += "/Info.plist"; 794 | else 795 | infoplistpath += "Info.plist"; 796 | 797 | extern plist_t plist_from_path(const char *path); 798 | infoplist = plist_from_path(infoplistpath.c_str()); 799 | executable = get_plist_string_value(infoplist, "CFBundleExecutable"); 800 | plist_free(infoplist); 801 | 802 | if (!executable) { 803 | printf("Unable to find bundle executable for app %s\n", disk_app_url.c_str()); 804 | exit(1); 805 | } 806 | 807 | executablepath = disk_app_url; 808 | if (executablepath[executablepath.size() - 1] != '/') 809 | executablepath += "/"; 810 | executablepath += executable; 811 | free(executable); 812 | 813 | return executablepath; 814 | } 815 | #endif 816 | 817 | string path_dirname(const string &path) { 818 | string r; 819 | char *str = strdup(path.c_str()); 820 | char *dname = dirname(str); 821 | if (dname != str) 822 | free(str); 823 | r = string(dname); 824 | free(dname); 825 | return r; 826 | } 827 | 828 | void replace_substring(string &content, const string &needle, const string &replacement) { 829 | size_t start = 0; 830 | while ((start = content.find(needle, start)) != string::npos) { 831 | content.replace(start, needle.length(), replacement); 832 | start += needle.length(); 833 | } 834 | } 835 | 836 | static string int_to_string(int val) { 837 | char buf[20]; 838 | sprintf(buf, "%d", val); 839 | return string(buf); 840 | } 841 | 842 | void write_lldb_prep_cmds(idevice_t device, const char *disk_app_url) { 843 | string ds_path = "DSPATH?"; //copy_device_support_path(device); 844 | string symbols_path = "SYMBOLPATH?"; //CFStringCreateWithFormat(NULL, NULL, CFSTR("'%@/Symbols'"), ds_path); 845 | 846 | string cmds(LLDB_PREP_CMDS); 847 | 848 | replace_substring(cmds, "{symbols_path}", symbols_path); 849 | replace_substring(cmds, "{ds_path}", ds_path); 850 | 851 | string pmodule(LLDB_FRUITSTRAP_MODULE); 852 | 853 | string exitcode_app_crash_str = int_to_string(exitcode_app_crash); 854 | 855 | replace_substring(pmodule, "{exitcode_app_crash}", exitcode_app_crash_str); 856 | 857 | if (args) { 858 | replace_substring(cmds, "{args}", args); 859 | replace_substring(pmodule, "{args}", args); 860 | //printf("write_lldb_prep_cmds:args: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), 861 | // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); 862 | } else { 863 | replace_substring(cmds, "{args}", ""); 864 | replace_substring(pmodule, "{args}", ""); 865 | //printf("write_lldb_prep_cmds: [%s][%s]\n", CFStringGetCStringPtr (cmds,kCFStringEncodingMacRoman), 866 | // CFStringGetCStringPtr(pmodule, kCFStringEncodingMacRoman)); 867 | } 868 | 869 | string bundle_identifier = copy_disk_app_identifier(disk_app_url); 870 | 871 | string device_app_path = copy_device_app_url(device, bundle_identifier.c_str()); 872 | replace_substring(cmds, "{device_app}", device_app_path); 873 | //printf("Device app path: %s\n", device_app_path.c_str()); 874 | #ifdef __APPLE__ 875 | replace_substring(cmds, "{disk_app}", disk_app_url); 876 | #else 877 | // Non-Apple LLDB hosts don't resolve .app bundles to their 878 | // contained binaries, so we have to do that here. 879 | string disk_executable = get_disk_app_executable(disk_app_url); 880 | replace_substring(cmds, "{disk_app}", disk_executable); 881 | #endif 882 | 883 | string device_port = int_to_string(port); 884 | replace_substring(cmds, "{device_port}", device_port); 885 | 886 | string device_container_path = path_dirname(device_app_path); 887 | string dcp_noprivate = device_container_path; 888 | 889 | replace_substring(dcp_noprivate, "/private/var/", "/var/"); 890 | 891 | replace_substring(cmds, "{device_container}", dcp_noprivate); 892 | //printf("dcp_noprivate: %s\n", dcp_noprivate.c_str()); 893 | 894 | 895 | // There aren't any references to {disk_container} ? 896 | //CFURLRef disk_container_url = CFURLCreateCopyDeletingLastPathComponent(NULL, disk_app_url); 897 | //CFStringRef disk_container_path = CFURLCopyFileSystemPath(disk_container_url, kCFURLPOSIXPathStyle); 898 | //CFStringFindAndReplace(cmds, CFSTR("{disk_container}"), disk_container_path, range, 0); 899 | 900 | 901 | string python_file_path = "/tmp/fruitstrap_"; 902 | string python_command = "fruitstrap_"; 903 | if(device_id != NULL) { 904 | python_file_path += device_id; 905 | python_command += device_id; 906 | } 907 | python_file_path += ".py"; 908 | 909 | replace_substring(cmds, "{python_command}", python_command); 910 | replace_substring(cmds, "{python_file_path}", python_file_path); 911 | 912 | 913 | string prep_cmds_path = PREP_CMDS_PATH; 914 | if(device_id != NULL) 915 | prep_cmds_path += device_id; 916 | FILE *out = fopen(prep_cmds_path.c_str(), "w"); 917 | fwrite(cmds.c_str(), cmds.size(), 1, out); 918 | 919 | // Write additional commands based on mode we're running in 920 | const char *extra_cmds; 921 | if (!interactive) 922 | { 923 | if (justlaunch) 924 | extra_cmds = lldb_prep_noninteractive_justlaunch_cmds; 925 | else 926 | extra_cmds = lldb_prep_noninteractive_cmds; 927 | } 928 | else if (nostart) 929 | extra_cmds = lldb_prep_no_cmds; 930 | else 931 | extra_cmds = lldb_prep_interactive_cmds; 932 | fwrite(extra_cmds, strlen(extra_cmds), 1, out); 933 | fclose(out); 934 | 935 | 936 | out = fopen(python_file_path.c_str(), "w"); 937 | fwrite(pmodule.c_str(), pmodule.size(), 1, out); 938 | fclose(out); 939 | } 940 | 941 | #if 0 942 | 943 | CFSocketRef server_socket; 944 | CFSocketRef lldb_socket; 945 | CFWriteStreamRef serverWriteStream = NULL; 946 | CFWriteStreamRef lldbWriteStream = NULL; 947 | 948 | int kill_ptree(pid_t root, int signum); 949 | void 950 | server_callback (CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) 951 | { 952 | int res; 953 | 954 | if (CFDataGetLength (data) == 0) { 955 | // FIXME: Close the socket 956 | //shutdown (CFSocketGetNative (lldb_socket), SHUT_RDWR); 957 | //close (CFSocketGetNative (lldb_socket)); 958 | CFSocketInvalidate(lldb_socket); 959 | CFSocketInvalidate(server_socket); 960 | int mypid = getpid(); 961 | assert((child != 0) && (child != mypid)); //child should not be here 962 | if ((parent != 0) && (parent == mypid) && (child != 0)) 963 | { 964 | if (verbose) 965 | { 966 | printf("Got an empty packet hence killing child (%d) tree\n", child); 967 | } 968 | kill_ptree(child, SIGHUP); 969 | } 970 | exit(exitcode_error); 971 | return; 972 | } 973 | res = write (CFSocketGetNative (lldb_socket), CFDataGetBytePtr (data), CFDataGetLength (data)); 974 | } 975 | 976 | void lldb_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) 977 | { 978 | //printf ("lldb: %s\n", CFDataGetBytePtr (data)); 979 | 980 | if (CFDataGetLength (data) == 0) 981 | return; 982 | write (gdbfd, CFDataGetBytePtr (data), CFDataGetLength (data)); 983 | } 984 | 985 | void fdvendor_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info) { 986 | CFSocketNativeHandle socket = (CFSocketNativeHandle)(*((CFSocketNativeHandle *)data)); 987 | 988 | assert (callbackType == kCFSocketAcceptCallBack); 989 | //PRINT ("callback!\n"); 990 | 991 | lldb_socket = CFSocketCreateWithNative(NULL, socket, kCFSocketDataCallBack, &lldb_callback, NULL); 992 | CFRunLoopAddSource(CFRunLoopGetMain(), CFSocketCreateRunLoopSource(NULL, lldb_socket, 0), kCFRunLoopCommonModes); 993 | } 994 | 995 | #endif 996 | #if 0 997 | #endif 998 | 999 | void kill_process(int exitstatus, void *arg) { 1000 | printf("In kill_process\n"); 1001 | (void)exitstatus; 1002 | pid_t pid = *(pid_t *)arg; 1003 | int st= kill(pid, SIGTERM); 1004 | printf("pid = %d\tst = %d\n", (int)pid, st); 1005 | perror("kill_process"); 1006 | } 1007 | 1008 | void start_remote_debug_server() { 1009 | int pid = fork(); 1010 | if (pid == 0) { 1011 | signal(SIGHUP, SIG_DFL); 1012 | signal(SIGLLDB, SIG_DFL); 1013 | child = getpid(); 1014 | debugserver = child; 1015 | 1016 | int status; 1017 | string portstr = int_to_string(port); 1018 | if (device_id) 1019 | status = execlp("idevicedebugserverproxy", "idevicedebugserverproxy", "--udid", device_id, portstr.c_str(), NULL); 1020 | else 1021 | status = execlp("idevicedebugserverproxy", "idevicedebugserverproxy", portstr.c_str(), NULL); 1022 | 1023 | if (status == -1) 1024 | perror("failed to exec debugserver"); 1025 | 1026 | // We might want to eventually determine if the debug server 1027 | // failed and act accordingly, rather than just ignore failure. 1028 | //kill(parent, SIGLLDB); 1029 | 1030 | // Pass lldb exit code 1031 | //_exit(0);// 1032 | _exit(WEXITSTATUS(status)); 1033 | } else if (pid > 0) { 1034 | on_exit(&kill_process, new pid_t(pid)); 1035 | debugserver = pid; 1036 | if (setpgid(debugserver, getpgid(0)) < 0) 1037 | perror("setpgid(debugserver)"); 1038 | } else { 1039 | perror("fork failed"); 1040 | exit(exitcode_error); 1041 | } 1042 | } 1043 | 1044 | #ifdef __APPLE__ // I guess this might also work on other BSDs? 1045 | 1046 | void kill_ptree_inner(pid_t root, int signum, struct kinfo_proc *kp, int kp_len) { 1047 | int i; 1048 | for (i = 0; i < kp_len; i++) { 1049 | if (kp[i].kp_eproc.e_ppid == root) { 1050 | kill_ptree_inner(kp[i].kp_proc.p_pid, signum, kp, kp_len); 1051 | } 1052 | } 1053 | if (root != getpid()) { 1054 | kill(root, signum); 1055 | } 1056 | } 1057 | 1058 | int kill_ptree(pid_t root, int signum) { 1059 | int mib[3]; 1060 | size_t len; 1061 | mib[0] = CTL_KERN; 1062 | mib[1] = KERN_PROC; 1063 | mib[2] = KERN_PROC_ALL; 1064 | if (sysctl(mib, 3, NULL, &len, NULL, 0) == -1) { 1065 | return -1; 1066 | } 1067 | 1068 | struct kinfo_proc *kp = calloc(1, len); 1069 | if (!kp) { 1070 | return -1; 1071 | } 1072 | 1073 | if (sysctl(mib, 3, kp, &len, NULL, 0) == -1) { 1074 | free(kp); 1075 | return -1; 1076 | } 1077 | 1078 | kill_ptree_inner(root, signum, kp, len / sizeof(struct kinfo_proc)); 1079 | 1080 | free(kp); 1081 | return 0; 1082 | } 1083 | 1084 | #else //ifdef __APPLE__ 1085 | #ifdef __linux__ 1086 | 1087 | int kill_ptree(pid_t root, int signum) { 1088 | fprintf(stderr, "TODO: Implement kill_ptree for linux.\n"); 1089 | return kill(root, signum); 1090 | //return kill(-root, signum); // Maybe this? 1091 | } 1092 | 1093 | #else //ifdef __linux 1094 | #error Unsupported platform. 1095 | #endif 1096 | #endif 1097 | 1098 | void killed(int signum) { 1099 | (void)signum; 1100 | // SIGKILL needed to kill lldb, probably a better way to do this. 1101 | kill(0, SIGKILL); 1102 | _exit(0); 1103 | } 1104 | 1105 | void lldb_finished_handler(int signum) 1106 | {printf("DQ: in lldb_finished_handler!\n"); 1107 | (void)signum; 1108 | int status = 0; 1109 | if (waitpid(child, &status, 0) == -1) 1110 | perror("waitpid failed"); 1111 | _exit(WEXITSTATUS(status)); 1112 | } 1113 | 1114 | void bring_process_to_foreground() { 1115 | if (setpgid(0, 0) == -1) 1116 | perror("setpgid failed"); 1117 | 1118 | signal(SIGTTOU, SIG_IGN); 1119 | if (tcsetpgrp(STDIN_FILENO, getpid()) == -1) 1120 | perror("tcsetpgrp failed"); 1121 | signal(SIGTTOU, SIG_DFL); 1122 | } 1123 | 1124 | void setup_dummy_pipe_on_stdin(int pfd[2]) { 1125 | if (pipe(pfd) == -1) 1126 | perror("pipe failed"); 1127 | if (dup2(pfd[0], STDIN_FILENO) == -1) 1128 | perror("dup2 failed"); 1129 | } 1130 | 1131 | void setup_lldb(idevice_t device, const char *url) { 1132 | const char *device_full_name; 1133 | 1134 | device_full_name = get_device_full_name(device); 1135 | 1136 | //device_interface_name = get_device_interface_name(device); 1137 | 1138 | //AMDeviceConnect(device); 1139 | //assert(AMDeviceIsPaired(device)); 1140 | //assert(AMDeviceValidatePairing(device) == 0); 1141 | //assert(AMDeviceStartSession(device) == 0); 1142 | 1143 | printf("------ Debug phase ------\n"); 1144 | 1145 | // if(AMDeviceGetInterfaceType(device) == 2) 1146 | // { 1147 | // printf("Cannot debug %s over %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding())); 1148 | // exit(0); 1149 | // } 1150 | 1151 | printf("Starting debug of %s connected through %s...\n", device_full_name, "USB"); 1152 | 1153 | mount_developer_image(device); // put debugserver on the device 1154 | start_remote_debug_server(); // start debugserver 1155 | write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file 1156 | 1157 | printf("[100%%] Connecting to remote debug server\n"); 1158 | printf("-------------------------\n"); 1159 | 1160 | setpgid(getpid(), 0); 1161 | signal(SIGHUP, killed); 1162 | signal(SIGINT, killed); 1163 | signal(SIGTERM, killed); 1164 | // Need this before fork to avoid race conditions. For child process we remove this right after fork. 1165 | signal(SIGLLDB, lldb_finished_handler); 1166 | 1167 | parent = getpid(); 1168 | } 1169 | 1170 | void launch_debugger(idevice_t device, char *url) { 1171 | setup_lldb(device, url); 1172 | 1173 | 1174 | int pid = fork(); 1175 | if (pid == 0) { 1176 | signal(SIGHUP, SIG_DFL); 1177 | signal(SIGLLDB, SIG_DFL); 1178 | child = getpid(); 1179 | 1180 | int pfd[2] = {-1, -1}; 1181 | if (isatty(STDIN_FILENO)) 1182 | // If we are running on a terminal, then we need to bring process to foreground for input 1183 | // to work correctly on lldb's end. 1184 | bring_process_to_foreground(); 1185 | else 1186 | // If lldb is running in a non terminal environment, then it freaks out spamming "^D" and 1187 | // "quit". It seems this is caused by read() on stdin returning EOF in lldb. To hack around 1188 | // this we setup a dummy pipe on stdin, so read() would block expecting "user's" input. 1189 | setup_dummy_pipe_on_stdin(pfd); 1190 | 1191 | char lldb_prep[400]; 1192 | sprintf(lldb_prep, PREP_CMDS_PATH); 1193 | if(device_id != NULL) 1194 | strcat(lldb_prep, device_id); 1195 | 1196 | printf("lldb_prep: %s\n", lldb_prep); 1197 | 1198 | const char *args[8] = { "ios-lldb", "-s", lldb_prep, NULL }; 1199 | int status = execvp("ios-lldb", (char* const*)args); 1200 | 1201 | if (status == -1) 1202 | perror("failed to exec lldb"); 1203 | 1204 | close(pfd[0]); 1205 | close(pfd[1]); 1206 | 1207 | // Notify parent we're exiting 1208 | kill(parent, SIGLLDB); 1209 | // Pass lldb exit code 1210 | _exit(WEXITSTATUS(status)); 1211 | } else if (pid > 0) { 1212 | child = pid; 1213 | int status = -1; 1214 | waitpid(child, &status, 0); 1215 | printf("child exited with status %d\n", status); 1216 | } else { 1217 | perror("fork failed"); 1218 | exit(exitcode_error); 1219 | } 1220 | } 1221 | 1222 | void launch_debugger_and_exit(idevice_t device, char *url) { 1223 | setup_lldb(device,url); 1224 | int pfd[2] = {-1, -1}; 1225 | if (pipe(pfd) == -1) 1226 | perror("Pipe failed"); 1227 | int pid = fork(); 1228 | if (pid == 0) { 1229 | signal(SIGHUP, SIG_DFL); 1230 | signal(SIGLLDB, SIG_DFL); 1231 | child = getpid(); 1232 | 1233 | if (dup2(pfd[0],STDIN_FILENO) == -1) 1234 | perror("dup2 failed"); 1235 | 1236 | char lldb_prep[400]; 1237 | sprintf(lldb_prep, PREP_CMDS_PATH); 1238 | if(device_id != NULL) 1239 | strcat(lldb_prep, device_id); 1240 | 1241 | printf("lldb_prep: %s\n", lldb_prep); 1242 | 1243 | const char *args[8] = { "ios-lldb", "-s", lldb_prep, NULL }; 1244 | int status = execvp("ios-lldb", (char* const*)args); 1245 | 1246 | close(pfd[0]); 1247 | 1248 | // Notify parent we're exiting 1249 | kill(parent, SIGLLDB); 1250 | // Pass lldb exit code 1251 | _exit(WEXITSTATUS(status)); 1252 | } else if (pid > 0) { 1253 | child = pid; 1254 | if (verbose) 1255 | printf("Waiting for child [Child: %d][Parent: %d]\n", child, parent); 1256 | int status = -1; 1257 | waitpid(child, &status, 0); 1258 | } else { 1259 | perror("fork failed"); 1260 | exit(exitcode_error); 1261 | } 1262 | } 1263 | 1264 | #if 0 1265 | 1266 | void launch_debugger_and_exit(AMDeviceRef device, CFURLRef url) { 1267 | setup_lldb(device,url); 1268 | int pfd[2] = {-1, -1}; 1269 | if (pipe(pfd) == -1) 1270 | perror("Pipe failed"); 1271 | int pid = fork(); 1272 | if (pid == 0) { 1273 | signal(SIGHUP, SIG_DFL); 1274 | signal(SIGLLDB, SIG_DFL); 1275 | child = getpid(); 1276 | 1277 | if (dup2(pfd[0],STDIN_FILENO) == -1) 1278 | perror("dup2 failed"); 1279 | 1280 | char lldb_shell[400]; 1281 | sprintf(lldb_shell, LLDB_SHELL); 1282 | if(device_id != NULL) 1283 | strcat(lldb_shell, device_id); 1284 | 1285 | int status = system(lldb_shell); // launch lldb 1286 | if (status == -1) 1287 | perror("failed launching lldb"); 1288 | 1289 | close(pfd[0]); 1290 | 1291 | // Notify parent we're exiting 1292 | kill(parent, SIGLLDB); 1293 | // Pass lldb exit code 1294 | _exit(WEXITSTATUS(status)); 1295 | } else if (pid > 0) { 1296 | child = pid; 1297 | if (verbose) 1298 | printf("Waiting for child [Child: %d][Parent: %d]\n", child, parent); 1299 | } else { 1300 | perror("fork failed"); 1301 | exit(exitcode_error); 1302 | } 1303 | } 1304 | 1305 | #endif 1306 | 1307 | plist_t plist_from_path(const char *path) 1308 | { 1309 | char buf[4096]; 1310 | FILE *file; 1311 | vector data; 1312 | size_t read; 1313 | plist_t root = NULL; 1314 | 1315 | if (!(file = fopen(path, "r"))) 1316 | return NULL; 1317 | 1318 | while ((read = fread(buf, 1, sizeof(buf), file))) 1319 | data.insert(data.end(), buf, &buf[read]); 1320 | assert(!ferror(file)); 1321 | fclose(file); 1322 | 1323 | if (!data.size()) 1324 | return NULL; 1325 | 1326 | if (data[0] == '<') 1327 | plist_from_xml(data.data(), data.size(), &root); 1328 | else 1329 | plist_from_bin(data.data(), data.size(), &root); 1330 | 1331 | return root; 1332 | } 1333 | 1334 | char* get_bundle_id(const char *app_url) 1335 | { 1336 | if (app_url == NULL) 1337 | return NULL; 1338 | 1339 | string url_s = string(app_url) + "/Info.plist"; 1340 | const char *url = url_s.c_str(); 1341 | plist_t infoplist, bundle_id; 1342 | char *value = NULL; 1343 | 1344 | infoplist = plist_from_path(url); 1345 | 1346 | if (infoplist == NULL) 1347 | return NULL; 1348 | 1349 | bundle_id = plist_dict_get_item(infoplist, "CFBundleIdentifier"); 1350 | if (!bundle_id) 1351 | return NULL; 1352 | 1353 | plist_get_string_val(bundle_id, &value); 1354 | 1355 | return value; 1356 | } 1357 | 1358 | #if 1 1359 | 1360 | typedef char **afc_dictionary_t; 1361 | 1362 | // Returns 0 if good, returns 1 if no more key/value pairs. 1363 | int afc_key_value_read(afc_dictionary_t dict, char **key, char **value) { 1364 | static afc_dictionary_t cur_dict = NULL; 1365 | static afc_dictionary_t cur_ptr = NULL; 1366 | 1367 | if (!dict) { 1368 | cur_dict = cur_ptr = NULL; 1369 | } 1370 | 1371 | *key = NULL; 1372 | *value = NULL; 1373 | 1374 | if (dict != cur_dict) { 1375 | cur_dict = dict; 1376 | cur_ptr = dict; 1377 | } 1378 | 1379 | if (*cur_ptr) { 1380 | *key = *cur_ptr; 1381 | cur_ptr++; 1382 | *value = *cur_ptr; 1383 | cur_ptr++; 1384 | return 0; 1385 | } 1386 | 1387 | return 1; 1388 | } 1389 | 1390 | void read_dir(house_arrest_client_t afcFd, afc_client_t afc_conn_p, const char* dir, 1391 | void(*callback)(afc_client_t conn,const char *dir,int file)) 1392 | { 1393 | char *dir_ent = NULL; 1394 | 1395 | if (!afc_conn_p) { 1396 | printf("================ !afc_conn_p. What? =================\n"); 1397 | // afc_conn_p = &afc_conn; 1398 | // AFCConnectionOpen(afcFd, 0, &afc_conn_p); 1399 | } 1400 | 1401 | printf("%s\n", dir); 1402 | 1403 | afc_dictionary_t afc_dict = NULL; 1404 | char *key, *val; 1405 | int not_dir = 0; 1406 | 1407 | unsigned int code = (unsigned int)afc_get_file_info(afc_conn_p, dir, &afc_dict); 1408 | if (code != 0) { 1409 | // there was a problem reading or opening the file to get info on it, abort 1410 | return; 1411 | } 1412 | 1413 | while((afc_key_value_read(afc_dict,&key,&val) == 0) && key && val) { 1414 | if (strcmp(key,"st_ifmt")==0) { 1415 | not_dir = strcmp(val,"S_IFDIR"); 1416 | break; 1417 | } 1418 | } 1419 | 1420 | afc_dictionary_free(afc_dict); 1421 | 1422 | if (not_dir) { 1423 | if (callback) (*callback)(afc_conn_p, dir, not_dir); 1424 | return; 1425 | } 1426 | 1427 | char **afc_dir_p = NULL; 1428 | afc_error_t err = afc_read_directory(afc_conn_p, dir, &afc_dir_p); 1429 | 1430 | if (err != 0) { 1431 | // Couldn't open dir - was probably a file 1432 | // dquesada: Assume the entry is actually a file. 1433 | if (err == AFC_E_READ_ERROR) { 1434 | if (callback) (*callback)(afc_conn_p, dir, 1); 1435 | } 1436 | return; 1437 | } else { 1438 | if (callback) (*callback)(afc_conn_p, dir, not_dir); 1439 | } 1440 | 1441 | char **dir_p = afc_dir_p; 1442 | while(true) { 1443 | dir_ent = *dir_p++; 1444 | if (!dir_ent) 1445 | break; 1446 | 1447 | if (strcmp(dir_ent, ".") == 0 || strcmp(dir_ent, "..") == 0) 1448 | continue; 1449 | 1450 | char* dir_joined = (char*)malloc(strlen(dir) + strlen(dir_ent) + 2); 1451 | strcpy(dir_joined, dir); 1452 | if (dir_joined[strlen(dir)-1] != '/') 1453 | strcat(dir_joined, "/"); 1454 | strcat(dir_joined, dir_ent); 1455 | read_dir(afcFd, afc_conn_p, dir_joined, callback); 1456 | free(dir_joined); 1457 | } 1458 | 1459 | // It's not a dictionary, but it frees the same way. 1460 | afc_dictionary_free(afc_dir_p); 1461 | } 1462 | 1463 | #endif 1464 | 1465 | // Used to send files to app-specific sandbox (Documents dir) 1466 | house_arrest_client_t start_house_arrest_service(idevice_t device) { 1467 | house_arrest_client_t house = NULL; 1468 | plist_t result = NULL; 1469 | 1470 | if (bundle_id == NULL) { 1471 | printf("Bundle id is not specified\n"); 1472 | exit(1); 1473 | } 1474 | 1475 | if (house_arrest_client_start_service(device, &house, "ios-deploy") != HOUSE_ARREST_E_SUCCESS) { 1476 | printf("failed to start house_arrest service.\n"); 1477 | exit(1); 1478 | } 1479 | 1480 | if (house_arrest_send_command(house, "VendDocuments", bundle_id) != HOUSE_ARREST_E_SUCCESS) { 1481 | // if (house_arrest_send_command(house, "VendContainer", bundle_id) != HOUSE_ARREST_E_SUCCESS) { 1482 | printf("failed to command VendDocuments.\n"); 1483 | exit(1); 1484 | } 1485 | 1486 | if (house_arrest_get_result(house, &result) != HOUSE_ARREST_E_SUCCESS 1487 | || !result) { 1488 | printf("failed to get house_arrest result.\n"); 1489 | exit(1); 1490 | } 1491 | 1492 | char *status, *error; 1493 | status = get_plist_string_value(result, "Status"); 1494 | error = get_plist_string_value(result, "Error"); 1495 | 1496 | if (error || !status || (status && strcmp(status, "Complete"))) { 1497 | //if (error && !strcmp(error, "InstallationLookupFailed")) 1498 | printf("Failed to find app with bundle id %s\n", bundle_id); 1499 | exit(1); 1500 | } 1501 | 1502 | if (result) 1503 | plist_free(result); 1504 | 1505 | return house; 1506 | } 1507 | 1508 | char* get_filename_from_path(char* path) 1509 | { 1510 | char *ptr = path + strlen(path); 1511 | while (ptr > path) 1512 | { 1513 | if (*ptr == '/') 1514 | break; 1515 | --ptr; 1516 | } 1517 | if (ptr+1 >= path+strlen(path)) 1518 | return NULL; 1519 | if (ptr == path) 1520 | return ptr; 1521 | return ptr+1; 1522 | } 1523 | 1524 | void* read_file_to_memory(char * path, size_t* file_size) 1525 | { 1526 | struct stat buf; 1527 | int err = stat(path, &buf); 1528 | if (err < 0) 1529 | { 1530 | return NULL; 1531 | } 1532 | 1533 | *file_size = buf.st_size; 1534 | FILE* fd = fopen(path, "r"); 1535 | char* content = (char*)malloc(*file_size); 1536 | if (fread(content, *file_size, 1, fd) != 1) 1537 | { 1538 | fclose(fd); 1539 | return NULL; 1540 | } 1541 | fclose(fd); 1542 | return content; 1543 | } 1544 | 1545 | void list_files(idevice_t device) 1546 | { 1547 | house_arrest_client_t house = NULL; 1548 | afc_client_t afc = NULL; 1549 | 1550 | house = start_house_arrest_service(device); 1551 | 1552 | if (afc_client_new_from_house_arrest_client(house, &afc) == AFC_E_SUCCESS) { 1553 | read_dir(house, afc, list_root?list_root:"/", NULL); 1554 | afc_client_free(afc); 1555 | } 1556 | 1557 | if (house) 1558 | house_arrest_client_free(house); 1559 | } 1560 | 1561 | void copy_file_callback(afc_client_t afc_conn_p, const char *name,int file) 1562 | { 1563 | typedef uint64_t afc_file_ref; 1564 | 1565 | const char *local_name=name; 1566 | 1567 | if (*local_name=='/') local_name++; 1568 | 1569 | if (*local_name=='\0') return; 1570 | 1571 | if (file) { 1572 | afc_file_ref fref; 1573 | int err = afc_file_open(afc_conn_p,name,AFC_FOPEN_RDONLY,&fref); 1574 | 1575 | if (err) { 1576 | fprintf(stderr,"afc_file_open(\"%s\") failed: %d\n",name,err); 1577 | return; 1578 | } 1579 | 1580 | FILE *fp = fopen(local_name,"w"); 1581 | 1582 | if (fp==NULL) { 1583 | fprintf(stderr,"fopen(\"%s\",\"w\") failer: %s\n",local_name,strerror(errno)); 1584 | afc_file_close(afc_conn_p,fref); 1585 | return; 1586 | } 1587 | 1588 | char buf[4096]; 1589 | uint32_t sz=sizeof(buf); 1590 | 1591 | while (afc_file_read(afc_conn_p,fref,buf,sz,&sz)==AFC_E_SUCCESS && sz) { 1592 | fwrite(buf,(size_t)sz,1,fp); 1593 | sz = sizeof(buf); 1594 | } 1595 | 1596 | afc_file_close(afc_conn_p,fref); 1597 | fclose(fp); 1598 | } else { 1599 | if (mkdir(local_name,0777) && errno!=EEXIST) 1600 | fprintf(stderr,"mkdir(\"%s\") failed: %s\n",local_name,strerror(errno)); 1601 | } 1602 | } 1603 | 1604 | void mkdirhier(char *path) 1605 | { 1606 | char *slash; 1607 | struct stat buf; 1608 | 1609 | if (path[0]=='.' && path[1]=='/') path+=2; 1610 | 1611 | if ((slash = strrchr(path,'/'))) { 1612 | *slash = '\0'; 1613 | if (stat(path,&buf)==0) { 1614 | *slash = '/'; 1615 | return; 1616 | } 1617 | mkdirhier(path); 1618 | mkdir (path,0777); 1619 | *slash = '/'; 1620 | } 1621 | 1622 | return; 1623 | } 1624 | 1625 | void download_tree(idevice_t device) 1626 | { 1627 | house_arrest_client_t house = start_house_arrest_service(device); 1628 | afc_client_t afc_conn_p = NULL; 1629 | char *dirname = NULL; 1630 | 1631 | if (house && afc_client_new_from_house_arrest_client(house, &afc_conn_p) == 0) do { 1632 | 1633 | if (target_filename) { 1634 | dirname = strdup(target_filename); 1635 | mkdirhier(dirname); 1636 | if (mkdir(dirname,0777) && errno!=EEXIST) { 1637 | fprintf(stderr,"mkdir(\"%s\") failed: %s\n",dirname,strerror(errno)); 1638 | break; 1639 | } 1640 | if (chdir(dirname)) { 1641 | fprintf(stderr,"chdir(\"%s\") failed: %s\n",dirname,strerror(errno)); 1642 | break; 1643 | } 1644 | } 1645 | 1646 | read_dir(house, afc_conn_p, list_root?list_root:"/", copy_file_callback); 1647 | 1648 | } while(0); 1649 | 1650 | if (dirname) free(dirname); 1651 | if (afc_conn_p) afc_client_free(afc_conn_p); 1652 | if (house) house_arrest_client_free(house); 1653 | } 1654 | 1655 | void upload_file(idevice_t device) { 1656 | typedef uint64_t afc_file_ref; 1657 | 1658 | house_arrest_client_t house = start_house_arrest_service(device); 1659 | 1660 | afc_file_ref file_ref = 0; 1661 | 1662 | afc_client_t afc_conn_p = NULL; 1663 | afc_client_new_from_house_arrest_client(house, &afc_conn_p); 1664 | 1665 | // read_dir(houseFd, NULL, "/", NULL); 1666 | 1667 | if (!target_filename) 1668 | { 1669 | target_filename = get_filename_from_path(upload_pathname); 1670 | } 1671 | 1672 | uint32_t file_size, size_written = 0; 1673 | void* file_content = read_file_to_memory(upload_pathname, (size_t*)&file_size); 1674 | 1675 | if (!file_content) 1676 | { 1677 | printf("Could not open file: %s\n", upload_pathname); 1678 | exit(-1); 1679 | } 1680 | 1681 | // Make sure the directory was created 1682 | { 1683 | char *dirpath = strdup(target_filename); 1684 | char *c = dirpath, *lastSlash = dirpath; 1685 | while(*c) { 1686 | if(*c == '/') { 1687 | lastSlash = c; 1688 | } 1689 | c++; 1690 | } 1691 | *lastSlash = '\0'; 1692 | assert(afc_make_directory(afc_conn_p, dirpath) == AFC_E_SUCCESS); 1693 | } 1694 | 1695 | 1696 | int ret = afc_file_open(afc_conn_p, target_filename, (afc_file_mode_t)3, &file_ref); 1697 | if (ret == AFC_E_PERM_DENIED) { 1698 | printf("Cannot write to %s. Permission error.\n", target_filename); 1699 | exit(1); 1700 | } 1701 | if (ret == AFC_E_OBJECT_IS_DIR) { 1702 | printf("Target %s is a directory.\n", target_filename); 1703 | exit(1); 1704 | } 1705 | assert(ret == AFC_E_SUCCESS); 1706 | assert(afc_file_write(afc_conn_p, file_ref, (char *)file_content, file_size, &size_written) == AFC_E_SUCCESS); 1707 | assert(size_written == file_size); 1708 | assert(afc_file_close(afc_conn_p, file_ref) == AFC_E_SUCCESS); 1709 | assert(afc_client_free(afc_conn_p) == AFC_E_SUCCESS); 1710 | assert(house_arrest_client_free(house) == HOUSE_ARREST_E_SUCCESS); 1711 | 1712 | free(file_content); 1713 | } 1714 | 1715 | void handle_device(idevice_t device) { 1716 | //if (found_device) 1717 | // return; // handle one device only 1718 | 1719 | idevice_error_t err; 1720 | char *found_device_id = NULL; 1721 | const char *device_full_name = get_device_full_name(device), 1722 | *device_interface_name = get_device_interface_name(device); 1723 | 1724 | idevice_get_udid(device, &found_device_id); 1725 | 1726 | if (detect_only) { 1727 | printf("[....] Found %s connected through %s.\n", device_full_name, device_interface_name); 1728 | found_device = true; 1729 | return; 1730 | } 1731 | 1732 | 1733 | if (device_id != NULL) { 1734 | if(strcmp(device_id, found_device_id) == 0) { 1735 | found_device = true; 1736 | } else { 1737 | printf("Skipping %s.\n", device_full_name); 1738 | return; 1739 | } 1740 | } else { 1741 | device_id = found_device_id; 1742 | found_device = true; 1743 | } 1744 | 1745 | printf("[....] Using %s (%s).\n", device_full_name, found_device_id); 1746 | if ((err = idevice_new(&device, found_device_id)) != IDEVICE_E_SUCCESS) 1747 | printf("Failed to create device, error code %d\n", err); 1748 | 1749 | if (command_only) { 1750 | if (strcmp("list", command) == 0) { 1751 | list_files(device); 1752 | } else if (strcmp("upload", command) == 0) { 1753 | upload_file(device); 1754 | } else if (strcmp("download", command) == 0) { 1755 | download_tree(device); 1756 | } 1757 | exit(0); 1758 | } 1759 | 1760 | 1761 | // CFStringRef path = CFStringCreateWithCString(NULL, app_path, kCFStringEncodingASCII); 1762 | char *path = strdup(app_path); 1763 | //CFURLRef relative_url = CFURLCreateWithFileSystemPath(NULL, path, kCFURLPOSIXPathStyle, false); 1764 | char *relative_url = path; 1765 | char *url = realpath(relative_url, NULL); 1766 | 1767 | if (uninstall) { 1768 | printf("------ Uninstall phase ------\n"); 1769 | 1770 | char *bundle_id = get_bundle_id(url); 1771 | if (bundle_id == NULL) { 1772 | printf("Error: Unable to get bundle id from package %s\n Uninstall failed\n", app_path); 1773 | } else { 1774 | char *uninstallCommand = NULL; 1775 | int code; 1776 | 1777 | asprintf(&uninstallCommand, "ideviceinstaller --udid %s --uninstall %s", device_id, bundle_id); 1778 | 1779 | if (verbose) 1780 | printf("system: %s\n", uninstallCommand); 1781 | 1782 | code = system(uninstallCommand);//AMDeviceSecureUninstallApplication(0, device, bundle_id, 0, NULL, 0); 1783 | 1784 | free(uninstallCommand); 1785 | 1786 | if (code == 0) { 1787 | printf("[ OK ] Uninstalled package with bundle id %s\n", bundle_id); 1788 | } else { 1789 | printf("[ ERROR ] Could not uninstall package with bundle id %s\n", bundle_id); 1790 | } 1791 | } 1792 | } 1793 | 1794 | if(install) { 1795 | printf("------ Install phase ------\n"); 1796 | printf("[ 0%%] Found %s connected through %s, beginning install\n", device_full_name, device_interface_name); 1797 | 1798 | char *installCommand = NULL; 1799 | int installStatus; 1800 | 1801 | // Outsource the work to ideviceinstaller, rather than porting the 1802 | // code to do this from Apple's private API to libimobiledevice's. 1803 | asprintf(&installCommand, "ideviceinstaller --udid %s --install %s", device_id, url); 1804 | if (verbose) 1805 | printf("system: %s\n", installCommand); 1806 | 1807 | installStatus = system(installCommand); 1808 | 1809 | if (installStatus) 1810 | exit(installStatus); 1811 | 1812 | free(installCommand); 1813 | 1814 | printf("[100%%] Installed package %s\n", app_path); 1815 | } 1816 | 1817 | if (!debug) 1818 | exit(0); // no debug phase 1819 | 1820 | if (justlaunch) 1821 | launch_debugger_and_exit(device, url); 1822 | else 1823 | launch_debugger(device, url); 1824 | } 1825 | 1826 | void device_callback(const idevice_event_t *event, void *) { 1827 | idevice_t device = NULL; 1828 | switch (event->event) { 1829 | case IDEVICE_DEVICE_ADD: 1830 | if(device_id != NULL || !debug) { 1831 | idevice_new(&device, event->udid); 1832 | //handle_device(event->udid); 1833 | handle_device(device); 1834 | idevice_free(device); 1835 | } else if(best_device_match == NULL) { 1836 | best_device_match = strdup(event->udid); 1837 | } 1838 | default: 1839 | break; 1840 | } 1841 | } 1842 | 1843 | void timeout_callback() { 1844 | idevice_t device = NULL; 1845 | if ((!found_device) && (!detect_only)) { 1846 | if(best_device_match != NULL) { 1847 | //handle_device(best_device_match); 1848 | idevice_new(&device, best_device_match); 1849 | handle_device(device); 1850 | idevice_free(device); 1851 | 1852 | free(best_device_match); 1853 | best_device_match = NULL; 1854 | } 1855 | 1856 | if(!found_device) { 1857 | printf("[....] Timed out waiting for device.\n"); 1858 | exit(exitcode_error); 1859 | } 1860 | } 1861 | else 1862 | { 1863 | if (!debug) { 1864 | printf("[....] No more devices found.\n"); 1865 | } 1866 | 1867 | if (detect_only && !found_device) { 1868 | exit(exitcode_error); 1869 | return; 1870 | } else { 1871 | int mypid = getpid(); 1872 | if ((parent != 0) && (parent == mypid) && (child != 0)) 1873 | { 1874 | if (verbose) 1875 | { 1876 | printf("Timeout. Killing child (%d) tree\n", child); 1877 | } 1878 | kill_ptree(child, SIGHUP); 1879 | } 1880 | } 1881 | exit(0); 1882 | } 1883 | } 1884 | 1885 | void usage(const char* app) { 1886 | printf( 1887 | "Usage: %s [OPTION]...\n" 1888 | " -d, --debug launch the app in lldb after installation\n" 1889 | " -i, --id the id of the device to connect to\n" 1890 | " -c, --detect only detect if the device is connected\n" 1891 | " -b, --bundle the path to the app bundle to be installed\n" 1892 | " -a, --args command line arguments to pass to the app when launching it\n" 1893 | " -t, --timeout number of seconds to wait for a device to be connected\n" 1894 | " -u, --unbuffered don't buffer stdout\n" 1895 | " -n, --nostart do not start the app when debugging\n" 1896 | " -I, --noninteractive start in non interactive mode (quit when app crashes or exits)\n" 1897 | " -L, --justlaunch just launch the app and exit lldb\n" 1898 | " -v, --verbose enable verbose output\n" 1899 | " -m, --noinstall directly start debugging without app install (-d not required)\n" 1900 | " -p, --port port used for device, default: 12345 \n" 1901 | " -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) \n" 1902 | " -1, --bundle_id specify bundle id for list and upload\n" 1903 | " -l, --list list files\n" 1904 | " -o, --upload upload file\n" 1905 | " -w, --download download app tree\n" 1906 | " -2, --to use together with up/download file/tree. specify target\n" 1907 | " -V, --version print the executable version \n", 1908 | app); 1909 | } 1910 | 1911 | void show_version() { 1912 | printf("%s\n", APP_VERSION); 1913 | } 1914 | 1915 | int main(int argc, char *argv[]) { 1916 | static struct option longopts[] = { 1917 | { "debug", no_argument, NULL, 'd' }, 1918 | { "id", required_argument, NULL, 'i' }, 1919 | { "bundle", required_argument, NULL, 'b' }, 1920 | { "args", required_argument, NULL, 'a' }, 1921 | { "verbose", no_argument, NULL, 'v' }, 1922 | { "timeout", required_argument, NULL, 't' }, 1923 | { "unbuffered", no_argument, NULL, 'u' }, 1924 | { "nostart", no_argument, NULL, 'n' }, 1925 | { "noninteractive", no_argument, NULL, 'I' }, 1926 | { "justlaunch", no_argument, NULL, 'L' }, 1927 | { "detect", no_argument, NULL, 'c' }, 1928 | { "version", no_argument, NULL, 'V' }, 1929 | { "noinstall", no_argument, NULL, 'm' }, 1930 | { "port", required_argument, NULL, 'p' }, 1931 | { "uninstall", no_argument, NULL, 'r' }, 1932 | { "list", optional_argument, NULL, 'l' }, 1933 | { "bundle_id", required_argument, NULL, '1'}, 1934 | { "upload", required_argument, NULL, 'o'}, 1935 | { "download", optional_argument, NULL, 'w'}, 1936 | { "to", required_argument, NULL, '2'}, 1937 | { NULL, 0, NULL, 0 }, 1938 | }; 1939 | char ch; 1940 | 1941 | while ((ch = getopt_long(argc, argv, "VmcdvunrILi:b:a:t:g:x:p:1:2:o:l::w::", longopts, NULL)) != -1) 1942 | { 1943 | switch (ch) { 1944 | case 'm': 1945 | install = 0; 1946 | debug = 1; 1947 | break; 1948 | case 'd': 1949 | debug = 1; 1950 | break; 1951 | case 'i': 1952 | device_id = optarg; 1953 | break; 1954 | case 'b': 1955 | app_path = optarg; 1956 | break; 1957 | case 'a': 1958 | args = optarg; 1959 | break; 1960 | case 'v': 1961 | verbose = 1; 1962 | break; 1963 | case 't': 1964 | timeout = atoi(optarg); 1965 | break; 1966 | case 'u': 1967 | unbuffered = 1; 1968 | break; 1969 | case 'n': 1970 | nostart = 1; 1971 | break; 1972 | case 'I': 1973 | interactive = false; 1974 | break; 1975 | case 'L': 1976 | interactive = false; 1977 | justlaunch = true; 1978 | break; 1979 | case 'c': 1980 | detect_only = true; 1981 | debug = 1; 1982 | break; 1983 | case 'V': 1984 | show_version(); 1985 | return 0; 1986 | case 'p': 1987 | port = atoi(optarg); 1988 | break; 1989 | case 'r': 1990 | uninstall = 1; 1991 | break; 1992 | case '1': 1993 | bundle_id = optarg; 1994 | break; 1995 | case '2': 1996 | target_filename = optarg; 1997 | break; 1998 | case 'o': 1999 | command_only = true; 2000 | upload_pathname = optarg; 2001 | command = "upload"; 2002 | break; 2003 | case 'l': 2004 | command_only = true; 2005 | command = "list"; 2006 | list_root = optarg; 2007 | break; 2008 | case 'w': 2009 | command_only = true; 2010 | command = "download"; 2011 | list_root = optarg; 2012 | break; 2013 | default: 2014 | usage(argv[0]); 2015 | return exitcode_error; 2016 | } 2017 | } 2018 | 2019 | if (!app_path && !detect_only && !command_only) { 2020 | usage(argv[0]); 2021 | exit(exitcode_error); 2022 | } 2023 | 2024 | if (unbuffered) { 2025 | setbuf(stdout, NULL); 2026 | setbuf(stderr, NULL); 2027 | } 2028 | 2029 | if (detect_only && timeout == 0) { 2030 | timeout = 5; 2031 | } 2032 | 2033 | if (app_path) { 2034 | assert(access(app_path, F_OK) == 0); 2035 | } 2036 | 2037 | // if (timeout > 0) 2038 | // { 2039 | // CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + timeout, 0, 0, 0, timeout_callback, NULL); 2040 | // CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); 2041 | // printf("[....] Waiting up to %d seconds for iOS device to be connected\n", timeout); 2042 | // } 2043 | // else 2044 | // { 2045 | // printf("[....] Waiting for iOS device to be connected\n"); 2046 | // } 2047 | // 2048 | // struct am_device_notification *notify; 2049 | // AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, ¬ify); 2050 | // CFRunLoopRun(); 2051 | 2052 | // TODO: Handle timeout w/ libimobiledevice. 2053 | 2054 | char **devlist = NULL; 2055 | int devcount = 0; 2056 | if (idevice_get_device_list(&devlist, &devcount) < 0) 2057 | printf("Failed to get devlist\n"); 2058 | 2059 | for (int i = 0; i < devcount; ++i) { 2060 | idevice_event_t fake_event; 2061 | fake_event.event = IDEVICE_DEVICE_ADD; 2062 | fake_event.udid = devlist[i]; 2063 | fake_event.conn_type = 1; 2064 | device_callback(&fake_event, NULL); 2065 | } 2066 | if (!found_device) 2067 | timeout_callback(); 2068 | } 2069 | 2070 | --------------------------------------------------------------------------------