/** * 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) { 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 (res != INSTPROXY_E_SUCCESS) { debug_info("could not send plist, error %d", res); goto leave_unlock; } 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_E_SUCCESS; leave_unlock: instproxy_unlock(client); return res; }
/** * 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. */ 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; }
/** * Connects to the installation_proxy service on the specified device. * * @param device The device to connect to * @param service The service descriptor returned 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, 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->status_updater = (thread_t)NULL; *client = client_loc; return INSTPROXY_E_SUCCESS; }
/** * Send a command with specified options 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. */ 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_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; }
/** * 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 operation Operation name. Will be passed to the callback function * in async mode or shown in debug messages in sync mode. * @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) { instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; int ok = 1; plist_t dict = NULL; do { instproxy_lock(client); res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); instproxy_unlock(client); if (res != INSTPROXY_E_SUCCESS) { debug_info("could not receive plist, error %d", res); break; } if (dict) { /* invoke callback function */ if (status_cb) { status_cb(operation, dict, user_data); } /* check for 'Error', so we can abort cleanly */ plist_t err = plist_dict_get_item(dict, "Error"); if (err) { #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); } #endif ok = 0; res = INSTPROXY_E_OP_FAILED; } /* 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); } } plist_free(dict); dict = NULL; } } while (ok && client->parent); return res; }
/** * 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; } else { plist_free(apps_array); } leave_unlock: instproxy_unlock(client); 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("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 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; }