| /* |
| * installation_proxy.c |
| * com.apple.mobile.installation_proxy service implementation. |
| * |
| * 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 |
| */ |
| |
| #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 "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; |
| 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("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("Unlocked"); |
| mutex_unlock(&client->mutex); |
| } |
| |
| /** |
| * 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 |
| * |
| * @return A matching instproxy_error_t error code, |
| * INSTPROXY_E_UNKNOWN_ERROR otherwise. |
| */ |
| static instproxy_error_t instproxy_error(property_list_service_error_t err) |
| { |
| switch (err) { |
| case PROPERTY_LIST_SERVICE_E_SUCCESS: |
| return INSTPROXY_E_SUCCESS; |
| case PROPERTY_LIST_SERVICE_E_INVALID_ARG: |
| return INSTPROXY_E_INVALID_ARG; |
| case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: |
| 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; |
| } |
| |
| LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client) |
| { |
| property_list_service_client_t plistclient = NULL; |
| 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; |
| mutex_init(&client_loc->mutex); |
| client_loc->receive_status_thread = THREAD_T_NULL; |
| |
| *client = client_loc; |
| return INSTPROXY_E_SUCCESS; |
| } |
| |
| LIBIMOBILEDEVICE_API 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; |
| } |
| |
| LIBIMOBILEDEVICE_API instproxy_error_t instproxy_client_free(instproxy_client_t client) |
| { |
| if (!client) |
| return INSTPROXY_E_INVALID_ARG; |
| |
| property_list_service_client_t parent = client->parent; |
| client->parent = 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; |
| } |
| property_list_service_client_free(parent); |
| mutex_destroy(&client->mutex); |
| free(client); |
| |
| return INSTPROXY_E_SUCCESS; |
| } |
| |
| /** |
| * Sends a command to the device. |
| * Only used internally. |
| * |
| * @param client The connected installation_proxy client. |
| * @param command The command to execute. Required. |
| * |
| * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if |
| * an error occurred. |
| */ |
| static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) |
| { |
| if (!client || !command) |
| return INSTPROXY_E_INVALID_ARG; |
| |
| instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); |
| |
| if (res != INSTPROXY_E_SUCCESS) { |
| debug_info("could not send command plist, error %d", res); |
| return res; |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Internally used function that will synchronously receive messages from |
| * the specified installation_proxy until it completes or an error occurs. |
| * |
| * If status_cb is not NULL, the callback function will be called each time |
| * a status update or error message is received. |
| * |
| * @param client The connected installation proxy client |
| * @param status_cb Pointer to a callback function or NULL |
| * @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_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 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, &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; |
| } |
| |
| /* 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 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 |
| 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 |
| free(status_name); |
| status_name = NULL; |
| } |
| |
| /* invoke status callback function */ |
| if (status_cb) { |
| status_cb(command, node, user_data); |
| } |
| |
| plist_free(node); |
| node = NULL; |
| } |
| } while (!complete && client->parent); |
| |
| if (command_name) |
| free(command_name); |
| |
| return res; |
| } |
| |
| /** |
| * Internally used "receive status" thread function that will call the specified |
| * callback function when status update messages (or error messages) are |
| * received. |
| * |
| * @param arg Pointer to an allocated struct instproxy_status_data that holds |
| * the required data about the connected client and the callback function. |
| * |
| * @return Always NULL. |
| */ |
| static void* instproxy_receive_status_loop_thread(void* arg) |
| { |
| struct instproxy_status_data *data = (struct instproxy_status_data*)arg; |
| |
| /* 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->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; |
| } |
| |
| instproxy_unlock(data->client); |
| free(data); |
| |
| return NULL; |
| } |
| |
| /** |
| * 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 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 command completed successfully (sync). |
| * An INSTPROXY_E_* error value is returned if an error occurred. |
| */ |
| 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 (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->user_data = user_data; |
| |
| if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) { |
| res = INSTPROXY_E_SUCCESS; |
| } |
| } |
| } else { |
| /* sync mode as a fallback */ |
| res = instproxy_receive_status_loop(client, command, status_cb, user_data); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Internal core function to send a command and process the response. |
| * |
| * @param client The connected installation_proxy client |
| * @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 occurred. |
| */ |
| 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 || !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; |
| |
| /* send command */ |
| instproxy_lock(client); |
| res = instproxy_send_command(client, command); |
| instproxy_unlock(client); |
| |
| /* loop until status or error is received */ |
| res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data); |
| |
| return res; |
| } |
| |
| 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) |
| { |
| 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; |
| } |
| |
| static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *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_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; |
| |
| plist_t result_array = plist_new_array(); |
| |
| 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); |
| } |
| |
| plist_free(command); |
| |
| return res; |
| } |
| |
| 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, 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; |
| |
| 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(); |
| } |
| |
| /* 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); |
| } |
| |
| if (node) { |
| plist_dict_set_item(command, "ClientOptions", node); |
| } |
| |
| 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; |
| } |
| |
| 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, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : 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, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : 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, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : 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); |
| |
| 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) |
| { |
| 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; |
| } |
| |
| 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, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : 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, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : 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, const char** capabilities, plist_t client_options, plist_t *result) |
| { |
| if (!client || !capabilities || !result) |
| return INSTPROXY_E_INVALID_ARG; |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| LIBIMOBILEDEVICE_API instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code) |
| { |
| instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; |
| |
| if (!status || !name) |
| return INSTPROXY_E_INVALID_ARG; |
| |
| 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; |
| } |
| |
| if (code != NULL) { |
| *code = 0; |
| node = plist_dict_get_item(status, "ErrorDetail"); |
| if (node) { |
| plist_get_uint_val(node, code); |
| *code &= 0xffffffff; |
| } |
| } |
| |
| 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 res; |
| } |
| |
| LIBIMOBILEDEVICE_API void instproxy_status_get_name(plist_t status, char **name) |
| { |
| if (name) { |
| plist_t node = plist_dict_get_item(status, "Status"); |
| if (node) { |
| plist_get_string_val(node, name); |
| } else { |
| *name = NULL; |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |
| } |
| |
| 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 (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) |
| { |
| if (name) { |
| plist_t node = plist_dict_get_item(command, "Command"); |
| if (node) { |
| plist_get_string_val(node, name); |
| } else { |
| *name = NULL; |
| } |
| } |
| } |
| |
| LIBIMOBILEDEVICE_API plist_t instproxy_client_options_new(void) |
| { |
| return plist_new_dict(); |
| } |
| |
| LIBIMOBILEDEVICE_API 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*); |
| while (arg) { |
| char *key = strdup(arg); |
| if (!strcmp(key, "SkipUninstall")) { |
| int intval = va_arg(args, int); |
| plist_dict_set_item(client_options, key, plist_new_bool(intval)); |
| } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes")) { |
| plist_t plistval = va_arg(args, plist_t); |
| if (!plistval) { |
| free(key); |
| break; |
| } |
| plist_dict_set_item(client_options, key, plist_copy(plistval)); |
| } else { |
| char *strval = va_arg(args, char*); |
| if (!strval) { |
| free(key); |
| break; |
| } |
| plist_dict_set_item(client_options, key, plist_new_string(strval)); |
| } |
| free(key); |
| arg = va_arg(args, char*); |
| } |
| va_end(args); |
| } |
| |
| LIBIMOBILEDEVICE_API 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); |
| } |
| |
| LIBIMOBILEDEVICE_API void instproxy_client_options_free(plist_t client_options) |
| { |
| if (client_options) { |
| plist_free(client_options); |
| } |
| } |
| |
| LIBIMOBILEDEVICE_API 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; |
| } |