From 4c00fb43042a1e8d1a4e2d29827d00ed3144575f Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Wed, 9 Dec 2009 20:27:09 +0100 Subject: Documentation cleanup and a new error code [#96 state:resolved] Signed-off-by: Matt Colyer --- src/NotificationProxy.c | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/NotificationProxy.c b/src/NotificationProxy.c index 884be5f..160ac4a 100644 --- a/src/NotificationProxy.c +++ b/src/NotificationProxy.c @@ -62,7 +62,9 @@ static void np_unlock(np_client_t client) * @param client NP to send data to * @param dict plist to send * - * @return NP_E_SUCCESS or an error code. + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client or dict + * are NULL, NP_E_PLIST_ERROR when dict is not a valid plist, + * or NP_E_UNKNOWN_ERROR when an unspecified error occurs. */ static np_error_t np_plist_send(np_client_t client, plist_t dict) { @@ -105,11 +107,14 @@ static np_error_t np_plist_send(np_client_t client, plist_t dict) /** Makes a connection to the NP service on the phone. * - * @param phone The iPhone to connect on. - * @param s_port The source port. - * @param d_port The destination port. + * @param device The device to connect to. + * @param dst_port Destination port (usually given by lockdownd_start_service). + * @param client Pointer that will be set to a newly allocated np_client_t + * upon successful return. * - * @return A handle to the newly-connected client or NULL upon error. + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when device is NULL, + * or NP_E_CONN_FAILED when the connection to the device could not be + * established. */ np_error_t np_client_new(iphone_device_t device, int dst_port, np_client_t *client) { @@ -123,7 +128,7 @@ np_error_t np_client_new(iphone_device_t device, int dst_port, np_client_t *clie /* Attempt connection */ iphone_connection_t connection = NULL; if (iphone_device_connect(device, dst_port, &connection) != IPHONE_E_SUCCESS) { - return NP_E_UNKNOWN_ERROR; + return NP_E_CONN_FAILED; } np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_int)); @@ -137,9 +142,11 @@ np_error_t np_client_new(iphone_device_t device, int dst_port, np_client_t *clie return NP_E_SUCCESS; } -/** Disconnects an NP client from the phone. +/** Disconnects an NP client from the device. * * @param client The client to disconnect. + * + * @return NP_E_SUCCESS on success, or NP_E_INVALID_ARG when client is NULL. */ np_error_t np_client_free(np_client_t client) { @@ -168,6 +175,8 @@ np_error_t np_client_free(np_client_t client) * * @param client The client to send to * @param notification The notification message to send + * + * @return NP_E_SUCCESS on success, or an error returned by np_plist_send */ np_error_t np_post_notification(np_client_t client, const char *notification) { @@ -201,6 +210,9 @@ np_error_t np_post_notification(np_client_t client, const char *notification) * * @param client The client to send to * @param notification The notifications that should be observed. + * + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client or + * notification are NULL, or an error returned by np_plist_send. */ np_error_t np_observe_notification( np_client_t client, const char *notification ) { @@ -241,6 +253,9 @@ np_error_t np_observe_notification( np_client_t client, const char *notification * observed. This is expected to be an array of const char* that MUST have a * terminating NULL entry. However this parameter can be NULL; in this case, * the default set of notifications will be used. + * + * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client is null, + * or an error returned by np_observe_notification. */ np_error_t np_observe_notifications(np_client_t client, const char **notification_spec) { @@ -275,7 +290,7 @@ np_error_t np_observe_notifications(np_client_t client, const char **notificatio * with the notification that has been received. * * @return 0 if a notification has been received or nothing has been received, - * or an error value if an error occured. + * or a negative value if an error occured. * * @note You probably want to check out np_set_notify_callback * @see np_set_notify_callback @@ -401,10 +416,14 @@ gpointer np_notifier( gpointer arg ) * * @param client the NP client * @param notify_cb pointer to a callback function or NULL to de-register a - * previously set callback function + * previously set callback function. + * + * @note Only one callback function can be registered at the same time; + * any previously set callback function will be removed automatically. * * @return NP_E_SUCCESS when the callback was successfully registered, - * or an error value when an error occured. + * NP_E_INVALID_ARG when client is NULL, or NP_E_UNKNOWN_ERROR when + * the callback thread could no be created. */ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb ) { -- cgit v1.1-32-gdbae From c6982451d82c340ee8a57c76e39db160c625a1a3 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Tue, 15 Dec 2009 00:02:50 +0100 Subject: Support for new SBServices interface This lockdown service has been introduced in firmware 3.1 and allows to re-arrange the Spr*ngboard icons from the computer. [#99 state:resolved] Signed-off-by: Matt Colyer --- src/Makefile.am | 1 + src/SBServices.c | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/SBServices.h | 33 +++++++ 3 files changed, 325 insertions(+) create mode 100644 src/SBServices.c create mode 100644 src/SBServices.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index ce1d237..d351a8a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,6 +8,7 @@ libiphone_la_SOURCES = iphone.c iphone.h \ lockdown.c lockdown.h\ AFC.c AFC.h\ NotificationProxy.c NotificationProxy.h\ + SBServices.c SBServices.h\ userpref.c userpref.h\ utils.c utils.h\ MobileSync.c MobileSync.h diff --git a/src/SBServices.c b/src/SBServices.c new file mode 100644 index 0000000..9849415 --- /dev/null +++ b/src/SBServices.c @@ -0,0 +1,291 @@ +/* + * SBServices.c + * SpringBoard Services implementation. + * + * Copyright (c) 2009 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 + */ + +#include +#include +#include +#include +#include + +#include "SBServices.h" +#include "iphone.h" +#include "utils.h" + +/** Locks an sbservices client, done for thread safety stuff. + * + * @param client The sbservices client to lock. + */ +static void sbs_lock(sbservices_client_t client) +{ + log_debug_msg("SBServices: Locked\n"); + g_mutex_lock(client->mutex); +} + +/** Unlocks an sbservices client, done for thread safety stuff. + * + * @param client The sbservices client to unlock + */ +static void sbs_unlock(sbservices_client_t client) +{ + log_debug_msg("SBServices: Unlocked\n"); + g_mutex_unlock(client->mutex); +} + +sbservices_error_t sbservices_client_new(iphone_device_t device, int dst_port, sbservices_client_t *client) +{ + /* makes sure thread environment is available */ + if (!g_thread_supported()) + g_thread_init(NULL); + + if (!device) + return SBSERVICES_E_INVALID_ARG; + + /* Attempt connection */ + iphone_connection_t connection = NULL; + if (iphone_device_connect(device, dst_port, &connection) != IPHONE_E_SUCCESS) { + return SBSERVICES_E_CONN_FAILED; + } + + sbservices_client_t client_loc = (sbservices_client_t) malloc(sizeof(struct sbservices_client_int)); + client_loc->connection = connection; + client_loc->mutex = g_mutex_new(); + + *client = client_loc; + return SBSERVICES_E_SUCCESS; +} + +sbservices_error_t sbservices_client_free(sbservices_client_t client) +{ + if (!client) + return SBSERVICES_E_INVALID_ARG; + + iphone_device_disconnect(client->connection); + client->connection = NULL; + if (client->mutex) { + g_mutex_free(client->mutex); + } + free(client); + + return SBSERVICES_E_SUCCESS; +} + +/** + * Sends a binary plist to the device using the connection specified in client. + * This function is only used internally. + * + * @param client InstallationProxy to send data to + * @param dict plist to send + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or dict are NULL, SBSERVICES_E_PLIST_ERROR when dict is not a + * valid plist, or SBSERVICES_E_UNKNOWN_ERROR when an unspecified error + * occurs. + */ +static sbservices_error_t sbservices_plist_send(sbservices_client_t client, plist_t dict) +{ + char *content = NULL; + uint32_t length = 0; + uint32_t nlen = 0; + int bytes = 0; + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + + if (!client || !dict) { + return SBSERVICES_E_INVALID_ARG; + } + + plist_to_bin(dict, &content, &length); + + if (!content || length == 0) { + return SBSERVICES_E_PLIST_ERROR; + } + + nlen = htonl(length); + log_debug_msg("%s: sending %d bytes\n", __func__, length); + iphone_device_send(client->connection, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes); + if (bytes == sizeof(nlen)) { + iphone_device_send(client->connection, content, length, (uint32_t*)&bytes); + if (bytes > 0) { + if ((uint32_t)bytes == length) { + res = SBSERVICES_E_SUCCESS; + } else { + log_debug_msg("%s: ERROR: Could not send all data (%d of %d)!\n", __func__, bytes, length); + } + } + } + if (bytes <= 0) { + log_debug_msg("%s: ERROR: sending to device failed.\n", __func__); + } + + free(content); + + return res; +} + +sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state) +{ + if (!client || !client->connection || !state) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + uint32_t pktlen = 0; + uint32_t bytes = 0; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "command", plist_new_string("getIconState")); + + sbs_lock(client); + + res = sbservices_plist_send(client, dict); + plist_free(dict); + if (res != SBSERVICES_E_SUCCESS) { + log_debug_msg("%s: could not send plist\n", __func__); + goto leave_unlock; + } + + iphone_device_recv(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes); + log_debug_msg("%s: initial read=%i\n", __func__, bytes); + if (bytes < 4) { + log_debug_msg("%s: initial read failed!\n"); + res = 0; + } else { + if ((char)pktlen == 0) { + char *content = NULL; + uint32_t curlen = 0; + pktlen = ntohl(pktlen); + log_debug_msg("%s: %d bytes following\n", __func__, pktlen); + content = (char*)malloc(pktlen); + log_debug_msg("pointer %p\n", content); + + while (curlen < pktlen) { + iphone_device_recv(client->connection, content+curlen, pktlen-curlen, &bytes); + if (bytes <= 0) { + res = SBSERVICES_E_UNKNOWN_ERROR; + break; + } + log_debug_msg("%s: received %d bytes\n", __func__, bytes); + curlen += bytes; + } + log_debug_buffer(content, pktlen); + plist_from_bin(content, pktlen, state); + res = SBSERVICES_E_SUCCESS; + free(content); + } else { + res = SBSERVICES_E_UNKNOWN_ERROR; + } + } + +leave_unlock: + sbs_unlock(client); + return res; +} + +sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate) +{ + if (!client || !client->connection || !newstate) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "command", plist_new_string("setIconState")); + plist_dict_insert_item(dict, "iconState", plist_copy(newstate)); + + sbs_lock(client); + + res = sbservices_plist_send(client, dict); + plist_free(dict); + if (res != SBSERVICES_E_SUCCESS) { + log_debug_msg("%s: could not send plist\n", __func__); + goto leave_unlock; + } + // NO RESPONSE + +leave_unlock: + sbs_unlock(client); + return res; +} + +sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize) +{ + if (!client || !client->connection || !pngdata) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + uint32_t pktlen = 0; + uint32_t bytes = 0; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "command", plist_new_string("getIconPNGData")); + plist_dict_insert_item(dict, "bundleId", plist_new_string(bundleId)); + + sbs_lock(client); + + res = sbservices_plist_send(client, dict); + plist_free(dict); + if (res != SBSERVICES_E_SUCCESS) { + log_debug_msg("%s: could not send plist\n", __func__); + goto leave_unlock; + } + + iphone_device_recv(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes); + log_debug_msg("%s: initial read=%i\n", __func__, bytes); + if (bytes < 4) { + log_debug_msg("%s: initial read failed!\n"); + res = 0; + } else { + if ((char)pktlen == 0) { + char *content = NULL; + uint32_t curlen = 0; + pktlen = ntohl(pktlen); + log_debug_msg("%s: %d bytes following\n", __func__, pktlen); + content = (char*)malloc(pktlen); + log_debug_msg("pointer %p\n", content); + + while (curlen < pktlen) { + iphone_device_recv(client->connection, content+curlen, pktlen-curlen, &bytes); + if (bytes <= 0) { + res = SBSERVICES_E_UNKNOWN_ERROR; + break; + } + log_debug_msg("%s: received %d bytes\n", __func__, bytes); + curlen += bytes; + } + log_debug_buffer(content, pktlen); + plist_t pngdict = NULL; + plist_from_bin(content, pktlen, &pngdict); + plist_t node = plist_dict_get_item(pngdict, "pngData"); + if (node) { + plist_get_data_val(node, pngdata, pngsize); + } + plist_free(pngdict); + res = SBSERVICES_E_SUCCESS; + free(content); + } else { + res = SBSERVICES_E_UNKNOWN_ERROR; + } + } + +leave_unlock: + sbs_unlock(client); + return res; + +} + diff --git a/src/SBServices.h b/src/SBServices.h new file mode 100644 index 0000000..8f923b9 --- /dev/null +++ b/src/SBServices.h @@ -0,0 +1,33 @@ +/* + * SBServices.h + * SpringBoard Services header file. + * + * Copyright (c) 2009 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 + */ +#ifndef ISBSERVICES_H +#define ISBSERVICES_H + +#include + +#include "libiphone/sbservices.h" + +struct sbservices_client_int { + iphone_connection_t connection; + GMutex *mutex; +}; + +#endif -- cgit v1.1-32-gdbae From dd3d8700dcdc7acc28e2db58405f247bb98228ab Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Thu, 31 Dec 2009 03:19:43 +0100 Subject: New installation_proxy interface. Allows enumeration, install, uninstall, upgrade, and some other stuff with apps. --- src/InstallationProxy.c | 809 ++++++++++++++++++++++++++++++++++++++++++++++++ src/InstallationProxy.h | 34 ++ src/Makefile.am | 1 + 3 files changed, 844 insertions(+) create mode 100644 src/InstallationProxy.c create mode 100644 src/InstallationProxy.h (limited to 'src') diff --git a/src/InstallationProxy.c b/src/InstallationProxy.c new file mode 100644 index 0000000..7a8ef5a --- /dev/null +++ b/src/InstallationProxy.c @@ -0,0 +1,809 @@ +/* + * InstallationProxy.c + * Installation Proxy implementation. + * + * Copyright (c) 2009 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 + */ + +#include +#include +#include +#include +#include + +#include "InstallationProxy.h" +#include "iphone.h" +#include "utils.h" + +struct instproxy_status_data { + instproxy_client_t client; + instproxy_status_cb_t cbfunc; + char *operation; +}; + +/** Locks an installation_proxy client, done for thread safety stuff. + * + * @param client The installation_proxy client to lock + */ +static void instproxy_lock(instproxy_client_t client) +{ + log_debug_msg("InstallationProxy: Locked\n"); + g_mutex_lock(client->mutex); +} + +/** Unlocks an installation_proxy client, done for thread safety stuff. + * + * @param client The installation_proxy client to lock + */ +static void instproxy_unlock(instproxy_client_t client) +{ + log_debug_msg("InstallationProxy: Unlocked\n"); + g_mutex_unlock(client->mutex); +} + +/** + * Sends an xml plist to the device using the connection specified in client. + * This function is only used internally. + * + * @param client The installation_proxy to send data to + * @param plist plist to send + * + * @return INSTPROXY_E_SUCCESS on success, INSTPROXY_E_INVALID_ARG when client + * or plist are NULL, INSTPROXY_E_PLIST_ERROR when dict is not a valid + * plist, or INSTPROXY_E_UNKNOWN_ERROR when an unspecified error occurs. + */ +static instproxy_error_t instproxy_plist_send(instproxy_client_t client, plist_t plist) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + char *XML_content = NULL; + uint32_t length = 0; + uint32_t nlen = 0; + int bytes = 0; + + if (!client || !plist) { + return INSTPROXY_E_INVALID_ARG; + } + + plist_to_xml(plist, &XML_content, &length); + + if (!XML_content || length == 0) { + return INSTPROXY_E_PLIST_ERROR; + } + + nlen = htonl(length); + log_debug_msg("%s: sending %d bytes\n", __func__, length); + iphone_device_send(client->connection, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes); + if (bytes == sizeof(nlen)) { + iphone_device_send(client->connection, XML_content, length, (uint32_t*)&bytes); + if (bytes > 0) { + log_debug_msg("%s: received %d bytes\n", __func__, bytes); + log_debug_buffer(XML_content, bytes); + if ((uint32_t)bytes == length) { + res = INSTPROXY_E_SUCCESS; + } else { + log_debug_msg("%s: ERROR: Could not send all data (%d of %d)!\n", __func__, bytes, length); + } + } + } + if (bytes <= 0) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: ERROR: sending to device failed.\n", __func__); + } + + free(XML_content); + + return res; +} + +/** + * Receives an xml plist from the device using the connection specified in + * client. + * This function is only used internally. + * + * @param client The installation_proxy to receive data from + * @param plist pointer to a plist_t that will point to the received plist + * upon successful return + * + * @return INSTPROXY_E_SUCCESS on success, INSTPROXY_E_INVALID_ARG when client + * or *plist are NULL, INSTPROXY_E_PLIST_ERROR when dict is not a valid + * plist, or INSTPROXY_E_UNKNOWN_ERROR when an unspecified error occurs. + */ +static instproxy_error_t instproxy_plist_recv(instproxy_client_t client, plist_t *plist) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + char *XML_content = NULL; + uint32_t pktlen = 0; + uint32_t bytes = 0; + + if (!client || !plist) { + return INSTPROXY_E_INVALID_ARG; + } + + iphone_device_recv_timeout(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes, 300000); /* 5 minute timeout should be enough */ + log_debug_msg("%s: initial read=%i\n", __func__, bytes); + if (bytes < 4) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: initial read failed!\n"); + } else { + if ((char)pktlen == 0) { + uint32_t curlen = 0; + pktlen = ntohl(pktlen); + log_debug_msg("%s: %d bytes following\n", __func__, pktlen); + XML_content = (char*)malloc(pktlen); + + while (curlen < pktlen) { + iphone_device_recv(client->connection, XML_content+curlen, pktlen-curlen, &bytes); + if (bytes <= 0) { + res = INSTPROXY_E_UNKNOWN_ERROR; + break; + } + log_debug_msg("%s: received %d bytes\n", __func__, bytes); + curlen += bytes; + } + log_debug_buffer(XML_content, pktlen); + plist_from_xml(XML_content, pktlen, plist); + res = INSTPROXY_E_SUCCESS; + free(XML_content); + XML_content = NULL; + } else { + res = INSTPROXY_E_UNKNOWN_ERROR; + } + } + return res; +} + +/** + * Creates a new installation_proxy client + * + * @param device The device to connect to + * @param dst_port Destination port (usually given by lockdownd_start_service). + * @param client Pointer that will be set to a newly allocated + * instproxy_client_t upon successful return. + * + * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value + * when an error occured. + */ +instproxy_error_t instproxy_client_new(iphone_device_t device, int dst_port, instproxy_client_t *client) +{ + /* makes sure thread environment is available */ + if (!g_thread_supported()) + g_thread_init(NULL); + + if (!device) + return INSTPROXY_E_INVALID_ARG; + + /* Attempt connection */ + iphone_connection_t connection = NULL; + if (iphone_device_connect(device, dst_port, &connection) != IPHONE_E_SUCCESS) { + return INSTPROXY_E_CONN_FAILED; + } + + instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_int)); + client_loc->connection = connection; + client_loc->mutex = g_mutex_new(); + client_loc->status_updater = NULL; + + *client = client_loc; + return INSTPROXY_E_SUCCESS; +} + +/** + * Frees an installation_proxy client. + * + * @param client The installation_proxy client to free. + * + * @return INSTPROXY_E_SUCCESS on success + * or INSTPROXY_E_INVALID_ARG if client is NULL. + */ +instproxy_error_t instproxy_client_free(instproxy_client_t client) +{ + if (!client) + return INSTPROXY_E_INVALID_ARG; + + iphone_device_disconnect(client->connection); + client->connection = NULL; + if (client->status_updater) { + log_dbg_msg(DBGMASK_INSTPROXY, "joining status_updater"); + g_thread_join(client->status_updater); + } + if (client->mutex) { + g_mutex_free(client->mutex); + } + free(client); + + return INSTPROXY_E_SUCCESS; +} + +/** + * List installed applications. This function runs synchronously. + * + * @param client The connected installation_proxy client + * @param apptype The type of applications to list. + * @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) +{ + if (!client || !client->connection || !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: + log_dbg_msg(DBGMASK_INSTPROXY, "%s: unknown apptype %d given, using INSTPROXY_APPTYPE_USER instead\n", __func__, 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_plist_send(client, dict); + plist_free(dict); + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist\n", __func__); + goto leave_unlock; + } + + apps_array = plist_new_array(); + + do { + browsing = 0; + dict = NULL; + res = instproxy_plist_recv(client, &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")) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: Browsing applications completed\n"); + res = INSTPROXY_E_SUCCESS; + } + free(status); + } + plist_free(dict); + } + } while (browsing); + + if (res == INSTPROXY_E_SUCCESS) { + *result = 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 operation Operation name. Will be passed to the callback function + * in async mode or shown in debug messages in sync mode. + */ +static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + int ok = 1; + plist_t dict = NULL; + + do { + instproxy_lock(client); + res = instproxy_plist_recv(client, &dict); + instproxy_unlock(client); + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not receive plist, error %d\n", __func__, res); + break; + } + if (dict) { + /* invoke callback function */ + if (status_cb) { + status_cb(operation, dict); + } + /* 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) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s(%s): ERROR: %s\n", __func__, 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; + log_dbg_msg(DBGMASK_INSTPROXY, "%s(%s): %s (%d%%)\n", __func__, operation, status_msg, percent); + } else { + log_dbg_msg(DBGMASK_INSTPROXY, "%s(%s): %s\n", __func__, operation, status_msg); + } +#endif + free(status_msg); + } + } + plist_free(dict); + dict = NULL; + } + } while (ok && client->connection); + + return res; +} + +/** + * Internally used status updater 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 gpointer instproxy_status_updater(gpointer 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); + + /* cleanup */ + instproxy_lock(data->client); + log_dbg_msg(DBGMASK_INSTPROXY, "%s: done, cleaning up.\n", __func__); + if (data->operation) { + free(data->operation); + } + data->client->status_updater = NULL; + instproxy_unlock(data->client); + free(data); + + return NULL; +} + +/** + * 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. + * + * @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. + * + * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or + * when the operation completed successfully (sync). + * An INSTPROXY_E_* error value is returned if an error occured. + */ +static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + if (status_cb) { + /* async mode */ + struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); + if (data) { + data->client = client; + data->cbfunc = status_cb; + data->operation = strdup(operation); + + client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); + if (client->status_updater) { + res = INSTPROXY_E_SUCCESS; + } + } + } else { + /* sync mode */ + res = instproxy_perform_operation(client, NULL, operation); + } + return res; +} + + +/** + * Internal function used by instproxy_install and instproxy_upgrade. + * + * @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 status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * @param command The command to execute. + * + * @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) +{ + if (!client || !client->connection || !pkg_path) { + return INSTPROXY_E_INVALID_ARG; + } + if (sinf && (plist_get_node_type(sinf) != PLIST_DATA)) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s(%s): ERROR: sinf data is not a PLIST_DATA node!\n", __func__, command); + return INSTPROXY_E_INVALID_ARG; + } + if (metadata && (plist_get_node_type(metadata) != PLIST_DATA)) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s(%s): ERROR: metadata is not a PLIST_DATA node!\n", __func__, 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_plist_send(client, dict); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + return res; + } + + return instproxy_create_status_updater(client, status_cb, command); +} + +/** + * Install an application on the device. + * + * @param client The connected installation_proxy client + * @param pkg_path Path of the installation package (inside the AFC jail) + * @param 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 status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb) +{ + return instproxy_install_or_upgrade(client, pkg_path, sinf, metadata, status_cb, "Install"); +} + +/** + * Upgrade an application on the device. This function is nearly the same as + * instproxy_install; the difference is that the installation progress on the + * device is faster if the application is already installed. + * + * @param client The connected installation_proxy client + * @param pkg_path Path of the installation package (inside the AFC jail) + * @param 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 status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t sinf, plist_t metadata, instproxy_status_cb_t status_cb) +{ + return instproxy_install_or_upgrade(client, pkg_path, sinf, metadata, status_cb, "Upgrade"); +} + +/** + * Uninstall an application from the device. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to uninstall + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +{ + if (!client || !client->connection || !appid) { + return INSTPROXY_E_INVALID_ARG; + } + + if (client->status_updater) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); + plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall")); + + instproxy_lock(client); + res = instproxy_plist_send(client, dict); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + return res; + } + + return instproxy_create_status_updater(client, status_cb, "Uninstall"); +} + +/** + * List archived applications. This function runs synchronously. + * + * @see instproxy_archive + * + * @param client The connected installation_proxy client + * @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) +{ + if (!client || !client->connection || !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_plist_send(client, dict); + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + goto leave_unlock; + } + + res = instproxy_plist_recv(client, result); + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not receive plist, error %d\n", __func__, res); + goto leave_unlock; + } + + res = INSTPROXY_E_SUCCESS; + +leave_unlock: + instproxy_unlock(client); + return res; +} + +/** + * Archive an application on the device. + * This function tells the device to make an archive of the specified + * application. This results in the device creating a ZIP archive in the + * 'ApplicationArchives' directory and uninstalling the application. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to archive. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +{ + if (!client || !client->connection || !appid) + return INSTPROXY_E_INVALID_ARG; + + if (client->status_updater) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); + plist_dict_insert_item(dict, "Command", plist_new_string("Archive")); + + instproxy_lock(client); + res = instproxy_plist_send(client, dict); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + return res; + } + return instproxy_create_status_updater(client, status_cb, "Archive"); +} + +/** + * Restore a previously archived application on the device. + * This function is the counterpart to instproxy_archive. + * @see instproxy_archive + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the app to restore. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +{ + if (!client || !client->connection || !appid) + return INSTPROXY_E_INVALID_ARG; + + if (client->status_updater) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); + plist_dict_insert_item(dict, "Command", plist_new_string("Restore")); + + instproxy_lock(client); + res = instproxy_plist_send(client, dict); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + return res; + } + return instproxy_create_status_updater(client, status_cb, "Restore"); +} + +/** + * Removes a previously archived application from the device. + * This function removes the ZIP archive from the 'ApplicationArchives' + * directory. + * + * @param client The connected installation proxy client + * @param appid ApplicationIdentifier of the archived app to remove. + * @param status_cb Callback function for progress and status information. If + * NULL is passed, this function will run synchronously. + * + * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if + * an error occured. + * + * @note If a callback function is given (async mode), this function returns + * INSTPROXY_E_SUCCESS immediately if the status updater thread has been + * created successfully; any error occuring during the operation has to be + * handled inside the specified callback function. + */ +instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, instproxy_status_cb_t status_cb) +{ + if (!client || !client->connection || !appid) + return INSTPROXY_E_INVALID_ARG; + + if (client->status_updater) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); + plist_dict_insert_item(dict, "Command", plist_new_string("RemoveArchive")); + + instproxy_lock(client); + res = instproxy_plist_send(client, dict); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + log_dbg_msg(DBGMASK_INSTPROXY, "%s: could not send plist, error %d\n", __func__, res); + return res; + } + return instproxy_create_status_updater(client, status_cb, "RemoveArchive"); +} + diff --git a/src/InstallationProxy.h b/src/InstallationProxy.h new file mode 100644 index 0000000..c8c5ef1 --- /dev/null +++ b/src/InstallationProxy.h @@ -0,0 +1,34 @@ +/* + * InstallationProxy.h + * Installation Proxy header file. + * + * Copyright (c) 2009 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 + */ +#ifndef IINSTALLATION_PROXY_H +#define IINSTALLATION_PROXY_H + +#include + +#include "libiphone/installation_proxy.h" + +struct instproxy_client_int { + iphone_connection_t connection; + GMutex *mutex; + GThread *status_updater; +}; + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index d351a8a..9b42f1c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,6 +8,7 @@ libiphone_la_SOURCES = iphone.c iphone.h \ lockdown.c lockdown.h\ AFC.c AFC.h\ NotificationProxy.c NotificationProxy.h\ + InstallationProxy.c InstallationProxy.h\ SBServices.c SBServices.h\ userpref.c userpref.h\ utils.c utils.h\ -- cgit v1.1-32-gdbae From daee8be013dae3821463fd85f4076ca2ab3adce6 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Thu, 31 Dec 2009 14:29:05 +0100 Subject: Add options to instproxy_archive function This change allows to specify the following options: INSTPROXY_ARCHIVE_APP_ONLY - Archive only the application data INSTPROXY_ARCHIVE_SKIP_UNINSTALL - Do not uninstall the application. Combine these options with logical OR to specify both. These two options combined are used by iTunes to create app archives of on-device downloaded apps that are later copied as *.ipa files to the computer. [#104 state:resolved] Signed-off-by: Matt Colyer --- src/InstallationProxy.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/InstallationProxy.c b/src/InstallationProxy.c index 7a8ef5a..917886d 100644 --- a/src/InstallationProxy.c +++ b/src/InstallationProxy.c @@ -676,6 +676,11 @@ 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 status_cb Callback function for progress and status information. If * NULL is passed, this function will run synchronously. * @@ -687,7 +692,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, instproxy_status_cb_t status_cb) +instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, uint32_t options, instproxy_status_cb_t status_cb) { if (!client || !client->connection || !appid) return INSTPROXY_E_INVALID_ARG; @@ -700,6 +705,16 @@ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid 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); -- cgit v1.1-32-gdbae