diff options
Diffstat (limited to 'src/installation_proxy.c')
-rw-r--r-- | src/installation_proxy.c | 1232 |
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 @@ * installation_proxy.c * com.apple.mobile.installation_proxy service implementation. * - * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2013 Nikias Bassen, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. - * + * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <string.h> #include <stdlib.h> +#include <inttypes.h> #include <unistd.h> #include <plist/plist.h> #include "installation_proxy.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +typedef enum { + INSTPROXY_COMMAND_TYPE_ASYNC, + INSTPROXY_COMMAND_TYPE_SYNC +} instproxy_command_type_t; struct instproxy_status_data { instproxy_client_t client; + plist_t command; instproxy_status_cb_t cbfunc; - char *operation; void *user_data; }; /** + * Converts an error string identifier to a instproxy_error_t value. + * Used internally to get correct error codes from a response. + * + * @param name The error name to convert. + * @param error_detail Pointer to store error detail text if available. The + * caller is reponsible for freeing the allocated buffer after use. If NULL + * is passed no error detail will be returned. + * + * @return A matching instproxy_error_t error code or + * INSTPROXY_E_UNKNOWN_ERROR otherwise. + */ +static instproxy_error_t instproxy_strtoerr(const char* name) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + + if (strcmp(name, "AlreadyArchived") == 0) { + err = INSTPROXY_E_ALREADY_ARCHIVED; + } else if (strcmp(name, "APIInternalError") == 0) { + err = INSTPROXY_E_API_INTERNAL_ERROR; + } else if (strcmp(name, "ApplicationAlreadyInstalled") == 0) { + err = INSTPROXY_E_APPLICATION_ALREADY_INSTALLED; + } else if (strcmp(name, "ApplicationMoveFailed") == 0) { + err = INSTPROXY_E_APPLICATION_MOVE_FAILED; + } else if (strcmp(name, "ApplicationSINFCaptureFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED; + } else if (strcmp(name, "ApplicationSandboxFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SANDBOX_FAILED; + } else if (strcmp(name, "ApplicationVerificationFailed") == 0) { + err = INSTPROXY_E_APPLICATION_VERIFICATION_FAILED; + } else if (strcmp(name, "ArchiveDestructionFailed") == 0) { + err = INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED; + } else if (strcmp(name, "BundleVerificationFailed") == 0) { + err = INSTPROXY_E_BUNDLE_VERIFICATION_FAILED; + } else if (strcmp(name, "CarrierBundleCopyFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED; + } else if (strcmp(name, "CarrierBundleDirectoryCreationFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED; + } else if (strcmp(name, "CarrierBundleMissingSupportedSIMs") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS; + } else if (strcmp(name, "CommCenterNotificationFailed") == 0) { + err = INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED; + } else if (strcmp(name, "ContainerCreationFailed") == 0) { + err = INSTPROXY_E_CONTAINER_CREATION_FAILED; + } else if (strcmp(name, "ContainerP0wnFailed") == 0) { + err = INSTPROXY_E_CONTAINER_P0WN_FAILED; + } else if (strcmp(name, "ContainerRemovalFailed") == 0) { + err = INSTPROXY_E_CONTAINER_REMOVAL_FAILED; + } else if (strcmp(name, "EmbeddedProfileInstallFailed") == 0) { + err = INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED; + } else if (strcmp(name, "ExecutableTwiddleFailed") == 0) { + err = INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED; + } else if (strcmp(name, "ExistenceCheckFailed") == 0) { + err = INSTPROXY_E_EXISTENCE_CHECK_FAILED; + } else if (strcmp(name, "InstallMapUpdateFailed") == 0) { + err = INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED; + } else if (strcmp(name, "ManifestCaptureFailed") == 0) { + err = INSTPROXY_E_MANIFEST_CAPTURE_FAILED; + } else if (strcmp(name, "MapGenerationFailed") == 0) { + err = INSTPROXY_E_MAP_GENERATION_FAILED; + } else if (strcmp(name, "MissingBundleExecutable") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE; + } else if (strcmp(name, "MissingBundleIdentifier") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER; + } else if (strcmp(name, "MissingBundlePath") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_PATH; + } else if (strcmp(name, "MissingContainer") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER; + } else if (strcmp(name, "NotificationFailed") == 0) { + err = INSTPROXY_E_NOTIFICATION_FAILED; + } else if (strcmp(name, "PackageExtractionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_EXTRACTION_FAILED; + } else if (strcmp(name, "PackageInspectionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_INSPECTION_FAILED; + } else if (strcmp(name, "PackageMoveFailed") == 0) { + err = INSTPROXY_E_PACKAGE_MOVE_FAILED; + } else if (strcmp(name, "PathConversionFailed") == 0) { + err = INSTPROXY_E_PATH_CONVERSION_FAILED; + } else if (strcmp(name, "RestoreContainerFailed") == 0) { + err = INSTPROXY_E_RESTORE_CONTAINER_FAILED; + } else if (strcmp(name, "SeatbeltProfileRemovalFailed") == 0) { + err = INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED; + } else if (strcmp(name, "StageCreationFailed") == 0) { + err = INSTPROXY_E_STAGE_CREATION_FAILED; + } else if (strcmp(name, "SymlinkFailed") == 0) { + err = INSTPROXY_E_SYMLINK_FAILED; + } else if (strcmp(name, "UnknownCommand") == 0) { + err = INSTPROXY_E_UNKNOWN_COMMAND; + } else if (strcmp(name, "iTunesArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "iTunesMetadataCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED; + } else if (strcmp(name, "DeviceOSVersionTooLow") == 0) { + err = INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW; + } else if (strcmp(name, "DeviceFamilyNotSupported") == 0) { + err = INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED; + } else if (strcmp(name, "PackagePatchFailed") == 0) { + err = INSTPROXY_E_PACKAGE_PATCH_FAILED; + } else if (strcmp(name, "IncorrectArchitecture") == 0) { + err = INSTPROXY_E_INCORRECT_ARCHITECTURE; + } else if (strcmp(name, "PluginCopyFailed") == 0) { + err = INSTPROXY_E_PLUGIN_COPY_FAILED; + } else if (strcmp(name, "BreadcrumbFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_FAILED; + } else if (strcmp(name, "BreadcrumbUnlockFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED; + } else if (strcmp(name, "GeoJSONCaptureFailed") == 0) { + err = INSTPROXY_E_GEOJSON_CAPTURE_FAILED; + } else if (strcmp(name, "NewsstandArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "MissingCommand") == 0) { + err = INSTPROXY_E_MISSING_COMMAND; + } else if (strcmp(name, "NotEntitled") == 0) { + err = INSTPROXY_E_NOT_ENTITLED; + } else if (strcmp(name, "MissingPackagePath") == 0) { + err = INSTPROXY_E_MISSING_PACKAGE_PATH; + } else if (strcmp(name, "MissingContainerPath") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER_PATH; + } else if (strcmp(name, "MissingApplicationIdentifier") == 0) { + err = INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER; + } else if (strcmp(name, "MissingAttributeValue") == 0) { + err = INSTPROXY_E_MISSING_ATTRIBUTE_VALUE; + } else if (strcmp(name, "LookupFailed") == 0) { + err = INSTPROXY_E_LOOKUP_FAILED; + } else if (strcmp(name, "DictCreationFailed") == 0) { + err = INSTPROXY_E_DICT_CREATION_FAILED; + } else if (strcmp(name, "InstallProhibited") == 0) { + err = INSTPROXY_E_INSTALL_PROHIBITED; + } else if (strcmp(name, "UninstallProhibited") == 0) { + err = INSTPROXY_E_UNINSTALL_PROHIBITED; + } else if (strcmp(name, "MissingBundleVersion") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_VERSION; + } + + return err; +} + +/** * Locks an installation_proxy client, used for thread safety. * * @param client The installation_proxy client to lock */ static void instproxy_lock(instproxy_client_t client) { - debug_info("InstallationProxy: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks an installation_proxy client, used for thread safety. - * + * * @param client The installation_proxy client to lock */ static void instproxy_unlock(instproxy_client_t client) { - debug_info("InstallationProxy: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** - * Convert a property_list_service_error_t value to an instproxy_error_t value. + * Converts a property_list_service_error_t value to an instproxy_error_t value. * Used internally to get correct error codes. * * @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) return INSTPROXY_E_PLIST_ERROR; case PROPERTY_LIST_SERVICE_E_MUX_ERROR: return INSTPROXY_E_CONN_FAILED; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return INSTPROXY_E_RECEIVE_TIMEOUT; default: break; } return INSTPROXY_E_UNKNOWN_ERROR; } -/** - * Connects to the installation_proxy service on the specified device. - * - * @param device The device to connect to - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * instproxy_client_t upon successful return. - * - * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value - * when an error occured. - */ -instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client) +instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return INSTPROXY_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return INSTPROXY_E_CONN_FAILED; + instproxy_error_t err = instproxy_error(property_list_service_client_new(device, service, &plistclient)); + if (err != INSTPROXY_E_SUCCESS) { + return err; } instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - client_loc->status_updater = NULL; + mutex_init(&client_loc->mutex); + client_loc->receive_status_thread = THREAD_T_NULL; *client = client_loc; return INSTPROXY_E_SUCCESS; } -/** - * Disconnects an installation_proxy client from the device and frees up the - * installation_proxy client data. - * - * @param client The installation_proxy client to disconnect and free. - * - * @return INSTPROXY_E_SUCCESS on success - * or INSTPROXY_E_INVALID_ARG if client is NULL. - */ +instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, INSTPROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(instproxy_client_new), &err); + return err; +} + instproxy_error_t instproxy_client_free(instproxy_client_t client) { if (!client) return INSTPROXY_E_INVALID_ARG; - property_list_service_client_free(client->parent); + property_list_service_client_t parent = client->parent; client->parent = NULL; - if (client->status_updater) { - debug_info("joining status_updater"); - g_thread_join(client->status_updater); - } - if (client->mutex) { - g_mutex_free(client->mutex); + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; } + property_list_service_client_free(parent); + mutex_destroy(&client->mutex); free(client); return INSTPROXY_E_SUCCESS; } /** - * Send a command with specified options to the device. + * Sends a command to the device. * Only used internally. * * @param client The connected installation_proxy client. * @param command The command to execute. Required. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param appid The ApplicationIdentifier to add or NULL if not required. - * @param package_path The installation package path or NULL if not required. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static instproxy_error_t instproxy_send_command(instproxy_client_t client, const char *command, plist_t client_options, const char *appid, const char *package_path) +static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) { - if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) + if (!client || !command) return INSTPROXY_E_INVALID_ARG; - plist_t dict = plist_new_dict(); - if (appid) { - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - } - if (client_options && (plist_dict_get_size(client_options) > 0)) { - plist_dict_insert_item(dict, "ClientOptions", plist_copy(client_options)); - } - plist_dict_insert_item(dict, "Command", plist_new_string(command)); - if (package_path) { - plist_dict_insert_item(dict, "PackagePath", plist_new_string(package_path)); - } - - instproxy_error_t err = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - return err; -} + instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); -/** - * List installed applications. This function runs synchronously. - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid client options include: - * "ApplicationType" -> "User" - * "ApplicationType" -> "System" - * @param result Pointer that will be set to a plist that will hold an array - * of PLIST_DICT holding information about the applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) -{ - if (!client || !client->parent || !result) - return INSTPROXY_E_INVALID_ARG; - - instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - - instproxy_lock(client); - res = instproxy_send_command(client, "Browse", client_options, NULL, NULL); if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist"); - goto leave_unlock; - } - - int browsing = 0; - plist_t apps_array = plist_new_array(); - plist_t dict = NULL; - - do { - browsing = 0; - dict = NULL; - res = instproxy_error(property_list_service_receive_plist(client->parent, &dict)); - if (res != INSTPROXY_E_SUCCESS) { - break; - } - if (dict) { - uint64_t i; - uint64_t current_amount = 0; - char *status = NULL; - plist_t camount = plist_dict_get_item(dict, "CurrentAmount"); - plist_t pstatus = plist_dict_get_item(dict, "Status"); - if (camount) { - plist_get_uint_val(camount, ¤t_amount); - } - if (current_amount > 0) { - plist_t current_list = plist_dict_get_item(dict, "CurrentList"); - for (i = 0; current_list && (i < current_amount); i++) { - plist_t item = plist_array_get_item(current_list, i); - plist_array_append_item(apps_array, plist_copy(item)); - } - } - if (pstatus) { - plist_get_string_val(pstatus, &status); - } - if (status) { - if (!strcmp(status, "BrowsingApplications")) { - browsing = 1; - } else if (!strcmp(status, "Complete")) { - debug_info("Browsing applications completed"); - res = INSTPROXY_E_SUCCESS; - } - free(status); - } - plist_free(dict); - } - } while (browsing); - - if (res == INSTPROXY_E_SUCCESS) { - *result = apps_array; + debug_info("could not send command plist, error %d", res); + return res; } -leave_unlock: - instproxy_unlock(client); return res; } @@ -269,78 +310,99 @@ leave_unlock: * * @param client The connected installation proxy client * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function - * in async mode or shown in debug messages in sync mode. + * @param command Operation specificiation in plist. Will be passed to the + * status_cb callback. * @param user_data Callback data passed to status_cb. */ -static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static instproxy_error_t instproxy_receive_status_loop(instproxy_client_t client, plist_t command, instproxy_status_cb_t status_cb, void *user_data) { instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - int ok = 1; - plist_t dict = NULL; + int complete = 0; + plist_t node = NULL; + char* command_name = NULL; + char* status_name = NULL; + char* error_name = NULL; + char* error_description = NULL; + uint64_t error_code = 0; +#ifndef STRIP_DEBUG_CODE + int percent_complete = 0; +#endif + + instproxy_command_get_name(command, &command_name); do { + /* receive status response */ instproxy_lock(client); - res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); + res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { + + /* break out if we have a communication problem */ + if (res != INSTPROXY_E_SUCCESS && res != INSTPROXY_E_RECEIVE_TIMEOUT) { debug_info("could not receive plist, error %d", res); break; } - if (dict) { - /* invoke callback function */ - if (status_cb) { - status_cb(operation, dict, user_data); + + /* parse status response */ + if (node) { + /* check status for possible error to allow reporting it and aborting it gracefully */ + res = instproxy_status_get_error(node, &error_name, &error_description, &error_code); + if (res != INSTPROXY_E_SUCCESS) { + 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"); + complete = 1; + } + + if (error_name) { + free(error_name); + error_name = NULL; } - /* check for 'Error', so we can abort cleanly */ - plist_t err = plist_dict_get_item(dict, "Error"); - if (err) { + + if (error_description) { + free(error_description); + error_description = NULL; + } + + /* check status from response */ + instproxy_status_get_name(node, &status_name); + if (!status_name) { + debug_info("ignoring message without Status key:"); + debug_plist(node); + } else { + if (!strcmp(status_name, "Complete")) { + complete = 1; + } else { + res = INSTPROXY_E_OP_IN_PROGRESS; + } #ifndef STRIP_DEBUG_CODE - char *err_msg = NULL; - plist_get_string_val(err, &err_msg); - if (err_msg) { - debug_info("(%s): ERROR: %s", operation, err_msg); - free(err_msg); + percent_complete = -1; + instproxy_status_get_percent_complete(node, &percent_complete); + if (percent_complete >= 0) { + debug_info("command: %s, status: %s, percent (%d%%)", command_name, status_name, percent_complete); + } else { + debug_info("command: %s, status: %s", command_name, status_name); } #endif - ok = 0; - res = INSTPROXY_E_OP_FAILED; + free(status_name); + status_name = NULL; } - /* get 'Status' */ - plist_t status = plist_dict_get_item(dict, "Status"); - if (status) { - char *status_msg = NULL; - plist_get_string_val(status, &status_msg); - if (status_msg) { - if (!strcmp(status_msg, "Complete")) { - ok = 0; - res = INSTPROXY_E_SUCCESS; - } -#ifndef STRIP_DEBUG_CODE - plist_t npercent = plist_dict_get_item(dict, "PercentComplete"); - if (npercent) { - uint64_t val = 0; - int percent; - plist_get_uint_val(npercent, &val); - percent = val; - debug_info("(%s): %s (%d%%)", operation, status_msg, percent); - } else { - debug_info("(%s): %s", operation, status_msg); - } -#endif - free(status_msg); - } + + /* invoke status callback function */ + if (status_cb) { + status_cb(command, node, user_data); } - plist_free(dict); - dict = NULL; + + plist_free(node); + node = NULL; } - } while (ok && client->parent); + } while (!complete && client->parent); + + if (command_name) + free(command_name); return res; } /** - * Internally used status updater thread function that will call the specified + * Internally used "receive status" thread function that will call the specified * callback function when status update messages (or error messages) are * received. * @@ -349,20 +411,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, * * @return Always NULL. */ -static gpointer instproxy_status_updater(gpointer arg) -{ +static void* instproxy_receive_status_loop_thread(void* arg) +{ struct instproxy_status_data *data = (struct instproxy_status_data*)arg; - /* run until the operation is complete or an error occurs */ - (void)instproxy_perform_operation(data->client, data->cbfunc, data->operation, data->user_data); + /* run until the command is complete or an error occurs */ + (void)instproxy_receive_status_loop(data->client, data->command, data->cbfunc, data->user_data); /* cleanup */ instproxy_lock(data->client); + debug_info("done, cleaning up."); - if (data->operation) { - free(data->operation); + + if (data->command) { + plist_free(data->command); + } + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; } - data->client->status_updater = NULL; + instproxy_unlock(data->client); free(data); @@ -370,379 +439,493 @@ static gpointer instproxy_status_updater(gpointer arg) } /** - * Internally used helper function that creates a status updater thread which - * will call the passed callback function when status updates occur. - * If status_cb is NULL no thread will be created, but the operation will - * run synchronously until it completes or an error occurs. + * Internally used helper function that creates a "receive status" thread which + * will call the passed callback function when a status is received. + * + * If async is 0 no thread will be created and the command will run + * synchronously until it completes or an error occurs. * * @param client The connected installation proxy client - * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function + * @param command Operation name. Will be passed to the callback function * in async mode or shown in debug messages in sync mode. + * @param async A boolean indicating if receive loop should be run + * asynchronously or block. + * @param status_cb Pointer to a callback function or NULL. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or - * when the operation completed successfully (sync). - * An INSTPROXY_E_* error value is returned if an error occured. + * when the command completed successfully (sync). + * An INSTPROXY_E_* error value is returned if an error occurred. */ -static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static 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) { + if (!client || !client->parent || !command) { + return INSTPROXY_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - if (status_cb) { + if (async == INSTPROXY_COMMAND_TYPE_ASYNC) { /* async mode */ struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); if (data) { data->client = client; + data->command = plist_copy(command); data->cbfunc = status_cb; - data->operation = strdup(operation); data->user_data = user_data; - client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); - if (client->status_updater) { + if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) { res = INSTPROXY_E_SUCCESS; } } } else { - /* sync mode */ - res = instproxy_perform_operation(client, NULL, operation, NULL); + /* sync mode as a fallback */ + res = instproxy_receive_status_loop(client, command, status_cb, user_data); } + return res; } - /** - * Internal function used by instproxy_install and instproxy_upgrade. + * Internal core function to send a command and process the response. * * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param command The command to execute. + * @param command The command specification dictionary. + * @param async A boolean indicating whether the receive loop should be run + * asynchronously or block until completing the command. + * @param status_cb Callback function to call if a command status is received. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static 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) +static 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) { - if (!client || !client->parent || !pkg_path) { + if (!client || !client->parent || !command) { return INSTPROXY_E_INVALID_ARG; } - if (client->status_updater) { + + if (client->receive_status_thread) { return INSTPROXY_E_OP_IN_PROGRESS; } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + /* send command */ instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path); + res = instproxy_send_command(client, command); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; - } + /* loop until status or error is received */ + res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data); - return instproxy_create_status_updater(client, status_cb, command, user_data); + return res; } -/** - * Install an application on the device. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_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) +instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install", user_data); + if (!client || !client->parent || !status_cb) + return INSTPROXY_E_INVALID_ARG; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, (void*)user_data); + + plist_free(command); + + return res; } -/** - * Upgrade an application on the device. This function is nearly the same as - * instproxy_install; the difference is that the installation progress on the - * device is faster if the application is already installed. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_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) +static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade", user_data); + plist_t *result_array = (plist_t*)user_data; + uint64_t current_amount = 0; + plist_t current_list = NULL; + uint64_t i; + + instproxy_status_get_current_list(status, NULL, NULL, ¤t_amount, ¤t_list); + + debug_info("current_amount: %d", current_amount); + + if (current_amount > 0) { + for (i = 0; current_list && (i < current_amount); i++) { + plist_t item = plist_array_get_item(current_list, i); + plist_array_append_item(*result_array, plist_copy(item)); + } + } + + if (current_list) + plist_free(current_list); } -/** - * Uninstall an application from the device. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to uninstall - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) { - if (!client || !client->parent || !appid) { + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - } - - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; - } instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall")); - instproxy_lock(client); - res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL); - instproxy_unlock(client); + plist_t result_array = plist_new_array(); - plist_free(dict); + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_append_current_list_to_result_cb, (void*)&result_array); + + if (res == INSTPROXY_E_SUCCESS) { + *result = result_array; + } else { + plist_free(result_array); } - return instproxy_create_status_updater(client, status_cb, "Uninstall", user_data); + plist_free(command); + + return res; } -/** - * List archived applications. This function runs synchronously. - * - * @see instproxy_archive - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param result Pointer that will be set to a plist containing a PLIST_DICT - * holding information about the archived applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +static void instproxy_copy_lookup_result_cb(plist_t command, plist_t status, void *user_data) +{ + plist_t* result = (plist_t*)user_data; + + plist_t node = plist_dict_get_item(status, "LookupResult"); + if (node) { + *result = plist_copy(node); + } +} + +instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result) { + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + int i = 0; + plist_t lookup_result = NULL; + plist_t command = NULL; + plist_t appid_array = NULL; + plist_t node = NULL; + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "LookupArchives", client_options, NULL, NULL); + command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Lookup")); + if (client_options) { + node = plist_copy(client_options); + } else if (appids) { + node = plist_new_dict(); + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; + /* add bundle identifiers to client options */ + if (appids) { + appid_array = plist_new_array(); + while (appids[i]) { + plist_array_append_item(appid_array, plist_new_string(appids[i])); + i++; + } + plist_dict_set_item(node, "BundleIDs", appid_array); } - res = instproxy_error(property_list_service_receive_plist(client->parent, result)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not receive plist, error %d", res); - goto leave_unlock; + if (node) { + plist_dict_set_item(command, "ClientOptions", node); } - res = INSTPROXY_E_SUCCESS; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); + + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); + } + + plist_free(command); + + return res; +} + +instproxy_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) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Install")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_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) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Upgrade")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Uninstall")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("LookupArchives")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)result); + + plist_free(command); -leave_unlock: - instproxy_unlock(client); return res; } -/** - * Archive an application on the device. - * This function tells the device to make an archive of the specified - * application. This results in the device creating a ZIP archive in the - * 'ApplicationArchives' directory and uninstalling the application. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to archive. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "SkipUninstall" -> Boolean - * "ArchiveType" -> "ApplicationOnly" - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Archive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Restore")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_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) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("RemoveArchive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result) +{ + if (!client || !capabilities || !result) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t lookup_result = NULL; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("CheckCapabilitiesMatch")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + if (capabilities) { + int i = 0; + plist_t capabilities_array = plist_new_array(); + while (capabilities[i]) { + plist_array_append_item(capabilities_array, plist_new_string(capabilities[i])); + i++; + } + plist_dict_set_item(command, "Capabilities", capabilities_array); } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL); - instproxy_unlock(client); + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); } - return instproxy_create_status_updater(client, status_cb, "Archive", user_data); + + plist_free(command); + + return res; } -/** - * Restore a previously archived application on the device. - * This function is the counterpart to instproxy_archive. - * @see instproxy_archive - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to restore. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + if (!status || !name) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t node = plist_dict_get_item(status, "Error"); + if (node) { + plist_get_string_val(node, name); + } else { + /* no error here */ + res = INSTPROXY_E_SUCCESS; } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL); - instproxy_unlock(client); + if (code != NULL) { + *code = 0; + node = plist_dict_get_item(status, "ErrorDetail"); + if (node) { + plist_get_uint_val(node, code); + *code &= 0xffffffff; + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (description != NULL) { + node = plist_dict_get_item(status, "ErrorDescription"); + if (node) { + plist_get_string_val(node, description); + } + } + + if (*name) { + res = instproxy_strtoerr(*name); } - return instproxy_create_status_updater(client, status_cb, "Restore", user_data); + + return res; } -/** - * Removes a previously archived application from the device. - * This function removes the ZIP archive from the 'ApplicationArchives' - * directory. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the archived app to remove. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so passing NULL is fine. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_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) +void instproxy_status_get_name(plist_t status, char **name) { - if (!client || !client->parent || !appid) - return INSTPROXY_E_INVALID_ARG; + if (name) { + plist_t node = plist_dict_get_item(status, "Status"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; +void instproxy_status_get_percent_complete(plist_t status, int *percent) +{ + uint64_t val = 0; + if (percent) { + plist_t node = plist_dict_get_item(status, "PercentComplete"); + if (node) { + plist_get_uint_val(node, &val); + *percent = val; + } } +} - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); - instproxy_unlock(client); +void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list) +{ + plist_t node = NULL; + + if (status && plist_get_node_type(status) == PLIST_DICT) { + /* command specific logic: parse browsed list */ + if (list != NULL) { + node = plist_dict_get_item(status, "CurrentList"); + if (node) { + *current_amount = plist_array_get_size(node); + *list = plist_copy(node); + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (total != NULL) { + node = plist_dict_get_item(status, "Total"); + if (node) { + plist_get_uint_val(node, total); + } + } + + if (current_amount != NULL) { + node = plist_dict_get_item(status, "CurrentAmount"); + if (node) { + plist_get_uint_val(node, current_amount); + } + } + + if (current_index != NULL) { + node = plist_dict_get_item(status, "CurrentIndex"); + if (node) { + plist_get_uint_val(node, current_index); + } + } } - return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data); } -/** - * Create a new client_options plist. - * - * @return A new plist_t of type PLIST_DICT. - */ -plist_t instproxy_client_options_new() +void instproxy_command_get_name(plist_t command, char** name) +{ + if (name) { + plist_t node = plist_dict_get_item(command, "Command"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} + +plist_t instproxy_client_options_new(void) { return plist_new_dict(); } -/** - * Add one or more new key:value pairs to the given client_options. - * - * @param client_options The client options to modify. - * @param ... KEY, VALUE, [KEY, VALUE], NULL - * - * @note The keys and values passed are expected to be strings, except for - * "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type - * PLIST_DATA as value, or "SkipUninstall" needing int as value. - */ void instproxy_client_options_add(plist_t client_options, ...) { if (!client_options) return; + va_list args; va_start(args, client_options); char *arg = va_arg(args, char*); @@ -750,21 +933,21 @@ void instproxy_client_options_add(plist_t client_options, ...) char *key = strdup(arg); if (!strcmp(key, "SkipUninstall")) { int intval = va_arg(args, int); - plist_dict_insert_item(client_options, key, plist_new_bool(intval)); - } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata")) { + plist_dict_set_item(client_options, key, plist_new_bool(intval)); + } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes") || !strcmp(key, "BundleIDs")) { plist_t plistval = va_arg(args, plist_t); if (!plistval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_copy(plistval)); + plist_dict_set_item(client_options, key, plist_copy(plistval)); } else { char *strval = va_arg(args, char*); if (!strval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_new_string(strval)); + plist_dict_set_item(client_options, key, plist_new_string(strval)); } free(key); arg = va_arg(args, char*); @@ -772,15 +955,106 @@ void instproxy_client_options_add(plist_t client_options, ...) va_end(args); } -/** - * Free client_options plist. - * - * @param client_options The client options plist to free. Does nothing if NULL - * is passed. - */ +void instproxy_client_options_set_return_attributes(plist_t client_options, ...) +{ + if (!client_options) + return; + + plist_t return_attributes = plist_new_array(); + + va_list args; + va_start(args, client_options); + char *arg = va_arg(args, char*); + while (arg) { + char *attribute = strdup(arg); + plist_array_append_item(return_attributes, plist_new_string(attribute)); + free(attribute); + arg = va_arg(args, char*); + } + va_end(args); + + plist_dict_set_item(client_options, "ReturnAttributes", return_attributes); +} + void instproxy_client_options_free(plist_t client_options) { if (client_options) { plist_free(client_options); } } + +instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path) +{ + if (!client || !client->parent || !bundle_id) + return INSTPROXY_E_INVALID_ARG; + + plist_t apps = NULL; + + // create client options for any application types + plist_t client_opts = instproxy_client_options_new(); + instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL); + + // only return attributes we need + instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", "Path", NULL); + + // only query for specific appid + const char* appids[] = {bundle_id, NULL}; + + // query device for list of apps + instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps); + + instproxy_client_options_free(client_opts); + + if (ierr != INSTPROXY_E_SUCCESS) { + return ierr; + } + + plist_t app_found = plist_access_path(apps, 1, bundle_id); + if (!app_found) { + if (apps) + plist_free(apps); + *path = NULL; + return INSTPROXY_E_OP_FAILED; + } + + char* path_str = NULL; + plist_t path_p = plist_dict_get_item(app_found, "Path"); + if (path_p) { + plist_get_string_val(path_p, &path_str); + } + + char* exec_str = NULL; + plist_t exec_p = plist_dict_get_item(app_found, "CFBundleExecutable"); + if (exec_p) { + plist_get_string_val(exec_p, &exec_str); + } + + if (!path_str) { + debug_info("app path not found"); + return INSTPROXY_E_OP_FAILED; + } + + if (!exec_str) { + debug_info("bundle executable not found"); + return INSTPROXY_E_OP_FAILED; + } + + plist_free(apps); + + char* ret = (char*)malloc(strlen(path_str) + 1 + strlen(exec_str) + 1); + strcpy(ret, path_str); + strcat(ret, "/"); + strcat(ret, exec_str); + + *path = ret; + + if (path_str) { + free(path_str); + } + + if (exec_str) { + free(exec_str); + } + + return INSTPROXY_E_SUCCESS; +} |