diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile.am | 16 | ||||
| -rw-r--r-- | src/collection.c | 81 | ||||
| -rw-r--r-- | src/collection.h | 48 | ||||
| -rw-r--r-- | src/libusbmuxd.c | 980 | ||||
| -rw-r--r-- | src/sock_stuff.c | 375 | ||||
| -rw-r--r-- | src/sock_stuff.h | 65 | 
6 files changed, 1565 insertions, 0 deletions
| diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..bf9198f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,16 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +AM_CFLAGS = $(GLOBAL_CFLAGS) $(libplist_CFLAGS) +AM_LDFLAGS = $(GLOBAL_LIBS) $(libplist_LIBS) + +lib_LTLIBRARIES = libusbmuxd.la +libusbmuxd_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBUSBMUXD_SO_VERSION) -no-undefined +libusbmuxd_la_LIBADD = +libusbmuxd_la_SOURCES = \ +		collection.c collection.h \ +		sock_stuff.c sock_stuff.h \ +		libusbmuxd.c + +if WIN32 +libusbmuxd_la_LIBADD += ws2_32 +endif diff --git a/src/collection.c b/src/collection.c new file mode 100644 index 0000000..423cc4e --- /dev/null +++ b/src/collection.c @@ -0,0 +1,81 @@ +/* +	libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009	Hector Martin "marcan" <hector@marcansoft.com> +Copyright (C) 2009	Nikias Bassen <nikias@gmx.li> + +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 General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include "collection.h" + +void collection_init(struct collection *col) +{ +	col->list = malloc(sizeof(void *)); +	memset(col->list, 0, sizeof(void *)); +	col->capacity = 1; +} + +void collection_free(struct collection *col) +{ +	free(col->list); +	col->list = NULL; +	col->capacity = 0; +} + +void collection_add(struct collection *col, void *element) +{ +	int i; +	for(i=0; i<col->capacity; i++) { +		if(!col->list[i]) { +			col->list[i] = element; +			return; +		} +	} +	col->list = realloc(col->list, sizeof(void*) * col->capacity * 2); +	memset(&col->list[col->capacity], 0, sizeof(void *) * col->capacity); +	col->list[col->capacity] = element; +	col->capacity *= 2; +} + +void collection_remove(struct collection *col, void *element) +{ +	int i; +	for(i=0; i<col->capacity; i++) { +		if(col->list[i] == element) { +			col->list[i] = NULL; +			return; +		} +	} +	fprintf(stderr, "%s: WARNING: element %p not present in collection %p (cap %d)", __func__, element, col, col->capacity); +} + +int collection_count(struct collection *col) +{ +	int i, cnt = 0; +	for(i=0; i<col->capacity; i++) { +		if(col->list[i]) +			cnt++; +	} +	return cnt; +} diff --git a/src/collection.h b/src/collection.h new file mode 100644 index 0000000..e9b6403 --- /dev/null +++ b/src/collection.h @@ -0,0 +1,48 @@ +/* +	libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009	Hector Martin "marcan" <hector@marcansoft.com> +Copyright (C) 2009	Nikias Bassen <nikias@gmx.li> + +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 General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +*/ + +#ifndef __COLLECTION_H__ +#define __COLLECTION_H__ + +struct collection { +	void **list; +	int capacity; +}; + +void collection_init(struct collection *col); +void collection_add(struct collection *col, void *element); +void collection_remove(struct collection *col, void *element); +int collection_count(struct collection *col); +void collection_free(struct collection *col); + +#define FOREACH(var, col) \ +	do { \ +		int _iter; \ +		for(_iter=0; _iter<(col)->capacity; _iter++) { \ +			if(!(col)->list[_iter]) continue; \ +			var = (col)->list[_iter]; + +#define ENDFOREACH \ +		} \ +	} while(0); + +#endif diff --git a/src/libusbmuxd.c b/src/libusbmuxd.c new file mode 100644 index 0000000..64b3725 --- /dev/null +++ b/src/libusbmuxd.c @@ -0,0 +1,980 @@ +/* +	libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009-2010	Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009	Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009	Martin Szulecki <opensuse@sukimashita.com> + +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 General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +*/ + +#include <stdint.h> +#include <stdlib.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#include <winsock2.h> +#define sleep(x) Sleep(x*1000) +#ifndef EPROTO +#define EPROTO 134 +#endif +#ifndef EBADMSG +#define EBADMSG 104 +#endif +#else +#include <sys/socket.h> +#include <arpa/inet.h> +#include <pthread.h> +#endif + +#ifdef HAVE_INOTIFY +#include <sys/inotify.h> +#define EVENT_SIZE  (sizeof (struct inotify_event)) +#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16)) +#define USBMUXD_DIRNAME "/var/run" +#define USBMUXD_SOCKET_NAME "usbmuxd" +#endif /* HAVE_INOTIFY */ + +#include <unistd.h> +#include <signal.h> + +#ifdef HAVE_PLIST +#include <plist/plist.h> +#define PLIST_BUNDLE_ID "com.marcansoft.usbmuxd" +#define PLIST_CLIENT_VERSION_STRING "usbmuxd built for freedom" +#define PLIST_PROGNAME "libusbmuxd" +#endif + +// usbmuxd public interface +#include "usbmuxd.h" +// usbmuxd protocol +#include "usbmuxd-proto.h" +// socket utility functions +#include "sock_stuff.h" +// misc utility functions +#include "collection.h" + +static int libusbmuxd_debug = 2; +#define DEBUG(x, y, ...) if (x <= libusbmuxd_debug) fprintf(stderr, (y), __VA_ARGS__); + +static struct collection devices; +static usbmuxd_event_cb_t event_cb = NULL; +#ifdef WIN32 +HANDLE devmon = NULL; +CRITICAL_SECTION mutex; +static int mutex_initialized = 0; +#define LOCK if (!mutex_initialized) { InitializeCriticalSection(&mutex); mutex_initialized = 1; } EnterCriticalSection(&mutex); +#define UNLOCK LeaveCriticalSection(&mutex); +#else +pthread_t devmon; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +#define LOCK pthread_mutex_lock(&mutex) +#define UNLOCK pthread_mutex_unlock(&mutex)	 +#endif +static int listenfd = -1; + +static int use_tag = 0; +static int proto_version = 0; + +/** + * Finds a device info record by its handle. + * if the record is not found, NULL is returned. + */ +static usbmuxd_device_info_t *devices_find(uint32_t handle) +{ +	FOREACH(usbmuxd_device_info_t *dev, &devices) { +		if (dev && dev->handle == handle) { +			return dev; +		} +	} ENDFOREACH +	return NULL; +} + +/** + * Creates a socket connection to usbmuxd. + * For Mac/Linux it is a unix domain socket, + * for Windows it is a tcp socket. + */ +static int connect_usbmuxd_socket() +{ +#if defined(WIN32) || defined(__CYGWIN__) +	return connect_socket("127.0.0.1", USBMUXD_SOCKET_PORT); +#else +	return connect_unix_socket(USBMUXD_SOCKET_FILE); +#endif +} + +static int receive_packet(int sfd, struct usbmuxd_header *header, void **payload, int timeout) +{ +	int recv_len; +	struct usbmuxd_header hdr; +	char *payload_loc = NULL; + +	header->length = 0; +	header->version = 0; +	header->message = 0; +	header->tag = 0; + +	recv_len = recv_buf_timeout(sfd, &hdr, sizeof(hdr), 0, timeout); +	if (recv_len < 0) { +		return recv_len; +	} else if ((size_t)recv_len < sizeof(hdr)) { +		return recv_len; +	} + +	uint32_t payload_size = hdr.length - sizeof(hdr); +	if (payload_size > 0) { +		payload_loc = (char*)malloc(payload_size); +		if (recv_buf_timeout(sfd, payload_loc, payload_size, 0, 5000) != (int)payload_size) { +			DEBUG(1, "%s: Error receiving payload of size %d\n", __func__, payload_size); +			free(payload_loc); +			return -EBADMSG; +		} +	} + +#ifdef HAVE_PLIST +	if (hdr.message == MESSAGE_PLIST) { +		char *message = NULL; +		plist_t plist = NULL; +		plist_from_xml(payload_loc, payload_size, &plist); +		free(payload_loc); + +		if (!plist) { +			DEBUG(1, "%s: Error getting plist from payload!\n", __func__); +			return -EBADMSG; +		} + +		plist_t node = plist_dict_get_item(plist, "MessageType"); +		if (plist_get_node_type(node) != PLIST_STRING) { +			DEBUG(1, "%s: Error getting message type from plist!\n", __func__); +			free(plist); +			return -EBADMSG; +		} +		 +		plist_get_string_val(node, &message); +		if (message) { +			uint64_t val = 0; +			if (strcmp(message, "Result") == 0) { +				/* result message */ +				uint32_t dwval = 0; +				plist_t n = plist_dict_get_item(plist, "Number"); +				plist_get_uint_val(n, &val); +				*payload = malloc(sizeof(uint32_t)); +				dwval = val; +				memcpy(*payload, &dwval, sizeof(dwval)); +				hdr.length = sizeof(hdr) + sizeof(dwval); +				hdr.message = MESSAGE_RESULT; +			} else if (strcmp(message, "Attached") == 0) { +				/* device add message */ +				struct usbmuxd_device_record *dev = NULL; +				plist_t props = plist_dict_get_item(plist, "Properties"); +				if (!props) { +					DEBUG(1, "%s: Could not get properties for message '%s' from plist!\n", __func__, message); +					free(message); +					plist_free(plist); +					return -EBADMSG; +				} +				dev = (struct usbmuxd_device_record*)malloc(sizeof(struct usbmuxd_device_record)); +				memset(dev, 0, sizeof(struct usbmuxd_device_record)); + +				plist_t n = plist_dict_get_item(props, "DeviceID"); +				plist_get_uint_val(n, &val); +				dev->device_id = (uint32_t)val; + +				n = plist_dict_get_item(props, "ProductID"); +				plist_get_uint_val(n, &val); +				dev->product_id = (uint32_t)val; + +				n = plist_dict_get_item(props, "SerialNumber"); +				char *strval = NULL; +				plist_get_string_val(n, &strval); +				if (strval) { +					strncpy(dev->serial_number, strval, 255); +					free(strval); +				} +				n = plist_dict_get_item(props, "LocationID"); +				plist_get_uint_val(n, &val); +				dev->location = (uint32_t)val; +				*payload = (void*)dev; +				hdr.length = sizeof(hdr) + sizeof(struct usbmuxd_device_record); +				hdr.message = MESSAGE_DEVICE_ADD; +			} else if (strcmp(message, "Detached") == 0) { +				/* device remove message */ +				uint32_t dwval = 0; +				plist_t n = plist_dict_get_item(plist, "DeviceID"); +				if (n) { +					plist_get_uint_val(n, &val); +					*payload = malloc(sizeof(uint32_t)); +					dwval = val; +					memcpy(*payload, &dwval, sizeof(dwval)); +					hdr.length = sizeof(hdr) + sizeof(dwval); +					hdr.message = MESSAGE_DEVICE_REMOVE; +				} +			} else { +				DEBUG(1, "%s: Unexpected message '%s' in plist!\n", __func__, message); +				free(message); +				plist_free(plist); +				return -EBADMSG; +			} +			free(message); +		} +		plist_free(plist); +	} else +#endif +	{ +		*payload = payload_loc; +	} + +	memcpy(header, &hdr, sizeof(hdr)); + +	return hdr.length; +} + +/** + * Retrieves the result code to a previously sent request. + */ +static int usbmuxd_get_result(int sfd, uint32_t tag, uint32_t * result) +{ +	struct usbmuxd_header hdr; +	int recv_len; +	uint32_t *res = NULL; + +	if (!result) { +		return -EINVAL; +	} +	*result = -1; + +	if ((recv_len = receive_packet(sfd, &hdr, (void**)&res, 5000)) < 0) { +		DEBUG(1, "%s: Error receiving packet: %d\n", __func__, errno); +		if (res) +			free(res); +		return -errno; +	} +	if ((size_t)recv_len < sizeof(hdr)) { +		DEBUG(1, "%s: Received packet is too small!\n", __func__); +		if (res) +			free(res); +		return -EPROTO; +	} + +	if (hdr.message == MESSAGE_RESULT) { +		int ret = 0; +		if (res && (hdr.tag == tag)) { +			memcpy(result, res, sizeof(uint32_t)); +			ret = 1; +		} +		if (res) +			free(res); +		return ret; +	} +	DEBUG(1, "%s: Unexpected message of type %d received!\n", __func__, hdr.message); +	if (res) +		free(res); +	return -EPROTO; +} + +static int send_packet(int sfd, uint32_t message, uint32_t tag, void *payload, uint32_t payload_size) +{ +	struct usbmuxd_header header; + +	header.length = sizeof(struct usbmuxd_header); +	header.version = proto_version; +	header.message = message; +	header.tag = tag; +	if (payload && (payload_size > 0)) { +		header.length += payload_size; +	} +	int sent = send_buf(sfd, &header, sizeof(header)); +	if (sent != sizeof(header)) { +		DEBUG(1, "%s: ERROR: could not send packet header\n", __func__); +		return -1; +	} +	if (payload && (payload_size > 0)) { +		sent += send_buf(sfd, payload, payload_size); +	} +	if (sent != (int)header.length) { +		DEBUG(1, "%s: ERROR: could not send whole packet\n", __func__); +		close_socket(sfd); +		return -1; +	} +	return sent; +} + +static int send_listen_packet(int sfd, uint32_t tag) +{ +	int res = 0; +#ifdef HAVE_PLIST +	if (proto_version == 1) { +		/* plist packet */ +		char *payload = NULL; +		uint32_t payload_size = 0; +		plist_t plist; + +		/* construct message plist */ +		plist = plist_new_dict(); +		plist_dict_insert_item(plist, "BundleID", plist_new_string(PLIST_BUNDLE_ID)); +		plist_dict_insert_item(plist, "ClientVersionString", plist_new_string(PLIST_CLIENT_VERSION_STRING)); +		plist_dict_insert_item(plist, "MessageType", plist_new_string("Listen")); +		plist_dict_insert_item(plist, "ProgName", plist_new_string(PLIST_PROGNAME)); +		plist_to_xml(plist, &payload, &payload_size); +		plist_free(plist); + +		res = send_packet(sfd, MESSAGE_PLIST, tag, payload, payload_size); +		free(payload); +	} else +#endif +	{ +		/* binary packet */ +		res = send_packet(sfd, MESSAGE_LISTEN, tag, NULL, 0); +	} +	return res; +} + +static int send_connect_packet(int sfd, uint32_t tag, uint32_t device_id, uint16_t port) +{ +	int res = 0; +#ifdef HAVE_PLIST +	if (proto_version == 1) { +		/* plist packet */ +		char *payload = NULL; +		uint32_t payload_size = 0; +		plist_t plist; + +		/* construct message plist */ +		plist = plist_new_dict(); +		plist_dict_insert_item(plist, "BundleID", plist_new_string(PLIST_BUNDLE_ID)); +		plist_dict_insert_item(plist, "ClientVersionString", plist_new_string(PLIST_CLIENT_VERSION_STRING)); +		plist_dict_insert_item(plist, "MessageType", plist_new_string("Connect")); +		plist_dict_insert_item(plist, "DeviceID", plist_new_uint(device_id)); +		plist_dict_insert_item(plist, "PortNumber", plist_new_uint(htons(port))); +		plist_dict_insert_item(plist, "ProgName", plist_new_string(PLIST_PROGNAME)); +		plist_to_xml(plist, &payload, &payload_size); +		plist_free(plist); + +		res = send_packet(sfd, MESSAGE_PLIST, tag, (void*)payload, payload_size); +		free(payload); +	} else +#endif +	{ +		/* binary packet */ +		struct { +			uint32_t device_id; +			uint16_t port; +			uint16_t reserved; +		} conninfo; + +		conninfo.device_id = device_id; +		conninfo.port = htons(port); +		conninfo.reserved = 0; + +		res = send_packet(sfd, MESSAGE_CONNECT, tag, &conninfo, sizeof(conninfo)); +	} +	return res; +} + +/** + * Generates an event, i.e. calls the callback function. + * A reference to a populated usbmuxd_event_t with information about the event + * and the corresponding device will be passed to the callback function. + */ +static void generate_event(usbmuxd_event_cb_t callback, const usbmuxd_device_info_t *dev, enum usbmuxd_event_type event, void *user_data) +{ +	usbmuxd_event_t ev; + +	if (!callback || !dev) { +		return; +	} + +	ev.event = event; +	memcpy(&ev.device, dev, sizeof(usbmuxd_device_info_t)); + +	callback(&ev, user_data); +} + +static int usbmuxd_listen_poll() +{ +	int sfd; + +	sfd = connect_usbmuxd_socket(); +	if (sfd < 0) { +		while (event_cb) { +			if ((sfd = connect_usbmuxd_socket()) > 0) { +				break; +			} +			sleep(1); +		} +	} + +	return sfd; +} + +#ifdef HAVE_INOTIFY +static int use_inotify = 1; + +static int usbmuxd_listen_inotify() +{ +	int inot_fd; +	int watch_d; +	int sfd; + +	if (!use_inotify) { +		return -2; +	} + +	sfd = connect_usbmuxd_socket(); +	if (sfd >= 0) +		return sfd; + +	sfd = -1; +	inot_fd = inotify_init (); +	if (inot_fd < 0) { +		DEBUG(1, "%s: Failed to setup inotify\n", __func__); +		return -2; +	} + +	/* inotify is setup, listen for events that concern us */ +	watch_d = inotify_add_watch (inot_fd, USBMUXD_DIRNAME, IN_CREATE); +	if (watch_d < 0) { +		DEBUG(1, "%s: Failed to setup watch descriptor for socket dir\n", __func__); +		close (inot_fd); +		return -2; +	} + +	while (1) { +		ssize_t len, i; +		char buff[EVENT_BUF_LEN] = {0}; + +		i = 0; +		len = read (inot_fd, buff, EVENT_BUF_LEN -1); +		if (len < 0) +			goto end; +		while (i < len) { +			struct inotify_event *pevent = (struct inotify_event *) & buff[i]; + +			/* check that it's ours */ +			if (pevent->mask & IN_CREATE && +			    pevent->len && +			    pevent->name != NULL && +			    strcmp(pevent->name, USBMUXD_SOCKET_NAME) == 0) { +				sfd = connect_usbmuxd_socket (); +				goto end; +			} +			i += EVENT_SIZE + pevent->len; +		} +	} + +end: +	inotify_rm_watch(inot_fd, watch_d); +	close(inot_fd); + +	return sfd; +} +#endif /* HAVE_INOTIFY */ + +/** + * Tries to connect to usbmuxd and wait if it is not running. + */ +static int usbmuxd_listen() +{ +	int sfd; +	uint32_t res = -1; + +#ifdef HAVE_PLIST +retry: +#endif + +#ifdef HAVE_INOTIFY +	sfd = usbmuxd_listen_inotify(); +	if (sfd == -2) +		sfd = usbmuxd_listen_poll(); +#else +	sfd = usbmuxd_listen_poll(); +#endif + +	if (sfd < 0) { +		DEBUG(1, "%s: ERROR: usbmuxd was supposed to be running here...\n", __func__); +		return sfd; +	} + +	use_tag++; +	LOCK; +	if (send_listen_packet(sfd, use_tag) <= 0) { +		UNLOCK; +		DEBUG(1, "%s: ERROR: could not send listen packet\n", __func__); +		close_socket(sfd); +		return -1; +	} +	if (usbmuxd_get_result(sfd, use_tag, &res) && (res != 0)) { +		UNLOCK; +		close_socket(sfd); +#ifdef HAVE_PLIST +		if ((res == RESULT_BADVERSION) && (proto_version != 1)) { +			proto_version = 1; +			goto retry; +		} +#endif +		DEBUG(1, "%s: ERROR: did not get OK but %d\n", __func__, res); +		return -1; +	} +	UNLOCK; + +	return sfd; +} + +/** + * Waits for an event to occur, i.e. a packet coming from usbmuxd. + * Calls generate_event to pass the event via callback to the client program. + */ +static int get_next_event(int sfd, usbmuxd_event_cb_t callback, void *user_data) +{ +	struct usbmuxd_header hdr; +	void *payload = NULL; + +	/* block until we receive something */ +	if (receive_packet(sfd, &hdr, &payload, 0) < 0) { +		// when then usbmuxd connection fails, +		// generate remove events for every device that +		// is still present so applications know about it +		FOREACH(usbmuxd_device_info_t *dev, &devices) { +			generate_event(callback, dev, UE_DEVICE_REMOVE, user_data); +			collection_remove(&devices, dev); +			free(dev); +		} ENDFOREACH +		return -EIO; +	} + +	if ((hdr.length > sizeof(hdr)) && !payload) { +		DEBUG(1, "%s: Invalid packet received, payload is missing!\n", __func__); +		return -EBADMSG; +	} + +	if (hdr.message == MESSAGE_DEVICE_ADD) { +		struct usbmuxd_device_record *dev = payload; +		usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); +		if (!devinfo) { +			DEBUG(1, "%s: Out of memory!\n", __func__); +			free(payload); +			return -1; +		} + +		devinfo->handle = dev->device_id; +		devinfo->product_id = dev->product_id; +		memset(devinfo->udid, '\0', sizeof(devinfo->udid)); +		memcpy(devinfo->udid, dev->serial_number, sizeof(devinfo->udid)); + +		if (strcasecmp(devinfo->udid, "ffffffffffffffffffffffffffffffffffffffff") == 0) { +			sprintf(devinfo->udid + 32, "%08x", devinfo->handle); +		} + +		collection_add(&devices, devinfo); +		generate_event(callback, devinfo, UE_DEVICE_ADD, user_data); +	} else if (hdr.message == MESSAGE_DEVICE_REMOVE) { +		uint32_t handle; +		usbmuxd_device_info_t *devinfo; + +		memcpy(&handle, payload, sizeof(uint32_t)); + +		devinfo = devices_find(handle); +		if (!devinfo) { +			DEBUG(1, "%s: WARNING: got device remove message for handle %d, but couldn't find the corresponding handle in the device list. This event will be ignored.\n", __func__, handle); +		} else { +			generate_event(callback, devinfo, UE_DEVICE_REMOVE, user_data); +			collection_remove(&devices, devinfo); +			free(devinfo); +		} +	} else if (hdr.length > 0) { +		DEBUG(1, "%s: Unexpected message type %d length %d received!\n", __func__, hdr.message, hdr.length); +	} +	if (payload) { +		free(payload); +	} +	return 0; +} + +static void device_monitor_cleanup(void* data) +{ +	FOREACH(usbmuxd_device_info_t *dev, &devices) { +		collection_remove(&devices, dev); +		free(dev); +	} ENDFOREACH +	collection_free(&devices); + +	close_socket(listenfd); +	listenfd = -1; +} + +/** + * Device Monitor thread function. + * + * This function sets up a connection to usbmuxd + */ +static void *device_monitor(void *data) +{ +	collection_init(&devices); + +#ifndef WIN32 +	pthread_cleanup_push(device_monitor_cleanup, NULL); +#endif +	while (event_cb) { + +		listenfd = usbmuxd_listen(); +		if (listenfd < 0) { +			continue; +		} + +		while (event_cb) { +			int res = get_next_event(listenfd, event_cb, data); +			if (res < 0) { +			    break; +			} +		} +	} + +#ifndef WIN32 +	pthread_cleanup_pop(1); +#else +	device_monitor_cleanup(NULL); +#endif +	return NULL; +} + +int usbmuxd_subscribe(usbmuxd_event_cb_t callback, void *user_data) +{ +	int res; + +	if (!callback) { +		return -EINVAL; +	} +	event_cb = callback; + +#ifdef WIN32 +	res = 0; +	devmon = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)device_monitor, user_data, 0, NULL); +	if (devmon == NULL) { +		res = GetLastError(); +	} +#else +	res = pthread_create(&devmon, NULL, device_monitor, user_data); +#endif +	if (res != 0) { +		DEBUG(1, "%s: ERROR: Could not start device watcher thread!\n", __func__); +		return res; +	} +	return 0; +} + +int usbmuxd_unsubscribe() +{ +	event_cb = NULL; + +	shutdown_socket(listenfd, SHUT_RDWR); + +#ifdef WIN32 +	if (devmon != NULL) { +		WaitForSingleObject(devmon, INFINITE); +	} +#else +	if (pthread_kill(devmon, 0) == 0) { +		pthread_cancel(devmon); +		pthread_join(devmon, NULL); +	} +#endif + +	return 0; +} + +int usbmuxd_get_device_list(usbmuxd_device_info_t **device_list) +{ +	int sfd; +	int listen_success = 0; +	uint32_t res; +	struct collection tmpdevs; +	usbmuxd_device_info_t *newlist = NULL; +	struct usbmuxd_header hdr; +	struct usbmuxd_device_record *dev; +	int dev_cnt = 0; +	void *payload = NULL; + +	*device_list = NULL; + +#ifdef HAVE_PLIST +retry: +#endif +	sfd = connect_usbmuxd_socket(); +	if (sfd < 0) { +		DEBUG(1, "%s: error opening socket!\n", __func__); +		return sfd; +	} + +	use_tag++; +	LOCK; +	if (send_listen_packet(sfd, use_tag) > 0) { +		res = -1; +		// get response +		if (usbmuxd_get_result(sfd, use_tag, &res) && (res == 0)) { +			listen_success = 1; +		} else { +			UNLOCK; +			close_socket(sfd); +#ifdef HAVE_PLIST +			if ((res == RESULT_BADVERSION) && (proto_version != 1)) { +				proto_version = 1; +				goto retry; +			} +#endif +			DEBUG(1, "%s: Did not get response to scan request (with result=0)...\n", __func__); +			return res; +		} +	} + +	if (!listen_success) { +		UNLOCK; +		DEBUG(1, "%s: Could not send listen request!\n", __func__); +		return -1; +	} + +	collection_init(&tmpdevs); + +	// receive device list +	while (1) { +		if (receive_packet(sfd, &hdr, &payload, 1000) > 0) { +			if (hdr.message == MESSAGE_DEVICE_ADD) { +				dev = payload; +				usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); +				if (!devinfo) { +					UNLOCK; +					DEBUG(1, "%s: Out of memory!\n", __func__); +					free(payload); +					return -1; +				} + +				devinfo->handle = dev->device_id; +				devinfo->product_id = dev->product_id; +				memset(devinfo->udid, '\0', sizeof(devinfo->udid)); +				memcpy(devinfo->udid, dev->serial_number, sizeof(devinfo->udid)); + +				if (strcasecmp(devinfo->udid, "ffffffffffffffffffffffffffffffffffffffff") == 0) { +					sprintf(devinfo->udid + 32, "%08x", devinfo->handle); +				} + +				collection_add(&tmpdevs, devinfo); + +			} else if (hdr.message == MESSAGE_DEVICE_REMOVE) { +				uint32_t handle; +				usbmuxd_device_info_t *devinfo = NULL; + +				memcpy(&handle, payload, sizeof(uint32_t)); + +				FOREACH(usbmuxd_device_info_t *di, &tmpdevs) { +					if (di && di->handle == handle) { +						devinfo = di; +						break; +					} +				} ENDFOREACH +				if (devinfo) { +					collection_remove(&tmpdevs, devinfo); +					free(devinfo); +				} +			} else { +				DEBUG(1, "%s: Unexpected message %d\n", __func__, hdr.message); +			} +			if (payload) +				free(payload); +		} else { +			// we _should_ have all of them now. +			// or perhaps an error occured. +			break; +		} +	} +	UNLOCK; + +	// explicitly close connection +	close_socket(sfd); + +	// create copy of device info entries from collection +	newlist = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t) * (collection_count(&tmpdevs) + 1)); +	dev_cnt = 0; +	FOREACH(usbmuxd_device_info_t *di, &tmpdevs) { +		if (di) { +			memcpy(&newlist[dev_cnt], di, sizeof(usbmuxd_device_info_t)); +			free(di); +			dev_cnt++; +		} +	} ENDFOREACH +	collection_free(&tmpdevs); + +	memset(&newlist[dev_cnt], 0, sizeof(usbmuxd_device_info_t)); +	*device_list = newlist; + +	return dev_cnt; +} + +int usbmuxd_device_list_free(usbmuxd_device_info_t **device_list) +{ +	if (device_list) { +		free(*device_list); +	} +	return 0; +} + +int usbmuxd_get_device_by_udid(const char *udid, usbmuxd_device_info_t *device) +{ +	usbmuxd_device_info_t *dev_list = NULL; + +	if (!device) { +		return -EINVAL; +	} +	if (usbmuxd_get_device_list(&dev_list) < 0) { +		return -ENODEV; +	} + +	int i; +	int result = 0; +	for (i = 0; dev_list[i].handle > 0; i++) { +	 	if (!udid) { +			device->handle = dev_list[i].handle; +			device->product_id = dev_list[i].product_id; +			strcpy(device->udid, dev_list[i].udid); +			result = 1; +			break; +		} +		if (!strcmp(udid, dev_list[i].udid)) { +			device->handle = dev_list[i].handle; +			device->product_id = dev_list[i].product_id; +			strcpy(device->udid, dev_list[i].udid); +			result = 1; +			break; +		} +	} + +	free(dev_list); + +	return result; +} + +int usbmuxd_connect(const int handle, const unsigned short port) +{ +	int sfd; +	int connected = 0; +	uint32_t res = -1; + +#ifdef HAVE_PLIST +retry: +#endif +	sfd = connect_usbmuxd_socket(); +	if (sfd < 0) { +		DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", +				__func__, strerror(errno)); +		return sfd; +	} + +	use_tag++; +	if (send_connect_packet(sfd, use_tag, (uint32_t)handle, (uint16_t)port) <= 0) { +		DEBUG(1, "%s: Error sending connect message!\n", __func__); +	} else { +		// read ACK +		DEBUG(2, "%s: Reading connect result...\n", __func__); +		if (usbmuxd_get_result(sfd, use_tag, &res)) { +			if (res == 0) { +				DEBUG(2, "%s: Connect success!\n", __func__); +				connected = 1; +			} else { +#ifdef HAVE_PLIST +				if ((res == RESULT_BADVERSION) && (proto_version == 0)) { +					proto_version = 1; +					close_socket(sfd); +					goto retry; +				} +#endif +				DEBUG(1, "%s: Connect failed, Error code=%d\n", __func__, res); +			} +		} +	} + +	if (connected) { +		return sfd; +	} + +	close_socket(sfd); + +	return -1; +} + +int usbmuxd_disconnect(int sfd) +{ +	return close_socket(sfd); +} + +int usbmuxd_send(int sfd, const char *data, uint32_t len, uint32_t *sent_bytes) +{ +	int num_sent; + +	if (sfd < 0) { +		return -EINVAL; +	} +	 +	num_sent = send(sfd, (void*)data, len, 0); +	if (num_sent < 0) { +		*sent_bytes = 0; +		DEBUG(1, "%s: Error %d when sending: %s\n", __func__, num_sent, strerror(errno)); +		return num_sent; +	} else if ((uint32_t)num_sent < len) { +		DEBUG(1, "%s: Warning: Did not send enough (only %d of %d)\n", __func__, num_sent, len); +	} + +	*sent_bytes = num_sent; + +	return 0; +} + +int usbmuxd_recv_timeout(int sfd, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) +{ +	int num_recv = recv_buf_timeout(sfd, (void*)data, len, 0, timeout); +	if (num_recv < 0) { +		*recv_bytes = 0; +		return num_recv; +	} + +	*recv_bytes = num_recv; + +	return 0; +} + +int usbmuxd_recv(int sfd, char *data, uint32_t len, uint32_t *recv_bytes) +{ +	return usbmuxd_recv_timeout(sfd, data, len, recv_bytes, 5000); +} + +void libusbmuxd_set_use_inotify(int set) +{ +#ifdef HAVE_INOTIFY +	use_inotify = set; +#endif +	return; +} + +void libusbmuxd_set_debug_level(int level) +{ +	libusbmuxd_debug = level; +	sock_stuff_set_verbose(level); +} diff --git a/src/sock_stuff.c b/src/sock_stuff.c new file mode 100644 index 0000000..609c8ad --- /dev/null +++ b/src/sock_stuff.c @@ -0,0 +1,375 @@ +/* +	libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009	Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009	Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009	Martin Szulecki <opensuse@sukimashita.com> + +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 General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +*/ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> +#ifdef WIN32 +#include <windows.h> +#include <winsock2.h> +static int wsa_init = 0; +#else +#include <sys/socket.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netdb.h> +#include <arpa/inet.h> +#endif +#include "sock_stuff.h" + +#define RECV_TIMEOUT 20000 + +static int verbose = 0; + +void sock_stuff_set_verbose(int level) +{ +	verbose = level; +} + +#ifndef WIN32 +int create_unix_socket(const char *filename) +{ +	struct sockaddr_un name; +	int sock; +	size_t size; + +	// remove if still present +	unlink(filename); + +	/* Create the socket. */ +	sock = socket(PF_LOCAL, SOCK_STREAM, 0); +	if (sock < 0) { +		perror("socket"); +		return -1; +	} + +	/* Bind a name to the socket. */ +	name.sun_family = AF_LOCAL; +	strncpy(name.sun_path, filename, sizeof(name.sun_path)); +	name.sun_path[sizeof(name.sun_path) - 1] = '\0'; + +	/* The size of the address is +	   the offset of the start of the filename, +	   plus its length, +	   plus one for the terminating null byte. +	   Alternatively you can just do: +	   size = SUN_LEN (&name); +	 */ +	size = (offsetof(struct sockaddr_un, sun_path) +			+ strlen(name.sun_path) + 1); + +	if (bind(sock, (struct sockaddr *) &name, size) < 0) { +		perror("bind"); +		close_socket(sock); +		return -1; +	} + +	if (listen(sock, 10) < 0) { +		perror("listen"); +		close_socket(sock); +		return -1; +	} + +	return sock; +} + +int connect_unix_socket(const char *filename) +{ +	struct sockaddr_un name; +	int sfd = -1; +	size_t size; +	struct stat fst; + +	// check if socket file exists... +	if (stat(filename, &fst) != 0) { +		if (verbose >= 2) +			fprintf(stderr, "%s: stat '%s': %s\n", __func__, filename, +					strerror(errno)); +		return -1; +	} +	// ... and if it is a unix domain socket +	if (!S_ISSOCK(fst.st_mode)) { +		if (verbose >= 2) +			fprintf(stderr, "%s: File '%s' is not a socket!\n", __func__, +					filename); +		return -1; +	} +	// make a new socket +	if ((sfd = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { +		if (verbose >= 2) +			fprintf(stderr, "%s: socket: %s\n", __func__, strerror(errno)); +		return -1; +	} +	// and connect to 'filename' +	name.sun_family = AF_LOCAL; +	strncpy(name.sun_path, filename, sizeof(name.sun_path)); +	name.sun_path[sizeof(name.sun_path) - 1] = 0; + +	size = (offsetof(struct sockaddr_un, sun_path) +			+ strlen(name.sun_path) + 1); + +	if (connect(sfd, (struct sockaddr *) &name, size) < 0) { +		close_socket(sfd); +		if (verbose >= 2) +			fprintf(stderr, "%s: connect: %s\n", __func__, +					strerror(errno)); +		return -1; +	} + +	return sfd; +} +#endif + +int create_socket(uint16_t port) +{ +	int sfd = -1; +	int yes = 1; +#ifdef WIN32 +	WSADATA wsa_data; +	if (!wsa_init) { +		if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) { +			fprintf(stderr, "WSAStartup failed!\n"); +			ExitProcess(-1); +		} +		wsa_init = 1; +	} +#endif +	struct sockaddr_in saddr; + +	if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { +		perror("socket()"); +		return -1; +	} + +	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) { +		perror("setsockopt()"); +		close_socket(sfd); +		return -1; +	} + +	memset((void *) &saddr, 0, sizeof(saddr)); +	saddr.sin_family = AF_INET; +	saddr.sin_addr.s_addr = htonl(INADDR_ANY); +	saddr.sin_port = htons(port); + +	if (0 > bind(sfd, (struct sockaddr *) &saddr, sizeof(saddr))) { +		perror("bind()"); +		close_socket(sfd); +		return -1; +	} + +	if (listen(sfd, 1) == -1) { +		perror("listen()"); +		close_socket(sfd); +		return -1; +	} + +	return sfd; +} + +#if defined(WIN32) || defined(__CYGWIN__) +int connect_socket(const char *addr, uint16_t port) +{ +	int sfd = -1; +	int yes = 1; +	struct hostent *hp; +	struct sockaddr_in saddr; +#ifdef WIN32 +	WSADATA wsa_data; +	if (!wsa_init) { +		if (WSAStartup(MAKEWORD(2,2), &wsa_data) != ERROR_SUCCESS) { +			fprintf(stderr, "WSAStartup failed!\n"); +			ExitProcess(-1); +		} +		wsa_init = 1; +	} +#endif + +	if (!addr) { +		errno = EINVAL; +		return -1; +	} + +	if ((hp = gethostbyname(addr)) == NULL) { +		if (verbose >= 2) +			fprintf(stderr, "%s: unknown host '%s'\n", __func__, addr); +		return -1; +	} + +	if (!hp->h_addr) { +		if (verbose >= 2) +			fprintf(stderr, "%s: gethostbyname returned NULL address!\n", +					__func__); +		return -1; +	} + +	if (0 > (sfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { +		perror("socket()"); +		return -1; +	} + +	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(int)) == -1) { +		perror("setsockopt()"); +		close_socket(sfd); +		return -1; +	} + +	memset((void *) &saddr, 0, sizeof(saddr)); +	saddr.sin_family = AF_INET; +	saddr.sin_addr.s_addr = *(uint32_t *) hp->h_addr; +	saddr.sin_port = htons(port); + +	if (connect(sfd, (struct sockaddr *) &saddr, sizeof(saddr)) < 0) { +		perror("connect"); +		close_socket(sfd); +		return -2; +	} + +	return sfd; +} +#endif /* WIN32 || __CYGWIN__ */ + +int check_fd(int fd, fd_mode fdm, unsigned int timeout) +{ +	fd_set fds; +	int sret; +	int eagain; +	struct timeval to; +	struct timeval *pto; + +	if (fd <= 0) { +		if (verbose >= 2) +			fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd); +		return -1; +	} + +	FD_ZERO(&fds); +	FD_SET(fd, &fds); + +	if (timeout > 0) { +		to.tv_sec = (time_t) (timeout / 1000); +		to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000); +		pto = &to; +	} else { +		pto = NULL; +	} + +	sret = -1; + +	do { +		eagain = 0; +		switch (fdm) { +		case FDM_READ: +			sret = select(fd + 1, &fds, NULL, NULL, pto); +			break; +		case FDM_WRITE: +			sret = select(fd + 1, NULL, &fds, NULL, pto); +			break; +		case FDM_EXCEPT: +			sret = select(fd + 1, NULL, NULL, &fds, pto); +			break; +		default: +			return -1; +		} + +		if (sret < 0) { +			switch (errno) { +			case EINTR: +				// interrupt signal in select +				if (verbose >= 2) +					fprintf(stderr, "%s: EINTR\n", __func__); +				eagain = 1; +				break; +			case EAGAIN: +				if (verbose >= 2) +					fprintf(stderr, "%s: EAGAIN\n", __func__); +				break; +			default: +				if (verbose >= 2) +					fprintf(stderr, "%s: select failed: %s\n", __func__, +							strerror(errno)); +				return -1; +			} +		} +	} while (eagain); + +	return sret; +} + +int shutdown_socket(int fd, int how) +{ +	return shutdown(fd, how); +} + +int close_socket(int fd) { +#ifdef WIN32 +	return closesocket(fd); +#else +	return close(fd); +#endif +} + +int recv_buf(int fd, void *data, size_t length) +{ +	return recv_buf_timeout(fd, data, length, 0, RECV_TIMEOUT); +} + +int peek_buf(int fd, void *data, size_t length) +{ +	return recv_buf_timeout(fd, data, length, MSG_PEEK, RECV_TIMEOUT); +} + +int recv_buf_timeout(int fd, void *data, size_t length, int flags, +					 unsigned int timeout) +{ +	int res; +	int result; + +	// check if data is available +	res = check_fd(fd, FDM_READ, timeout); +	if (res <= 0) { +		return res; +	} +	// if we get here, there _is_ data available +	result = recv(fd, data, length, flags); +	if (res > 0 && result == 0) { +		// but this is an error condition +		if (verbose >= 3) +			fprintf(stderr, "%s: fd=%d recv returned 0\n", __func__, fd); +		return -EAGAIN; +	} +	if (result < 0) { +		return -errno; +	} +	return result; +} + +int send_buf(int fd, void *data, size_t length) +{ +	return send(fd, data, length, 0); +} diff --git a/src/sock_stuff.h b/src/sock_stuff.h new file mode 100644 index 0000000..5efcd27 --- /dev/null +++ b/src/sock_stuff.h @@ -0,0 +1,65 @@ +/* +	libusbmuxd - client library to talk to usbmuxd + +Copyright (C) 2009	Nikias Bassen <nikias@gmx.li> +Copyright (C) 2009	Paul Sladen <libiphone@paul.sladen.org> +Copyright (C) 2009	Martin Szulecki <opensuse@sukimashita.com> + +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 General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + +*/ + +#ifndef __SOCK_STUFF_H +#define __SOCK_STUFF_H + +#include <stdint.h> + +enum fd_mode { +	FDM_READ, +	FDM_WRITE, +	FDM_EXCEPT +}; +typedef enum fd_mode fd_mode; + +#ifdef WIN32 +#include <winsock2.h> +#define SHUT_RD SD_READ +#define SHUT_WR SD_WRITE +#define SHUT_RDWR SD_BOTH +#endif + +#ifndef WIN32 +int create_unix_socket(const char *filename); +int connect_unix_socket(const char *filename); +#endif +int create_socket(uint16_t port); +#if defined(WIN32) || defined(__CYGWIN__) +int connect_socket(const char *addr, uint16_t port); +#endif +int check_fd(int fd, fd_mode fdm, unsigned int timeout); + +int shutdown_socket(int fd, int how); +int close_socket(int fd); + +int recv_buf(int fd, void *data, size_t size); +int peek_buf(int fd, void *data, size_t size); +int recv_buf_timeout(int fd, void *data, size_t size, int flags, +					 unsigned int timeout); + +int send_buf(int fd, void *data, size_t size); + +void sock_stuff_set_verbose(int level); + +#endif							/* __SOCK_STUFF_H */ | 
