37 Commits
1.0.6 ... 1.2.0

Author SHA1 Message Date
Shazron Abdullah
c57f208422 Update version to 1.2.0 2014-10-06 22:09:39 -07:00
Shazron Abdullah
0467b69fd8 Merge pull request #66 from dot-asm/master
Fix AFCFileRef[Read|Write] prototypes
2014-10-06 21:56:59 -07:00
Shazron Abdullah
77116e3f7c Merge pull request #65 from alexmipego/lldb-python-connect-fix
Fix lldb status checking
2014-10-06 21:55:19 -07:00
Andy Polyakov
4589cd5d05 arrange callback in read_dir for future use
Custom callback is called for every file or directory listed on device. This will be used for file download.
2014-10-06 23:16:40 +02:00
Andy Polyakov
9a2da6f642 Fix usage semantic and pair of resource leaks
AFCConnectionOpen and AFCFIleInfoOpen don't require initialized pointer to pointer to result, but do require corresponding Close calls.
2014-10-06 23:02:06 +02:00
Andy Polyakov
23afce0d37 handle no --args
When no --args are passed application was started with {args} as argument instead of no argument.
2014-10-06 22:40:43 +02:00
Andy Polyakov
4869a343e8 fix AFCFileRef[Read|Write] prototypes
Incorrect AFCFileRefRead prototype was causing crashes in 64-bit build when reading large files.
2014-10-06 22:32:36 +02:00
Alexandre Gomes
bf5a1e065e fix lldb status checking 2014-10-06 13:36:31 +01:00
Shazron Abdullah
e2f436ad52 Merge pull request #57 from hinesmr/patch-1
Include iPods for device search in the README "Listing Device IDs" section.
2014-08-29 16:36:51 -07:00
Michael R. Hines
35097bd5a0 Don't forget about iPods too =)
Update the readme to include iPods in the device identifier search.
2014-08-19 19:51:20 +08:00
Shazron Abdullah
0f1ef6f4ef Fixes #54 - Publish 1.1.0 version 2014-08-04 19:07:31 -07:00
Shazron Abdullah
6801052024 Merge pull request #53 from senthilmanick/master
Fixes #51 - When the device is locked, ios-deploy hangs.
Fixes #52 - Enhancement: Add "Just launch" option
2014-08-04 18:56:51 -07:00
senthil
616b6ab1ff updated to version 1.0.10 2014-08-03 13:43:38 -04:00
senthil
55a12cb02e Fix for issues 51 and 52
51: Updated the python:run_command to look for : Locked.
52: Added option -L that is a variation of non-interactive
Added the constant with safequit and implemented the equivalent python
2014-08-03 12:56:17 -04:00
Shazron Abdullah
5f90ebb32b Fixes #50 - Publish 1.0.9 version 2014-08-01 15:20:24 -07:00
Shazron Abdullah
54a62bfdd0 Fixes #49 - Update README with new help text #49 2014-07-31 17:49:44 -07:00
Alexandre Gomes
3799a8a535 Implements #48 - Wifi Support & Checks
Signed-off-by: Shazron Abdullah <shazron@gmail.com>
2014-07-31 12:42:54 -07:00
Neo Alienson
e466aeac66 Implements #42 - Merge file listing feature from phildrip/fruitstrap.
Signed-off-by: Shazron Abdullah <shazron@gmail.com>
2014-07-30 16:26:04 -07:00
Shazron Abdullah
ffe40a0fac Merge pull request #43 from black-square/master
--args command supports arguments with spaces
2014-07-29 16:54:03 -07:00
Dmitriy Shesterkin
c5d206b72c Added an ability to pass arguments with space character to the application by using the command "--args":
./ios-deploy  ...  --args "\"TEST_FILTER=some text here\" arg2 arg3"
2014-06-27 14:53:57 -03:00
Shazron Abdullah
6fc2ee5e38 Updated npm version to 1.0.8 2014-05-12 18:09:05 -07:00
Shazron Abdullah
443abe4d38 Updated version to 1.0.8 2014-05-12 18:05:45 -07:00
Shazron Abdullah
42a7dbcc1f Updated README for --uninstall option. 2014-05-09 16:57:46 -07:00
Shazron Abdullah
4f9afb7499 Update version to 1.0.7 2014-05-09 16:53:41 -07:00
Shazron Abdullah
f7283216ef Merge pull request #35 from wjywbs/remove-app
Fixes #35 - Add uninstall feature.
2014-05-09 16:51:37 -07:00
wjywbs
cfb086ee44 Add uninstall feature. 2014-05-09 16:02:51 -04:00
Shazron Abdullah
9e52fd16d7 Added CONTRIBUTING file 2014-05-01 23:11:25 -07:00
Shazron Abdullah
6c42fd3cf9 Merge pull request #33 from Unity-Technologies/master
Fix for lldb's "^D" and "quit" spam when running without a terminal.
2014-04-29 17:35:50 -07:00
Julius Trinkunas
3ee1bca513 Stop lldb's "^D" and "quit" spam in a non terminal environment. 2014-04-29 14:33:00 +03:00
Julius Trinkunas
0a9418a564 Add friendly message for code-signing check fails. 2014-04-29 14:33:00 +03:00
Shazron Abdullah
72e83d06e1 Merge pull request #27 from oNaiPs/better_sdk_makefile
Better sdk makefile
2014-04-03 12:35:22 -07:00
Shazron Abdullah
bab1d5bded Merge pull request #26 from oNaiPs/fix_402653028_error
Added version to Info.plist file, fixes -402653028 error
2014-04-03 12:34:40 -07:00
Jose Pereira
f0d5d58a8b Separated sdk version from full path, easier to set after updates 2014-04-03 11:45:57 -07:00
Jose Pereira
8fc098a877 Added version to Info.plist file, fixes -402653028 error 2014-04-03 11:42:57 -07:00
Shazron Abdullah
bad5fe321b Merge pull request #25 from Unity-Technologies/master
Fix exiting and propagate app exit code in non interactive mode.
2014-04-02 10:35:05 -07:00
Julius Trinkunas
79c4cc5c6a Fix exiting and propagate app exit code in non interactive mode. 2014-04-01 16:20:14 +03:00
Shazron Abdullah
f54f67c418 Added the two new flags from 1.0.6 to the README 2014-03-27 15:32:19 -07:00
8 changed files with 694 additions and 89 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,6 @@
demo
demo.app
ios-deploy
/.DS_Store
ios-deploy.dSYM
/.DS_Store
*~

31
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,31 @@
## Contributing to ios-deploy
Github url:
https://github.com/phonegap/ios-deploy
Git clone url:
https://github.com/phonegap/ios-deploy.git
## Filing an issue
Please run the commands below in your Terminal.app and include it in the issue:
```
1. sw_vers -productVersion
2. ios-deploy -V
3. xcodebuild -version
4. xcode-select --print-path
5. gcc --version
6. lldb --version
```
Also include **command line arguments** you used for ios-deploy.
## Sending a Pull Request
Please **create a topic branch** for your issue before submitting your pull request. You will be asked to re-submit if your pull request contains unrelated commits.
Please elaborate regarding the problem the pull request is supposed to solve, and perhaps also link to any relevant issues the pull request is trying to fix.

View File

@@ -10,6 +10,8 @@
</array>
<key>CFBundleExecutable</key>
<string>demo</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CFBundleIdentifier</key>
<string>demo</string>
<key>CFBundleResourceSpecification</key>

View File

@@ -1,6 +1,8 @@
IOS_SDK_VERSION = 7.1
IOS_CC = gcc -ObjC
IOS_SDK = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.1.sdk
DEVICE_SUPPORT = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/DeviceSupport
IOS_SDK = $(shell xcode-select --print-path)/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(IOS_SDK_VERSION).sdk
all: clean ios-deploy

View File

@@ -396,11 +396,11 @@ afc_error_t AFCFileRefOpen(afc_connection *conn, const char *path,
afc_error_t AFCFileRefSeek(afc_connection *conn, afc_file_ref ref,
unsigned long long offset1, unsigned long long offset2);
afc_error_t AFCFileRefRead(afc_connection *conn, afc_file_ref ref,
void *buf, unsigned int *len);
void *buf, size_t *len);
afc_error_t AFCFileRefSetFileSize(afc_connection *conn, afc_file_ref ref,
unsigned long long offset);
afc_error_t AFCFileRefWrite(afc_connection *conn, afc_file_ref ref,
const void *buf, unsigned int len);
const void *buf, size_t len);
afc_error_t AFCFileRefClose(afc_connection *conn, afc_file_ref ref);
afc_error_t AFCFileInfoOpen(afc_connection *conn, const char *path, struct
@@ -453,6 +453,8 @@ typedef int (*am_device_install_application_callback)(CFDictionaryRef, int);
mach_error_t AMDeviceInstallApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callback, void *user);
mach_error_t AMDeviceTransferApplication(service_conn_t socket, CFStringRef path, CFDictionaryRef options, am_device_install_application_callback callbackj, void *user);
int AMDeviceSecureUninstallApplication(int unknown0, struct am_device *device, CFStringRef bundle_id, int unknown1, void *callback, int callback_arg);
/* ----------------------------------------------------------------------------
* Semi-private routines
* ------------------------------------------------------------------------- */
@@ -491,4 +493,4 @@ typedef unsigned int (*t_performOperation)(struct am_restore_device *rdev,
}
#endif
#endif
#endif

View File

@@ -10,21 +10,29 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro
## Usage
./ios-deploy [OPTION]...
-d, --debug launch the app in GDB after installation
-i, --id <device_id> the id of the device to connect to
-c, --detect only detect if the device is connected
-b, --bundle <bundle.app> the path to the app bundle to be installed
-a, --args <args> command line arguments to pass to the app when launching it
-t, --timeout <timeout> number of seconds to wait for a device to be connected
-u, --unbuffered don't buffer stdout
-g, --gdbargs <args> extra arguments to pass to GDB when starting the debugger
-x, --gdbexec <file> GDB commands script file
-n, --nostart do not start the app when debugging
-v, --verbose enable verbose output
-m, --noinstall directly start debugging without app install (-d not required)
-V, --version print the executable version
Usage: ./ios-deploy [OPTION]...
-d, --debug launch the app in GDB after installation
-i, --id <device_id> the id of the device to connect to
-c, --detect only detect if the device is connected
-b, --bundle <bundle.app> the path to the app bundle to be installed
-a, --args <args> command line arguments to pass to the app when launching it
-t, --timeout <timeout> number of seconds to wait for a device to be connected
-u, --unbuffered don't buffer stdout
-g, --gdbargs <args> extra arguments to pass to GDB when starting the debugger
-x, --gdbexec <file> GDB commands script file
-n, --nostart do not start the app when debugging
-I, --noninteractive start in non interactive mode (quit when app crashes or exits)
-L, --justlaunch just launch the app and exit lldb
-v, --verbose enable verbose output
-m, --noinstall directly start debugging without app install (-d not required)
-p, --port <number> port used for device, default: 12345
-r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared)
-1, --bundle_id <bundle id> specify bundle id for list and upload
-l, --list list files
-o, --upload <file> upload file
-2, --to <target pathname> use together with upload file. specify target for upload
-V, --version print the executable version
## Demo
* The included demo.app represents the minimum required to get code running on iOS.
@@ -40,4 +48,4 @@ Install and debug iPhone apps without using Xcode. Designed to work on unjailbro
Device Ids are the UDIDs of the iOS devices. From the command line, you can list device ids [this way](http://javierhz.blogspot.com/2012/06/how-to-get-udid-of-iphone-using-shell.html):
system_profiler SPUSBDataType | sed -n -e '/iPad/,/Serial/p' -e '/iPhone/,/Serial/p' | grep "Serial Number:" | awk -F ": " '{print $2}'
system_profiler SPUSBDataType | sed -n -e '/iPod/,/Serial/p' | sed -n -e '/iPad/,/Serial/p' -e '/iPhone/,/Serial/p' | grep "Serial Number:" | awk -F ": " '{print $2}'

View File

@@ -16,7 +16,7 @@
#include <netinet/tcp.h>
#include "MobileDevice.h"
#define APP_VERSION "1.0.6"
#define APP_VERSION "1.2.0"
#define PREP_CMDS_PATH "/tmp/fruitstrap-lldb-prep-cmds-"
#define LLDB_SHELL "lldb -s " PREP_CMDS_PATH
@@ -34,6 +34,8 @@
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\
")
@@ -43,11 +45,15 @@ const char* lldb_prep_interactive_cmds = "\
run\n\
";
const char* lldb_prep_noninteractive_cmds = "\
settings set -- auto-confirm true\n\
target stop-hook add --one-liner \"bt\"\n\
target stop-hook add --one-liner \"quit\"\n\
const char* lldb_prep_noninteractive_justlaunch_cmds = "\
run\n\
detach\n\
safequit\n\
";
const char* lldb_prep_noninteractive_cmds = "\
run\n\
autoexit\n\
";
/*
@@ -57,6 +63,8 @@ const char* lldb_prep_noninteractive_cmds = "\
*/
#define LLDB_FRUITSTRAP_MODULE CFSTR("\
import lldb\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\
@@ -69,7 +77,7 @@ def connect_command(debugger, command, result, internal_dict):\n\
listener = lldb.target.GetDebugger().GetListener()\n\
listener.StartListeningForEvents(process.GetBroadcaster(), lldb.SBProcess.eBroadcastBitStateChanged)\n\
events = []\n\
state = lldb.eStateInvalid\n\
state = (process.GetState() or lldb.eStateInvalid)\n\
while state != lldb.eStateConnected:\n\
event = lldb.SBEvent()\n\
if listener.WaitForEvent(1, event):\n\
@@ -86,18 +94,63 @@ def run_command(debugger, command, result, internal_dict):\n\
device_app = internal_dict['fruitstrap_device_app']\n\
error = lldb.SBError()\n\
lldb.target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_app))\n\
lldb.target.Launch(lldb.SBLaunchInfo(['{args}']), error)\n\
print str(error)\n\
lldb.target.Launch(lldb.SBLaunchInfo(shlex.split('{args}')), error)\n\
lockedstr = ': Locked'\n\
if lockedstr in str(error):\n\
print('\\nDevice Locked\\n')\n\
sys.exit(254)\n\
else:\n\
print(str(error))\n\
\n\
def safequit_command(debugger, command, result, internal_dict):\n\
sys.exit(0);\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):\n\
state = process.GetStateFromEvent(event)\n\
else:\n\
state = lldb.eStateInvalid\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\
\n\
if lldb.SBProcess.EventIsProcessEvent(event):\n\
if state == lldb.eStateExited:\n\
sys.exit(process.GetExitStatus())\n\
if state == lldb.eStateStopped:\n\
debugger.HandleCommand('frame select')\n\
debugger.HandleCommand('bt')\n\
sys.exit({exitcode_app_crash})\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;
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;
@@ -106,6 +159,16 @@ int port = 12345;
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()) {
@@ -138,7 +201,7 @@ CFStringRef find_path(CFStringRef rootPath, CFStringRef namePattern, CFStringRef
if (!(fpipe = (FILE *)popen(command, "r")))
{
perror("Error encountered while opening pipe");
exit(EXIT_FAILURE);
exit(exitcode_error);
}
char buffer[256] = { '\0' };
@@ -163,7 +226,7 @@ CFStringRef copy_xcode_dev_path() {
if (!(fpipe = (FILE *)popen(command, "r")))
{
perror("Error encountered while opening pipe");
exit(EXIT_FAILURE);
exit(exitcode_error);
}
char buffer[256] = { '\0' };
@@ -224,6 +287,125 @@ CFStringRef copy_xcode_path_for(CFStringRef subPath, CFStringRef search) {
}
}
// 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"));
const char *hwmodel = CFStringGetCStringPtr(model, CFStringGetSystemEncoding());
if (hwmodel && !strcmp("M68AP", hwmodel))
return CFSTR("iPhone");
if (hwmodel && !strcmp("N45AP", hwmodel))
return CFSTR("iPod touch");
if (hwmodel && !strcmp("N82AP", hwmodel))
return CFSTR("iPhone 3G");
if (hwmodel && !strcmp("N72AP", hwmodel))
return CFSTR("iPod touch 2G");
if (hwmodel && !strcmp("N88AP", hwmodel))
return CFSTR("iPhone 3GS");
if (hwmodel && !strcmp("N18AP", hwmodel))
return CFSTR("iPod touch 3G");
if (hwmodel && !strcmp("K48AP", hwmodel))
return CFSTR("iPad");
if (hwmodel && !strcmp("N90AP", hwmodel))
return CFSTR("iPhone 4 (GSM)");
if (hwmodel && !strcmp("N81AP", hwmodel))
return CFSTR("iPod touch 4G");
if (hwmodel && !strcmp("K66AP", hwmodel))
return CFSTR("Apple TV 2G");
if (hwmodel && !strcmp("N92AP", hwmodel))
return CFSTR("iPhone 4 (CDMA)");
if (hwmodel && !strcmp("N90BAP", hwmodel))
return CFSTR("iPhone 4 (GSM, revision A)");
if (hwmodel && !strcmp("K93AP", hwmodel))
return CFSTR("iPad 2");
if (hwmodel && !strcmp("K94AP", hwmodel))
return CFSTR("iPad 2 (GSM)");
if (hwmodel && !strcmp("K95AP", hwmodel))
return CFSTR("iPad 2 (CDMA)");
if (hwmodel && !strcmp("K93AAP", hwmodel))
return CFSTR("iPad 2 (Wi-Fi, revision A)");
if (hwmodel && !strcmp("P105AP", hwmodel))
return CFSTR("iPad mini");
if (hwmodel && !strcmp("P106AP", hwmodel))
return CFSTR("iPad mini (GSM)");
if (hwmodel && !strcmp("P107AP", hwmodel))
return CFSTR("iPad mini (CDMA)");
if (hwmodel && !strcmp("N94AP", hwmodel))
return CFSTR("iPhone 4S");
if (hwmodel && !strcmp("N41AP", hwmodel))
return CFSTR("iPhone 5 (GSM)");
if (hwmodel && !strcmp("N42AP", hwmodel))
return CFSTR("iPhone 5 (Global/CDMA)");
if (hwmodel && !strcmp("N48AP", hwmodel))
return CFSTR("iPhone 5c (GSM)");
if (hwmodel && !strcmp("N49AP", hwmodel))
return CFSTR("iPhone 5c (Global/CDMA)");
if (hwmodel && !strcmp("N51AP", hwmodel))
return CFSTR("iPhone 5s (GSM)");
if (hwmodel && !strcmp("N53AP", hwmodel))
return CFSTR("iPhone 5s (Global/CDMA)");
if (hwmodel && !strcmp("J1AP", hwmodel))
return CFSTR("iPad 3");
if (hwmodel && !strcmp("J2AP", hwmodel))
return CFSTR("iPad 3 (GSM)");
if (hwmodel && !strcmp("J2AAP", hwmodel))
return CFSTR("iPad 3 (CDMA)");
if (hwmodel && !strcmp("P101AP", hwmodel))
return CFSTR("iPad 4");
if (hwmodel && !strcmp("P102AP", hwmodel))
return CFSTR("iPad 4 (GSM)");
if (hwmodel && !strcmp("P103AP", hwmodel))
return CFSTR("iPad 4 (CDMA)");
if (hwmodel && !strcmp("N78AP", hwmodel))
return CFSTR("iPod touch 5G");
if (hwmodel && !strcmp("J33AP", hwmodel))
return CFSTR("Apple TV 3G");
if (hwmodel && !strcmp("J33IAP", hwmodel))
return CFSTR("Apple TV 3.1G");
return CFSTR("Unknown Device");
}
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(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("(%@)"), device_udid);
AMDeviceDisconnect(device);
if(device_udid != NULL)
CFRelease(device_udid);
if(device_name != NULL)
CFRelease(device_name);
if(model_name != NULL)
CFRelease(model_name);
return full_name;
}
CFStringRef get_device_interface_name(const AMDeviceRef device) {
// AMDeviceGetInterfaceType(device) 0=Unknown, 1 = Direct/USB, 2 = Indirect/WIFI
switch(AMDeviceGetInterfaceType(device)) {
case 1:
return CFSTR("USB");
case 2:
return CFSTR("WIFI");
default:
return CFSTR("Unknown Connection");
}
}
CFMutableArrayRef get_device_product_version_parts(AMDeviceRef device) {
CFStringRef version = AMDeviceCopyValue(device, 0, CFSTR("ProductVersion"));
CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, version, CFSTR("."));
@@ -269,7 +451,7 @@ CFStringRef copy_device_support_path(AMDeviceRef device) {
if (path == NULL)
{
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");
exit(1);
exit(exitcode_error);
}
return path;
@@ -294,7 +476,7 @@ CFStringRef copy_developer_disk_image_path(CFStringRef deviceSupportPath) {
if (path == NULL)
{
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");
exit(1);
exit(exitcode_error);
}
return path;
@@ -342,7 +524,7 @@ void mount_developer_image(AMDeviceRef device) {
printf("[ 95%%] Developer disk image already mounted\n");
} else {
printf("[ !! ] Unable to mount developer disk image. (%x)\n", result);
exit(1);
exit(exitcode_error);
}
CFRelease(image_path);
@@ -453,22 +635,21 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) {
range.length = CFStringGetLength(cmds);
CFMutableStringRef pmodule = CFStringCreateMutableCopy(NULL, 0, LLDB_FRUITSTRAP_MODULE);
CFRange rangeLLDB = { 0, CFStringGetLength(pmodule) };
CFStringRef exitcode_app_crash_str = CFStringCreateWithFormat(NULL, NULL, CFSTR("%d"), exitcode_app_crash);
CFStringFindAndReplace(pmodule, CFSTR("{exitcode_app_crash}"), exitcode_app_crash_str, rangeLLDB, 0);
rangeLLDB.length = CFStringGetLength(pmodule);
if (args) {
CFStringRef cf_args = CFStringCreateWithCString(NULL, args, kCFStringEncodingASCII);
CFStringFindAndReplace(cmds, CFSTR("{args}"), cf_args, range, 0);
//format the arguments 'arg1 arg2 ....' to an argument list ['arg1', 'arg2', ...]
CFMutableStringRef argsListLLDB = CFStringCreateMutableCopy(NULL, 0, cf_args);
CFRange rangeLLDB = { 0, CFStringGetLength(argsListLLDB) };
CFStringFindAndReplace(argsListLLDB, CFSTR(" "), CFSTR(" "), rangeLLDB, 0);//remove multiple spaces
rangeLLDB.length = CFStringGetLength(argsListLLDB);
CFStringFindAndReplace(argsListLLDB, CFSTR(" "), CFSTR("', '"), rangeLLDB, 0);
rangeLLDB.length = CFStringGetLength(pmodule);
CFStringFindAndReplace(pmodule, CFSTR("{args}"), argsListLLDB, rangeLLDB, 0);
CFStringFindAndReplace(pmodule, CFSTR("{args}"), cf_args, rangeLLDB, 0);
CFRelease(cf_args);
} else {
CFStringFindAndReplace(cmds, CFSTR(" {args}"), CFSTR(""), range, 0);
CFStringFindAndReplace(cmds, CFSTR("{args}"), CFSTR(""), range, 0);
CFStringFindAndReplace(pmodule, CFSTR("{args}"), CFSTR(""), rangeLLDB, 0);
}
range.length = CFStringGetLength(cmds);
@@ -523,7 +704,12 @@ void write_lldb_prep_cmds(AMDeviceRef device, CFURLRef disk_app_url) {
// Write additional commands based on mode we're running in
const char* extra_cmds;
if (!interactive)
extra_cmds = lldb_prep_noninteractive_cmds;
{
if (justlaunch)
extra_cmds = lldb_prep_noninteractive_justlaunch_cmds;
else
extra_cmds = lldb_prep_noninteractive_cmds;
}
else if (nostart)
extra_cmds = lldb_prep_no_cmds;
else
@@ -681,7 +867,10 @@ void killed(int signum) {
void lldb_finished_handler(int signum)
{
_exit(0);
int status = 0;
if (waitpid(child, &status, 0) == -1)
perror("waitpid failed");
_exit(WEXITSTATUS(status));
}
void bring_process_to_foreground() {
@@ -694,7 +883,17 @@ void bring_process_to_foreground() {
signal(SIGTTOU, SIG_DFL);
}
void setup_dummy_pipe_on_stdin(int pfd[2]) {
if (pipe(pfd) == -1)
perror("pipe failed");
if (dup2(pfd[0], STDIN_FILENO) == -1)
perror("dup2 failed");
}
void launch_debugger(AMDeviceRef device, CFURLRef url) {
CFStringRef device_full_name = get_device_full_name(device),
device_interface_name = get_device_interface_name(device);
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
@@ -702,6 +901,14 @@ void launch_debugger(AMDeviceRef device, CFURLRef url) {
printf("------ Debug phase ------\n");
if(AMDeviceGetInterfaceType(device) == 2)
{
printf("Cannot debug %s over %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
exit(0);
}
printf("Starting debug of %s connected through %s...\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
mount_developer_image(device); // put debugserver on the device
start_remote_debug_server(device); // start debugserver
write_lldb_prep_cmds(device, url); // dump the necessary lldb commands into a file
@@ -711,31 +918,293 @@ void launch_debugger(AMDeviceRef device, CFURLRef url) {
printf("[100%%] Connecting to remote debug server\n");
printf("-------------------------\n");
signal(SIGHUP, lldb_finished_handler);
setpgid(getpid(), 0);
signal(SIGHUP, killed);
signal(SIGINT, killed);
signal(SIGTERM, killed);
// Need this before fork to avoid race conditions. For child process we remove this right after fork.
signal(SIGLLDB, lldb_finished_handler);
parent = getpid();
int pid = fork();
if (pid == 0) {
bring_process_to_foreground();
signal(SIGHUP, SIG_DFL);
signal(SIGLLDB, SIG_DFL);
child = getpid();
int pfd[2] = {-1, -1};
if (isatty(STDIN_FILENO))
// If we are running on a terminal, then we need to bring process to foreground for input
// to work correctly on lldb's end.
bring_process_to_foreground();
else
// If lldb is running in a non terminal environment, then it freaks out spamming "^D" and
// "quit". It seems this is caused by read() on stdin returning EOF in lldb. To hack around
// this we setup a dummy pipe on stdin, so read() would block expecting "user's" input.
setup_dummy_pipe_on_stdin(pfd);
char lldb_shell[400];
sprintf(lldb_shell, LLDB_SHELL);
if(device_id != NULL)
strcat(lldb_shell, device_id);
system(lldb_shell); // launch lldb
kill(parent, SIGHUP); // "No. I am your father."
_exit(0);
int status = system(lldb_shell); // launch lldb
if (status == -1)
perror("failed launching lldb");
close(pfd[0]);
close(pfd[1]);
// Notify parent we're exiting
kill(parent, SIGLLDB);
// Pass lldb exit code
_exit(WEXITSTATUS(status));
} else if (pid > 0) {
child = pid;
} else {
perror("fork failed");
exit(exitcode_error);
}
}
CFStringRef get_bundle_id(CFURLRef app_url)
{
if (app_url == NULL)
return NULL;
CFURLRef url = CFURLCreateCopyAppendingPathComponent(NULL, app_url, CFSTR("Info.plist"), false);
if (url == NULL)
return NULL;
CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, url);
CFRelease(url);
if (stream == NULL)
return NULL;
CFPropertyListRef plist = NULL;
if (CFReadStreamOpen(stream) == TRUE) {
plist = CFPropertyListCreateWithStream(NULL, stream, 0,
kCFPropertyListImmutable, NULL, NULL);
}
CFReadStreamClose(stream);
CFRelease(stream);
if (plist == NULL)
return NULL;
const void *value = CFDictionaryGetValue(plist, CFSTR("CFBundleIdentifier"));
CFStringRef bundle_id = NULL;
if (value != NULL)
bundle_id = CFRetain(value);
CFRelease(plist);
return bundle_id;
}
void read_dir(service_conn_t afcFd, afc_connection* afc_conn_p, const char* dir,
void(*callback)(afc_connection *conn,const char *dir,int file))
{
char *dir_ent;
afc_connection afc_conn;
if (!afc_conn_p) {
afc_conn_p = &afc_conn;
AFCConnectionOpen(afcFd, 0, &afc_conn_p);
}
printf("%s\n", dir);
afc_dictionary* afc_dict_p;
char *key, *val;
int not_dir;
AFCFileInfoOpen(afc_conn_p, dir, &afc_dict_p);
while((AFCKeyValueRead(afc_dict_p,&key,&val) == 0) && key && val) {
if (strcmp(key,"st_ifmt")==0) {
not_dir = strcmp(val,"S_IFDIR");
break;
}
}
AFCKeyValueClose(afc_dict_p);
if (not_dir) {
if (callback) (*callback)(afc_conn_p, dir, not_dir);
return;
}
afc_directory* afc_dir_p;
afc_error_t err = AFCDirectoryOpen(afc_conn_p, dir, &afc_dir_p);
if (err != 0) {
// Couldn't open dir - was probably a file
return;
} else {
if (callback) (*callback)(afc_conn_p, dir, not_dir);
}
while(true) {
err = AFCDirectoryRead(afc_conn_p, afc_dir_p, &dir_ent);
if (err != 0 || !dir_ent)
break;
if (strcmp(dir_ent, ".") == 0 || strcmp(dir_ent, "..") == 0)
continue;
char* dir_joined = malloc(strlen(dir) + strlen(dir_ent) + 2);
strcpy(dir_joined, dir);
if (dir_joined[strlen(dir)-1] != '/')
strcat(dir_joined, "/");
strcat(dir_joined, dir_ent);
read_dir(afcFd, afc_conn_p, dir_joined, callback);
free(dir_joined);
}
AFCDirectoryClose(afc_conn_p, afc_dir_p);
}
// Used to send files to app-specific sandbox (Documents dir)
service_conn_t start_house_arrest_service(AMDeviceRef device) {
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
assert(AMDeviceStartSession(device) == 0);
service_conn_t houseFd;
if (bundle_id == NULL) {
printf("Bundle id is not specified\n");
exit(1);
}
CFStringRef cf_bundle_id = CFStringCreateWithCString(NULL, bundle_id, kCFStringEncodingASCII);
if (AMDeviceStartHouseArrestService(device, cf_bundle_id, 0, &houseFd, 0) != 0)
{
printf("Unable to find bundle with id: %s\n", bundle_id);
exit(1);
}
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
CFRelease(cf_bundle_id);
return houseFd;
}
char* get_filename_from_path(char* path)
{
char *ptr = path + strlen(path);
while (ptr > path)
{
if (*ptr == '/')
break;
--ptr;
}
if (ptr+1 >= path+strlen(path))
return NULL;
if (ptr == path)
return ptr;
return ptr+1;
}
void* read_file_to_memory(char * path, size_t* file_size)
{
struct stat buf;
int err = stat(path, &buf);
if (err < 0)
{
return NULL;
}
*file_size = buf.st_size;
FILE* fd = fopen(path, "r");
char* content = malloc(*file_size);
if (fread(content, *file_size, 1, fd) != 1)
{
fclose(fd);
return NULL;
}
fclose(fd);
return content;
}
void list_files(AMDeviceRef device)
{
service_conn_t houseFd = start_house_arrest_service(device);
afc_connection* afc_conn_p;
if (AFCConnectionOpen(houseFd, 0, &afc_conn_p) == 0) {
read_dir(houseFd, afc_conn_p, "/", NULL);
AFCConnectionClose(afc_conn_p);
}
}
void upload_file(AMDeviceRef device) {
service_conn_t houseFd = start_house_arrest_service(device);
afc_file_ref file_ref;
afc_connection afc_conn;
afc_connection* afc_conn_p = &afc_conn;
AFCConnectionOpen(houseFd, 0, &afc_conn_p);
// read_dir(houseFd, NULL, "/", NULL);
if (!target_filename)
{
target_filename = get_filename_from_path(upload_pathname);
}
size_t file_size;
void* file_content = read_file_to_memory(upload_pathname, &file_size);
if (!file_content)
{
printf("Could not open file: %s\n", upload_pathname);
exit(-1);
}
// Make sure the directory was created
{
char *dirpath = strdup(target_filename);
char *c = dirpath, *lastSlash = dirpath;
while(*c) {
if(*c == '/') {
lastSlash = c;
}
c++;
}
*lastSlash = '\0';
assert(AFCDirectoryCreate(afc_conn_p, dirpath) == 0);
}
int ret = AFCFileRefOpen(afc_conn_p, target_filename, 3, &file_ref);
if (ret == 0x000a) {
printf("Cannot write to %s. Permission error.\n", target_filename);
exit(1);
}
if (ret == 0x0009) {
printf("Target %s is a directory.\n", target_filename);
exit(1);
}
assert(ret == 0);
assert(AFCFileRefWrite(afc_conn_p, file_ref, file_content, file_size) == 0);
assert(AFCFileRefClose(afc_conn_p, file_ref) == 0);
assert(AFCConnectionClose(afc_conn_p) == 0);
free(file_content);
}
void handle_device(AMDeviceRef device) {
if (found_device) return; // handle one device only
CFStringRef found_device_id = AMDeviceCopyDeviceIdentifier(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) {
@@ -748,7 +1217,16 @@ void handle_device(AMDeviceRef device) {
}
if (detect_only) {
printf("[....] Found device (%s).\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding()));
printf("[....] Found %s connected through %s.\n", CFStringGetCStringPtr(device_full_name, CFStringGetSystemEncoding()), CFStringGetCStringPtr(device_interface_name, CFStringGetSystemEncoding()));
exit(0);
}
if (command_only) {
if (strcmp("list", command) == 0) {
list_files(device);
} else if (strcmp("upload", command) == 0) {
upload_file(device);
}
exit(0);
}
@@ -761,8 +1239,30 @@ void handle_device(AMDeviceRef device) {
CFRelease(relative_url);
if (uninstall) {
printf("------ Uninstall phase ------\n");
CFStringRef bundle_id = get_bundle_id(url);
if (bundle_id == NULL) {
printf("Error: Unable to get bundle id from package %s\n Uninstall failed\n", app_path);
} else {
AMDeviceConnect(device);
assert(AMDeviceIsPaired(device));
assert(AMDeviceValidatePairing(device) == 0);
assert(AMDeviceStartSession(device) == 0);
assert(AMDeviceSecureUninstallApplication(0, device, bundle_id, 0, NULL, 0) == 0);
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
printf("[ OK ] Uninstalled package with bundle id %s\n", CFStringGetCStringPtr(bundle_id, CFStringGetSystemEncoding()));
}
}
if(install) {
printf("[ 0%%] Found device (%s), beginning install\n", CFStringGetCStringPtr(found_device_id, CFStringGetSystemEncoding()));
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));
@@ -770,53 +1270,72 @@ void handle_device(AMDeviceRef device) {
assert(AMDeviceStartSession(device) == 0);
// NOTE: the secure version doesn't seem to require us to start the AFC service
service_conn_t afcFd;
assert(AMDeviceStartService(device, CFSTR("com.apple.afc"), &afcFd, NULL) == 0);
assert(AMDeviceSecureStartService(device, CFSTR("com.apple.afc"), NULL, &afcFd) == 0);
assert(AMDeviceStopSession(device) == 0);
assert(AMDeviceDisconnect(device) == 0);
assert(AMDeviceTransferApplication(afcFd, path, NULL, transfer_callback, NULL) == 0);
close(afcFd);
CFStringRef keys[] = { CFSTR("PackageType") };
CFStringRef values[] = { CFSTR("Developer") };
CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
//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);
service_conn_t installFd;
assert(AMDeviceStartService(device, CFSTR("com.apple.mobile.installation_proxy"), &installFd, NULL) == 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);
mach_error_t result = AMDeviceInstallApplication(installFd, path, options, install_callback, NULL);
if (result != 0)
{
printf("AMDeviceInstallApplication failed: %d\n", result);
exit(1);
}
close(installFd);
CFRelease(path);
CFRelease(options);
printf("[100%%] Installed package %s\n", app_path);
}
if (!debug) exit(0); // no debug phase
if (!debug)
exit(0); // no debug phase
launch_debugger(device, url);
}
void device_callback(struct am_device_notification_callback_info *info, void *arg) {
switch (info->msg) {
case ADNCI_MSG_CONNECTED:
handle_device(info->dev);
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;
}
@@ -824,8 +1343,17 @@ void device_callback(struct am_device_notification_callback_info *info, void *ar
void timeout_callback(CFRunLoopTimerRef timer, void *info) {
if (!found_device) {
printf("[....] Timed out waiting for device.\n");
exit(1);
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);
}
}
}
@@ -843,9 +1371,15 @@ void usage(const char* app) {
" -x, --gdbexec <file> GDB commands script file\n"
" -n, --nostart do not start the app when debugging\n"
" -I, --noninteractive start in non interactive mode (quit when app crashes or exits)\n"
" -L, --justlaunch just launch the app and exit lldb\n"
" -v, --verbose enable verbose output\n"
" -m, --noinstall directly start debugging without app install (-d not required)\n"
" -p, --port <number> port used for device, default: 12345 \n"
" -r, --uninstall uninstall the app before install (do not use with -m; app cache and data are cleared) \n"
" -1, --bundle_id <bundle id> specify bundle id for list and upload\n"
" -l, --list list files\n"
" -o, --upload <file> upload file\n"
" -2, --to <target pathname> use together with upload file. specify target for upload\n"
" -V, --version print the executable version \n",
app);
}
@@ -866,15 +1400,21 @@ int main(int argc, char *argv[]) {
{ "unbuffered", no_argument, NULL, 'u' },
{ "nostart", no_argument, NULL, 'n' },
{ "noninteractive", no_argument, NULL, 'I' },
{ "justlaunch", no_argument, NULL, 'L' },
{ "detect", no_argument, NULL, 'c' },
{ "version", no_argument, NULL, 'V' },
{ "noinstall", no_argument, NULL, 'm' },
{ "port", required_argument, NULL, 'p' },
{ "uninstall", no_argument, NULL, 'r' },
{ "list", no_argument, NULL, 'l' },
{ "bundle_id", required_argument, NULL, '1'},
{ "upload", required_argument, NULL, 'o'},
{ "to", required_argument, NULL, '2'},
{ NULL, 0, NULL, 0 },
};
char ch;
while ((ch = getopt_long(argc, argv, "VmcdvunIi:b:a:t:g:x:p:", longopts, NULL)) != -1)
while ((ch = getopt_long(argc, argv, "VmcdvunlrILi:b:a:t:g:x:p:1:2:o:", longopts, NULL)) != -1)
{
switch (ch) {
case 'm':
@@ -908,24 +1448,46 @@ int main(int argc, char *argv[]) {
case 'I':
interactive = false;
break;
case 'L':
interactive = false;
justlaunch = true;
break;
case 'c':
detect_only = true;
break;
case 'V':
show_version();
return 1;
return exitcode_error;
case 'p':
port = atoi(optarg);
break;
case 'r':
uninstall = 1;
break;
case '1':
bundle_id = optarg;
break;
case '2':
target_filename = optarg;
break;
case 'o':
command_only = true;
upload_pathname = optarg;
command = "upload";
break;
case 'l':
command_only = true;
command = "list";
break;
default:
usage(argv[0]);
return 1;
return exitcode_error;
}
}
if (!app_path && !detect_only) {
if (!app_path && !detect_only && !command_only) {
usage(argv[0]);
exit(0);
exit(exitcode_error);
}
if (unbuffered) {
@@ -937,10 +1499,6 @@ int main(int argc, char *argv[]) {
timeout = 5;
}
if (!detect_only) {
printf("------ Install phase ------\n");
}
if (app_path) {
assert(access(app_path, F_OK) == 0);
}
@@ -961,3 +1519,4 @@ int main(int argc, char *argv[]) {
AMDeviceNotificationSubscribe(&device_callback, 0, 0, NULL, &notify);
CFRunLoopRun();
}

View File

@@ -1,7 +1,7 @@
{
"name": "ios-deploy",
"version": "1.0.6",
"description": "launch iOS apps iOS devices from the command line (Xcode 4)",
"version": "1.2.0",
"description": "launch iOS apps iOS devices from the command line (Xcode 6)",
"main": "ios-deploy",
"scripts": {
"preinstall": "make ios-deploy"