From 9d7667a1ace1da60f508fa075793bba87e98af4a Mon Sep 17 00:00:00 2001 From: Martin Szulecki Date: Mon, 26 Jan 2015 23:37:08 +0100 Subject: installation_proxy: Refactor implementation, add new commands and helpers --- cython/installation_proxy.pxi | 6 +- include/libimobiledevice/installation_proxy.h | 117 +++- src/installation_proxy.c | 830 ++++++++++++++++++-------- src/installation_proxy.h | 5 +- 4 files changed, 710 insertions(+), 248 deletions(-) diff --git a/cython/installation_proxy.pxi b/cython/installation_proxy.pxi index d2d9b38..bf2c1da 100644 --- a/cython/installation_proxy.pxi +++ b/cython/installation_proxy.pxi @@ -2,7 +2,7 @@ cdef extern from "libimobiledevice/installation_proxy.h": cdef struct instproxy_client_private: pass ctypedef instproxy_client_private *instproxy_client_t - ctypedef void (*instproxy_status_cb_t) (const_char_ptr operation, plist.plist_t status, void *user_data) + ctypedef void (*instproxy_status_cb_t) (plist.plist_t command, plist.plist_t status, void *user_data) ctypedef enum instproxy_error_t: INSTPROXY_E_SUCCESS = 0 @@ -27,8 +27,8 @@ cdef extern from "libimobiledevice/installation_proxy.h": instproxy_error_t instproxy_restore(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) instproxy_error_t instproxy_remove_archive(instproxy_client_t client, char *appid, plist.plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) -cdef void instproxy_notify_cb(const_char_ptr operation, plist.plist_t status, void *py_callback) with gil: - (py_callback)(operation, plist.plist_t_to_node(status, False)) +cdef void instproxy_notify_cb(plist.plist_t command, plist.plist_t status, void *py_callback) with gil: + (py_callback)(plist.plist_t_to_node(command, False), plist.plist_t_to_node(status, False)) cdef class InstallationProxyError(BaseError): def __init__(self, *args, **kwargs): diff --git a/include/libimobiledevice/installation_proxy.h b/include/libimobiledevice/installation_proxy.h index 82af71b..6810978 100644 --- a/include/libimobiledevice/installation_proxy.h +++ b/include/libimobiledevice/installation_proxy.h @@ -3,7 +3,8 @@ * @brief Manage applications on a device. * \internal * - * 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 @@ -107,8 +108,8 @@ typedef enum { typedef struct instproxy_client_private instproxy_client_private; typedef instproxy_client_private *instproxy_client_t; /**< The client handle. */ -/** Reports the status of the given operation */ -typedef void (*instproxy_status_cb_t) (const char *operation, plist_t status, void *user_data); +/** Reports the status response of the given command */ +typedef void (*instproxy_status_cb_t) (plist_t command, plist_t status, void *user_data); /* Interface */ @@ -169,6 +170,41 @@ instproxy_error_t instproxy_client_free(instproxy_client_t client); */ instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result); +/** + * List pages of installed applications in a callback. + * + * @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" -> "System" + * "ApplicationType" -> "User" + * "ApplicationType" -> "Internal" + * "ApplicationType" -> "Any" + * @param status_cb Callback function to process each page of application + * information. Passing a callback is required. + * @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. + */ +instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data); + +/** + * Lookup information about specific applications from the device. + * + * @param client The connected installation_proxy client + * @param appids A PLIST_ARRAY with PLIST_STRINGs of bundle identifiers, a + * single PLIST_STRING for one bundle identifier or NULL to lookup all. + * @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 requested information about the application or NULL on errors. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + */ +instproxy_error_t instproxy_lookup(instproxy_client_t client, plist_t appids, plist_t client_options, plist_t *result); + /** * Install an application on the device. * @@ -333,10 +369,83 @@ instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid */ 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); +/** + * Checks a device for certain capabilities. + * + * @param client The connected installation_proxy client + * @param capabilities A PLIST_ARRAY with PLIST_STRINGs of capability names. + * The capabilities are passed through and queried from MobileGestalt. + * @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 if the capabilities matched or NULL on errors. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + */ +instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, plist_t capabilities, plist_t client_options, plist_t *result); + /* Helper */ /** - * Create a new client_options plist. + * Gets the name from a command dictionary. + * + * @param command The dictionary describing the command. + * @param name Pointer to store the name of the command. + */ +void instproxy_command_get_name(plist_t command, char** name); + +/** + * Gets the name of a status. + * + * @param status The dictionary status response to use. + * @param name Pointer to store the name of the status. + */ +void instproxy_status_get_name(plist_t status, char **name); + +/** + * Gets error name, code and description from a response if available. + * + * @param status The dictionary status response to use. + * @param name Pointer to store the name of an error. + * @param description Pointer to store error description text if available. + * The caller is reponsible for freeing the allocated buffer after use. + * If NULL is passed no description will be returned. + * @param code Pointer to store the returned error code if available. + * If NULL is passed no error code will be returned. + * + * @return INSTPROXY_E_SUCCESS if no error is found or an INSTPROXY_E_* error + * value matching the error that ẃas found in the status. + */ +instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code); + +/** + * Gets total and current item information from a browse response if available. + * + * @param status The dictionary status response to use. + * @param total Pointer to store the total number of items. + * @param current_index Pointer to store the current index of all browsed items. + * @param current_amount Pointer to store the amount of items in the + * current list. + * @param list Pointer to store a newly allocated plist with items. + * The caller is reponsible for freeing the list after use. + * If NULL is passed no list will be returned. If NULL is returned no + * list was found in the status. + */ +void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list); + + +/** + * Gets progress in percentage from a status if available. + * + * @param status The dictionary status response to use. + * @param name Pointer to store the progress in percent (0-100) or -1 if not + * progress was found in the status. + */ +void instproxy_status_get_percent_complete(plist_t status, int *percent); + +/** + * Creates a new client_options plist. * * @return A new plist_t of type PLIST_DICT. */ diff --git a/src/installation_proxy.c b/src/installation_proxy.c index 3d9d314..b7326dc 100644 --- a/src/installation_proxy.c +++ b/src/installation_proxy.c @@ -2,8 +2,8 @@ * installation_proxy.c * com.apple.mobile.installation_proxy service implementation. * - * Copyright (c) 2013 Martin Szulecki All Rights Reserved. - * 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 @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -29,13 +30,155 @@ #include "property_list_service.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. * @@ -97,7 +240,7 @@ LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_new(idevice_t device, lo instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); client_loc->parent = plistclient; mutex_init(&client_loc->mutex); - client_loc->status_updater = (thread_t)NULL; + client_loc->receive_status_thread = (thread_t)NULL; *client = client_loc; return INSTPROXY_E_SUCCESS; @@ -117,11 +260,11 @@ LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_free(instproxy_client_t property_list_service_client_free(client->parent); client->parent = NULL; - if (client->status_updater) { - debug_info("joining status_updater"); - thread_join(client->status_updater); - thread_free(client->status_updater); - client->status_updater = (thread_t)NULL; + 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; } mutex_destroy(&client->mutex); free(client); @@ -142,93 +285,18 @@ LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_free(instproxy_client_t * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if * an error occured. */ -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) -{ - if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) - return INSTPROXY_E_INVALID_ARG; - - plist_t dict = plist_new_dict(); - if (appid) { - plist_dict_set_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - } - if (client_options && (plist_dict_get_size(client_options) > 0)) { - plist_dict_set_item(dict, "ClientOptions", plist_copy(client_options)); - } - plist_dict_set_item(dict, "Command", plist_new_string(command)); - if (package_path) { - plist_dict_set_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; -} - -LIBIMOBILEDEVICE_API instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) +static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) { - if (!client || !client->parent || !result) + if (!client || !command) return INSTPROXY_E_INVALID_ARG; - instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); - 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 && res != INSTPROXY_E_RECEIVE_TIMEOUT) { - 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; - } else { - plist_free(apps_array); + debug_info("could not send command plist, error %d", res); + return res; } -leave_unlock: - instproxy_unlock(client); return res; } @@ -241,78 +309,102 @@ 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, 1000)); + res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); instproxy_unlock(client); + + /* 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; + } + + if (error_description) { + free(error_description); + error_description = NULL; } - /* check for 'Error', so we can abort cleanly */ - plist_t err = plist_dict_get_item(dict, "Error"); - if (err) { + + /* check status from response */ + instproxy_status_get_name(node, &status_name); + if (!status_name) { + debug_info("failed to retrieve name from status response with error %d.", res); + complete = 1; + } + + if (status_name) { + 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. * @@ -321,23 +413,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, * * @return Always NULL. */ -static void* instproxy_status_updater(void* 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->status_updater) { - thread_free(data->client->status_updater); - data->client->status_updater = (thread_t)NULL; + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = (thread_t)NULL; } + instproxy_unlock(data->client); free(data); @@ -345,198 +441,454 @@ static void* instproxy_status_updater(void* 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). + * when the command completed successfully (sync). * An INSTPROXY_E_* error value is returned if an error occured. */ -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; - if (thread_new(&client->status_updater, instproxy_status_updater, data) == 0) { + 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. */ -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; } -LIBIMOBILEDEVICE_API 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) +LIBIMOBILEDEVICE_API 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; } -LIBIMOBILEDEVICE_API 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); } -LIBIMOBILEDEVICE_API 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) +LIBIMOBILEDEVICE_API 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; - instproxy_lock(client); - res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL); - instproxy_unlock(client); + plist_t result_array = plist_new_array(); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + 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_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; } -LIBIMOBILEDEVICE_API 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); + } +} + +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_lookup(instproxy_client_t client, plist_t appids, plist_t client_options, plist_t *result) { 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); + if (appids && (plist_get_node_type(appids) != PLIST_ARRAY && plist_get_node_type(appids) != PLIST_STRING)) + return INSTPROXY_E_INVALID_ARG; - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t lookup_result = NULL; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Lookup")); + if (appids) { + plist_dict_set_item(client_options, "BundleIDs", plist_copy(appids)); } + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); - 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; + 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); } - res = INSTPROXY_E_SUCCESS; + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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; } LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API 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, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +LIBIMOBILEDEVICE_API instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, plist_t capabilities, plist_t client_options, plist_t *result) +{ + if (!capabilities || (plist_get_node_type(capabilities) != PLIST_ARRAY && plist_get_node_type(capabilities) != PLIST_DICT)) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; - } + plist_t lookup_result = NULL; - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL); - instproxy_unlock(client); + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + 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)); + plist_dict_set_item(command, "Capabilities", plist_copy(capabilities)); + + 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); } - return instproxy_create_status_updater(client, status_cb, "Archive", user_data); + + plist_free(command); + + return res; } -LIBIMOBILEDEVICE_API 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) +LIBIMOBILEDEVICE_API 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); + } } - return instproxy_create_status_updater(client, status_cb, "Restore", user_data); + + if (*name) { + res = instproxy_strtoerr(*name); + } + + return res; } -LIBIMOBILEDEVICE_API 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) +LIBIMOBILEDEVICE_API void instproxy_status_get_name(plist_t status, char **name) { - if (!client || !client->parent || !appid) - return INSTPROXY_E_INVALID_ARG; + *name = NULL; + if (name) { + plist_t node = plist_dict_get_item(status, "Status"); + if (node) { + plist_get_string_val(node, name); + } + } +} - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; +LIBIMOBILEDEVICE_API 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; + } } +} - /* send command */ - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); - instproxy_unlock(client); +LIBIMOBILEDEVICE_API 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); + } + } + } +} + +LIBIMOBILEDEVICE_API void instproxy_command_get_name(plist_t command, char** name) +{ + *name = NULL; + plist_t node = plist_dict_get_item(command, "Command"); + if (node) { + plist_get_string_val(node, name); } - return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data); } LIBIMOBILEDEVICE_API plist_t instproxy_client_options_new() diff --git a/src/installation_proxy.h b/src/installation_proxy.h index 4f79c12..15dbb84 100644 --- a/src/installation_proxy.h +++ b/src/installation_proxy.h @@ -2,7 +2,8 @@ * installation_proxy.h * com.apple.mobile.installation_proxy service header file. * - * 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 @@ -29,7 +30,7 @@ struct instproxy_client_private { property_list_service_client_t parent; mutex_t mutex; - thread_t status_updater; + thread_t receive_status_thread; }; #endif -- cgit v1.1-32-gdbae