summaryrefslogtreecommitdiffstats
path: root/src/installation_proxy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/installation_proxy.c')
-rw-r--r--src/installation_proxy.c1232
1 files changed, 753 insertions, 479 deletions
diff --git a/src/installation_proxy.c b/src/installation_proxy.c
index 4a76dd2..ec19da0 100644
--- a/src/installation_proxy.c
+++ b/src/installation_proxy.c
@@ -2,63 +2,210 @@
2 * installation_proxy.c 2 * installation_proxy.c
3 * com.apple.mobile.installation_proxy service implementation. 3 * com.apple.mobile.installation_proxy service implementation.
4 * 4 *
5 * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. 5 * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved.
6 * Copyright (c) 2010-2013 Nikias Bassen, All Rights Reserved.
6 * 7 *
7 * This library is free software; you can redistribute it and/or 8 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public 9 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either 10 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version. 11 * version 2.1 of the License, or (at your option) any later version.
11 * 12 *
12 * This library is distributed in the hope that it will be useful, 13 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details. 16 * Lesser General Public License for more details.
16 * 17 *
17 * You should have received a copy of the GNU Lesser General Public 18 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software 19 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */ 21 */
21 22
23#ifdef HAVE_CONFIG_H
24#include <config.h>
25#endif
22#include <string.h> 26#include <string.h>
23#include <stdlib.h> 27#include <stdlib.h>
28#include <inttypes.h>
24#include <unistd.h> 29#include <unistd.h>
25#include <plist/plist.h> 30#include <plist/plist.h>
26 31
27#include "installation_proxy.h" 32#include "installation_proxy.h"
28#include "property_list_service.h" 33#include "property_list_service.h"
29#include "debug.h" 34#include "common/debug.h"
35
36typedef enum {
37 INSTPROXY_COMMAND_TYPE_ASYNC,
38 INSTPROXY_COMMAND_TYPE_SYNC
39} instproxy_command_type_t;
30 40
31struct instproxy_status_data { 41struct instproxy_status_data {
32 instproxy_client_t client; 42 instproxy_client_t client;
43 plist_t command;
33 instproxy_status_cb_t cbfunc; 44 instproxy_status_cb_t cbfunc;
34 char *operation;
35 void *user_data; 45 void *user_data;
36}; 46};
37 47
38/** 48/**
49 * Converts an error string identifier to a instproxy_error_t value.
50 * Used internally to get correct error codes from a response.
51 *
52 * @param name The error name to convert.
53 * @param error_detail Pointer to store error detail text if available. The
54 * caller is reponsible for freeing the allocated buffer after use. If NULL
55 * is passed no error detail will be returned.
56 *
57 * @return A matching instproxy_error_t error code or
58 * INSTPROXY_E_UNKNOWN_ERROR otherwise.
59 */
60static instproxy_error_t instproxy_strtoerr(const char* name)
61{
62 instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR;
63
64 if (strcmp(name, "AlreadyArchived") == 0) {
65 err = INSTPROXY_E_ALREADY_ARCHIVED;
66 } else if (strcmp(name, "APIInternalError") == 0) {
67 err = INSTPROXY_E_API_INTERNAL_ERROR;
68 } else if (strcmp(name, "ApplicationAlreadyInstalled") == 0) {
69 err = INSTPROXY_E_APPLICATION_ALREADY_INSTALLED;
70 } else if (strcmp(name, "ApplicationMoveFailed") == 0) {
71 err = INSTPROXY_E_APPLICATION_MOVE_FAILED;
72 } else if (strcmp(name, "ApplicationSINFCaptureFailed") == 0) {
73 err = INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED;
74 } else if (strcmp(name, "ApplicationSandboxFailed") == 0) {
75 err = INSTPROXY_E_APPLICATION_SANDBOX_FAILED;
76 } else if (strcmp(name, "ApplicationVerificationFailed") == 0) {
77 err = INSTPROXY_E_APPLICATION_VERIFICATION_FAILED;
78 } else if (strcmp(name, "ArchiveDestructionFailed") == 0) {
79 err = INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED;
80 } else if (strcmp(name, "BundleVerificationFailed") == 0) {
81 err = INSTPROXY_E_BUNDLE_VERIFICATION_FAILED;
82 } else if (strcmp(name, "CarrierBundleCopyFailed") == 0) {
83 err = INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED;
84 } else if (strcmp(name, "CarrierBundleDirectoryCreationFailed") == 0) {
85 err = INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED;
86 } else if (strcmp(name, "CarrierBundleMissingSupportedSIMs") == 0) {
87 err = INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS;
88 } else if (strcmp(name, "CommCenterNotificationFailed") == 0) {
89 err = INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED;
90 } else if (strcmp(name, "ContainerCreationFailed") == 0) {
91 err = INSTPROXY_E_CONTAINER_CREATION_FAILED;
92 } else if (strcmp(name, "ContainerP0wnFailed") == 0) {
93 err = INSTPROXY_E_CONTAINER_P0WN_FAILED;
94 } else if (strcmp(name, "ContainerRemovalFailed") == 0) {
95 err = INSTPROXY_E_CONTAINER_REMOVAL_FAILED;
96 } else if (strcmp(name, "EmbeddedProfileInstallFailed") == 0) {
97 err = INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED;
98 } else if (strcmp(name, "ExecutableTwiddleFailed") == 0) {
99 err = INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED;
100 } else if (strcmp(name, "ExistenceCheckFailed") == 0) {
101 err = INSTPROXY_E_EXISTENCE_CHECK_FAILED;
102 } else if (strcmp(name, "InstallMapUpdateFailed") == 0) {
103 err = INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED;
104 } else if (strcmp(name, "ManifestCaptureFailed") == 0) {
105 err = INSTPROXY_E_MANIFEST_CAPTURE_FAILED;
106 } else if (strcmp(name, "MapGenerationFailed") == 0) {
107 err = INSTPROXY_E_MAP_GENERATION_FAILED;
108 } else if (strcmp(name, "MissingBundleExecutable") == 0) {
109 err = INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE;
110 } else if (strcmp(name, "MissingBundleIdentifier") == 0) {
111 err = INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER;
112 } else if (strcmp(name, "MissingBundlePath") == 0) {
113 err = INSTPROXY_E_MISSING_BUNDLE_PATH;
114 } else if (strcmp(name, "MissingContainer") == 0) {
115 err = INSTPROXY_E_MISSING_CONTAINER;
116 } else if (strcmp(name, "NotificationFailed") == 0) {
117 err = INSTPROXY_E_NOTIFICATION_FAILED;
118 } else if (strcmp(name, "PackageExtractionFailed") == 0) {
119 err = INSTPROXY_E_PACKAGE_EXTRACTION_FAILED;
120 } else if (strcmp(name, "PackageInspectionFailed") == 0) {
121 err = INSTPROXY_E_PACKAGE_INSPECTION_FAILED;
122 } else if (strcmp(name, "PackageMoveFailed") == 0) {
123 err = INSTPROXY_E_PACKAGE_MOVE_FAILED;
124 } else if (strcmp(name, "PathConversionFailed") == 0) {
125 err = INSTPROXY_E_PATH_CONVERSION_FAILED;
126 } else if (strcmp(name, "RestoreContainerFailed") == 0) {
127 err = INSTPROXY_E_RESTORE_CONTAINER_FAILED;
128 } else if (strcmp(name, "SeatbeltProfileRemovalFailed") == 0) {
129 err = INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED;
130 } else if (strcmp(name, "StageCreationFailed") == 0) {
131 err = INSTPROXY_E_STAGE_CREATION_FAILED;
132 } else if (strcmp(name, "SymlinkFailed") == 0) {
133 err = INSTPROXY_E_SYMLINK_FAILED;
134 } else if (strcmp(name, "UnknownCommand") == 0) {
135 err = INSTPROXY_E_UNKNOWN_COMMAND;
136 } else if (strcmp(name, "iTunesArtworkCaptureFailed") == 0) {
137 err = INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED;
138 } else if (strcmp(name, "iTunesMetadataCaptureFailed") == 0) {
139 err = INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED;
140 } else if (strcmp(name, "DeviceOSVersionTooLow") == 0) {
141 err = INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW;
142 } else if (strcmp(name, "DeviceFamilyNotSupported") == 0) {
143 err = INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED;
144 } else if (strcmp(name, "PackagePatchFailed") == 0) {
145 err = INSTPROXY_E_PACKAGE_PATCH_FAILED;
146 } else if (strcmp(name, "IncorrectArchitecture") == 0) {
147 err = INSTPROXY_E_INCORRECT_ARCHITECTURE;
148 } else if (strcmp(name, "PluginCopyFailed") == 0) {
149 err = INSTPROXY_E_PLUGIN_COPY_FAILED;
150 } else if (strcmp(name, "BreadcrumbFailed") == 0) {
151 err = INSTPROXY_E_BREADCRUMB_FAILED;
152 } else if (strcmp(name, "BreadcrumbUnlockFailed") == 0) {
153 err = INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED;
154 } else if (strcmp(name, "GeoJSONCaptureFailed") == 0) {
155 err = INSTPROXY_E_GEOJSON_CAPTURE_FAILED;
156 } else if (strcmp(name, "NewsstandArtworkCaptureFailed") == 0) {
157 err = INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED;
158 } else if (strcmp(name, "MissingCommand") == 0) {
159 err = INSTPROXY_E_MISSING_COMMAND;
160 } else if (strcmp(name, "NotEntitled") == 0) {
161 err = INSTPROXY_E_NOT_ENTITLED;
162 } else if (strcmp(name, "MissingPackagePath") == 0) {
163 err = INSTPROXY_E_MISSING_PACKAGE_PATH;
164 } else if (strcmp(name, "MissingContainerPath") == 0) {
165 err = INSTPROXY_E_MISSING_CONTAINER_PATH;
166 } else if (strcmp(name, "MissingApplicationIdentifier") == 0) {
167 err = INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER;
168 } else if (strcmp(name, "MissingAttributeValue") == 0) {
169 err = INSTPROXY_E_MISSING_ATTRIBUTE_VALUE;
170 } else if (strcmp(name, "LookupFailed") == 0) {
171 err = INSTPROXY_E_LOOKUP_FAILED;
172 } else if (strcmp(name, "DictCreationFailed") == 0) {
173 err = INSTPROXY_E_DICT_CREATION_FAILED;
174 } else if (strcmp(name, "InstallProhibited") == 0) {
175 err = INSTPROXY_E_INSTALL_PROHIBITED;
176 } else if (strcmp(name, "UninstallProhibited") == 0) {
177 err = INSTPROXY_E_UNINSTALL_PROHIBITED;
178 } else if (strcmp(name, "MissingBundleVersion") == 0) {
179 err = INSTPROXY_E_MISSING_BUNDLE_VERSION;
180 }
181
182 return err;
183}
184
185/**
39 * Locks an installation_proxy client, used for thread safety. 186 * Locks an installation_proxy client, used for thread safety.
40 * 187 *
41 * @param client The installation_proxy client to lock 188 * @param client The installation_proxy client to lock
42 */ 189 */
43static void instproxy_lock(instproxy_client_t client) 190static void instproxy_lock(instproxy_client_t client)
44{ 191{
45 debug_info("InstallationProxy: Locked"); 192 debug_info("Locked");
46 g_mutex_lock(client->mutex); 193 mutex_lock(&client->mutex);
47} 194}
48 195
49/** 196/**
50 * Unlocks an installation_proxy client, used for thread safety. 197 * Unlocks an installation_proxy client, used for thread safety.
51 * 198 *
52 * @param client The installation_proxy client to lock 199 * @param client The installation_proxy client to lock
53 */ 200 */
54static void instproxy_unlock(instproxy_client_t client) 201static void instproxy_unlock(instproxy_client_t client)
55{ 202{
56 debug_info("InstallationProxy: Unlocked"); 203 debug_info("Unlocked");
57 g_mutex_unlock(client->mutex); 204 mutex_unlock(&client->mutex);
58} 205}
59 206
60/** 207/**
61 * Convert a property_list_service_error_t value to an instproxy_error_t value. 208 * Converts a property_list_service_error_t value to an instproxy_error_t value.
62 * Used internally to get correct error codes. 209 * Used internally to get correct error codes.
63 * 210 *
64 * @param err A property_list_service_error_t error code 211 * @param err A property_list_service_error_t error code
@@ -77,186 +224,80 @@ static instproxy_error_t instproxy_error(property_list_service_error_t err)
77 return INSTPROXY_E_PLIST_ERROR; 224 return INSTPROXY_E_PLIST_ERROR;
78 case PROPERTY_LIST_SERVICE_E_MUX_ERROR: 225 case PROPERTY_LIST_SERVICE_E_MUX_ERROR:
79 return INSTPROXY_E_CONN_FAILED; 226 return INSTPROXY_E_CONN_FAILED;
227 case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT:
228 return INSTPROXY_E_RECEIVE_TIMEOUT;
80 default: 229 default:
81 break; 230 break;
82 } 231 }
83 return INSTPROXY_E_UNKNOWN_ERROR; 232 return INSTPROXY_E_UNKNOWN_ERROR;
84} 233}
85 234
86/** 235instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client)
87 * Connects to the installation_proxy service on the specified device.
88 *
89 * @param device The device to connect to
90 * @param port Destination port (usually given by lockdownd_start_service).
91 * @param client Pointer that will be set to a newly allocated
92 * instproxy_client_t upon successful return.
93 *
94 * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value
95 * when an error occured.
96 */
97instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client)
98{ 236{
99 /* makes sure thread environment is available */
100 if (!g_thread_supported())
101 g_thread_init(NULL);
102
103 if (!device)
104 return INSTPROXY_E_INVALID_ARG;
105
106 property_list_service_client_t plistclient = NULL; 237 property_list_service_client_t plistclient = NULL;
107 if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { 238 instproxy_error_t err = instproxy_error(property_list_service_client_new(device, service, &plistclient));
108 return INSTPROXY_E_CONN_FAILED; 239 if (err != INSTPROXY_E_SUCCESS) {
240 return err;
109 } 241 }
110 242
111 instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); 243 instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private));
112 client_loc->parent = plistclient; 244 client_loc->parent = plistclient;
113 client_loc->mutex = g_mutex_new(); 245 mutex_init(&client_loc->mutex);
114 client_loc->status_updater = NULL; 246 client_loc->receive_status_thread = THREAD_T_NULL;
115 247
116 *client = client_loc; 248 *client = client_loc;
117 return INSTPROXY_E_SUCCESS; 249 return INSTPROXY_E_SUCCESS;
118} 250}
119 251
120/** 252instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label)
121 * Disconnects an installation_proxy client from the device and frees up the 253{
122 * installation_proxy client data. 254 instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR;
123 * 255 service_client_factory_start_service(device, INSTPROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(instproxy_client_new), &err);
124 * @param client The installation_proxy client to disconnect and free. 256 return err;
125 * 257}
126 * @return INSTPROXY_E_SUCCESS on success 258
127 * or INSTPROXY_E_INVALID_ARG if client is NULL.
128 */
129instproxy_error_t instproxy_client_free(instproxy_client_t client) 259instproxy_error_t instproxy_client_free(instproxy_client_t client)
130{ 260{
131 if (!client) 261 if (!client)
132 return INSTPROXY_E_INVALID_ARG; 262 return INSTPROXY_E_INVALID_ARG;
133 263
134 property_list_service_client_free(client->parent); 264 property_list_service_client_t parent = client->parent;
135 client->parent = NULL; 265 client->parent = NULL;
136 if (client->status_updater) { 266 if (client->receive_status_thread) {
137 debug_info("joining status_updater"); 267 debug_info("joining receive_status_thread");
138 g_thread_join(client->status_updater); 268 thread_join(client->receive_status_thread);
139 } 269 thread_free(client->receive_status_thread);
140 if (client->mutex) { 270 client->receive_status_thread = THREAD_T_NULL;
141 g_mutex_free(client->mutex);
142 } 271 }
272 property_list_service_client_free(parent);
273 mutex_destroy(&client->mutex);
143 free(client); 274 free(client);
144 275
145 return INSTPROXY_E_SUCCESS; 276 return INSTPROXY_E_SUCCESS;
146} 277}
147 278
148/** 279/**
149 * Send a command with specified options to the device. 280 * Sends a command to the device.
150 * Only used internally. 281 * Only used internally.
151 * 282 *
152 * @param client The connected installation_proxy client. 283 * @param client The connected installation_proxy client.
153 * @param command The command to execute. Required. 284 * @param command The command to execute. Required.
154 * @param client_options The client options to use, as PLIST_DICT, or NULL.
155 * @param appid The ApplicationIdentifier to add or NULL if not required.
156 * @param package_path The installation package path or NULL if not required.
157 * 285 *
158 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if 286 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
159 * an error occured. 287 * an error occurred.
160 */ 288 */
161static instproxy_error_t instproxy_send_command(instproxy_client_t client, const char *command, plist_t client_options, const char *appid, const char *package_path) 289static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command)
162{ 290{
163 if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) 291 if (!client || !command)
164 return INSTPROXY_E_INVALID_ARG; 292 return INSTPROXY_E_INVALID_ARG;
165 293
166 plist_t dict = plist_new_dict(); 294 instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command));
167 if (appid) {
168 plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid));
169 }
170 if (client_options && (plist_dict_get_size(client_options) > 0)) {
171 plist_dict_insert_item(dict, "ClientOptions", plist_copy(client_options));
172 }
173 plist_dict_insert_item(dict, "Command", plist_new_string(command));
174 if (package_path) {
175 plist_dict_insert_item(dict, "PackagePath", plist_new_string(package_path));
176 }
177
178 instproxy_error_t err = instproxy_error(property_list_service_send_xml_plist(client->parent, dict));
179 plist_free(dict);
180 return err;
181}
182 295
183/**
184 * List installed applications. This function runs synchronously.
185 *
186 * @param client The connected installation_proxy client
187 * @param client_options The client options to use, as PLIST_DICT, or NULL.
188 * Valid client options include:
189 * "ApplicationType" -> "User"
190 * "ApplicationType" -> "System"
191 * @param result Pointer that will be set to a plist that will hold an array
192 * of PLIST_DICT holding information about the applications found.
193 *
194 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
195 * an error occured.
196 */
197instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result)
198{
199 if (!client || !client->parent || !result)
200 return INSTPROXY_E_INVALID_ARG;
201
202 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
203
204 instproxy_lock(client);
205 res = instproxy_send_command(client, "Browse", client_options, NULL, NULL);
206 if (res != INSTPROXY_E_SUCCESS) { 296 if (res != INSTPROXY_E_SUCCESS) {
207 debug_info("could not send plist"); 297 debug_info("could not send command plist, error %d", res);
208 goto leave_unlock; 298 return res;
209 }
210
211 int browsing = 0;
212 plist_t apps_array = plist_new_array();
213 plist_t dict = NULL;
214
215 do {
216 browsing = 0;
217 dict = NULL;
218 res = instproxy_error(property_list_service_receive_plist(client->parent, &dict));
219 if (res != INSTPROXY_E_SUCCESS) {
220 break;
221 }
222 if (dict) {
223 uint64_t i;
224 uint64_t current_amount = 0;
225 char *status = NULL;
226 plist_t camount = plist_dict_get_item(dict, "CurrentAmount");
227 plist_t pstatus = plist_dict_get_item(dict, "Status");
228 if (camount) {
229 plist_get_uint_val(camount, &current_amount);
230 }
231 if (current_amount > 0) {
232 plist_t current_list = plist_dict_get_item(dict, "CurrentList");
233 for (i = 0; current_list && (i < current_amount); i++) {
234 plist_t item = plist_array_get_item(current_list, i);
235 plist_array_append_item(apps_array, plist_copy(item));
236 }
237 }
238 if (pstatus) {
239 plist_get_string_val(pstatus, &status);
240 }
241 if (status) {
242 if (!strcmp(status, "BrowsingApplications")) {
243 browsing = 1;
244 } else if (!strcmp(status, "Complete")) {
245 debug_info("Browsing applications completed");
246 res = INSTPROXY_E_SUCCESS;
247 }
248 free(status);
249 }
250 plist_free(dict);
251 }
252 } while (browsing);
253
254 if (res == INSTPROXY_E_SUCCESS) {
255 *result = apps_array;
256 } 299 }
257 300
258leave_unlock:
259 instproxy_unlock(client);
260 return res; 301 return res;
261} 302}
262 303
@@ -269,78 +310,99 @@ leave_unlock:
269 * 310 *
270 * @param client The connected installation proxy client 311 * @param client The connected installation proxy client
271 * @param status_cb Pointer to a callback function or NULL 312 * @param status_cb Pointer to a callback function or NULL
272 * @param operation Operation name. Will be passed to the callback function 313 * @param command Operation specificiation in plist. Will be passed to the
273 * in async mode or shown in debug messages in sync mode. 314 * status_cb callback.
274 * @param user_data Callback data passed to status_cb. 315 * @param user_data Callback data passed to status_cb.
275 */ 316 */
276static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) 317static instproxy_error_t instproxy_receive_status_loop(instproxy_client_t client, plist_t command, instproxy_status_cb_t status_cb, void *user_data)
277{ 318{
278 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; 319 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
279 int ok = 1; 320 int complete = 0;
280 plist_t dict = NULL; 321 plist_t node = NULL;
322 char* command_name = NULL;
323 char* status_name = NULL;
324 char* error_name = NULL;
325 char* error_description = NULL;
326 uint64_t error_code = 0;
327#ifndef STRIP_DEBUG_CODE
328 int percent_complete = 0;
329#endif
330
331 instproxy_command_get_name(command, &command_name);
281 332
282 do { 333 do {
334 /* receive status response */
283 instproxy_lock(client); 335 instproxy_lock(client);
284 res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); 336 res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000));
285 instproxy_unlock(client); 337 instproxy_unlock(client);
286 if (res != INSTPROXY_E_SUCCESS) { 338
339 /* break out if we have a communication problem */
340 if (res != INSTPROXY_E_SUCCESS && res != INSTPROXY_E_RECEIVE_TIMEOUT) {
287 debug_info("could not receive plist, error %d", res); 341 debug_info("could not receive plist, error %d", res);
288 break; 342 break;
289 } 343 }
290 if (dict) { 344
291 /* invoke callback function */ 345 /* parse status response */
292 if (status_cb) { 346 if (node) {
293 status_cb(operation, dict, user_data); 347 /* check status for possible error to allow reporting it and aborting it gracefully */
348 res = instproxy_status_get_error(node, &error_name, &error_description, &error_code);
349 if (res != INSTPROXY_E_SUCCESS) {
350 debug_info("command: %s, error %d, code 0x%08"PRIx64", name: %s, description: \"%s\"", command_name, res, error_code, error_name, error_description ? error_description: "N/A");
351 complete = 1;
352 }
353
354 if (error_name) {
355 free(error_name);
356 error_name = NULL;
294 } 357 }
295 /* check for 'Error', so we can abort cleanly */ 358
296 plist_t err = plist_dict_get_item(dict, "Error"); 359 if (error_description) {
297 if (err) { 360 free(error_description);
361 error_description = NULL;
362 }
363
364 /* check status from response */
365 instproxy_status_get_name(node, &status_name);
366 if (!status_name) {
367 debug_info("ignoring message without Status key:");
368 debug_plist(node);
369 } else {
370 if (!strcmp(status_name, "Complete")) {
371 complete = 1;
372 } else {
373 res = INSTPROXY_E_OP_IN_PROGRESS;
374 }
298#ifndef STRIP_DEBUG_CODE 375#ifndef STRIP_DEBUG_CODE
299 char *err_msg = NULL; 376 percent_complete = -1;
300 plist_get_string_val(err, &err_msg); 377 instproxy_status_get_percent_complete(node, &percent_complete);
301 if (err_msg) { 378 if (percent_complete >= 0) {
302 debug_info("(%s): ERROR: %s", operation, err_msg); 379 debug_info("command: %s, status: %s, percent (%d%%)", command_name, status_name, percent_complete);
303 free(err_msg); 380 } else {
381 debug_info("command: %s, status: %s", command_name, status_name);
304 } 382 }
305#endif 383#endif
306 ok = 0; 384 free(status_name);
307 res = INSTPROXY_E_OP_FAILED; 385 status_name = NULL;
308 } 386 }
309 /* get 'Status' */ 387
310 plist_t status = plist_dict_get_item(dict, "Status"); 388 /* invoke status callback function */
311 if (status) { 389 if (status_cb) {
312 char *status_msg = NULL; 390 status_cb(command, node, user_data);
313 plist_get_string_val(status, &status_msg);
314 if (status_msg) {
315 if (!strcmp(status_msg, "Complete")) {
316 ok = 0;
317 res = INSTPROXY_E_SUCCESS;
318 }
319#ifndef STRIP_DEBUG_CODE
320 plist_t npercent = plist_dict_get_item(dict, "PercentComplete");
321 if (npercent) {
322 uint64_t val = 0;
323 int percent;
324 plist_get_uint_val(npercent, &val);
325 percent = val;
326 debug_info("(%s): %s (%d%%)", operation, status_msg, percent);
327 } else {
328 debug_info("(%s): %s", operation, status_msg);
329 }
330#endif
331 free(status_msg);
332 }
333 } 391 }
334 plist_free(dict); 392
335 dict = NULL; 393 plist_free(node);
394 node = NULL;
336 } 395 }
337 } while (ok && client->parent); 396 } while (!complete && client->parent);
397
398 if (command_name)
399 free(command_name);
338 400
339 return res; 401 return res;
340} 402}
341 403
342/** 404/**
343 * Internally used status updater thread function that will call the specified 405 * Internally used "receive status" thread function that will call the specified
344 * callback function when status update messages (or error messages) are 406 * callback function when status update messages (or error messages) are
345 * received. 407 * received.
346 * 408 *
@@ -349,20 +411,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client,
349 * 411 *
350 * @return Always NULL. 412 * @return Always NULL.
351 */ 413 */
352static gpointer instproxy_status_updater(gpointer arg) 414static void* instproxy_receive_status_loop_thread(void* arg)
353{ 415{
354 struct instproxy_status_data *data = (struct instproxy_status_data*)arg; 416 struct instproxy_status_data *data = (struct instproxy_status_data*)arg;
355 417
356 /* run until the operation is complete or an error occurs */ 418 /* run until the command is complete or an error occurs */
357 (void)instproxy_perform_operation(data->client, data->cbfunc, data->operation, data->user_data); 419 (void)instproxy_receive_status_loop(data->client, data->command, data->cbfunc, data->user_data);
358 420
359 /* cleanup */ 421 /* cleanup */
360 instproxy_lock(data->client); 422 instproxy_lock(data->client);
423
361 debug_info("done, cleaning up."); 424 debug_info("done, cleaning up.");
362 if (data->operation) { 425
363 free(data->operation); 426 if (data->command) {
427 plist_free(data->command);
428 }
429
430 if (data->client->receive_status_thread) {
431 thread_free(data->client->receive_status_thread);
432 data->client->receive_status_thread = THREAD_T_NULL;
364 } 433 }
365 data->client->status_updater = NULL; 434
366 instproxy_unlock(data->client); 435 instproxy_unlock(data->client);
367 free(data); 436 free(data);
368 437
@@ -370,379 +439,493 @@ static gpointer instproxy_status_updater(gpointer arg)
370} 439}
371 440
372/** 441/**
373 * Internally used helper function that creates a status updater thread which 442 * Internally used helper function that creates a "receive status" thread which
374 * will call the passed callback function when status updates occur. 443 * will call the passed callback function when a status is received.
375 * If status_cb is NULL no thread will be created, but the operation will 444 *
376 * run synchronously until it completes or an error occurs. 445 * If async is 0 no thread will be created and the command will run
446 * synchronously until it completes or an error occurs.
377 * 447 *
378 * @param client The connected installation proxy client 448 * @param client The connected installation proxy client
379 * @param status_cb Pointer to a callback function or NULL 449 * @param command Operation name. Will be passed to the callback function
380 * @param operation Operation name. Will be passed to the callback function
381 * in async mode or shown in debug messages in sync mode. 450 * in async mode or shown in debug messages in sync mode.
451 * @param async A boolean indicating if receive loop should be run
452 * asynchronously or block.
453 * @param status_cb Pointer to a callback function or NULL.
382 * @param user_data Callback data passed to status_cb. 454 * @param user_data Callback data passed to status_cb.
383 * 455 *
384 * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or 456 * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or
385 * when the operation completed successfully (sync). 457 * when the command completed successfully (sync).
386 * An INSTPROXY_E_* error value is returned if an error occured. 458 * An INSTPROXY_E_* error value is returned if an error occurred.
387 */ 459 */
388static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) 460static instproxy_error_t instproxy_receive_status_loop_with_callback(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data)
389{ 461{
462 if (!client || !client->parent || !command) {
463 return INSTPROXY_E_INVALID_ARG;
464 }
465
466 if (client->receive_status_thread) {
467 return INSTPROXY_E_OP_IN_PROGRESS;
468 }
469
390 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; 470 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
391 if (status_cb) { 471 if (async == INSTPROXY_COMMAND_TYPE_ASYNC) {
392 /* async mode */ 472 /* async mode */
393 struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); 473 struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data));
394 if (data) { 474 if (data) {
395 data->client = client; 475 data->client = client;
476 data->command = plist_copy(command);
396 data->cbfunc = status_cb; 477 data->cbfunc = status_cb;
397 data->operation = strdup(operation);
398 data->user_data = user_data; 478 data->user_data = user_data;
399 479
400 client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); 480 if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) {
401 if (client->status_updater) {
402 res = INSTPROXY_E_SUCCESS; 481 res = INSTPROXY_E_SUCCESS;
403 } 482 }
404 } 483 }
405 } else { 484 } else {
406 /* sync mode */ 485 /* sync mode as a fallback */
407 res = instproxy_perform_operation(client, NULL, operation, NULL); 486 res = instproxy_receive_status_loop(client, command, status_cb, user_data);
408 } 487 }
488
409 return res; 489 return res;
410} 490}
411 491
412
413/** 492/**
414 * Internal function used by instproxy_install and instproxy_upgrade. 493 * Internal core function to send a command and process the response.
415 * 494 *
416 * @param client The connected installation_proxy client 495 * @param client The connected installation_proxy client
417 * @param pkg_path Path of the installation package (inside the AFC jail) 496 * @param command The command specification dictionary.
418 * @param client_options The client options to use, as PLIST_DICT, or NULL. 497 * @param async A boolean indicating whether the receive loop should be run
419 * @param status_cb Callback function for progress and status information. If 498 * asynchronously or block until completing the command.
420 * NULL is passed, this function will run synchronously. 499 * @param status_cb Callback function to call if a command status is received.
421 * @param command The command to execute.
422 * @param user_data Callback data passed to status_cb. 500 * @param user_data Callback data passed to status_cb.
423 * 501 *
424 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if 502 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
425 * an error occured. 503 * an error occurred.
426 */ 504 */
427static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, const char *command, void *user_data) 505static instproxy_error_t instproxy_perform_command(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data)
428{ 506{
429 if (!client || !client->parent || !pkg_path) { 507 if (!client || !client->parent || !command) {
430 return INSTPROXY_E_INVALID_ARG; 508 return INSTPROXY_E_INVALID_ARG;
431 } 509 }
432 if (client->status_updater) { 510
511 if (client->receive_status_thread) {
433 return INSTPROXY_E_OP_IN_PROGRESS; 512 return INSTPROXY_E_OP_IN_PROGRESS;
434 } 513 }
435 514
515 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
516
517 /* send command */
436 instproxy_lock(client); 518 instproxy_lock(client);
437 instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path); 519 res = instproxy_send_command(client, command);
438 instproxy_unlock(client); 520 instproxy_unlock(client);
439 521
440 if (res != INSTPROXY_E_SUCCESS) { 522 /* loop until status or error is received */
441 debug_info("could not send plist, error %d", res); 523 res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data);
442 return res;
443 }
444 524
445 return instproxy_create_status_updater(client, status_cb, command, user_data); 525 return res;
446} 526}
447 527
448/** 528instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
449 * Install an application on the device.
450 *
451 * @param client The connected installation_proxy client
452 * @param pkg_path Path of the installation package (inside the AFC jail)
453 * @param client_options The client options to use, as PLIST_DICT, or NULL.
454 * Valid options include:
455 * "iTunesMetadata" -> PLIST_DATA
456 * "ApplicationSINF" -> PLIST_DATA
457 * "PackageType" -> "Developer"
458 * If PackageType -> Developer is specified, then pkg_path points to
459 * an .app directory instead of an install package.
460 * @param status_cb Callback function for progress and status information. If
461 * NULL is passed, this function will run synchronously.
462 * @param user_data Callback data passed to status_cb.
463 *
464 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
465 * an error occured.
466 *
467 * @note If a callback function is given (async mode), this function returns
468 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
469 * created successfully; any error occuring during the operation has to be
470 * handled inside the specified callback function.
471 */
472instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
473{ 529{
474 return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install", user_data); 530 if (!client || !client->parent || !status_cb)
531 return INSTPROXY_E_INVALID_ARG;
532
533 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
534
535 plist_t command = plist_new_dict();
536 plist_dict_set_item(command, "Command", plist_new_string("Browse"));
537 if (client_options)
538 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
539
540 res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, (void*)user_data);
541
542 plist_free(command);
543
544 return res;
475} 545}
476 546
477/** 547static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *user_data)
478 * Upgrade an application on the device. This function is nearly the same as
479 * instproxy_install; the difference is that the installation progress on the
480 * device is faster if the application is already installed.
481 *
482 * @param client The connected installation_proxy client
483 * @param pkg_path Path of the installation package (inside the AFC jail)
484 * @param client_options The client options to use, as PLIST_DICT, or NULL.
485 * Valid options include:
486 * "iTunesMetadata" -> PLIST_DATA
487 * "ApplicationSINF" -> PLIST_DATA
488 * "PackageType" -> "Developer"
489 * If PackageType -> Developer is specified, then pkg_path points to
490 * an .app directory instead of an install package.
491 * @param status_cb Callback function for progress and status information. If
492 * NULL is passed, this function will run synchronously.
493 * @param user_data Callback data passed to status_cb.
494 *
495 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
496 * an error occured.
497 *
498 * @note If a callback function is given (async mode), this function returns
499 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
500 * created successfully; any error occuring during the operation has to be
501 * handled inside the specified callback function.
502 */
503instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
504{ 548{
505 return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade", user_data); 549 plist_t *result_array = (plist_t*)user_data;
550 uint64_t current_amount = 0;
551 plist_t current_list = NULL;
552 uint64_t i;
553
554 instproxy_status_get_current_list(status, NULL, NULL, &current_amount, &current_list);
555
556 debug_info("current_amount: %d", current_amount);
557
558 if (current_amount > 0) {
559 for (i = 0; current_list && (i < current_amount); i++) {
560 plist_t item = plist_array_get_item(current_list, i);
561 plist_array_append_item(*result_array, plist_copy(item));
562 }
563 }
564
565 if (current_list)
566 plist_free(current_list);
506} 567}
507 568
508/** 569instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result)
509 * Uninstall an application from the device.
510 *
511 * @param client The connected installation proxy client
512 * @param appid ApplicationIdentifier of the app to uninstall
513 * @param client_options The client options to use, as PLIST_DICT, or NULL.
514 * Currently there are no known client options, so pass NULL here.
515 * @param status_cb Callback function for progress and status information. If
516 * NULL is passed, this function will run synchronously.
517 * @param user_data Callback data passed to status_cb.
518 *
519 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
520 * an error occured.
521 *
522 * @note If a callback function is given (async mode), this function returns
523 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
524 * created successfully; any error occuring during the operation has to be
525 * handled inside the specified callback function.
526 */
527instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
528{ 570{
529 if (!client || !client->parent || !appid) { 571 if (!client || !client->parent || !result)
530 return INSTPROXY_E_INVALID_ARG; 572 return INSTPROXY_E_INVALID_ARG;
531 }
532
533 if (client->status_updater) {
534 return INSTPROXY_E_OP_IN_PROGRESS;
535 }
536 573
537 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; 574 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
538 plist_t dict = plist_new_dict();
539 plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid));
540 plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall"));
541 575
542 instproxy_lock(client); 576 plist_t result_array = plist_new_array();
543 res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL);
544 instproxy_unlock(client);
545 577
546 plist_free(dict); 578 plist_t command = plist_new_dict();
579 plist_dict_set_item(command, "Command", plist_new_string("Browse"));
580 if (client_options)
581 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
547 582
548 if (res != INSTPROXY_E_SUCCESS) { 583 res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_append_current_list_to_result_cb, (void*)&result_array);
549 debug_info("could not send plist, error %d", res); 584
550 return res; 585 if (res == INSTPROXY_E_SUCCESS) {
586 *result = result_array;
587 } else {
588 plist_free(result_array);
551 } 589 }
552 590
553 return instproxy_create_status_updater(client, status_cb, "Uninstall", user_data); 591 plist_free(command);
592
593 return res;
554} 594}
555 595
556/** 596static void instproxy_copy_lookup_result_cb(plist_t command, plist_t status, void *user_data)
557 * List archived applications. This function runs synchronously. 597{
558 * 598 plist_t* result = (plist_t*)user_data;
559 * @see instproxy_archive 599
560 * 600 plist_t node = plist_dict_get_item(status, "LookupResult");
561 * @param client The connected installation_proxy client 601 if (node) {
562 * @param client_options The client options to use, as PLIST_DICT, or NULL. 602 *result = plist_copy(node);
563 * Currently there are no known client options, so pass NULL here. 603 }
564 * @param result Pointer that will be set to a plist containing a PLIST_DICT 604}
565 * holding information about the archived applications found. 605
566 * 606instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result)
567 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
568 * an error occured.
569 */
570instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result)
571{ 607{
608 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
609 int i = 0;
610 plist_t lookup_result = NULL;
611 plist_t command = NULL;
612 plist_t appid_array = NULL;
613 plist_t node = NULL;
614
572 if (!client || !client->parent || !result) 615 if (!client || !client->parent || !result)
573 return INSTPROXY_E_INVALID_ARG; 616 return INSTPROXY_E_INVALID_ARG;
574 617
575 instproxy_lock(client); 618 command = plist_new_dict();
576 instproxy_error_t res = instproxy_send_command(client, "LookupArchives", client_options, NULL, NULL); 619 plist_dict_set_item(command, "Command", plist_new_string("Lookup"));
620 if (client_options) {
621 node = plist_copy(client_options);
622 } else if (appids) {
623 node = plist_new_dict();
624 }
577 625
578 if (res != INSTPROXY_E_SUCCESS) { 626 /* add bundle identifiers to client options */
579 debug_info("could not send plist, error %d", res); 627 if (appids) {
580 goto leave_unlock; 628 appid_array = plist_new_array();
629 while (appids[i]) {
630 plist_array_append_item(appid_array, plist_new_string(appids[i]));
631 i++;
632 }
633 plist_dict_set_item(node, "BundleIDs", appid_array);
581 } 634 }
582 635
583 res = instproxy_error(property_list_service_receive_plist(client->parent, result)); 636 if (node) {
584 if (res != INSTPROXY_E_SUCCESS) { 637 plist_dict_set_item(command, "ClientOptions", node);
585 debug_info("could not receive plist, error %d", res);
586 goto leave_unlock;
587 } 638 }
588 639
589 res = INSTPROXY_E_SUCCESS; 640 res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result);
641
642 if (res == INSTPROXY_E_SUCCESS) {
643 *result = lookup_result;
644 } else {
645 plist_free(lookup_result);
646 }
647
648 plist_free(command);
649
650 return res;
651}
652
653instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
654{
655 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
656
657 plist_t command = plist_new_dict();
658 plist_dict_set_item(command, "Command", plist_new_string("Install"));
659 if (client_options)
660 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
661 plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path));
662
663 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
664
665 plist_free(command);
666
667 return res;
668}
669
670instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
671{
672 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
673
674 plist_t command = plist_new_dict();
675 plist_dict_set_item(command, "Command", plist_new_string("Upgrade"));
676 if (client_options)
677 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
678 plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path));
679
680 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
681
682 plist_free(command);
683
684 return res;
685}
686
687instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
688{
689 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
690
691 plist_t command = plist_new_dict();
692 plist_dict_set_item(command, "Command", plist_new_string("Uninstall"));
693 if (client_options)
694 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
695 plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid));
696
697 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
698
699 plist_free(command);
700
701 return res;
702}
703
704instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result)
705{
706 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
707
708 plist_t command = plist_new_dict();
709 plist_dict_set_item(command, "Command", plist_new_string("LookupArchives"));
710 if (client_options)
711 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
712
713 res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)result);
714
715 plist_free(command);
590 716
591leave_unlock:
592 instproxy_unlock(client);
593 return res; 717 return res;
594} 718}
595 719
596/**
597 * Archive an application on the device.
598 * This function tells the device to make an archive of the specified
599 * application. This results in the device creating a ZIP archive in the
600 * 'ApplicationArchives' directory and uninstalling the application.
601 *
602 * @param client The connected installation proxy client
603 * @param appid ApplicationIdentifier of the app to archive.
604 * @param client_options The client options to use, as PLIST_DICT, or NULL.
605 * Valid options include:
606 * "SkipUninstall" -> Boolean
607 * "ArchiveType" -> "ApplicationOnly"
608 * @param status_cb Callback function for progress and status information. If
609 * NULL is passed, this function will run synchronously.
610 * @param user_data Callback data passed to status_cb.
611 *
612 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
613 * an error occured.
614 *
615 * @note If a callback function is given (async mode), this function returns
616 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
617 * created successfully; any error occuring during the operation has to be
618 * handled inside the specified callback function.
619 */
620instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) 720instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
621{ 721{
622 if (!client || !client->parent || !appid) 722 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
723
724 plist_t command = plist_new_dict();
725 plist_dict_set_item(command, "Command", plist_new_string("Archive"));
726 if (client_options)
727 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
728 plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid));
729
730 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
731
732 plist_free(command);
733
734 return res;
735}
736
737instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
738{
739 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
740
741 plist_t command = plist_new_dict();
742 plist_dict_set_item(command, "Command", plist_new_string("Restore"));
743 if (client_options)
744 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
745 plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid));
746
747 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
748
749 plist_free(command);
750
751 return res;
752}
753
754instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
755{
756 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
757
758 plist_t command = plist_new_dict();
759 plist_dict_set_item(command, "Command", plist_new_string("RemoveArchive"));
760 if (client_options)
761 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
762 plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid));
763
764 res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data);
765
766 plist_free(command);
767
768 return res;
769}
770
771instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result)
772{
773 if (!client || !capabilities || !result)
623 return INSTPROXY_E_INVALID_ARG; 774 return INSTPROXY_E_INVALID_ARG;
624 775
625 if (client->status_updater) { 776 plist_t lookup_result = NULL;
626 return INSTPROXY_E_OP_IN_PROGRESS; 777
778 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
779
780 plist_t command = plist_new_dict();
781 plist_dict_set_item(command, "Command", plist_new_string("CheckCapabilitiesMatch"));
782 if (client_options)
783 plist_dict_set_item(command, "ClientOptions", plist_copy(client_options));
784
785 if (capabilities) {
786 int i = 0;
787 plist_t capabilities_array = plist_new_array();
788 while (capabilities[i]) {
789 plist_array_append_item(capabilities_array, plist_new_string(capabilities[i]));
790 i++;
791 }
792 plist_dict_set_item(command, "Capabilities", capabilities_array);
627 } 793 }
628 794
629 instproxy_lock(client); 795 res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result);
630 instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL);
631 instproxy_unlock(client);
632 796
633 if (res != INSTPROXY_E_SUCCESS) { 797 if (res == INSTPROXY_E_SUCCESS) {
634 debug_info("could not send plist, error %d", res); 798 *result = lookup_result;
635 return res; 799 } else {
800 plist_free(lookup_result);
636 } 801 }
637 return instproxy_create_status_updater(client, status_cb, "Archive", user_data); 802
803 plist_free(command);
804
805 return res;
638} 806}
639 807
640/** 808instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code)
641 * Restore a previously archived application on the device.
642 * This function is the counterpart to instproxy_archive.
643 * @see instproxy_archive
644 *
645 * @param client The connected installation proxy client
646 * @param appid ApplicationIdentifier of the app to restore.
647 * @param client_options The client options to use, as PLIST_DICT, or NULL.
648 * Currently there are no known client options, so pass NULL here.
649 * @param status_cb Callback function for progress and status information. If
650 * NULL is passed, this function will run synchronously.
651 * @param user_data Callback data passed to status_cb.
652 *
653 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
654 * an error occured.
655 *
656 * @note If a callback function is given (async mode), this function returns
657 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
658 * created successfully; any error occuring during the operation has to be
659 * handled inside the specified callback function.
660 */
661instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
662{ 809{
663 if (!client || !client->parent || !appid) 810 instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR;
811
812 if (!status || !name)
664 return INSTPROXY_E_INVALID_ARG; 813 return INSTPROXY_E_INVALID_ARG;
665 814
666 if (client->status_updater) { 815 plist_t node = plist_dict_get_item(status, "Error");
667 return INSTPROXY_E_OP_IN_PROGRESS; 816 if (node) {
817 plist_get_string_val(node, name);
818 } else {
819 /* no error here */
820 res = INSTPROXY_E_SUCCESS;
668 } 821 }
669 822
670 instproxy_lock(client); 823 if (code != NULL) {
671 instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL); 824 *code = 0;
672 instproxy_unlock(client); 825 node = plist_dict_get_item(status, "ErrorDetail");
826 if (node) {
827 plist_get_uint_val(node, code);
828 *code &= 0xffffffff;
829 }
830 }
673 831
674 if (res != INSTPROXY_E_SUCCESS) { 832 if (description != NULL) {
675 debug_info("could not send plist, error %d", res); 833 node = plist_dict_get_item(status, "ErrorDescription");
676 return res; 834 if (node) {
835 plist_get_string_val(node, description);
836 }
837 }
838
839 if (*name) {
840 res = instproxy_strtoerr(*name);
677 } 841 }
678 return instproxy_create_status_updater(client, status_cb, "Restore", user_data); 842
843 return res;
679} 844}
680 845
681/** 846void instproxy_status_get_name(plist_t status, char **name)
682 * Removes a previously archived application from the device.
683 * This function removes the ZIP archive from the 'ApplicationArchives'
684 * directory.
685 *
686 * @param client The connected installation proxy client
687 * @param appid ApplicationIdentifier of the archived app to remove.
688 * @param client_options The client options to use, as PLIST_DICT, or NULL.
689 * Currently there are no known client options, so passing NULL is fine.
690 * @param status_cb Callback function for progress and status information. If
691 * NULL is passed, this function will run synchronously.
692 * @param user_data Callback data passed to status_cb.
693 *
694 * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if
695 * an error occured.
696 *
697 * @note If a callback function is given (async mode), this function returns
698 * INSTPROXY_E_SUCCESS immediately if the status updater thread has been
699 * created successfully; any error occuring during the operation has to be
700 * handled inside the specified callback function.
701 */
702instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data)
703{ 847{
704 if (!client || !client->parent || !appid) 848 if (name) {
705 return INSTPROXY_E_INVALID_ARG; 849 plist_t node = plist_dict_get_item(status, "Status");
850 if (node) {
851 plist_get_string_val(node, name);
852 } else {
853 *name = NULL;
854 }
855 }
856}
706 857
707 if (client->status_updater) { 858void instproxy_status_get_percent_complete(plist_t status, int *percent)
708 return INSTPROXY_E_OP_IN_PROGRESS; 859{
860 uint64_t val = 0;
861 if (percent) {
862 plist_t node = plist_dict_get_item(status, "PercentComplete");
863 if (node) {
864 plist_get_uint_val(node, &val);
865 *percent = val;
866 }
709 } 867 }
868}
710 869
711 instproxy_lock(client); 870void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list)
712 instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); 871{
713 instproxy_unlock(client); 872 plist_t node = NULL;
873
874 if (status && plist_get_node_type(status) == PLIST_DICT) {
875 /* command specific logic: parse browsed list */
876 if (list != NULL) {
877 node = plist_dict_get_item(status, "CurrentList");
878 if (node) {
879 *current_amount = plist_array_get_size(node);
880 *list = plist_copy(node);
881 }
882 }
714 883
715 if (res != INSTPROXY_E_SUCCESS) { 884 if (total != NULL) {
716 debug_info("could not send plist, error %d", res); 885 node = plist_dict_get_item(status, "Total");
717 return res; 886 if (node) {
887 plist_get_uint_val(node, total);
888 }
889 }
890
891 if (current_amount != NULL) {
892 node = plist_dict_get_item(status, "CurrentAmount");
893 if (node) {
894 plist_get_uint_val(node, current_amount);
895 }
896 }
897
898 if (current_index != NULL) {
899 node = plist_dict_get_item(status, "CurrentIndex");
900 if (node) {
901 plist_get_uint_val(node, current_index);
902 }
903 }
718 } 904 }
719 return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data);
720} 905}
721 906
722/** 907void instproxy_command_get_name(plist_t command, char** name)
723 * Create a new client_options plist. 908{
724 * 909 if (name) {
725 * @return A new plist_t of type PLIST_DICT. 910 plist_t node = plist_dict_get_item(command, "Command");
726 */ 911 if (node) {
727plist_t instproxy_client_options_new() 912 plist_get_string_val(node, name);
913 } else {
914 *name = NULL;
915 }
916 }
917}
918
919plist_t instproxy_client_options_new(void)
728{ 920{
729 return plist_new_dict(); 921 return plist_new_dict();
730} 922}
731 923
732/**
733 * Add one or more new key:value pairs to the given client_options.
734 *
735 * @param client_options The client options to modify.
736 * @param ... KEY, VALUE, [KEY, VALUE], NULL
737 *
738 * @note The keys and values passed are expected to be strings, except for
739 * "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type
740 * PLIST_DATA as value, or "SkipUninstall" needing int as value.
741 */
742void instproxy_client_options_add(plist_t client_options, ...) 924void instproxy_client_options_add(plist_t client_options, ...)
743{ 925{
744 if (!client_options) 926 if (!client_options)
745 return; 927 return;
928
746 va_list args; 929 va_list args;
747 va_start(args, client_options); 930 va_start(args, client_options);
748 char *arg = va_arg(args, char*); 931 char *arg = va_arg(args, char*);
@@ -750,21 +933,21 @@ void instproxy_client_options_add(plist_t client_options, ...)
750 char *key = strdup(arg); 933 char *key = strdup(arg);
751 if (!strcmp(key, "SkipUninstall")) { 934 if (!strcmp(key, "SkipUninstall")) {
752 int intval = va_arg(args, int); 935 int intval = va_arg(args, int);
753 plist_dict_insert_item(client_options, key, plist_new_bool(intval)); 936 plist_dict_set_item(client_options, key, plist_new_bool(intval));
754 } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata")) { 937 } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes") || !strcmp(key, "BundleIDs")) {
755 plist_t plistval = va_arg(args, plist_t); 938 plist_t plistval = va_arg(args, plist_t);
756 if (!plistval) { 939 if (!plistval) {
757 free(key); 940 free(key);
758 break; 941 break;
759 } 942 }
760 plist_dict_insert_item(client_options, key, plist_copy(plistval)); 943 plist_dict_set_item(client_options, key, plist_copy(plistval));
761 } else { 944 } else {
762 char *strval = va_arg(args, char*); 945 char *strval = va_arg(args, char*);
763 if (!strval) { 946 if (!strval) {
764 free(key); 947 free(key);
765 break; 948 break;
766 } 949 }
767 plist_dict_insert_item(client_options, key, plist_new_string(strval)); 950 plist_dict_set_item(client_options, key, plist_new_string(strval));
768 } 951 }
769 free(key); 952 free(key);
770 arg = va_arg(args, char*); 953 arg = va_arg(args, char*);
@@ -772,15 +955,106 @@ void instproxy_client_options_add(plist_t client_options, ...)
772 va_end(args); 955 va_end(args);
773} 956}
774 957
775/** 958void instproxy_client_options_set_return_attributes(plist_t client_options, ...)
776 * Free client_options plist. 959{
777 * 960 if (!client_options)
778 * @param client_options The client options plist to free. Does nothing if NULL 961 return;
779 * is passed. 962
780 */ 963 plist_t return_attributes = plist_new_array();
964
965 va_list args;
966 va_start(args, client_options);
967 char *arg = va_arg(args, char*);
968 while (arg) {
969 char *attribute = strdup(arg);
970 plist_array_append_item(return_attributes, plist_new_string(attribute));
971 free(attribute);
972 arg = va_arg(args, char*);
973 }
974 va_end(args);
975
976 plist_dict_set_item(client_options, "ReturnAttributes", return_attributes);
977}
978
781void instproxy_client_options_free(plist_t client_options) 979void instproxy_client_options_free(plist_t client_options)
782{ 980{
783 if (client_options) { 981 if (client_options) {
784 plist_free(client_options); 982 plist_free(client_options);
785 } 983 }
786} 984}
985
986instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path)
987{
988 if (!client || !client->parent || !bundle_id)
989 return INSTPROXY_E_INVALID_ARG;
990
991 plist_t apps = NULL;
992
993 // create client options for any application types
994 plist_t client_opts = instproxy_client_options_new();
995 instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL);
996
997 // only return attributes we need
998 instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", "Path", NULL);
999
1000 // only query for specific appid
1001 const char* appids[] = {bundle_id, NULL};
1002
1003 // query device for list of apps
1004 instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps);
1005
1006 instproxy_client_options_free(client_opts);
1007
1008 if (ierr != INSTPROXY_E_SUCCESS) {
1009 return ierr;
1010 }
1011
1012 plist_t app_found = plist_access_path(apps, 1, bundle_id);
1013 if (!app_found) {
1014 if (apps)
1015 plist_free(apps);
1016 *path = NULL;
1017 return INSTPROXY_E_OP_FAILED;
1018 }
1019
1020 char* path_str = NULL;
1021 plist_t path_p = plist_dict_get_item(app_found, "Path");
1022 if (path_p) {
1023 plist_get_string_val(path_p, &path_str);
1024 }
1025
1026 char* exec_str = NULL;
1027 plist_t exec_p = plist_dict_get_item(app_found, "CFBundleExecutable");
1028 if (exec_p) {
1029 plist_get_string_val(exec_p, &exec_str);
1030 }
1031
1032 if (!path_str) {
1033 debug_info("app path not found");
1034 return INSTPROXY_E_OP_FAILED;
1035 }
1036
1037 if (!exec_str) {
1038 debug_info("bundle executable not found");
1039 return INSTPROXY_E_OP_FAILED;
1040 }
1041
1042 plist_free(apps);
1043
1044 char* ret = (char*)malloc(strlen(path_str) + 1 + strlen(exec_str) + 1);
1045 strcpy(ret, path_str);
1046 strcat(ret, "/");
1047 strcat(ret, exec_str);
1048
1049 *path = ret;
1050
1051 if (path_str) {
1052 free(path_str);
1053 }
1054
1055 if (exec_str) {
1056 free(exec_str);
1057 }
1058
1059 return INSTPROXY_E_SUCCESS;
1060}