From c9e2217059f561f87cf8b6af5067505f827c7297 Mon Sep 17 00:00:00 2001 From: Martin Szulecki Date: Wed, 13 Jan 2010 15:08:14 +0100 Subject: Rename service implementation sources to lowercase for consistency --- src/AFC.c | 1285 ---------------------------------------------- src/AFC.h | 100 ---- src/InstallationProxy.c | 740 -------------------------- src/InstallationProxy.h | 35 -- src/Makefile.am | 14 +- src/MobileSync.c | 148 ------ src/MobileSync.h | 31 -- src/NotificationProxy.c | 392 -------------- src/NotificationProxy.h | 37 -- src/SBServices.c | 269 ---------- src/SBServices.h | 34 -- src/afc.c | 1285 ++++++++++++++++++++++++++++++++++++++++++++++ src/afc.h | 100 ++++ src/installation_proxy.c | 740 ++++++++++++++++++++++++++ src/installation_proxy.h | 35 ++ src/mobilesync.c | 148 ++++++ src/mobilesync.h | 31 ++ src/notification_proxy.c | 392 ++++++++++++++ src/notification_proxy.h | 37 ++ src/sbservices.c | 269 ++++++++++ src/sbservices.h | 34 ++ 21 files changed, 3078 insertions(+), 3078 deletions(-) delete mode 100644 src/AFC.c delete mode 100644 src/AFC.h delete mode 100644 src/InstallationProxy.c delete mode 100644 src/InstallationProxy.h delete mode 100644 src/MobileSync.c delete mode 100644 src/MobileSync.h delete mode 100644 src/NotificationProxy.c delete mode 100644 src/NotificationProxy.h delete mode 100644 src/SBServices.c delete mode 100644 src/SBServices.h create mode 100644 src/afc.c create mode 100644 src/afc.h create mode 100644 src/installation_proxy.c create mode 100644 src/installation_proxy.h create mode 100644 src/mobilesync.c create mode 100644 src/mobilesync.h create mode 100644 src/notification_proxy.c create mode 100644 src/notification_proxy.h create mode 100644 src/sbservices.c create mode 100644 src/sbservices.h diff --git a/src/AFC.c b/src/AFC.c deleted file mode 100644 index db00735..0000000 --- a/src/AFC.c +++ /dev/null @@ -1,1285 +0,0 @@ -/* - * AFC.c - * Contains functions for the built-in AFC client. - * - * Copyright (c) 2008 Zach C. 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 "AFC.h" -#include "iphone.h" -#include "debug.h" - -// This is the maximum size an AFC data packet can be -static const int MAXIMUM_PACKET_SIZE = (2 << 15); - -/** Locks an AFC client, done for thread safety stuff - * - * @param client The AFC client connection to lock - */ -static void afc_lock(afc_client_t client) -{ - debug_info("Locked"); - g_mutex_lock(client->mutex); -} - -/** Unlocks an AFC client, done for thread safety stuff. - * - * @param client The AFC - */ -static void afc_unlock(afc_client_t client) -{ - debug_info("Unlocked"); - g_mutex_unlock(client->mutex); -} - -/** Makes a connection to the AFC service on the phone. - * - * @param device The device to connect to. - * @param port The destination port. - * @param client Pointer that will be set to a newly allocated afc_client_t - * upon successful return. - * - * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARGUMENT when device or port - * is invalid, AFC_E_MUX_ERROR when the connection failed, or AFC_E_NO_MEM - * when there's a memory allocation problem. - */ -afc_error_t afc_client_new(iphone_device_t device, uint16_t port, afc_client_t * client) -{ - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device || port==0) - return AFC_E_INVALID_ARGUMENT; - - /* attempt connection */ - iphone_connection_t connection = NULL; - if (iphone_device_connect(device, port, &connection) != IPHONE_E_SUCCESS) { - return AFC_E_MUX_ERROR; - } - - afc_client_t client_loc = (afc_client_t) malloc(sizeof(struct afc_client_int)); - client_loc->connection = connection; - - /* allocate a packet */ - client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket)); - if (!client_loc->afc_packet) { - iphone_device_disconnect(client_loc->connection); - free(client_loc); - return AFC_E_NO_MEM; - } - - client_loc->afc_packet->packet_num = 0; - client_loc->afc_packet->entire_length = 0; - client_loc->afc_packet->this_length = 0; - memcpy(client_loc->afc_packet->magic, AFC_MAGIC, AFC_MAGIC_LEN); - client_loc->file_handle = 0; - client_loc->lock = 0; - client_loc->mutex = g_mutex_new(); - - *client = client_loc; - return AFC_E_SUCCESS; -} - -/** Disconnects an AFC client from the phone. - * - * @param client The client to disconnect. - */ -afc_error_t afc_client_free(afc_client_t client) -{ - if (!client || !client->connection || !client->afc_packet) - return AFC_E_INVALID_ARGUMENT; - - iphone_device_disconnect(client->connection); - free(client->afc_packet); - if (client->mutex) { - g_mutex_free(client->mutex); - } - free(client); - return AFC_E_SUCCESS; -} - -/** Dispatches an AFC packet over a client. - * - * @param client The client to send data through. - * @param data The data to send. - * @param length The length to send. - * @param bytes_sent The number of bytes actually sent. - * - * @return AFC_E_SUCCESS on success, or an AFC_E_* error value on error. - * - * @warning set client->afc_packet->this_length and - * client->afc_packet->entire_length to 0 before calling this. The - * reason is that if you set them to different values, it indicates - * you want to send the data as two packets. - */ -static afc_error_t afc_dispatch_packet(afc_client_t client, const char *data, uint32_t length, uint32_t *bytes_sent) -{ - uint32_t offset = 0; - uint32_t sent = 0; - - if (!client || !client->connection || !client->afc_packet) - return AFC_E_INVALID_ARGUMENT; - - *bytes_sent = 0; - - if (!data || !length) - length = 0; - - client->afc_packet->packet_num++; - if (!client->afc_packet->entire_length) { - client->afc_packet->entire_length = (length) ? sizeof(AFCPacket) + length : sizeof(AFCPacket); - client->afc_packet->this_length = client->afc_packet->entire_length; - } - if (!client->afc_packet->this_length) { - client->afc_packet->this_length = sizeof(AFCPacket); - } - // We want to send two segments; buffer+sizeof(AFCPacket) to - // this_length is the parameters - // And everything beyond that is the next packet. (for writing) - if (client->afc_packet->this_length != client->afc_packet->entire_length) { - offset = client->afc_packet->this_length - sizeof(AFCPacket); - - debug_info("Offset: %i", offset); - if ((length) < (client->afc_packet->entire_length - client->afc_packet->this_length)) { - debug_info("Length did not resemble what it was supposed to based on packet"); - debug_info("length minus offset: %i", length - offset); - debug_info("rest of packet: %i\n", client->afc_packet->entire_length - client->afc_packet->this_length); - return AFC_E_INTERNAL_ERROR; - } - - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - iphone_device_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - if (sent == 0) { - /* FIXME: should this be handled as success?! */ - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - /* send AFC packet data */ - sent = 0; - iphone_device_send(client->connection, data, offset, &sent); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - debug_info("sent the first now go with the second"); - debug_info("Length: %i", length - offset); - debug_info("Buffer: "); - debug_buffer(data + offset, length - offset); - - sent = 0; - iphone_device_send(client->connection, data + offset, length - offset, &sent); - - *bytes_sent = sent; - return AFC_E_SUCCESS; - } else { - debug_info("doin things the old way"); - debug_info("packet length = %i", client->afc_packet->this_length); - - debug_buffer((char*)client->afc_packet, sizeof(AFCPacket)); - - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - iphone_device_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - /* send AFC packet data (if there's data to send) */ - if (length > 0) { - debug_info("packet data follows"); - - debug_buffer(data, length); - iphone_device_send(client->connection, data, length, &sent); - *bytes_sent += sent; - } - return AFC_E_SUCCESS; - } - return AFC_E_INTERNAL_ERROR; -} - -/** Receives data through an AFC client and sets a variable to the received data. - * - * @param client The client to receive data on. - * @param dump_here The char* to point to the newly-received data. - * @param bytes_recv How much data was received. - * - * @return AFC_E_SUCCESS when data has been received, or an AFC_E_* error value - * when an error occured. - */ -static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint32_t *bytes_recv) -{ - AFCPacket header; - uint32_t entire_len = 0; - uint32_t this_len = 0; - uint32_t current_count = 0; - uint64_t param1 = -1; - - *bytes_recv = 0; - - /* first, read the AFC header */ - iphone_device_recv(client->connection, (char*)&header, sizeof(AFCPacket), bytes_recv); - AFCPacket_from_LE(&header); - if (*bytes_recv == 0) { - debug_info("Just didn't get enough."); - *dump_here = NULL; - return AFC_E_MUX_ERROR; - } else if (*bytes_recv < sizeof(AFCPacket)) { - debug_info("Did not even get the AFCPacket header"); - *dump_here = NULL; - return AFC_E_MUX_ERROR; - } - - /* check if it's a valid AFC header */ - if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN)) { - debug_info("Invalid AFC packet received (magic != " AFC_MAGIC ")!"); - } - - /* check if it has the correct packet number */ - if (header.packet_num != client->afc_packet->packet_num) { - /* otherwise print a warning but do not abort */ - debug_info("ERROR: Unexpected packet number (%lld != %lld) aborting.", header.packet_num, client->afc_packet->packet_num); - *dump_here = NULL; - return AFC_E_OP_HEADER_INVALID; - } - - /* then, read the attached packet */ - if (header.this_length < sizeof(AFCPacket)) { - debug_info("Invalid AFCPacket header received!"); - *dump_here = NULL; - return AFC_E_OP_HEADER_INVALID; - } else if ((header.this_length == header.entire_length) - && header.entire_length == sizeof(AFCPacket)) { - debug_info("Empty AFCPacket received!"); - *dump_here = NULL; - *bytes_recv = 0; - if (header.operation == AFC_OP_DATA) { - return AFC_E_SUCCESS; - } else { - return AFC_E_IO_ERROR; - } - } - - debug_info("received AFC packet, full len=%lld, this len=%lld, operation=0x%llx", header.entire_length, header.this_length, header.operation); - - entire_len = (uint32_t)header.entire_length - sizeof(AFCPacket); - this_len = (uint32_t)header.this_length - sizeof(AFCPacket); - - /* this is here as a check (perhaps a different upper limit is good?) */ - if (entire_len > (uint32_t)MAXIMUM_PACKET_SIZE) { - fprintf(stderr, "%s: entire_len is larger than MAXIMUM_PACKET_SIZE, (%d > %d)!", __func__, entire_len, MAXIMUM_PACKET_SIZE); - } - - *dump_here = (char*)malloc(entire_len); - if (this_len > 0) { - iphone_device_recv(client->connection, *dump_here, this_len, bytes_recv); - if (*bytes_recv <= 0) { - free(*dump_here); - *dump_here = NULL; - debug_info("Did not get packet contents!"); - return AFC_E_NOT_ENOUGH_DATA; - } else if (*bytes_recv < this_len) { - free(*dump_here); - *dump_here = NULL; - debug_info("Could not receive this_len=%d bytes", this_len); - return AFC_E_NOT_ENOUGH_DATA; - } - } - - current_count = this_len; - - if (entire_len > this_len) { - while (current_count < entire_len) { - iphone_device_recv(client->connection, (*dump_here)+current_count, entire_len - current_count, bytes_recv); - if (*bytes_recv <= 0) { - debug_info("Error receiving data (recv returned %d)", *bytes_recv); - break; - } - current_count += *bytes_recv; - } - if (current_count < entire_len) { - debug_info("WARNING: could not receive full packet (read %s, size %d)", current_count, entire_len); - } - } - - if (current_count >= sizeof(uint64_t)) { - param1 = *(uint64_t*)(*dump_here); - } - - debug_info("packet data size = %i", current_count); - debug_info("packet data follows"); - debug_buffer(*dump_here, current_count); - - /* check operation types */ - if (header.operation == AFC_OP_STATUS) { - /* status response */ - debug_info("got a status response, code=%lld", param1); - - if (param1 != AFC_E_SUCCESS) { - /* error status */ - /* free buffer */ - free(*dump_here); - *dump_here = NULL; - return (afc_error_t)param1; - } - } else if (header.operation == AFC_OP_DATA) { - /* data response */ - debug_info("got a data response"); - } else if (header.operation == AFC_OP_FILE_OPEN_RES) { - /* file handle response */ - debug_info("got a file handle response, handle=%lld", param1); - } else if (header.operation == AFC_OP_FILE_TELL_RES) { - /* tell response */ - debug_info("got a tell response, position=%lld", param1); - } else { - /* unknown operation code received */ - free(*dump_here); - *dump_here = NULL; - *bytes_recv = 0; - - debug_info("WARNING: Unknown operation code received 0x%llx param1=%lld", header.operation, param1); - fprintf(stderr, "%s: WARNING: Unknown operation code received 0x%llx param1=%lld", __func__, (long long)header.operation, (long long)param1); - - return AFC_E_OP_NOT_SUPPORTED; - } - - *bytes_recv = current_count; - return AFC_E_SUCCESS; -} - -static uint32_t count_nullspaces(char *string, uint32_t number) -{ - uint32_t i = 0, nulls = 0; - - for (i = 0; i < number; i++) { - if (string[i] == '\0') - nulls++; - } - - return nulls; -} - -static char **make_strings_list(char *tokens, uint32_t true_length) -{ - uint32_t nulls = 0, i = 0, j = 0; - char **list = NULL; - - if (!tokens || !true_length) - return NULL; - - nulls = count_nullspaces(tokens, true_length); - list = (char **) malloc(sizeof(char *) * (nulls + 1)); - for (i = 0; i < nulls; i++) { - list[i] = strdup(tokens + j); - j += strlen(list[i]) + 1; - } - list[i] = NULL; - - return list; -} - -/** Gets a directory listing of the directory requested. - * - * @param client The client to get a directory listing from. - * @param dir The directory to list. (must be a fully-qualified path) - * - * @return A char ** list of files in that directory, terminated by an empty - * string for now or NULL if there was an error. - */ -afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list) -{ - uint32_t bytes = 0; - char *data = NULL, **list_loc = NULL; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !dir || !list || (list && *list)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send the command - client->afc_packet->operation = AFC_OP_READ_DIR; - client->afc_packet->entire_length = 0; - client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive the data - ret = afc_receive_data(client, &data, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } - // Parse the data - list_loc = make_strings_list(data, bytes); - if (data) - free(data); - - afc_unlock(client); - *list = list_loc; - - return ret; -} - -/** Get device info for a client connection to phone. (free space on disk, etc.) - * - * @param client The client to get device info for. - * - * @return A char ** list of parameters as given by AFC or NULL if there was an - * error. - */ -afc_error_t afc_get_device_info(afc_client_t client, char ***infos) -{ - uint32_t bytes = 0; - char *data = NULL, **list = NULL; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !infos) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send the command - client->afc_packet->operation = AFC_OP_GET_DEVINFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, NULL, 0, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive the data - ret = afc_receive_data(client, &data, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } - // Parse the data - list = make_strings_list(data, bytes); - if (data) - free(data); - - afc_unlock(client); - - *infos = list; - - return ret; -} - -/** Get a specific key of the device info list for a client connection. - * Known key values are: Model, FSTotalBytes, FSFreeBytes and FSBlockSize. - * This is a helper function for afc_get_device_info(). - * - * @param client The client to get device info for. - * @param key The key to get the value of. - * @param value The value for the key if successful or NULL otherwise. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value) -{ - afc_error_t ret = AFC_E_INTERNAL_ERROR; - char **kvps, **ptr; - - *value = NULL; - if (key == NULL) - return AFC_E_INVALID_ARGUMENT; - - ret = afc_get_device_info(client, &kvps); - if (ret != AFC_E_SUCCESS) - return ret; - - for (ptr = kvps; *ptr; ptr++) { - if (!strcmp(*ptr, key)) { - *value = strdup(*(ptr+1)); - break; - } - } - - g_strfreev(kvps); - - return ret; -} - -/** Deletes a file or directory. - * - * @param client The client to use. - * @param path The path to delete. (must be a fully-qualified path) - * - * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_remove_path(afc_client_t client, const char *path) -{ - char *response = NULL; - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !path || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - client->afc_packet->operation = AFC_OP_REMOVE_PATH; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - /* special case; unknown error actually means directory not empty */ - if (ret == AFC_E_UNKNOWN_ERROR) - ret = AFC_E_DIR_NOT_EMPTY; - - afc_unlock(client); - - return ret; -} - -/** Renames a file or directory on the phone. - * - * @param client The client to have rename. - * @param from The name to rename from. (must be a fully-qualified path) - * @param to The new name. (must also be a fully-qualified path) - * - * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to) -{ - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(from) + strlen(to) + 1 + sizeof(uint32_t))); - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !from || !to || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - memcpy(send, from, strlen(from) + 1); - memcpy(send + strlen(from) + 1, to, strlen(to) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_RENAME_PATH; - ret = afc_dispatch_packet(client, send, strlen(to)+1 + strlen(from)+1, &bytes); - free(send); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - afc_unlock(client); - - return ret; -} - -/** Creates a directory on the phone. - * - * @param client The client to use to make a directory. - * @param dir The directory's path. (must be a fully-qualified path, I assume - * all other mkdir restrictions apply as well) - * - * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_make_directory(afc_client_t client, const char *dir) -{ - uint32_t bytes = 0; - char *response = NULL; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - client->afc_packet->operation = AFC_OP_MAKE_DIR; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - afc_unlock(client); - - return ret; -} - -/** Gets information about a specific file. - * - * @param client The client to use to get the information of the file. - * @param path The fully-qualified path to the file. - * @param infolist Pointer to a buffer that will be filled with a NULL-terminated - * list of strings with the file information. - * Set to NULL before calling this function. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value - * when something went wrong. - */ -afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***infolist) -{ - char *received = NULL; - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !path || !infolist) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - client->afc_packet->operation = AFC_OP_GET_FILE_INFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - - // Receive data - ret = afc_receive_data(client, &received, &bytes); - if (received) { - *infolist = make_strings_list(received, bytes); - free(received); - } - - afc_unlock(client); - - return ret; -} - -/** Opens a file on the phone. - * - * @param client The client to use to open the file. - * @param filename The file to open. (must be a fully-qualified path) - * @param file_mode The mode to use to open the file. Can be AFC_FILE_READ or - * AFC_FILE_WRITE; the former lets you read and write, - * however, and the second one will *create* the file, - * destroying anything previously there. - * @param handle Pointer to a uint64_t that will hold the handle of the file - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error on failure. - */ -iphone_error_t -afc_file_open(afc_client_t client, const char *filename, - afc_file_mode_t file_mode, uint64_t *handle) -{ - uint64_t file_mode_loc = GUINT64_TO_LE(file_mode); - uint32_t bytes = 0; - char *data = (char *) malloc(sizeof(char) * (8 + strlen(filename) + 1)); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - // set handle to 0 so in case an error occurs, the handle is invalid - *handle = 0; - - if (!client || !client->connection || !client->afc_packet) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - memcpy(data, &file_mode_loc, 8); - memcpy(data + 8, filename, strlen(filename)); - data[8 + strlen(filename)] = '\0'; - client->afc_packet->operation = AFC_OP_FILE_OPEN; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, data, 8 + strlen(filename) + 1, &bytes); - free(data); - - if (ret != AFC_E_SUCCESS) { - debug_info("Didn't receive a response to the command"); - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive the data - ret = afc_receive_data(client, &data, &bytes); - if ((ret == AFC_E_SUCCESS) && (bytes > 0) && data) { - afc_unlock(client); - - // Get the file handle - memcpy(handle, data, sizeof(uint64_t)); - free(data); - return ret; - } - - debug_info("Didn't get any further data"); - - afc_unlock(client); - - return ret; -} - -/** Attempts to the read the given number of bytes from the given file. - * - * @param client The relevant AFC client - * @param handle File handle of a previously opened file - * @param data The pointer to the memory region to store the read data - * @param length The number of bytes to read - * @param bytes_read The number of bytes actually read. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value on error. - */ -iphone_error_t -afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) -{ - char *input = NULL; - uint32_t current_count = 0, bytes_loc = 0; - const uint32_t MAXIMUM_READ_SIZE = 1 << 16; - afc_error_t ret = AFC_E_SUCCESS; - - if (!client || !client->afc_packet || !client->connection || handle == 0) - return AFC_E_INVALID_ARGUMENT; - debug_info("called for length %i", length); - - afc_lock(client); - - // Looping here to get around the maximum amount of data that - // afc_receive_data can handle - while (current_count < length) { - debug_info("current count is %i but length is %i", current_count, length); - - // Send the read command - AFCFilePacket *packet = (AFCFilePacket *) malloc(sizeof(AFCFilePacket)); - packet->filehandle = handle; - packet->size = GUINT64_TO_LE(((length - current_count) < MAXIMUM_READ_SIZE) ? (length - current_count) : MAXIMUM_READ_SIZE); - client->afc_packet->operation = AFC_OP_READ; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, (char *) packet, sizeof(AFCFilePacket), &bytes_loc); - free(packet); - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive the data - ret = afc_receive_data(client, &input, &bytes_loc); - debug_info("afc_receive_data returned error: %d", ret); - debug_info("bytes returned: %i", bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else if (bytes_loc == 0) { - if (input) - free(input); - afc_unlock(client); - *bytes_read = current_count; - /* FIXME: check that's actually a success */ - return ret; - } else { - if (input) { - debug_info("%d", bytes_loc); - memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); - free(input); - input = NULL; - current_count += (bytes_loc > length) ? length : bytes_loc; - } - } - } - debug_info("returning current_count as %i", current_count); - - afc_unlock(client); - *bytes_read = current_count; - return ret; -} - -/** Writes a given number of bytes to a file. - * - * @param client The client to use to write to the file. - * @param handle File handle of previously opened file. - * @param data The data to write to the file. - * @param length How much data to write. - * @param bytes_written The number of bytes actually written to the file. - * - * @return AFC_E_SUCCESS on success, or an AFC_E_* error value on error. - */ -iphone_error_t -afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) -{ - char *acknowledgement = NULL; - const uint32_t MAXIMUM_WRITE_SIZE = 1 << 15; - uint32_t current_count = 0, i = 0; - uint32_t segments = (length / MAXIMUM_WRITE_SIZE); - uint32_t bytes_loc = 0; - char *out_buffer = NULL; - afc_error_t ret = AFC_E_SUCCESS; - - if (!client || !client->afc_packet || !client->connection || !bytes_written || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - debug_info("Write length: %i", length); - - // Divide the file into segments. - for (i = 0; i < segments; i++) { - // Send the segment - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + MAXIMUM_WRITE_SIZE; - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *)&handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, MAXIMUM_WRITE_SIZE); - ret = afc_dispatch_packet(client, out_buffer, MAXIMUM_WRITE_SIZE + 8, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - free(out_buffer); - out_buffer = NULL; - - current_count += bytes_loc; - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else { - free(acknowledgement); - } - } - - // By this point, we should be at the end. i.e. the last segment that - // didn't get sent in the for loop - // this length is fine because it's always sizeof(AFCPacket) + 8, but - // to be sure we do it again - if (current_count == length) { - afc_unlock(client); - *bytes_written = current_count; - return ret; - } - - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + (length - current_count); - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *) &handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, (length - current_count)); - ret = afc_dispatch_packet(client, out_buffer, (length - current_count) + 8, &bytes_loc); - free(out_buffer); - out_buffer = NULL; - - current_count += bytes_loc; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - *bytes_written = current_count; - return AFC_E_SUCCESS; - } - - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); - afc_unlock(client); - if (ret != AFC_E_SUCCESS) { - debug_info("uh oh?"); - } else { - free(acknowledgement); - } - *bytes_written = current_count; - return ret; -} - -/** Closes a file on the phone. - * - * @param client The client to close the file with. - * @param handle File handle of a previously opened file. - */ -afc_error_t afc_file_close(afc_client_t client, uint64_t handle) -{ - char *buffer = malloc(sizeof(char) * 8); - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - debug_info("File handle %i", handle); - - // Send command - memcpy(buffer, &handle, sizeof(uint64_t)); - client->afc_packet->operation = AFC_OP_FILE_CLOSE; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_UNKNOWN_ERROR; - } - - // Receive the response - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); - - afc_unlock(client); - - return ret; -} - -/** Locks or unlocks a file on the phone. - * - * makes use of flock on the device, see - * http://developer.apple.com/documentation/Darwin/Reference/ManPages/man2/flock.2.html - * - * @param client The client to lock the file with. - * @param handle File handle of a previously opened file. - * @param operation the lock or unlock operation to perform, this is one of - * AFC_LOCK_SH (shared lock), AFC_LOCK_EX (exclusive lock), - * or AFC_LOCK_UN (unlock). - */ -afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation) -{ - char *buffer = malloc(16); - uint32_t bytes = 0; - uint64_t op = GUINT64_TO_LE(operation); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - debug_info("file handle %i", handle); - - // Send command - memcpy(buffer, &handle, sizeof(uint64_t)); - memcpy(buffer + 8, &op, 8); - - client->afc_packet->operation = AFC_OP_FILE_LOCK; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - debug_info("could not send lock command"); - return AFC_E_UNKNOWN_ERROR; - } - // Receive the response - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) { - debug_buffer(buffer, bytes); - free(buffer); - } - afc_unlock(client); - - return ret; -} - -/** Seeks to a given position of a pre-opened file on the phone. - * - * @param client The client to use to seek to the position. - * @param handle File handle of a previously opened. - * @param offset Seek offset. - * @param whence Seeking direction, one of SEEK_SET, SEEK_CUR, or SEEK_END. - * - * @return AFC_E_SUCCESS on success, AFC_E_NOT_ENOUGH_DATA on failure. - */ -afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence) -{ - char *buffer = (char *) malloc(sizeof(char) * 24); - int64_t offset_loc = (int64_t)GUINT64_TO_LE(offset); - uint64_t whence_loc = GUINT64_TO_LE(whence); - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send the command - memcpy(buffer, &handle, sizeof(uint64_t)); // handle - memcpy(buffer + 8, &whence_loc, sizeof(uint64_t)); // fromwhere - memcpy(buffer + 16, &offset_loc, sizeof(uint64_t)); // offset - client->afc_packet->operation = AFC_OP_FILE_SEEK; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 24, &bytes); - free(buffer); - buffer = NULL; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); - - afc_unlock(client); - - return ret; -} - -/** Returns current position in a pre-opened file on the phone. - * - * @param client The client to use. - * @param handle File handle of a previously opened file. - * @param position Position in bytes of indicator - * - * @return AFC_E_SUCCESS on success, AFC_E_NOT_ENOUGH_DATA on failure. - */ -afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position) -{ - char *buffer = (char *) malloc(sizeof(char) * 8); - uint32_t bytes = 0; - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send the command - memcpy(buffer, &handle, sizeof(uint64_t)); // handle - client->afc_packet->operation = AFC_OP_FILE_TELL; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - - // Receive the data - ret = afc_receive_data(client, &buffer, &bytes); - if (bytes > 0 && buffer) { - /* Get the position */ - memcpy(position, buffer, sizeof(uint64_t)); - *position = GUINT64_FROM_LE(*position); - } - if (buffer) - free(buffer); - - afc_unlock(client); - - return ret; -} - -/** Sets the size of a file on the phone. - * - * @param client The client to use to set the file size. - * @param handle File handle of a previously opened file. - * @param newsize The size to set the file to. - * - * @return 0 on success, -1 on failure. - * - * @note This function is more akin to ftruncate than truncate, and truncate - * calls would have to open the file before calling this, sadly. - */ -afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize) -{ - char *buffer = (char *) malloc(sizeof(char) * 16); - uint32_t bytes = 0; - uint64_t newsize_loc = GUINT64_TO_LE(newsize); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || (handle == 0)) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - memcpy(buffer, &handle, sizeof(uint64_t)); // handle - memcpy(buffer + 8, &newsize_loc, sizeof(uint64_t)); // newsize - client->afc_packet->operation = AFC_OP_FILE_SET_SIZE; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); - - afc_unlock(client); - - return ret; -} - -/** Sets the size of a file on the phone without prior opening it. - * - * @param client The client to use to set the file size. - * @param path The path of the file to be truncated. - * @param newsize The size to set the file to. - * - * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize) -{ - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); - uint32_t bytes = 0; - uint64_t size_requested = GUINT64_TO_LE(newsize); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !path || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - memcpy(send, &size_requested, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_TRUNCATE; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - afc_unlock(client); - - return ret; -} - -/** Creates a hard link or symbolic link on the device. - * - * @param client The client to use for making a link - * @param type 1 = hard link, 2 = symlink - * @param target The file to be linked. - * @param linkname The name of link. - * - * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname) -{ - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(target)+1 + strlen(linkname)+1 + 8)); - uint32_t bytes = 0; - uint64_t type = GUINT64_TO_LE(linktype); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !target || !linkname || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - debug_info("link type: %lld", type); - debug_info("target: %s, length:%d", target, strlen(target)); - debug_info("linkname: %s, length:%d", linkname, strlen(linkname)); - - // Send command - memcpy(send, &type, 8); - memcpy(send + 8, target, strlen(target) + 1); - memcpy(send + 8 + strlen(target) + 1, linkname, strlen(linkname) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_MAKE_LINK; - ret = afc_dispatch_packet(client, send, 8 + strlen(linkname) + 1 + strlen(target) + 1, &bytes); - free(send); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - afc_unlock(client); - - return ret; -} - -/** Sets the modification time of a file on the phone. - * - * @param client The client to use to set the file size. - * @param path Path of the file for which the modification time should be set. - * @param mtime The modification time to set in nanoseconds since epoch. - * - * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT - * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. - */ -afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime) -{ - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); - uint32_t bytes = 0; - uint64_t mtime_loc = GUINT64_TO_LE(mtime); - afc_error_t ret = AFC_E_UNKNOWN_ERROR; - - if (!client || !path || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARGUMENT; - - afc_lock(client); - - // Send command - memcpy(send, &mtime_loc, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_SET_FILE_TIME; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - // Receive response - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); - - afc_unlock(client); - - return ret; -} - diff --git a/src/AFC.h b/src/AFC.h deleted file mode 100644 index 5db865f..0000000 --- a/src/AFC.h +++ /dev/null @@ -1,100 +0,0 @@ -/* - * AFC.h - * Defines and structs and the like for the built-in AFC client - * - * Copyright (c) 2008 Zach C. 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 - -#include "libiphone/afc.h" - -#define AFC_MAGIC "CFA6LPAA" -#define AFC_MAGIC_LEN (8) - -typedef struct { - char magic[AFC_MAGIC_LEN]; - uint64_t entire_length, this_length, packet_num, operation; -} AFCPacket; - -#define AFCPacket_to_LE(x) \ - (x)->entire_length = GUINT64_TO_LE((x)->entire_length); \ - (x)->this_length = GUINT64_TO_LE((x)->this_length); \ - (x)->packet_num = GUINT64_TO_LE((x)->packet_num); \ - (x)->operation = GUINT64_TO_LE((x)->operation); - -#define AFCPacket_from_LE(x) \ - (x)->entire_length = GUINT64_FROM_LE((x)->entire_length); \ - (x)->this_length = GUINT64_FROM_LE((x)->this_length); \ - (x)->packet_num = GUINT64_FROM_LE((x)->packet_num); \ - (x)->operation = GUINT64_FROM_LE((x)->operation); - -typedef struct { - uint64_t filehandle, size; -} AFCFilePacket; - -typedef struct __AFCToken { - struct __AFCToken *last, *next; - char *token; -} AFCToken; - -struct afc_client_int { - iphone_connection_t connection; - AFCPacket *afc_packet; - int file_handle; - int lock; - GMutex *mutex; -}; - -/* AFC Operations */ -enum { - AFC_OP_STATUS = 0x00000001, // Status - AFC_OP_DATA = 0x00000002, // Data - AFC_OP_READ_DIR = 0x00000003, // ReadDir - AFC_OP_READ_FILE = 0x00000004, // ReadFile - AFC_OP_WRITE_FILE = 0x00000005, // WriteFile - AFC_OP_WRITE_PART = 0x00000006, // WritePart - AFC_OP_TRUNCATE = 0x00000007, // TruncateFile - AFC_OP_REMOVE_PATH = 0x00000008, // RemovePath - AFC_OP_MAKE_DIR = 0x00000009, // MakeDir - AFC_OP_GET_FILE_INFO = 0x0000000a, // GetFileInfo - AFC_OP_GET_DEVINFO = 0x0000000b, // GetDeviceInfo - AFC_OP_WRITE_FILE_ATOM = 0x0000000c, // WriteFileAtomic (tmp file+rename) - AFC_OP_FILE_OPEN = 0x0000000d, // FileRefOpen - AFC_OP_FILE_OPEN_RES = 0x0000000e, // FileRefOpenResult - AFC_OP_READ = 0x0000000f, // FileRefRead - AFC_OP_WRITE = 0x00000010, // FileRefWrite - AFC_OP_FILE_SEEK = 0x00000011, // FileRefSeek - AFC_OP_FILE_TELL = 0x00000012, // FileRefTell - AFC_OP_FILE_TELL_RES = 0x00000013, // FileRefTellResult - AFC_OP_FILE_CLOSE = 0x00000014, // FileRefClose - AFC_OP_FILE_SET_SIZE = 0x00000015, // FileRefSetFileSize (ftruncate) - AFC_OP_GET_CON_INFO = 0x00000016, // GetConnectionInfo - AFC_OP_SET_CON_OPTIONS = 0x00000017, // SetConnectionOptions - AFC_OP_RENAME_PATH = 0x00000018, // RenamePath - AFC_OP_SET_FS_BS = 0x00000019, // SetFSBlockSize (0x800000) - AFC_OP_SET_SOCKET_BS = 0x0000001A, // SetSocketBlockSize (0x800000) - AFC_OP_FILE_LOCK = 0x0000001B, // FileRefLock - AFC_OP_MAKE_LINK = 0x0000001C, // MakeLink - AFC_OP_SET_FILE_TIME = 0x0000001E // set st_mtime -}; - diff --git a/src/InstallationProxy.c b/src/InstallationProxy.c deleted file mode 100644 index 9ada994..0000000 --- a/src/InstallationProxy.c +++ /dev/null @@ -1,740 +0,0 @@ -/* - * 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 "property_list_service.h" -#include "debug.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) -{ - debug_info("InstallationProxy: Locked"); - 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) -{ - debug_info("InstallationProxy: Unlocked"); - g_mutex_unlock(client->mutex); -} - -/** - * Convert 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; - default: - break; - } - return INSTPROXY_E_UNKNOWN_ERROR; -} - -/** - * Creates a new installation_proxy client - * - * @param device The device to connect to - * @param 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, uint16_t 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; - - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return INSTPROXY_E_CONN_FAILED; - } - - instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_int)); - client_loc->parent = plistclient; - 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; - - property_list_service_client_free(client->parent); - client->parent = NULL; - if (client->status_updater) { - debug_info("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->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); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist"); - goto leave_unlock; - } - - apps_array = plist_new_array(); - - 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; - } - -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_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); - } - /* 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; -} - -/** - * 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); - debug_info("done, cleaning up."); - 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->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_unlock(client); - - plist_free(dict); - - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); - instproxy_unlock(client); - - plist_free(dict); - - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", 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->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); - - 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; -} - -/** - * 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 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. - * - * @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, uint32_t options, instproxy_status_cb_t status_cb) -{ - if (!client || !client->parent || !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)); - 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_unlock(client); - - plist_free(dict); - - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); - instproxy_unlock(client); - - plist_free(dict); - - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); - instproxy_unlock(client); - - plist_free(dict); - - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; - } - return instproxy_create_status_updater(client, status_cb, "RemoveArchive"); -} - diff --git a/src/InstallationProxy.h b/src/InstallationProxy.h deleted file mode 100644 index f0b5691..0000000 --- a/src/InstallationProxy.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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" -#include "property_list_service.h" - -struct instproxy_client_int { - property_list_service_client_t parent; - GMutex *mutex; - GThread *status_updater; -}; - -#endif diff --git a/src/Makefile.am b/src/Makefile.am index bb7252e..2e92382 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,13 +5,13 @@ AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_L lib_LTLIBRARIES = libiphone.la libiphone_la_SOURCES = iphone.c iphone.h \ + debug.c debug.h\ + userpref.c userpref.h\ property_list_service.c property_list_service.h\ device_link_service.c device_link_service.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\ - debug.c debug.h\ - MobileSync.c MobileSync.h + afc.c afc.h\ + notification_proxy.c notification_proxy.h\ + installation_proxy.c installation_proxy.h\ + sbservices.c sbservices.h\ + mobilesync.c mobilesync.h diff --git a/src/MobileSync.c b/src/MobileSync.c deleted file mode 100644 index 827ed35..0000000 --- a/src/MobileSync.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * MobileSync.c - * Contains functions for the built-in MobileSync client. - * - * Copyright (c) 2009 Jonathan Beck 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 "MobileSync.h" -#include "device_link_service.h" -#include "debug.h" - -#define MSYNC_VERSION_INT1 100 -#define MSYNC_VERSION_INT2 100 - -/** - * Convert an device_link_service_error_t value to an mobilesync_error_t value. - * Used internally to get correct error codes when using device_link_service stuff. - * - * @param err An device_link_service_error_t error code - * - * @return A matching mobilesync_error_t error code, - * MOBILESYNC_E_UNKNOWN_ERROR otherwise. - */ -static mobilesync_error_t mobilesync_error(device_link_service_error_t err) -{ - switch (err) { - case DEVICE_LINK_SERVICE_E_SUCCESS: - return MOBILESYNC_E_SUCCESS; - case DEVICE_LINK_SERVICE_E_INVALID_ARG: - return MOBILESYNC_E_INVALID_ARG; - case DEVICE_LINK_SERVICE_E_PLIST_ERROR: - return MOBILESYNC_E_PLIST_ERROR; - case DEVICE_LINK_SERVICE_E_MUX_ERROR: - return MOBILESYNC_E_MUX_ERROR; - case DEVICE_LINK_SERVICE_E_BAD_VERSION: - return MOBILESYNC_E_BAD_VERSION; - default: - break; - } - return MOBILESYNC_E_UNKNOWN_ERROR; -} - -mobilesync_error_t mobilesync_client_new(iphone_device_t device, uint16_t port, - mobilesync_client_t * client) -{ - if (!device || port == 0 || !client || *client) - return MOBILESYNC_E_INVALID_ARG; - - device_link_service_client_t dlclient = NULL; - mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, port, &dlclient)); - if (ret != MOBILESYNC_E_SUCCESS) { - return ret; - } - - mobilesync_client_t client_loc = (mobilesync_client_t) malloc(sizeof(struct mobilesync_client_int)); - client_loc->parent = dlclient; - - /* perform handshake */ - ret = mobilesync_error(device_link_service_version_exchange(dlclient, MSYNC_VERSION_INT1, MSYNC_VERSION_INT2)); - if (ret != MOBILESYNC_E_SUCCESS) { - debug_info("version exchange failed, error %d", ret); - mobilesync_client_free(client_loc); - return ret; - } - - *client = client_loc; - - return ret; -} - -mobilesync_error_t mobilesync_client_free(mobilesync_client_t client) -{ - if (!client) - return MOBILESYNC_E_INVALID_ARG; - device_link_service_disconnect(client->parent); - mobilesync_error_t err = mobilesync_error(device_link_service_client_free(client->parent)); - free(client); - return err; -} - -/** Polls the iPhone for MobileSync data. - * - * @param client The MobileSync client - * @param plist A pointer to the location where the plist should be stored - * - * @return an error code - */ -mobilesync_error_t mobilesync_recv(mobilesync_client_t client, plist_t * plist) -{ - if (!client) - return MOBILESYNC_E_INVALID_ARG; - mobilesync_error_t ret = mobilesync_error(device_link_service_receive(client->parent, plist)); -#ifndef STRIP_DEBUG_CODE - if (ret != MOBILESYNC_E_SUCCESS) { - return ret; - } - char *XMLContent = NULL; - uint32_t length = 0; - plist_to_xml(*plist, &XMLContent, &length); - debug_info("plist size: %i\nbuffer :\n%s", length, XMLContent); - free(XMLContent); -#endif - return ret; -} - -/** Sends MobileSync data to the iPhone - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The MobileSync client - * @param plist The location of the plist to send - * - * @return an error code - */ -mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) -{ - if (!client || !plist) - return MOBILESYNC_E_INVALID_ARG; - -#ifndef STRIP_DEBUG_CODE - char *XMLContent = NULL; - uint32_t length = 0; - plist_to_xml(plist, &XMLContent, &length); - debug_info("plist size: %i\nbuffer :\n%s", length, XMLContent); - free(XMLContent); -#endif - return mobilesync_error(device_link_service_send(client->parent, plist)); -} diff --git a/src/MobileSync.h b/src/MobileSync.h deleted file mode 100644 index 6538343..0000000 --- a/src/MobileSync.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * MobileSync.h - * Definitions for the built-in MobileSync client - * - * Copyright (c) 2009 Jonathan Beck 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 MOBILESYNC_H -#define MOBILESYNC_H - -#include "libiphone/mobilesync.h" -#include "device_link_service.h" - -struct mobilesync_client_int { - device_link_service_client_t parent; -}; - -#endif diff --git a/src/NotificationProxy.c b/src/NotificationProxy.c deleted file mode 100644 index e994c16..0000000 --- a/src/NotificationProxy.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - * NotificationProxy.c - * Notification 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 - -#include "NotificationProxy.h" -#include "property_list_service.h" -#include "debug.h" - -struct np_thread { - np_client_t client; - np_notify_cb_t cbfunc; -}; - -/** Locks an NP client, done for thread safety stuff. - * - * @param client The NP - */ -static void np_lock(np_client_t client) -{ - debug_info("NP: Locked"); - g_mutex_lock(client->mutex); -} - -/** Unlocks an NP client, done for thread safety stuff. - * - * @param client The NP - */ -static void np_unlock(np_client_t client) -{ - debug_info("NP: Unlocked"); - g_mutex_unlock(client->mutex); -} - -/** - * Convert a property_list_service_error_t value to an np_error_t value. - * Used internally to get correct error codes. - * - * @param err A property_list_service_error_t error code - * - * @return A matching np_error_t error code, - * NP_E_UNKNOWN_ERROR otherwise. - */ -static np_error_t np_error(property_list_service_error_t err) -{ - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return NP_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return NP_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return NP_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return NP_E_CONN_FAILED; - default: - break; - } - return NP_E_UNKNOWN_ERROR; -} - -/** Makes a connection to the NP service on the phone. - * - * @param device The device to connect to. - * @param 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 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, uint16_t port, np_client_t *client) -{ - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return NP_E_INVALID_ARG; - - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return NP_E_CONN_FAILED; - } - - np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_int)); - client_loc->parent = plistclient; - - client_loc->mutex = g_mutex_new(); - - client_loc->notifier = NULL; - - *client = client_loc; - return NP_E_SUCCESS; -} - -/** 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) -{ - if (!client) - return NP_E_INVALID_ARG; - - property_list_service_client_free(client->parent); - client->parent = NULL; - if (client->notifier) { - debug_info("joining np callback"); - g_thread_join(client->notifier); - } - if (client->mutex) { - g_mutex_free(client->mutex); - } - free(client); - - return NP_E_SUCCESS; -} - -/** Sends a notification to the device's Notification Proxy. - * - * @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) -{ - if (!client || !notification) { - return NP_E_INVALID_ARG; - } - np_lock(client); - - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("PostNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); - - np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - - dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("Shutdown")); - - res = np_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - - if (res != NP_E_SUCCESS) { - debug_info("Error sending XML plist to device!"); - } - - np_unlock(client); - return res; -} - -/** Notifies the iphone to send a notification on the specified event. - * - * @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 ) -{ - if (!client || !notification) { - return NP_E_INVALID_ARG; - } - np_lock(client); - - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("ObserveNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); - - np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); - if (res != NP_E_SUCCESS) { - debug_info("Error sending XML plist to device!"); - } - plist_free(dict); - - np_unlock(client); - return res; -} - -/** Notifies the iphone to send a notification on specified events. - * - * @param client The client to send to - * @param notification_spec Specification of the notifications that should be - * observed. This is expected to be an array of const char* that MUST have a - * terminating NULL entry. - * - * @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) -{ - int i = 0; - np_error_t res = NP_E_UNKNOWN_ERROR; - const char **notifications = notification_spec; - - if (!client) { - return NP_E_INVALID_ARG; - } - - if (!notifications) { - return NP_E_INVALID_ARG; - } - - while (notifications[i]) { - res = np_observe_notification(client, notifications[i]); - if (res != NP_E_SUCCESS) { - break; - } - i++; - } - - return res; -} - -/** - * Checks if a notification has been sent. - * - * @param client NP to get a notification from - * @param notification Pointer to a buffer that will be allocated and filled - * with the notification that has been received. - * - * @return 0 if a notification has been received or nothing has been received, - * or a negative value if an error occured. - * - * @note You probably want to check out np_set_notify_callback - * @see np_set_notify_callback - */ -static int np_get_notification(np_client_t client, char **notification) -{ - int res = 0; - plist_t dict = NULL; - - if (!client || !client->parent || *notification) - return -1; - - np_lock(client); - - property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); - if (!dict) { - debug_info("NotificationProxy: no notification received!"); - res = 0; - } else { - char *cmd_value = NULL; - plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); - - if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { - plist_get_string_val(cmd_value_node, &cmd_value); - } - - if (cmd_value && !strcmp(cmd_value, "RelayNotification")) { - char *name_value = NULL; - plist_t name_value_node = plist_dict_get_item(dict, "Name"); - - if (plist_get_node_type(name_value_node) == PLIST_STRING) { - plist_get_string_val(name_value_node, &name_value); - } - - res = -2; - if (name_value_node && name_value) { - *notification = name_value; - debug_info("got notification %s\n", __func__, name_value); - res = 0; - } - } else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { - debug_info("ERROR: NotificationProxy died!"); - res = -1; - } else if (cmd_value) { - debug_info("unknown NotificationProxy command '%s' received!", cmd_value); - res = -1; - } else { - res = -2; - } - if (cmd_value) { - free(cmd_value); - } - plist_free(dict); - dict = NULL; - } - - np_unlock(client); - - return res; -} - -/** - * Internally used thread function. - */ -gpointer np_notifier( gpointer arg ) -{ - char *notification = NULL; - struct np_thread *npt = (struct np_thread*)arg; - - if (!npt) return NULL; - - debug_info("starting callback."); - while (npt->client->parent) { - np_get_notification(npt->client, ¬ification); - if (notification) { - npt->cbfunc(notification); - free(notification); - notification = NULL; - } - sleep(1); - } - if (npt) { - free(npt); - } - - return NULL; -} - -/** - * This function allows an application to define a callback function that will - * be called when a notification has been received. - * It will start a thread that polls for notifications and calls the callback - * function if a notification has been received. - * - * @param client the NP client - * @param notify_cb pointer to a callback function or NULL to de-register a - * 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, - * 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 ) -{ - if (!client) - return NP_E_INVALID_ARG; - - np_error_t res = NP_E_UNKNOWN_ERROR; - - np_lock(client); - if (client->notifier) { - debug_info("callback already set, removing\n"); - property_list_service_client_t parent = client->parent; - client->parent = NULL; - g_thread_join(client->notifier); - client->notifier = NULL; - client->parent = parent; - } - - if (notify_cb) { - struct np_thread *npt = (struct np_thread*)malloc(sizeof(struct np_thread)); - if (npt) { - npt->client = client; - npt->cbfunc = notify_cb; - - client->notifier = g_thread_create(np_notifier, npt, TRUE, NULL); - if (client->notifier) { - res = NP_E_SUCCESS; - } - } - } else { - debug_info("no callback set"); - } - np_unlock(client); - - return res; -} diff --git a/src/NotificationProxy.h b/src/NotificationProxy.h deleted file mode 100644 index a2b3001..0000000 --- a/src/NotificationProxy.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * NotificationProxy.h - * Notification 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 INOTIFICATION_PROXY_H -#define INOTIFICATION_PROXY_H - -#include - -#include "libiphone/notification_proxy.h" -#include "property_list_service.h" - -struct np_client_int { - property_list_service_client_t parent; - GMutex *mutex; - GThread *notifier; -}; - -gpointer np_notifier(gpointer arg); - -#endif diff --git a/src/SBServices.c b/src/SBServices.c deleted file mode 100644 index 69c7425..0000000 --- a/src/SBServices.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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 "property_list_service.h" -#include "debug.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) -{ - debug_info("SBServices: Locked"); - 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) -{ - debug_info("SBServices: Unlocked"); - g_mutex_unlock(client->mutex); -} - -/** - * Convert a property_list_service_error_t value to a sbservices_error_t value. - * Used internally to get correct error codes. - * - * @param err A property_list_service_error_t error code - * - * @return A matching sbservices_error_t error code, - * SBSERVICES_E_UNKNOWN_ERROR otherwise. - */ -static sbservices_error_t sbservices_error(property_list_service_error_t err) -{ - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return SBSERVICES_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return SBSERVICES_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return SBSERVICES_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return SBSERVICES_E_CONN_FAILED; - default: - break; - } - return SBSERVICES_E_UNKNOWN_ERROR; -} - -/** - * Creates a new sbservices client. - * - * @param device The device to connect to. - * @param port The port on device to connect to. - * @param client Pointer that will point to a newly allocated - * sbservices_client_t upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_client_new(iphone_device_t device, uint16_t 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; - - property_list_service_client_t plistclient = NULL; - sbservices_error_t err = sbservices_error(property_list_service_client_new(device, port, &plistclient)); - if (err != SBSERVICES_E_SUCCESS) { - return err; - } - - sbservices_client_t client_loc = (sbservices_client_t) malloc(sizeof(struct sbservices_client_int)); - client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - - *client = client_loc; - return SBSERVICES_E_SUCCESS; -} - -/** - * Frees an sbservices client. - * - * @param client The sbservices client to free. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_client_free(sbservices_client_t client) -{ - if (!client) - return SBSERVICES_E_INVALID_ARG; - - sbservices_error_t err = sbservices_error(property_list_service_client_free(client->parent)); - client->parent = NULL; - if (client->mutex) { - g_mutex_free(client->mutex); - } - free(client); - - return err; -} - -/** - * Gets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param state Pointer that will point to a newly allocated plist containing - * the current icon state. It is up to the caller to free the memory. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or state is invalid, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state) -{ - if (!client || !client->parent || !state) - 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("getIconState")); - - sbs_lock(client); - - res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); - if (res != SBSERVICES_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; - } - plist_free(dict); - dict = NULL; - - res = sbservices_error(property_list_service_receive_plist(client->parent, state)); - if (res != SBSERVICES_E_SUCCESS) { - debug_info("could not get icon state, error %d", res); - if (*state) { - plist_free(*state); - *state = NULL; - } - } - -leave_unlock: - if (dict) { - plist_free(dict); - } - sbs_unlock(client); - return res; -} - -/** - * Sets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param newstate A plist containing the new iconstate. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or newstate is NULL, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate) -{ - if (!client || !client->parent || !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_error(property_list_service_send_binary_plist(client->parent, dict)); - if (res != SBSERVICES_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - } - // NO RESPONSE - - if (dict) { - plist_free(dict); - } - sbs_unlock(client); - return res; -} - -/** - * Get the icon of the specified app as PNG data. - * - * @param client The connected sbservices client to use. - * @param bundleId The bundle identifier of the app to retrieve the icon for. - * @param pngdata Pointer that will point to a newly allocated buffer - * containing the PNG data upon successful return. It is up to the caller - * to free the memory. - * @param pngsize Pointer to a uint64_t that will be set to the size of the - * buffer pngdata points to upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client, bundleId, or pngdata are invalid, or an SBSERVICES_E_* error - * code otherwise. - */ -sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize) -{ - if (!client || !client->parent || !bundleId || !pngdata) - 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("getIconPNGData")); - plist_dict_insert_item(dict, "bundleId", plist_new_string(bundleId)); - - sbs_lock(client); - - res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); - if (res != SBSERVICES_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; - } - plist_free(dict); - - dict = NULL; - res = sbservices_error(property_list_service_receive_plist(client->parent, &dict)); - if (res == SBSERVICES_E_SUCCESS) { - plist_t node = plist_dict_get_item(dict, "pngData"); - if (node) { - plist_get_data_val(node, pngdata, pngsize); - } - } - -leave_unlock: - if (dict) { - plist_free(dict); - } - sbs_unlock(client); - return res; - -} - diff --git a/src/SBServices.h b/src/SBServices.h deleted file mode 100644 index d24828a..0000000 --- a/src/SBServices.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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" -#include "property_list_service.h" - -struct sbservices_client_int { - property_list_service_client_t parent; - GMutex *mutex; -}; - -#endif diff --git a/src/afc.c b/src/afc.c new file mode 100644 index 0000000..fc09287 --- /dev/null +++ b/src/afc.c @@ -0,0 +1,1285 @@ +/* + * afc.c + * Contains functions for the built-in AFC client. + * + * Copyright (c) 2008 Zach C. 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 "afc.h" +#include "iphone.h" +#include "debug.h" + +// This is the maximum size an AFC data packet can be +static const int MAXIMUM_PACKET_SIZE = (2 << 15); + +/** Locks an AFC client, done for thread safety stuff + * + * @param client The AFC client connection to lock + */ +static void afc_lock(afc_client_t client) +{ + debug_info("Locked"); + g_mutex_lock(client->mutex); +} + +/** Unlocks an AFC client, done for thread safety stuff. + * + * @param client The AFC + */ +static void afc_unlock(afc_client_t client) +{ + debug_info("Unlocked"); + g_mutex_unlock(client->mutex); +} + +/** Makes a connection to the AFC service on the phone. + * + * @param device The device to connect to. + * @param port The destination port. + * @param client Pointer that will be set to a newly allocated afc_client_t + * upon successful return. + * + * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARGUMENT when device or port + * is invalid, AFC_E_MUX_ERROR when the connection failed, or AFC_E_NO_MEM + * when there's a memory allocation problem. + */ +afc_error_t afc_client_new(iphone_device_t device, uint16_t port, afc_client_t * client) +{ + /* makes sure thread environment is available */ + if (!g_thread_supported()) + g_thread_init(NULL); + + if (!device || port==0) + return AFC_E_INVALID_ARGUMENT; + + /* attempt connection */ + iphone_connection_t connection = NULL; + if (iphone_device_connect(device, port, &connection) != IPHONE_E_SUCCESS) { + return AFC_E_MUX_ERROR; + } + + afc_client_t client_loc = (afc_client_t) malloc(sizeof(struct afc_client_int)); + client_loc->connection = connection; + + /* allocate a packet */ + client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket)); + if (!client_loc->afc_packet) { + iphone_device_disconnect(client_loc->connection); + free(client_loc); + return AFC_E_NO_MEM; + } + + client_loc->afc_packet->packet_num = 0; + client_loc->afc_packet->entire_length = 0; + client_loc->afc_packet->this_length = 0; + memcpy(client_loc->afc_packet->magic, AFC_MAGIC, AFC_MAGIC_LEN); + client_loc->file_handle = 0; + client_loc->lock = 0; + client_loc->mutex = g_mutex_new(); + + *client = client_loc; + return AFC_E_SUCCESS; +} + +/** Disconnects an AFC client from the phone. + * + * @param client The client to disconnect. + */ +afc_error_t afc_client_free(afc_client_t client) +{ + if (!client || !client->connection || !client->afc_packet) + return AFC_E_INVALID_ARGUMENT; + + iphone_device_disconnect(client->connection); + free(client->afc_packet); + if (client->mutex) { + g_mutex_free(client->mutex); + } + free(client); + return AFC_E_SUCCESS; +} + +/** Dispatches an AFC packet over a client. + * + * @param client The client to send data through. + * @param data The data to send. + * @param length The length to send. + * @param bytes_sent The number of bytes actually sent. + * + * @return AFC_E_SUCCESS on success, or an AFC_E_* error value on error. + * + * @warning set client->afc_packet->this_length and + * client->afc_packet->entire_length to 0 before calling this. The + * reason is that if you set them to different values, it indicates + * you want to send the data as two packets. + */ +static afc_error_t afc_dispatch_packet(afc_client_t client, const char *data, uint32_t length, uint32_t *bytes_sent) +{ + uint32_t offset = 0; + uint32_t sent = 0; + + if (!client || !client->connection || !client->afc_packet) + return AFC_E_INVALID_ARGUMENT; + + *bytes_sent = 0; + + if (!data || !length) + length = 0; + + client->afc_packet->packet_num++; + if (!client->afc_packet->entire_length) { + client->afc_packet->entire_length = (length) ? sizeof(AFCPacket) + length : sizeof(AFCPacket); + client->afc_packet->this_length = client->afc_packet->entire_length; + } + if (!client->afc_packet->this_length) { + client->afc_packet->this_length = sizeof(AFCPacket); + } + // We want to send two segments; buffer+sizeof(AFCPacket) to + // this_length is the parameters + // And everything beyond that is the next packet. (for writing) + if (client->afc_packet->this_length != client->afc_packet->entire_length) { + offset = client->afc_packet->this_length - sizeof(AFCPacket); + + debug_info("Offset: %i", offset); + if ((length) < (client->afc_packet->entire_length - client->afc_packet->this_length)) { + debug_info("Length did not resemble what it was supposed to based on packet"); + debug_info("length minus offset: %i", length - offset); + debug_info("rest of packet: %i\n", client->afc_packet->entire_length - client->afc_packet->this_length); + return AFC_E_INTERNAL_ERROR; + } + + /* send AFC packet header */ + AFCPacket_to_LE(client->afc_packet); + sent = 0; + iphone_device_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); + if (sent == 0) { + /* FIXME: should this be handled as success?! */ + return AFC_E_SUCCESS; + } + *bytes_sent += sent; + + /* send AFC packet data */ + sent = 0; + iphone_device_send(client->connection, data, offset, &sent); + if (sent == 0) { + return AFC_E_SUCCESS; + } + *bytes_sent += sent; + + debug_info("sent the first now go with the second"); + debug_info("Length: %i", length - offset); + debug_info("Buffer: "); + debug_buffer(data + offset, length - offset); + + sent = 0; + iphone_device_send(client->connection, data + offset, length - offset, &sent); + + *bytes_sent = sent; + return AFC_E_SUCCESS; + } else { + debug_info("doin things the old way"); + debug_info("packet length = %i", client->afc_packet->this_length); + + debug_buffer((char*)client->afc_packet, sizeof(AFCPacket)); + + /* send AFC packet header */ + AFCPacket_to_LE(client->afc_packet); + sent = 0; + iphone_device_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); + if (sent == 0) { + return AFC_E_SUCCESS; + } + *bytes_sent += sent; + /* send AFC packet data (if there's data to send) */ + if (length > 0) { + debug_info("packet data follows"); + + debug_buffer(data, length); + iphone_device_send(client->connection, data, length, &sent); + *bytes_sent += sent; + } + return AFC_E_SUCCESS; + } + return AFC_E_INTERNAL_ERROR; +} + +/** Receives data through an AFC client and sets a variable to the received data. + * + * @param client The client to receive data on. + * @param dump_here The char* to point to the newly-received data. + * @param bytes_recv How much data was received. + * + * @return AFC_E_SUCCESS when data has been received, or an AFC_E_* error value + * when an error occured. + */ +static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint32_t *bytes_recv) +{ + AFCPacket header; + uint32_t entire_len = 0; + uint32_t this_len = 0; + uint32_t current_count = 0; + uint64_t param1 = -1; + + *bytes_recv = 0; + + /* first, read the AFC header */ + iphone_device_recv(client->connection, (char*)&header, sizeof(AFCPacket), bytes_recv); + AFCPacket_from_LE(&header); + if (*bytes_recv == 0) { + debug_info("Just didn't get enough."); + *dump_here = NULL; + return AFC_E_MUX_ERROR; + } else if (*bytes_recv < sizeof(AFCPacket)) { + debug_info("Did not even get the AFCPacket header"); + *dump_here = NULL; + return AFC_E_MUX_ERROR; + } + + /* check if it's a valid AFC header */ + if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN)) { + debug_info("Invalid AFC packet received (magic != " AFC_MAGIC ")!"); + } + + /* check if it has the correct packet number */ + if (header.packet_num != client->afc_packet->packet_num) { + /* otherwise print a warning but do not abort */ + debug_info("ERROR: Unexpected packet number (%lld != %lld) aborting.", header.packet_num, client->afc_packet->packet_num); + *dump_here = NULL; + return AFC_E_OP_HEADER_INVALID; + } + + /* then, read the attached packet */ + if (header.this_length < sizeof(AFCPacket)) { + debug_info("Invalid AFCPacket header received!"); + *dump_here = NULL; + return AFC_E_OP_HEADER_INVALID; + } else if ((header.this_length == header.entire_length) + && header.entire_length == sizeof(AFCPacket)) { + debug_info("Empty AFCPacket received!"); + *dump_here = NULL; + *bytes_recv = 0; + if (header.operation == AFC_OP_DATA) { + return AFC_E_SUCCESS; + } else { + return AFC_E_IO_ERROR; + } + } + + debug_info("received AFC packet, full len=%lld, this len=%lld, operation=0x%llx", header.entire_length, header.this_length, header.operation); + + entire_len = (uint32_t)header.entire_length - sizeof(AFCPacket); + this_len = (uint32_t)header.this_length - sizeof(AFCPacket); + + /* this is here as a check (perhaps a different upper limit is good?) */ + if (entire_len > (uint32_t)MAXIMUM_PACKET_SIZE) { + fprintf(stderr, "%s: entire_len is larger than MAXIMUM_PACKET_SIZE, (%d > %d)!", __func__, entire_len, MAXIMUM_PACKET_SIZE); + } + + *dump_here = (char*)malloc(entire_len); + if (this_len > 0) { + iphone_device_recv(client->connection, *dump_here, this_len, bytes_recv); + if (*bytes_recv <= 0) { + free(*dump_here); + *dump_here = NULL; + debug_info("Did not get packet contents!"); + return AFC_E_NOT_ENOUGH_DATA; + } else if (*bytes_recv < this_len) { + free(*dump_here); + *dump_here = NULL; + debug_info("Could not receive this_len=%d bytes", this_len); + return AFC_E_NOT_ENOUGH_DATA; + } + } + + current_count = this_len; + + if (entire_len > this_len) { + while (current_count < entire_len) { + iphone_device_recv(client->connection, (*dump_here)+current_count, entire_len - current_count, bytes_recv); + if (*bytes_recv <= 0) { + debug_info("Error receiving data (recv returned %d)", *bytes_recv); + break; + } + current_count += *bytes_recv; + } + if (current_count < entire_len) { + debug_info("WARNING: could not receive full packet (read %s, size %d)", current_count, entire_len); + } + } + + if (current_count >= sizeof(uint64_t)) { + param1 = *(uint64_t*)(*dump_here); + } + + debug_info("packet data size = %i", current_count); + debug_info("packet data follows"); + debug_buffer(*dump_here, current_count); + + /* check operation types */ + if (header.operation == AFC_OP_STATUS) { + /* status response */ + debug_info("got a status response, code=%lld", param1); + + if (param1 != AFC_E_SUCCESS) { + /* error status */ + /* free buffer */ + free(*dump_here); + *dump_here = NULL; + return (afc_error_t)param1; + } + } else if (header.operation == AFC_OP_DATA) { + /* data response */ + debug_info("got a data response"); + } else if (header.operation == AFC_OP_FILE_OPEN_RES) { + /* file handle response */ + debug_info("got a file handle response, handle=%lld", param1); + } else if (header.operation == AFC_OP_FILE_TELL_RES) { + /* tell response */ + debug_info("got a tell response, position=%lld", param1); + } else { + /* unknown operation code received */ + free(*dump_here); + *dump_here = NULL; + *bytes_recv = 0; + + debug_info("WARNING: Unknown operation code received 0x%llx param1=%lld", header.operation, param1); + fprintf(stderr, "%s: WARNING: Unknown operation code received 0x%llx param1=%lld", __func__, (long long)header.operation, (long long)param1); + + return AFC_E_OP_NOT_SUPPORTED; + } + + *bytes_recv = current_count; + return AFC_E_SUCCESS; +} + +static uint32_t count_nullspaces(char *string, uint32_t number) +{ + uint32_t i = 0, nulls = 0; + + for (i = 0; i < number; i++) { + if (string[i] == '\0') + nulls++; + } + + return nulls; +} + +static char **make_strings_list(char *tokens, uint32_t true_length) +{ + uint32_t nulls = 0, i = 0, j = 0; + char **list = NULL; + + if (!tokens || !true_length) + return NULL; + + nulls = count_nullspaces(tokens, true_length); + list = (char **) malloc(sizeof(char *) * (nulls + 1)); + for (i = 0; i < nulls; i++) { + list[i] = strdup(tokens + j); + j += strlen(list[i]) + 1; + } + list[i] = NULL; + + return list; +} + +/** Gets a directory listing of the directory requested. + * + * @param client The client to get a directory listing from. + * @param dir The directory to list. (must be a fully-qualified path) + * + * @return A char ** list of files in that directory, terminated by an empty + * string for now or NULL if there was an error. + */ +afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list) +{ + uint32_t bytes = 0; + char *data = NULL, **list_loc = NULL; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !dir || !list || (list && *list)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send the command + client->afc_packet->operation = AFC_OP_READ_DIR; + client->afc_packet->entire_length = 0; + client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive the data + ret = afc_receive_data(client, &data, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } + // Parse the data + list_loc = make_strings_list(data, bytes); + if (data) + free(data); + + afc_unlock(client); + *list = list_loc; + + return ret; +} + +/** Get device info for a client connection to phone. (free space on disk, etc.) + * + * @param client The client to get device info for. + * + * @return A char ** list of parameters as given by AFC or NULL if there was an + * error. + */ +afc_error_t afc_get_device_info(afc_client_t client, char ***infos) +{ + uint32_t bytes = 0; + char *data = NULL, **list = NULL; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !infos) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send the command + client->afc_packet->operation = AFC_OP_GET_DEVINFO; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, NULL, 0, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive the data + ret = afc_receive_data(client, &data, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } + // Parse the data + list = make_strings_list(data, bytes); + if (data) + free(data); + + afc_unlock(client); + + *infos = list; + + return ret; +} + +/** Get a specific key of the device info list for a client connection. + * Known key values are: Model, FSTotalBytes, FSFreeBytes and FSBlockSize. + * This is a helper function for afc_get_device_info(). + * + * @param client The client to get device info for. + * @param key The key to get the value of. + * @param value The value for the key if successful or NULL otherwise. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value. + */ +afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value) +{ + afc_error_t ret = AFC_E_INTERNAL_ERROR; + char **kvps, **ptr; + + *value = NULL; + if (key == NULL) + return AFC_E_INVALID_ARGUMENT; + + ret = afc_get_device_info(client, &kvps); + if (ret != AFC_E_SUCCESS) + return ret; + + for (ptr = kvps; *ptr; ptr++) { + if (!strcmp(*ptr, key)) { + *value = strdup(*(ptr+1)); + break; + } + } + + g_strfreev(kvps); + + return ret; +} + +/** Deletes a file or directory. + * + * @param client The client to use. + * @param path The path to delete. (must be a fully-qualified path) + * + * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_remove_path(afc_client_t client, const char *path) +{ + char *response = NULL; + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !client->afc_packet || !client->connection) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + client->afc_packet->this_length = client->afc_packet->entire_length = 0; + client->afc_packet->operation = AFC_OP_REMOVE_PATH; + ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + /* special case; unknown error actually means directory not empty */ + if (ret == AFC_E_UNKNOWN_ERROR) + ret = AFC_E_DIR_NOT_EMPTY; + + afc_unlock(client); + + return ret; +} + +/** Renames a file or directory on the phone. + * + * @param client The client to have rename. + * @param from The name to rename from. (must be a fully-qualified path) + * @param to The new name. (must also be a fully-qualified path) + * + * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to) +{ + char *response = NULL; + char *send = (char *) malloc(sizeof(char) * (strlen(from) + strlen(to) + 1 + sizeof(uint32_t))); + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !from || !to || !client->afc_packet || !client->connection) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + memcpy(send, from, strlen(from) + 1); + memcpy(send + strlen(from) + 1, to, strlen(to) + 1); + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + client->afc_packet->operation = AFC_OP_RENAME_PATH; + ret = afc_dispatch_packet(client, send, strlen(to)+1 + strlen(from)+1, &bytes); + free(send); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + afc_unlock(client); + + return ret; +} + +/** Creates a directory on the phone. + * + * @param client The client to use to make a directory. + * @param dir The directory's path. (must be a fully-qualified path, I assume + * all other mkdir restrictions apply as well) + * + * @return AFC_E_SUCCESS if everythong went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_make_directory(afc_client_t client, const char *dir) +{ + uint32_t bytes = 0; + char *response = NULL; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + client->afc_packet->operation = AFC_OP_MAKE_DIR; + client->afc_packet->this_length = client->afc_packet->entire_length = 0; + ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + afc_unlock(client); + + return ret; +} + +/** Gets information about a specific file. + * + * @param client The client to use to get the information of the file. + * @param path The fully-qualified path to the file. + * @param infolist Pointer to a buffer that will be filled with a NULL-terminated + * list of strings with the file information. + * Set to NULL before calling this function. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value + * when something went wrong. + */ +afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***infolist) +{ + char *received = NULL; + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !infolist) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + client->afc_packet->operation = AFC_OP_GET_FILE_INFO; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + + // Receive data + ret = afc_receive_data(client, &received, &bytes); + if (received) { + *infolist = make_strings_list(received, bytes); + free(received); + } + + afc_unlock(client); + + return ret; +} + +/** Opens a file on the phone. + * + * @param client The client to use to open the file. + * @param filename The file to open. (must be a fully-qualified path) + * @param file_mode The mode to use to open the file. Can be AFC_FILE_READ or + * AFC_FILE_WRITE; the former lets you read and write, + * however, and the second one will *create* the file, + * destroying anything previously there. + * @param handle Pointer to a uint64_t that will hold the handle of the file + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error on failure. + */ +iphone_error_t +afc_file_open(afc_client_t client, const char *filename, + afc_file_mode_t file_mode, uint64_t *handle) +{ + uint64_t file_mode_loc = GUINT64_TO_LE(file_mode); + uint32_t bytes = 0; + char *data = (char *) malloc(sizeof(char) * (8 + strlen(filename) + 1)); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + // set handle to 0 so in case an error occurs, the handle is invalid + *handle = 0; + + if (!client || !client->connection || !client->afc_packet) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + memcpy(data, &file_mode_loc, 8); + memcpy(data + 8, filename, strlen(filename)); + data[8 + strlen(filename)] = '\0'; + client->afc_packet->operation = AFC_OP_FILE_OPEN; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, data, 8 + strlen(filename) + 1, &bytes); + free(data); + + if (ret != AFC_E_SUCCESS) { + debug_info("Didn't receive a response to the command"); + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive the data + ret = afc_receive_data(client, &data, &bytes); + if ((ret == AFC_E_SUCCESS) && (bytes > 0) && data) { + afc_unlock(client); + + // Get the file handle + memcpy(handle, data, sizeof(uint64_t)); + free(data); + return ret; + } + + debug_info("Didn't get any further data"); + + afc_unlock(client); + + return ret; +} + +/** Attempts to the read the given number of bytes from the given file. + * + * @param client The relevant AFC client + * @param handle File handle of a previously opened file + * @param data The pointer to the memory region to store the read data + * @param length The number of bytes to read + * @param bytes_read The number of bytes actually read. + * + * @return AFC_E_SUCCESS on success or an AFC_E_* error value on error. + */ +iphone_error_t +afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) +{ + char *input = NULL; + uint32_t current_count = 0, bytes_loc = 0; + const uint32_t MAXIMUM_READ_SIZE = 1 << 16; + afc_error_t ret = AFC_E_SUCCESS; + + if (!client || !client->afc_packet || !client->connection || handle == 0) + return AFC_E_INVALID_ARGUMENT; + debug_info("called for length %i", length); + + afc_lock(client); + + // Looping here to get around the maximum amount of data that + // afc_receive_data can handle + while (current_count < length) { + debug_info("current count is %i but length is %i", current_count, length); + + // Send the read command + AFCFilePacket *packet = (AFCFilePacket *) malloc(sizeof(AFCFilePacket)); + packet->filehandle = handle; + packet->size = GUINT64_TO_LE(((length - current_count) < MAXIMUM_READ_SIZE) ? (length - current_count) : MAXIMUM_READ_SIZE); + client->afc_packet->operation = AFC_OP_READ; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, (char *) packet, sizeof(AFCFilePacket), &bytes_loc); + free(packet); + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive the data + ret = afc_receive_data(client, &input, &bytes_loc); + debug_info("afc_receive_data returned error: %d", ret); + debug_info("bytes returned: %i", bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } else if (bytes_loc == 0) { + if (input) + free(input); + afc_unlock(client); + *bytes_read = current_count; + /* FIXME: check that's actually a success */ + return ret; + } else { + if (input) { + debug_info("%d", bytes_loc); + memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); + free(input); + input = NULL; + current_count += (bytes_loc > length) ? length : bytes_loc; + } + } + } + debug_info("returning current_count as %i", current_count); + + afc_unlock(client); + *bytes_read = current_count; + return ret; +} + +/** Writes a given number of bytes to a file. + * + * @param client The client to use to write to the file. + * @param handle File handle of previously opened file. + * @param data The data to write to the file. + * @param length How much data to write. + * @param bytes_written The number of bytes actually written to the file. + * + * @return AFC_E_SUCCESS on success, or an AFC_E_* error value on error. + */ +iphone_error_t +afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) +{ + char *acknowledgement = NULL; + const uint32_t MAXIMUM_WRITE_SIZE = 1 << 15; + uint32_t current_count = 0, i = 0; + uint32_t segments = (length / MAXIMUM_WRITE_SIZE); + uint32_t bytes_loc = 0; + char *out_buffer = NULL; + afc_error_t ret = AFC_E_SUCCESS; + + if (!client || !client->afc_packet || !client->connection || !bytes_written || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + debug_info("Write length: %i", length); + + // Divide the file into segments. + for (i = 0; i < segments; i++) { + // Send the segment + client->afc_packet->this_length = sizeof(AFCPacket) + 8; + client->afc_packet->entire_length = client->afc_packet->this_length + MAXIMUM_WRITE_SIZE; + client->afc_packet->operation = AFC_OP_WRITE; + out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); + memcpy(out_buffer, (char *)&handle, sizeof(uint64_t)); + memcpy(out_buffer + 8, data + current_count, MAXIMUM_WRITE_SIZE); + ret = afc_dispatch_packet(client, out_buffer, MAXIMUM_WRITE_SIZE + 8, &bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + free(out_buffer); + out_buffer = NULL; + + current_count += bytes_loc; + ret = afc_receive_data(client, &acknowledgement, &bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } else { + free(acknowledgement); + } + } + + // By this point, we should be at the end. i.e. the last segment that + // didn't get sent in the for loop + // this length is fine because it's always sizeof(AFCPacket) + 8, but + // to be sure we do it again + if (current_count == length) { + afc_unlock(client); + *bytes_written = current_count; + return ret; + } + + client->afc_packet->this_length = sizeof(AFCPacket) + 8; + client->afc_packet->entire_length = client->afc_packet->this_length + (length - current_count); + client->afc_packet->operation = AFC_OP_WRITE; + out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); + memcpy(out_buffer, (char *) &handle, sizeof(uint64_t)); + memcpy(out_buffer + 8, data + current_count, (length - current_count)); + ret = afc_dispatch_packet(client, out_buffer, (length - current_count) + 8, &bytes_loc); + free(out_buffer); + out_buffer = NULL; + + current_count += bytes_loc; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + *bytes_written = current_count; + return AFC_E_SUCCESS; + } + + ret = afc_receive_data(client, &acknowledgement, &bytes_loc); + afc_unlock(client); + if (ret != AFC_E_SUCCESS) { + debug_info("uh oh?"); + } else { + free(acknowledgement); + } + *bytes_written = current_count; + return ret; +} + +/** Closes a file on the phone. + * + * @param client The client to close the file with. + * @param handle File handle of a previously opened file. + */ +afc_error_t afc_file_close(afc_client_t client, uint64_t handle) +{ + char *buffer = malloc(sizeof(char) * 8); + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + debug_info("File handle %i", handle); + + // Send command + memcpy(buffer, &handle, sizeof(uint64_t)); + client->afc_packet->operation = AFC_OP_FILE_CLOSE; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, buffer, 8, &bytes); + free(buffer); + buffer = NULL; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_UNKNOWN_ERROR; + } + + // Receive the response + ret = afc_receive_data(client, &buffer, &bytes); + if (buffer) + free(buffer); + + afc_unlock(client); + + return ret; +} + +/** Locks or unlocks a file on the phone. + * + * makes use of flock on the device, see + * http://developer.apple.com/documentation/Darwin/Reference/ManPages/man2/flock.2.html + * + * @param client The client to lock the file with. + * @param handle File handle of a previously opened file. + * @param operation the lock or unlock operation to perform, this is one of + * AFC_LOCK_SH (shared lock), AFC_LOCK_EX (exclusive lock), + * or AFC_LOCK_UN (unlock). + */ +afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation) +{ + char *buffer = malloc(16); + uint32_t bytes = 0; + uint64_t op = GUINT64_TO_LE(operation); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + debug_info("file handle %i", handle); + + // Send command + memcpy(buffer, &handle, sizeof(uint64_t)); + memcpy(buffer + 8, &op, 8); + + client->afc_packet->operation = AFC_OP_FILE_LOCK; + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + ret = afc_dispatch_packet(client, buffer, 16, &bytes); + free(buffer); + buffer = NULL; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + debug_info("could not send lock command"); + return AFC_E_UNKNOWN_ERROR; + } + // Receive the response + ret = afc_receive_data(client, &buffer, &bytes); + if (buffer) { + debug_buffer(buffer, bytes); + free(buffer); + } + afc_unlock(client); + + return ret; +} + +/** Seeks to a given position of a pre-opened file on the phone. + * + * @param client The client to use to seek to the position. + * @param handle File handle of a previously opened. + * @param offset Seek offset. + * @param whence Seeking direction, one of SEEK_SET, SEEK_CUR, or SEEK_END. + * + * @return AFC_E_SUCCESS on success, AFC_E_NOT_ENOUGH_DATA on failure. + */ +afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence) +{ + char *buffer = (char *) malloc(sizeof(char) * 24); + int64_t offset_loc = (int64_t)GUINT64_TO_LE(offset); + uint64_t whence_loc = GUINT64_TO_LE(whence); + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send the command + memcpy(buffer, &handle, sizeof(uint64_t)); // handle + memcpy(buffer + 8, &whence_loc, sizeof(uint64_t)); // fromwhere + memcpy(buffer + 16, &offset_loc, sizeof(uint64_t)); // offset + client->afc_packet->operation = AFC_OP_FILE_SEEK; + client->afc_packet->this_length = client->afc_packet->entire_length = 0; + ret = afc_dispatch_packet(client, buffer, 24, &bytes); + free(buffer); + buffer = NULL; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &buffer, &bytes); + if (buffer) + free(buffer); + + afc_unlock(client); + + return ret; +} + +/** Returns current position in a pre-opened file on the phone. + * + * @param client The client to use. + * @param handle File handle of a previously opened file. + * @param position Position in bytes of indicator + * + * @return AFC_E_SUCCESS on success, AFC_E_NOT_ENOUGH_DATA on failure. + */ +afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position) +{ + char *buffer = (char *) malloc(sizeof(char) * 8); + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send the command + memcpy(buffer, &handle, sizeof(uint64_t)); // handle + client->afc_packet->operation = AFC_OP_FILE_TELL; + client->afc_packet->this_length = client->afc_packet->entire_length = 0; + ret = afc_dispatch_packet(client, buffer, 8, &bytes); + free(buffer); + buffer = NULL; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + + // Receive the data + ret = afc_receive_data(client, &buffer, &bytes); + if (bytes > 0 && buffer) { + /* Get the position */ + memcpy(position, buffer, sizeof(uint64_t)); + *position = GUINT64_FROM_LE(*position); + } + if (buffer) + free(buffer); + + afc_unlock(client); + + return ret; +} + +/** Sets the size of a file on the phone. + * + * @param client The client to use to set the file size. + * @param handle File handle of a previously opened file. + * @param newsize The size to set the file to. + * + * @return 0 on success, -1 on failure. + * + * @note This function is more akin to ftruncate than truncate, and truncate + * calls would have to open the file before calling this, sadly. + */ +afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize) +{ + char *buffer = (char *) malloc(sizeof(char) * 16); + uint32_t bytes = 0; + uint64_t newsize_loc = GUINT64_TO_LE(newsize); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || (handle == 0)) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + memcpy(buffer, &handle, sizeof(uint64_t)); // handle + memcpy(buffer + 8, &newsize_loc, sizeof(uint64_t)); // newsize + client->afc_packet->operation = AFC_OP_FILE_SET_SIZE; + client->afc_packet->this_length = client->afc_packet->entire_length = 0; + ret = afc_dispatch_packet(client, buffer, 16, &bytes); + free(buffer); + buffer = NULL; + + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &buffer, &bytes); + if (buffer) + free(buffer); + + afc_unlock(client); + + return ret; +} + +/** Sets the size of a file on the phone without prior opening it. + * + * @param client The client to use to set the file size. + * @param path The path of the file to be truncated. + * @param newsize The size to set the file to. + * + * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize) +{ + char *response = NULL; + char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + uint32_t bytes = 0; + uint64_t size_requested = GUINT64_TO_LE(newsize); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !client->afc_packet || !client->connection) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + memcpy(send, &size_requested, 8); + memcpy(send + 8, path, strlen(path) + 1); + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + client->afc_packet->operation = AFC_OP_TRUNCATE; + ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); + free(send); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + afc_unlock(client); + + return ret; +} + +/** Creates a hard link or symbolic link on the device. + * + * @param client The client to use for making a link + * @param type 1 = hard link, 2 = symlink + * @param target The file to be linked. + * @param linkname The name of link. + * + * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname) +{ + char *response = NULL; + char *send = (char *) malloc(sizeof(char) * (strlen(target)+1 + strlen(linkname)+1 + 8)); + uint32_t bytes = 0; + uint64_t type = GUINT64_TO_LE(linktype); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !target || !linkname || !client->afc_packet || !client->connection) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + debug_info("link type: %lld", type); + debug_info("target: %s, length:%d", target, strlen(target)); + debug_info("linkname: %s, length:%d", linkname, strlen(linkname)); + + // Send command + memcpy(send, &type, 8); + memcpy(send + 8, target, strlen(target) + 1); + memcpy(send + 8 + strlen(target) + 1, linkname, strlen(linkname) + 1); + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + client->afc_packet->operation = AFC_OP_MAKE_LINK; + ret = afc_dispatch_packet(client, send, 8 + strlen(linkname) + 1 + strlen(target) + 1, &bytes); + free(send); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + afc_unlock(client); + + return ret; +} + +/** Sets the modification time of a file on the phone. + * + * @param client The client to use to set the file size. + * @param path Path of the file for which the modification time should be set. + * @param mtime The modification time to set in nanoseconds since epoch. + * + * @return AFC_E_SUCCESS if everything went well, AFC_E_INVALID_ARGUMENT + * if arguments are NULL or invalid, AFC_E_NOT_ENOUGH_DATA otherwise. + */ +afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime) +{ + char *response = NULL; + char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + uint32_t bytes = 0; + uint64_t mtime_loc = GUINT64_TO_LE(mtime); + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !client->afc_packet || !client->connection) + return AFC_E_INVALID_ARGUMENT; + + afc_lock(client); + + // Send command + memcpy(send, &mtime_loc, 8); + memcpy(send + 8, path, strlen(path) + 1); + client->afc_packet->entire_length = client->afc_packet->this_length = 0; + client->afc_packet->operation = AFC_OP_SET_FILE_TIME; + ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); + free(send); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + // Receive response + ret = afc_receive_data(client, &response, &bytes); + if (response) + free(response); + + afc_unlock(client); + + return ret; +} + diff --git a/src/afc.h b/src/afc.h new file mode 100644 index 0000000..f8a5da3 --- /dev/null +++ b/src/afc.h @@ -0,0 +1,100 @@ +/* + * afc.h + * Defines and structs and the like for the built-in AFC client + * + * Copyright (c) 2008 Zach C. 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 + +#include "libiphone/afc.h" + +#define AFC_MAGIC "CFA6LPAA" +#define AFC_MAGIC_LEN (8) + +typedef struct { + char magic[AFC_MAGIC_LEN]; + uint64_t entire_length, this_length, packet_num, operation; +} AFCPacket; + +#define AFCPacket_to_LE(x) \ + (x)->entire_length = GUINT64_TO_LE((x)->entire_length); \ + (x)->this_length = GUINT64_TO_LE((x)->this_length); \ + (x)->packet_num = GUINT64_TO_LE((x)->packet_num); \ + (x)->operation = GUINT64_TO_LE((x)->operation); + +#define AFCPacket_from_LE(x) \ + (x)->entire_length = GUINT64_FROM_LE((x)->entire_length); \ + (x)->this_length = GUINT64_FROM_LE((x)->this_length); \ + (x)->packet_num = GUINT64_FROM_LE((x)->packet_num); \ + (x)->operation = GUINT64_FROM_LE((x)->operation); + +typedef struct { + uint64_t filehandle, size; +} AFCFilePacket; + +typedef struct __AFCToken { + struct __AFCToken *last, *next; + char *token; +} AFCToken; + +struct afc_client_int { + iphone_connection_t connection; + AFCPacket *afc_packet; + int file_handle; + int lock; + GMutex *mutex; +}; + +/* AFC Operations */ +enum { + AFC_OP_STATUS = 0x00000001, // Status + AFC_OP_DATA = 0x00000002, // Data + AFC_OP_READ_DIR = 0x00000003, // ReadDir + AFC_OP_READ_FILE = 0x00000004, // ReadFile + AFC_OP_WRITE_FILE = 0x00000005, // WriteFile + AFC_OP_WRITE_PART = 0x00000006, // WritePart + AFC_OP_TRUNCATE = 0x00000007, // TruncateFile + AFC_OP_REMOVE_PATH = 0x00000008, // RemovePath + AFC_OP_MAKE_DIR = 0x00000009, // MakeDir + AFC_OP_GET_FILE_INFO = 0x0000000a, // GetFileInfo + AFC_OP_GET_DEVINFO = 0x0000000b, // GetDeviceInfo + AFC_OP_WRITE_FILE_ATOM = 0x0000000c, // WriteFileAtomic (tmp file+rename) + AFC_OP_FILE_OPEN = 0x0000000d, // FileRefOpen + AFC_OP_FILE_OPEN_RES = 0x0000000e, // FileRefOpenResult + AFC_OP_READ = 0x0000000f, // FileRefRead + AFC_OP_WRITE = 0x00000010, // FileRefWrite + AFC_OP_FILE_SEEK = 0x00000011, // FileRefSeek + AFC_OP_FILE_TELL = 0x00000012, // FileRefTell + AFC_OP_FILE_TELL_RES = 0x00000013, // FileRefTellResult + AFC_OP_FILE_CLOSE = 0x00000014, // FileRefClose + AFC_OP_FILE_SET_SIZE = 0x00000015, // FileRefSetFileSize (ftruncate) + AFC_OP_GET_CON_INFO = 0x00000016, // GetConnectionInfo + AFC_OP_SET_CON_OPTIONS = 0x00000017, // SetConnectionOptions + AFC_OP_RENAME_PATH = 0x00000018, // RenamePath + AFC_OP_SET_FS_BS = 0x00000019, // SetFSBlockSize (0x800000) + AFC_OP_SET_SOCKET_BS = 0x0000001A, // SetSocketBlockSize (0x800000) + AFC_OP_FILE_LOCK = 0x0000001B, // FileRefLock + AFC_OP_MAKE_LINK = 0x0000001C, // MakeLink + AFC_OP_SET_FILE_TIME = 0x0000001E // set st_mtime +}; + diff --git a/src/installation_proxy.c b/src/installation_proxy.c new file mode 100644 index 0000000..a99b5cb --- /dev/null +++ b/src/installation_proxy.c @@ -0,0 +1,740 @@ +/* + * installation_proxy.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 "installation_proxy.h" +#include "property_list_service.h" +#include "debug.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) +{ + debug_info("InstallationProxy: Locked"); + 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) +{ + debug_info("InstallationProxy: Unlocked"); + g_mutex_unlock(client->mutex); +} + +/** + * Convert 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; + default: + break; + } + return INSTPROXY_E_UNKNOWN_ERROR; +} + +/** + * Creates a new installation_proxy client + * + * @param device The device to connect to + * @param 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, uint16_t 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; + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return INSTPROXY_E_CONN_FAILED; + } + + instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_int)); + client_loc->parent = plistclient; + 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; + + property_list_service_client_free(client->parent); + client->parent = NULL; + if (client->status_updater) { + debug_info("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->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); + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist"); + goto leave_unlock; + } + + apps_array = plist_new_array(); + + 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; + } + +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_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); + } + /* 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; +} + +/** + * 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); + debug_info("done, cleaning up."); + 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->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_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist, error %d", 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->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); + + 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; +} + +/** + * 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 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. + * + * @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, uint32_t options, instproxy_status_cb_t status_cb) +{ + if (!client || !client->parent || !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)); + 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_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist, error %d", 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->parent || !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_error(property_list_service_send_xml_plist(client->parent, dict)); + instproxy_unlock(client); + + plist_free(dict); + + if (res != INSTPROXY_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + return instproxy_create_status_updater(client, status_cb, "RemoveArchive"); +} + diff --git a/src/installation_proxy.h b/src/installation_proxy.h new file mode 100644 index 0000000..0204533 --- /dev/null +++ b/src/installation_proxy.h @@ -0,0 +1,35 @@ +/* + * installation_proxy.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" +#include "property_list_service.h" + +struct instproxy_client_int { + property_list_service_client_t parent; + GMutex *mutex; + GThread *status_updater; +}; + +#endif diff --git a/src/mobilesync.c b/src/mobilesync.c new file mode 100644 index 0000000..64c06d5 --- /dev/null +++ b/src/mobilesync.c @@ -0,0 +1,148 @@ +/* + * mobilesync.c + * Contains functions for the built-in MobileSync client. + * + * Copyright (c) 2009 Jonathan Beck 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 "mobilesync.h" +#include "device_link_service.h" +#include "debug.h" + +#define MSYNC_VERSION_INT1 100 +#define MSYNC_VERSION_INT2 100 + +/** + * Convert an device_link_service_error_t value to an mobilesync_error_t value. + * Used internally to get correct error codes when using device_link_service stuff. + * + * @param err An device_link_service_error_t error code + * + * @return A matching mobilesync_error_t error code, + * MOBILESYNC_E_UNKNOWN_ERROR otherwise. + */ +static mobilesync_error_t mobilesync_error(device_link_service_error_t err) +{ + switch (err) { + case DEVICE_LINK_SERVICE_E_SUCCESS: + return MOBILESYNC_E_SUCCESS; + case DEVICE_LINK_SERVICE_E_INVALID_ARG: + return MOBILESYNC_E_INVALID_ARG; + case DEVICE_LINK_SERVICE_E_PLIST_ERROR: + return MOBILESYNC_E_PLIST_ERROR; + case DEVICE_LINK_SERVICE_E_MUX_ERROR: + return MOBILESYNC_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_BAD_VERSION: + return MOBILESYNC_E_BAD_VERSION; + default: + break; + } + return MOBILESYNC_E_UNKNOWN_ERROR; +} + +mobilesync_error_t mobilesync_client_new(iphone_device_t device, uint16_t port, + mobilesync_client_t * client) +{ + if (!device || port == 0 || !client || *client) + return MOBILESYNC_E_INVALID_ARG; + + device_link_service_client_t dlclient = NULL; + mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, port, &dlclient)); + if (ret != MOBILESYNC_E_SUCCESS) { + return ret; + } + + mobilesync_client_t client_loc = (mobilesync_client_t) malloc(sizeof(struct mobilesync_client_int)); + client_loc->parent = dlclient; + + /* perform handshake */ + ret = mobilesync_error(device_link_service_version_exchange(dlclient, MSYNC_VERSION_INT1, MSYNC_VERSION_INT2)); + if (ret != MOBILESYNC_E_SUCCESS) { + debug_info("version exchange failed, error %d", ret); + mobilesync_client_free(client_loc); + return ret; + } + + *client = client_loc; + + return ret; +} + +mobilesync_error_t mobilesync_client_free(mobilesync_client_t client) +{ + if (!client) + return MOBILESYNC_E_INVALID_ARG; + device_link_service_disconnect(client->parent); + mobilesync_error_t err = mobilesync_error(device_link_service_client_free(client->parent)); + free(client); + return err; +} + +/** Polls the iPhone for MobileSync data. + * + * @param client The MobileSync client + * @param plist A pointer to the location where the plist should be stored + * + * @return an error code + */ +mobilesync_error_t mobilesync_recv(mobilesync_client_t client, plist_t * plist) +{ + if (!client) + return MOBILESYNC_E_INVALID_ARG; + mobilesync_error_t ret = mobilesync_error(device_link_service_receive(client->parent, plist)); +#ifndef STRIP_DEBUG_CODE + if (ret != MOBILESYNC_E_SUCCESS) { + return ret; + } + char *XMLContent = NULL; + uint32_t length = 0; + plist_to_xml(*plist, &XMLContent, &length); + debug_info("plist size: %i\nbuffer :\n%s", length, XMLContent); + free(XMLContent); +#endif + return ret; +} + +/** Sends MobileSync data to the iPhone + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The MobileSync client + * @param plist The location of the plist to send + * + * @return an error code + */ +mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) +{ + if (!client || !plist) + return MOBILESYNC_E_INVALID_ARG; + +#ifndef STRIP_DEBUG_CODE + char *XMLContent = NULL; + uint32_t length = 0; + plist_to_xml(plist, &XMLContent, &length); + debug_info("plist size: %i\nbuffer :\n%s", length, XMLContent); + free(XMLContent); +#endif + return mobilesync_error(device_link_service_send(client->parent, plist)); +} diff --git a/src/mobilesync.h b/src/mobilesync.h new file mode 100644 index 0000000..e69cb25 --- /dev/null +++ b/src/mobilesync.h @@ -0,0 +1,31 @@ +/* + * mobilesync.h + * Definitions for the built-in MobileSync client + * + * Copyright (c) 2009 Jonathan Beck 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 MOBILESYNC_H +#define MOBILESYNC_H + +#include "libiphone/mobilesync.h" +#include "device_link_service.h" + +struct mobilesync_client_int { + device_link_service_client_t parent; +}; + +#endif diff --git a/src/notification_proxy.c b/src/notification_proxy.c new file mode 100644 index 0000000..0969985 --- /dev/null +++ b/src/notification_proxy.c @@ -0,0 +1,392 @@ +/* + * notification_proxy.c + * Notification 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 + +#include "notification_proxy.h" +#include "property_list_service.h" +#include "debug.h" + +struct np_thread { + np_client_t client; + np_notify_cb_t cbfunc; +}; + +/** Locks an NP client, done for thread safety stuff. + * + * @param client The NP + */ +static void np_lock(np_client_t client) +{ + debug_info("NP: Locked"); + g_mutex_lock(client->mutex); +} + +/** Unlocks an NP client, done for thread safety stuff. + * + * @param client The NP + */ +static void np_unlock(np_client_t client) +{ + debug_info("NP: Unlocked"); + g_mutex_unlock(client->mutex); +} + +/** + * Convert a property_list_service_error_t value to an np_error_t value. + * Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching np_error_t error code, + * NP_E_UNKNOWN_ERROR otherwise. + */ +static np_error_t np_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return NP_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return NP_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return NP_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return NP_E_CONN_FAILED; + default: + break; + } + return NP_E_UNKNOWN_ERROR; +} + +/** Makes a connection to the NP service on the phone. + * + * @param device The device to connect to. + * @param 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 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, uint16_t port, np_client_t *client) +{ + /* makes sure thread environment is available */ + if (!g_thread_supported()) + g_thread_init(NULL); + + if (!device) + return NP_E_INVALID_ARG; + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return NP_E_CONN_FAILED; + } + + np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_int)); + client_loc->parent = plistclient; + + client_loc->mutex = g_mutex_new(); + + client_loc->notifier = NULL; + + *client = client_loc; + return NP_E_SUCCESS; +} + +/** 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) +{ + if (!client) + return NP_E_INVALID_ARG; + + property_list_service_client_free(client->parent); + client->parent = NULL; + if (client->notifier) { + debug_info("joining np callback"); + g_thread_join(client->notifier); + } + if (client->mutex) { + g_mutex_free(client->mutex); + } + free(client); + + return NP_E_SUCCESS; +} + +/** Sends a notification to the device's Notification Proxy. + * + * @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) +{ + if (!client || !notification) { + return NP_E_INVALID_ARG; + } + np_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict,"Command", plist_new_string("PostNotification")); + plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + + np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + dict = plist_new_dict(); + plist_dict_insert_item(dict,"Command", plist_new_string("Shutdown")); + + res = np_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != NP_E_SUCCESS) { + debug_info("Error sending XML plist to device!"); + } + + np_unlock(client); + return res; +} + +/** Notifies the iphone to send a notification on the specified event. + * + * @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 ) +{ + if (!client || !notification) { + return NP_E_INVALID_ARG; + } + np_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_insert_item(dict,"Command", plist_new_string("ObserveNotification")); + plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + + np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); + if (res != NP_E_SUCCESS) { + debug_info("Error sending XML plist to device!"); + } + plist_free(dict); + + np_unlock(client); + return res; +} + +/** Notifies the iphone to send a notification on specified events. + * + * @param client The client to send to + * @param notification_spec Specification of the notifications that should be + * observed. This is expected to be an array of const char* that MUST have a + * terminating NULL entry. + * + * @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) +{ + int i = 0; + np_error_t res = NP_E_UNKNOWN_ERROR; + const char **notifications = notification_spec; + + if (!client) { + return NP_E_INVALID_ARG; + } + + if (!notifications) { + return NP_E_INVALID_ARG; + } + + while (notifications[i]) { + res = np_observe_notification(client, notifications[i]); + if (res != NP_E_SUCCESS) { + break; + } + i++; + } + + return res; +} + +/** + * Checks if a notification has been sent. + * + * @param client NP to get a notification from + * @param notification Pointer to a buffer that will be allocated and filled + * with the notification that has been received. + * + * @return 0 if a notification has been received or nothing has been received, + * or a negative value if an error occured. + * + * @note You probably want to check out np_set_notify_callback + * @see np_set_notify_callback + */ +static int np_get_notification(np_client_t client, char **notification) +{ + int res = 0; + plist_t dict = NULL; + + if (!client || !client->parent || *notification) + return -1; + + np_lock(client); + + property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); + if (!dict) { + debug_info("NotificationProxy: no notification received!"); + res = 0; + } else { + char *cmd_value = NULL; + plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); + + if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { + plist_get_string_val(cmd_value_node, &cmd_value); + } + + if (cmd_value && !strcmp(cmd_value, "RelayNotification")) { + char *name_value = NULL; + plist_t name_value_node = plist_dict_get_item(dict, "Name"); + + if (plist_get_node_type(name_value_node) == PLIST_STRING) { + plist_get_string_val(name_value_node, &name_value); + } + + res = -2; + if (name_value_node && name_value) { + *notification = name_value; + debug_info("got notification %s\n", __func__, name_value); + res = 0; + } + } else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { + debug_info("ERROR: NotificationProxy died!"); + res = -1; + } else if (cmd_value) { + debug_info("unknown NotificationProxy command '%s' received!", cmd_value); + res = -1; + } else { + res = -2; + } + if (cmd_value) { + free(cmd_value); + } + plist_free(dict); + dict = NULL; + } + + np_unlock(client); + + return res; +} + +/** + * Internally used thread function. + */ +gpointer np_notifier( gpointer arg ) +{ + char *notification = NULL; + struct np_thread *npt = (struct np_thread*)arg; + + if (!npt) return NULL; + + debug_info("starting callback."); + while (npt->client->parent) { + np_get_notification(npt->client, ¬ification); + if (notification) { + npt->cbfunc(notification); + free(notification); + notification = NULL; + } + sleep(1); + } + if (npt) { + free(npt); + } + + return NULL; +} + +/** + * This function allows an application to define a callback function that will + * be called when a notification has been received. + * It will start a thread that polls for notifications and calls the callback + * function if a notification has been received. + * + * @param client the NP client + * @param notify_cb pointer to a callback function or NULL to de-register a + * 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, + * 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 ) +{ + if (!client) + return NP_E_INVALID_ARG; + + np_error_t res = NP_E_UNKNOWN_ERROR; + + np_lock(client); + if (client->notifier) { + debug_info("callback already set, removing\n"); + property_list_service_client_t parent = client->parent; + client->parent = NULL; + g_thread_join(client->notifier); + client->notifier = NULL; + client->parent = parent; + } + + if (notify_cb) { + struct np_thread *npt = (struct np_thread*)malloc(sizeof(struct np_thread)); + if (npt) { + npt->client = client; + npt->cbfunc = notify_cb; + + client->notifier = g_thread_create(np_notifier, npt, TRUE, NULL); + if (client->notifier) { + res = NP_E_SUCCESS; + } + } + } else { + debug_info("no callback set"); + } + np_unlock(client); + + return res; +} diff --git a/src/notification_proxy.h b/src/notification_proxy.h new file mode 100644 index 0000000..6ff2cde --- /dev/null +++ b/src/notification_proxy.h @@ -0,0 +1,37 @@ +/* + * notification_proxy.h + * Notification 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 INOTIFICATION_PROXY_H +#define INOTIFICATION_PROXY_H + +#include + +#include "libiphone/notification_proxy.h" +#include "property_list_service.h" + +struct np_client_int { + property_list_service_client_t parent; + GMutex *mutex; + GThread *notifier; +}; + +gpointer np_notifier(gpointer arg); + +#endif diff --git a/src/sbservices.c b/src/sbservices.c new file mode 100644 index 0000000..2254c64 --- /dev/null +++ b/src/sbservices.c @@ -0,0 +1,269 @@ +/* + * 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 "property_list_service.h" +#include "debug.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) +{ + debug_info("SBServices: Locked"); + 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) +{ + debug_info("SBServices: Unlocked"); + g_mutex_unlock(client->mutex); +} + +/** + * Convert a property_list_service_error_t value to a sbservices_error_t value. + * Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching sbservices_error_t error code, + * SBSERVICES_E_UNKNOWN_ERROR otherwise. + */ +static sbservices_error_t sbservices_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return SBSERVICES_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return SBSERVICES_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return SBSERVICES_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return SBSERVICES_E_CONN_FAILED; + default: + break; + } + return SBSERVICES_E_UNKNOWN_ERROR; +} + +/** + * Creates a new sbservices client. + * + * @param device The device to connect to. + * @param port The port on device to connect to. + * @param client Pointer that will point to a newly allocated + * sbservices_client_t upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client is NULL, or an SBSERVICES_E_* error code otherwise. + */ +sbservices_error_t sbservices_client_new(iphone_device_t device, uint16_t 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; + + property_list_service_client_t plistclient = NULL; + sbservices_error_t err = sbservices_error(property_list_service_client_new(device, port, &plistclient)); + if (err != SBSERVICES_E_SUCCESS) { + return err; + } + + sbservices_client_t client_loc = (sbservices_client_t) malloc(sizeof(struct sbservices_client_int)); + client_loc->parent = plistclient; + client_loc->mutex = g_mutex_new(); + + *client = client_loc; + return SBSERVICES_E_SUCCESS; +} + +/** + * Frees an sbservices client. + * + * @param client The sbservices client to free. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client is NULL, or an SBSERVICES_E_* error code otherwise. + */ +sbservices_error_t sbservices_client_free(sbservices_client_t client) +{ + if (!client) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t err = sbservices_error(property_list_service_client_free(client->parent)); + client->parent = NULL; + if (client->mutex) { + g_mutex_free(client->mutex); + } + free(client); + + return err; +} + +/** + * Gets the icon state of the connected device. + * + * @param client The connected sbservices client to use. + * @param state Pointer that will point to a newly allocated plist containing + * the current icon state. It is up to the caller to free the memory. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or state is invalid, or an SBSERVICES_E_* error code otherwise. + */ +sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state) +{ + if (!client || !client->parent || !state) + 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("getIconState")); + + sbs_lock(client); + + res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + goto leave_unlock; + } + plist_free(dict); + dict = NULL; + + res = sbservices_error(property_list_service_receive_plist(client->parent, state)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not get icon state, error %d", res); + if (*state) { + plist_free(*state); + *state = NULL; + } + } + +leave_unlock: + if (dict) { + plist_free(dict); + } + sbs_unlock(client); + return res; +} + +/** + * Sets the icon state of the connected device. + * + * @param client The connected sbservices client to use. + * @param newstate A plist containing the new iconstate. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client or newstate is NULL, or an SBSERVICES_E_* error code otherwise. + */ +sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate) +{ + if (!client || !client->parent || !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_error(property_list_service_send_binary_plist(client->parent, dict)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + } + // NO RESPONSE + + if (dict) { + plist_free(dict); + } + sbs_unlock(client); + return res; +} + +/** + * Get the icon of the specified app as PNG data. + * + * @param client The connected sbservices client to use. + * @param bundleId The bundle identifier of the app to retrieve the icon for. + * @param pngdata Pointer that will point to a newly allocated buffer + * containing the PNG data upon successful return. It is up to the caller + * to free the memory. + * @param pngsize Pointer to a uint64_t that will be set to the size of the + * buffer pngdata points to upon successful return. + * + * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when + * client, bundleId, or pngdata are invalid, or an SBSERVICES_E_* error + * code otherwise. + */ +sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize) +{ + if (!client || !client->parent || !bundleId || !pngdata) + 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("getIconPNGData")); + plist_dict_insert_item(dict, "bundleId", plist_new_string(bundleId)); + + sbs_lock(client); + + res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + goto leave_unlock; + } + plist_free(dict); + + dict = NULL; + res = sbservices_error(property_list_service_receive_plist(client->parent, &dict)); + if (res == SBSERVICES_E_SUCCESS) { + plist_t node = plist_dict_get_item(dict, "pngData"); + if (node) { + plist_get_data_val(node, pngdata, pngsize); + } + } + +leave_unlock: + if (dict) { + plist_free(dict); + } + sbs_unlock(client); + return res; + +} + diff --git a/src/sbservices.h b/src/sbservices.h new file mode 100644 index 0000000..5c95eaf --- /dev/null +++ b/src/sbservices.h @@ -0,0 +1,34 @@ +/* + * 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" +#include "property_list_service.h" + +struct sbservices_client_int { + property_list_service_client_t parent; + GMutex *mutex; +}; + +#endif -- cgit v1.1-32-gdbae