diff options
| -rw-r--r-- | include/libiphone/installation_proxy.h | 30 | ||||
| -rw-r--r-- | src/installation_proxy.c | 271 | 
2 files changed, 165 insertions, 136 deletions
| diff --git a/include/libiphone/installation_proxy.h b/include/libiphone/installation_proxy.h index f796054..b7bbb60 100644 --- a/include/libiphone/installation_proxy.h +++ b/include/libiphone/installation_proxy.h @@ -28,6 +28,7 @@ extern "C" {  #endif  #include <libiphone/libiphone.h> +#include <glib.h>  /* Error Codes */  #define INSTPROXY_E_SUCCESS                0 @@ -41,15 +42,6 @@ extern "C" {  typedef int16_t instproxy_error_t; -typedef enum { -    INSTPROXY_APPTYPE_ALL = 0, -    INSTPROXY_APPTYPE_SYSTEM = 1, -    INSTPROXY_APPTYPE_USER = 2 -} instproxy_apptype_t; - -#define INSTPROXY_ARCHIVE_APP_ONLY (1 << 0) -#define INSTPROXY_ARCHIVE_SKIP_UNINSTALL (1 << 1) -  struct instproxy_client_int;  typedef struct instproxy_client_int *instproxy_client_t; @@ -59,15 +51,19 @@ typedef void (*instproxy_status_cb_t) (const char *operation, plist_t status);  instproxy_error_t instproxy_client_new(iphone_device_t device, uint16_t port, instproxy_client_t *client);  instproxy_error_t instproxy_client_free(instproxy_client_t client); -instproxy_error_t instproxy_browse(instproxy_client_t client, instproxy_apptype_t apptype, plist_t *result); -instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb); -instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb); -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb); +instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result); +instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb); +instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb); +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb); + +instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result); +instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb); +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb); +instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb); -instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t *result); -instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, uint32_t options, instproxy_status_cb_t status_cb); -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb); -instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb); +plist_t instproxy_client_options_new(); +void instproxy_client_options_add(plist_t client_options, ...) G_GNUC_NULL_TERMINATED; +void instproxy_client_options_free(plist_t client_options);  #ifdef __cplusplus  } diff --git a/src/installation_proxy.c b/src/installation_proxy.c index a99b5cb..e0bccd3 100644 --- a/src/installation_proxy.c +++ b/src/installation_proxy.c @@ -143,55 +143,71 @@ instproxy_error_t instproxy_client_free(instproxy_client_t client)  }  /** + * Send a command with specified options to the device. + * Internally used only. + * + * @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; +} + +/**   * List installed applications. This function runs synchronously.   *   * @param client The connected installation_proxy client - * @param apptype The type of applications to list. + * @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, instproxy_apptype_t apptype, plist_t *result) +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; -	int browsing = 0; -	plist_t apps_array = NULL; - -	plist_t dict = plist_new_dict(); -	if (apptype != INSTPROXY_APPTYPE_ALL) { -		plist_t client_opts = plist_new_dict(); -		plist_t p_apptype = NULL; -		switch (apptype) { -			case INSTPROXY_APPTYPE_SYSTEM: -				p_apptype = plist_new_string("System"); -				break; -			case INSTPROXY_APPTYPE_USER: -				p_apptype = plist_new_string("User"); -				break; -			default: -				debug_info("unknown apptype %d given, using INSTPROXY_APPTYPE_USER instead", apptype); -				p_apptype = plist_new_string("User"); -				break; -		} -		plist_dict_insert_item(client_opts, "ApplicationType", p_apptype); -		plist_dict_insert_item(dict, "ClientOptions", client_opts); -	} -	plist_dict_insert_item(dict, "Command", plist_new_string("Browse"));  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); -	plist_free(dict); +	res = instproxy_send_command(client, "Browse", client_options, NULL, NULL);  	if (res != INSTPROXY_E_SUCCESS) {  		debug_info("could not send plist");  		goto leave_unlock;  	} -	apps_array = plist_new_array(); +	int browsing = 0; +	plist_t apps_array = plist_new_array(); +	plist_t dict = NULL;  	do {  		browsing = 0; @@ -393,8 +409,7 @@ static instproxy_error_t instproxy_create_status_updater(instproxy_client_t clie   *   * @param client The connected installation_proxy client   * @param pkg_path Path of the installation package (inside the AFC jail) - * @param sinf PLIST_DATA node holding the package's SINF data or NULL. - * @param metadata PLIST_DATA node holding the packages's Metadata or NULL. + * @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. @@ -402,42 +417,19 @@ static instproxy_error_t instproxy_create_status_updater(instproxy_client_t clie   * @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 sinf, plist_t metadata, instproxy_status_cb_t status_cb, const char *command) +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)  {  	if (!client || !client->parent || !pkg_path) {  		return INSTPROXY_E_INVALID_ARG;  	} -	if (sinf && (plist_get_node_type(sinf) != PLIST_DATA)) { -		debug_info("(%s): ERROR: sinf data is not a PLIST_DATA node!", command); -		return INSTPROXY_E_INVALID_ARG; -	} -	if (metadata && (plist_get_node_type(metadata) != PLIST_DATA)) { -		debug_info("(%s): ERROR: metadata is not a PLIST_DATA node!", command); -		return INSTPROXY_E_INVALID_ARG; -	} -  	if (client->status_updater) {  		return INSTPROXY_E_OP_IN_PROGRESS;  	} -	instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - -	plist_t dict = plist_new_dict(); -	if (sinf && metadata) { -		plist_t client_opts = plist_new_dict(); -		plist_dict_insert_item(client_opts, "ApplicationSINF", plist_copy(sinf)); -		plist_dict_insert_item(client_opts, "iTunesMetadata", plist_copy(metadata)); -		plist_dict_insert_item(dict, "ClientOptions", client_opts); -	} -	plist_dict_insert_item(dict, "Command", plist_new_string(command)); -	plist_dict_insert_item(dict, "PackagePath", plist_new_string(pkg_path)); -  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); +	instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path);  	instproxy_unlock(client); -	plist_free(dict); -  	if (res != INSTPROXY_E_SUCCESS) {  		debug_info("could not send plist, error %d", res);  		return res; @@ -451,8 +443,13 @@ static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client,   *   * @param client The connected installation_proxy client   * @param pkg_path Path of the installation package (inside the AFC jail) - * @param sinf PLIST_DATA node holding the package's SINF data or NULL. - * @param metadata PLIST_DATA node holding the packages's Metadata or NULL. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Valid options include: + *          "iTunesMetadata" -> PLIST_DATA + *          "ApplicationSINF" -> PLIST_DATA + *          "PackageType" -> "Developer" + *        If PackageType -> Developer is specified, then pkg_path points to + *        an .app directory instead of an install package.   * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -464,9 +461,9 @@ static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client,   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb)  { -	return instproxy_install_or_upgrade(client, pkg_path, sinf, metadata, status_cb, "Install"); +	return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install");  }  /** @@ -476,8 +473,13 @@ instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_p   *   * @param client The connected installation_proxy client   * @param pkg_path Path of the installation package (inside the AFC jail) - * @param sinf PLIST_DATA node holding the package's SINF data or NULL. - * @param metadata PLIST_DATA node holding the packages's Metadata or NULL. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Valid options include: + *          "iTunesMetadata" -> PLIST_DATA + *          "ApplicationSINF" -> PLIST_DATA + *          "PackageType" -> "Developer" + *        If PackageType -> Developer is specified, then pkg_path points to + *        an .app directory instead of an install package.   * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -489,9 +491,9 @@ instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_p   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb)  { -	return instproxy_install_or_upgrade(client, pkg_path, sinf, metadata, status_cb, "Upgrade"); +	return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade");  }  /** @@ -499,6 +501,8 @@ instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_p   *   * @param client The connected installation proxy client   * @param appid ApplicationIdentifier of the app to uninstall + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Currently there are no known client options, so pass NULL here.   * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -510,7 +514,7 @@ instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_p   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb)  {  	if (!client || !client->parent || !appid) {  		return INSTPROXY_E_INVALID_ARG; @@ -526,7 +530,7 @@ instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *app  	plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall"));  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); +	res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL);  	instproxy_unlock(client);  	plist_free(dict); @@ -545,26 +549,21 @@ instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *app   * @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 *result) +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_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - -	plist_t dict = plist_new_dict(); -	plist_dict_insert_item(dict, "Command", plist_new_string("LookupArchives")); -  	instproxy_lock(client); - -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); -	plist_free(dict); +	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); @@ -592,11 +591,10 @@ leave_unlock:   *   * @param client The connected installation proxy client   * @param appid ApplicationIdentifier of the app to archive. - * @param options This is either 0 for default behaviour (make an archive - *        including app/user settings etc. AND uninstall the application), - *        or one or a combination of the following options: - *        INSTPROXY_ARCHIVE_APP_ONLY (1) - *        INSTPROXY_ARCHIVE_SKIP_UNINSTALL (2) + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Valid options include: + *          "SkipUninstall" -> Boolean + *          "ArchiveType" -> "ApplicationOnly"    * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -608,7 +606,7 @@ leave_unlock:   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, uint32_t options, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb)  {  	if (!client || !client->parent || !appid)  		return INSTPROXY_E_INVALID_ARG; @@ -617,28 +615,10 @@ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid  		return INSTPROXY_E_OP_IN_PROGRESS;  	} -	instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - -	plist_t dict = plist_new_dict(); -	plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); -	if (options > 0) { -		plist_t client_opts = plist_new_dict(); -		if (options & INSTPROXY_ARCHIVE_APP_ONLY) { -			plist_dict_insert_item(client_opts, "ArchiveType", plist_new_string("ApplicationOnly")); -		} -		if (options & INSTPROXY_ARCHIVE_SKIP_UNINSTALL) { -			plist_dict_insert_item(client_opts, "SkipUninstall", plist_new_bool(1)); -		} -		plist_dict_insert_item(dict, "ClientOptions", client_opts); -	} -	plist_dict_insert_item(dict, "Command", plist_new_string("Archive")); -  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); +	instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL);  	instproxy_unlock(client); -	plist_free(dict); -  	if (res != INSTPROXY_E_SUCCESS) {  		debug_info("could not send plist, error %d", res);  		return res; @@ -653,6 +633,8 @@ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid   *   * @param client The connected installation proxy client   * @param appid ApplicationIdentifier of the app to restore. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Currently there are no known client options, so pass NULL here.   * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -664,7 +646,7 @@ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb)  {  	if (!client || !client->parent || !appid)  		return INSTPROXY_E_INVALID_ARG; @@ -673,18 +655,10 @@ instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid  		return INSTPROXY_E_OP_IN_PROGRESS;  	} -	instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - -	plist_t dict = plist_new_dict(); -	plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); -	plist_dict_insert_item(dict, "Command", plist_new_string("Restore")); -  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); +	instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL);  	instproxy_unlock(client); -	plist_free(dict); -  	if (res != INSTPROXY_E_SUCCESS) {  		debug_info("could not send plist, error %d", res);  		return res; @@ -699,6 +673,8 @@ instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid   *   * @param client The connected installation proxy client   * @param appid ApplicationIdentifier of the archived app to remove. + * @param client_options The client options to use, as PLIST_DICT, or NULL. + *        Currently there are no known client options, so passing NULL is fine.   * @param status_cb Callback function for progress and status information. If   *        NULL is passed, this function will run synchronously.   * @@ -710,7 +686,7 @@ instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid   *     created successfully; any error occuring during the operation has to be   *     handled inside the specified callback function.   */ -instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb)  {  	if (!client || !client->parent || !appid)  		return INSTPROXY_E_INVALID_ARG; @@ -719,18 +695,10 @@ instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char  		return INSTPROXY_E_OP_IN_PROGRESS;  	} -	instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - -	plist_t dict = plist_new_dict(); -	plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); -	plist_dict_insert_item(dict, "Command", plist_new_string("RemoveArchive")); -  	instproxy_lock(client); -	res = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); +	instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL);  	instproxy_unlock(client); -	plist_free(dict); -  	if (res != INSTPROXY_E_SUCCESS) {  		debug_info("could not send plist, error %d", res);  		return res; @@ -738,3 +706,68 @@ instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char  	return instproxy_create_status_updater(client, status_cb, "RemoveArchive");  } +/** + * Create a new client_options plist. + * + * @return A new plist_t of type PLIST_DICT.  + */ +plist_t instproxy_client_options_new() +{ +	return plist_new_dict(); +} + +/** + * Add one or more new key:value pairs to the given client_options. + * + * @param client_options The client options to modify. + * @param ... KEY, VALUE, [KEY, VALUE], NULL + * + * @note The keys and values passed are expected to be strings, except for + *       "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type + *       PLIST_DATA as value, or "SkipUninstall" needing int as value. + */ +void instproxy_client_options_add(plist_t client_options, ...) +{ +	if (!client_options) +		return; +	va_list args; +	va_start(args, client_options); +	char *arg = va_arg(args, char*); +	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")) { +			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); +} + +/** + * Free client_options plist. + * + * @param client_options The client options plist to free. Does nothing if NULL + *        is passed. + */ +void instproxy_client_options_free(plist_t client_options) +{ +	if (client_options) { +		plist_free(client_options); +	} +} | 
