| /* | |
| * libusbmuxd.c | |
| * | |
| * Copyright (C) 2009-2019 Nikias Bassen <nikias@gmx.li> | |
| * Copyright (C) 2009-2014 Martin Szulecki <m.szulecki@libimobiledevice.org> | |
| * Copyright (C) 2009 Paul Sladen <libiphone@paul.sladen.org> | |
| * | |
| * 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 | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <errno.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #ifdef LIBUSBMUXD_STATIC | |
| #define USBMUXD_API | |
| #elif defined(_WIN32) | |
| #define USBMUXD_API __declspec( dllexport ) | |
| #else | |
| #if __GNUC__ >= 4 | |
| #define USBMUXD_API __attribute__((visibility("default"))) | |
| #else | |
| #define USBMUXD_API | |
| #endif | |
| #endif | |
| #ifndef EPROTO | |
| #define EPROTO 134 | |
| #endif | |
| #ifndef EBADMSG | |
| #define EBADMSG 104 | |
| #endif | |
| #ifndef ECONNREFUSED | |
| #define ECONNREFUSED 107 | |
| #endif | |
| #ifdef _WIN32 | |
| #include <winsock2.h> | |
| #include <windows.h> | |
| #define sleep(x) Sleep(x*1000) | |
| #else | |
| #include <unistd.h> | |
| #include <signal.h> | |
| #include <sys/socket.h> | |
| #include <arpa/inet.h> | |
| #if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME) && !defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME_ERRNO_H) | |
| extern char *program_invocation_short_name; | |
| #endif | |
| #ifdef __APPLE__ | |
| extern int _NSGetExecutablePath(char* buf, uint32_t* bufsize); | |
| #include <sys/stat.h> | |
| #endif | |
| #endif | |
| #ifdef HAVE_INOTIFY | |
| #include <sys/inotify.h> | |
| #include <sys/select.h> | |
| #include <pthread.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" | |
| static int use_inotify = 1; | |
| #endif /* HAVE_INOTIFY */ | |
| #ifndef HAVE_STPNCPY | |
| static char* stpncpy(char *dst, const char *src, size_t len) | |
| { | |
| size_t n = strlen(src); | |
| if (n > len) | |
| n = len; | |
| return strncpy(dst, src, len) + n; | |
| } | |
| #endif | |
| #ifdef _MSC_VER | |
| #define strcasecmp _stricmp | |
| #endif | |
| #include <plist/plist.h> | |
| #define PLIST_CLIENT_VERSION_STRING PACKAGE_STRING | |
| #define PLIST_LIBUSBMUX_VERSION 3 | |
| static char *bundle_id = NULL; | |
| static char *prog_name = NULL; | |
| // usbmuxd public interface | |
| #include "usbmuxd.h" | |
| // usbmuxd protocol | |
| #include "usbmuxd-proto.h" | |
| // socket utility functions | |
| #include <libimobiledevice-glue/socket.h> | |
| // misc utility functions | |
| #include <libimobiledevice-glue/collection.h> | |
| // threads | |
| #include <libimobiledevice-glue/thread.h> | |
| static int libusbmuxd_debug = 0; | |
| #ifndef PACKAGE | |
| #define PACKAGE "libusbmuxd" | |
| #endif | |
| #define LIBUSBMUXD_DEBUG(level, format, ...) if (level <= libusbmuxd_debug) fprintf(stderr, ("[" PACKAGE "] " format), __VA_ARGS__); fflush(stderr); | |
| #define LIBUSBMUXD_ERROR(format, ...) LIBUSBMUXD_DEBUG(0, format, __VA_ARGS__) | |
| static struct collection devices; | |
| static THREAD_T devmon = THREAD_T_NULL; | |
| static int listenfd = -1; | |
| static int running = 0; | |
| static int cancelling = 0; | |
| static volatile int use_tag = 0; | |
| static volatile int proto_version = 1; | |
| static volatile int try_list_devices = 1; | |
| struct usbmuxd_subscription_context { | |
| usbmuxd_event_cb_t callback; | |
| void *user_data; | |
| }; | |
| static struct usbmuxd_subscription_context *event_ctx = NULL; | |
| static struct collection listeners; | |
| thread_once_t listener_init_once = THREAD_ONCE_INIT; | |
| mutex_t listener_mutex; | |
| /** | |
| * 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() | |
| { | |
| int res = -1; | |
| char *usbmuxd_socket_addr = getenv("USBMUXD_SOCKET_ADDRESS"); | |
| if (usbmuxd_socket_addr) { | |
| if (strncmp(usbmuxd_socket_addr, "UNIX:", 5) == 0) { | |
| #if defined(_WIN32) || defined(__CYGWIN__) | |
| /* not supported, ignore */ | |
| #else | |
| if (usbmuxd_socket_addr[5] != '\0') { | |
| res = socket_connect_unix(usbmuxd_socket_addr+5); | |
| if (res < 0) { | |
| res = -errno; | |
| } | |
| return res; | |
| } | |
| #endif | |
| } else { | |
| uint16_t port = 0; | |
| char *p = strrchr(usbmuxd_socket_addr, ':'); | |
| if (p) { | |
| char *endp = NULL; | |
| long l_port = strtol(p+1, &endp, 10); | |
| if (endp && *endp == '\0') { | |
| if (l_port > 0 && l_port < 65536) { | |
| port = (uint16_t)l_port; | |
| } | |
| } | |
| } | |
| if (p && port > 0) { | |
| char *connect_addr = NULL; | |
| if (usbmuxd_socket_addr[0] == '[') { | |
| connect_addr = strdup(usbmuxd_socket_addr+1); | |
| connect_addr[p - usbmuxd_socket_addr - 1] = '\0'; | |
| p = strrchr(connect_addr, ']'); | |
| if (p) { | |
| *p = '\0'; | |
| } | |
| } else { | |
| connect_addr = strdup(usbmuxd_socket_addr); | |
| connect_addr[p - usbmuxd_socket_addr] = '\0'; | |
| } | |
| if (connect_addr && *connect_addr != '\0') { | |
| res = socket_connect(connect_addr, port); | |
| #ifdef HAVE_INOTIFY | |
| use_inotify = 0; | |
| #endif | |
| free(connect_addr); | |
| if (res < 0) { | |
| res = -errno; | |
| } | |
| return res; | |
| } | |
| free(connect_addr); | |
| } | |
| } | |
| } | |
| #if defined(_WIN32) || defined(__CYGWIN__) | |
| res = socket_connect("127.0.0.1", USBMUXD_SOCKET_PORT); | |
| #else | |
| res = socket_connect_unix(USBMUXD_SOCKET_FILE); | |
| #endif | |
| if (res < 0) { | |
| res = -errno; | |
| } | |
| return res; | |
| } | |
| static void sanitize_udid(usbmuxd_device_info_t *devinfo) | |
| { | |
| if (!devinfo) | |
| return; | |
| if (strlen(devinfo->udid) == 24) { | |
| memmove(&devinfo->udid[9], &devinfo->udid[8], 17); | |
| devinfo->udid[8] = '-'; | |
| } | |
| if (strcasecmp(devinfo->udid, "ffffffffffffffffffffffffffffffffffffffff") == 0) { | |
| sprintf(devinfo->udid + 32, "%08x", devinfo->handle); | |
| } | |
| } | |
| static usbmuxd_device_info_t *device_info_from_plist(plist_t props) | |
| { | |
| usbmuxd_device_info_t* devinfo = NULL; | |
| plist_t n = NULL; | |
| uint64_t val = 0; | |
| char *strval = NULL; | |
| devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); | |
| if (!devinfo) | |
| return NULL; | |
| memset(devinfo, 0, sizeof(usbmuxd_device_info_t)); | |
| n = plist_dict_get_item(props, "DeviceID"); | |
| if (n && plist_get_node_type(n) == PLIST_UINT) { | |
| plist_get_uint_val(n, &val); | |
| devinfo->handle = (uint32_t)val; | |
| } | |
| n = plist_dict_get_item(props, "ProductID"); | |
| if (n && plist_get_node_type(n) == PLIST_UINT) { | |
| plist_get_uint_val(n, &val); | |
| devinfo->product_id = (uint32_t)val; | |
| } | |
| n = plist_dict_get_item(props, "SerialNumber"); | |
| if (n && plist_get_node_type(n) == PLIST_STRING) { | |
| plist_get_string_val(n, &strval); | |
| if (strval) { | |
| char *t = stpncpy(devinfo->udid, strval, sizeof(devinfo->udid)-1); | |
| *t = '\0'; | |
| sanitize_udid(devinfo); | |
| free(strval); | |
| } | |
| } | |
| n = plist_dict_get_item(props, "ConnectionType"); | |
| if (n && plist_get_node_type(n) == PLIST_STRING) { | |
| plist_get_string_val(n, &strval); | |
| if (strval) { | |
| if (strcmp(strval, "USB") == 0) { | |
| devinfo->conn_type = CONNECTION_TYPE_USB; | |
| } else if (strcmp(strval, "Network") == 0) { | |
| devinfo->conn_type = CONNECTION_TYPE_NETWORK; | |
| n = plist_dict_get_item(props, "NetworkAddress"); | |
| if (n && plist_get_node_type(n) == PLIST_DATA) { | |
| uint64_t addr_len = 0; | |
| const char *netaddr = plist_get_data_ptr(n, &addr_len); | |
| if (netaddr && addr_len > 0 && addr_len < sizeof(devinfo->conn_data)) { | |
| memcpy(devinfo->conn_data, netaddr, addr_len); | |
| } | |
| } | |
| } else { | |
| LIBUSBMUXD_ERROR("%s: Unexpected ConnectionType '%s'\n", __func__, strval); | |
| } | |
| free(strval); | |
| } | |
| } | |
| if (!devinfo->udid[0]) { | |
| LIBUSBMUXD_ERROR("%s: Failed to get SerialNumber (UDID)!\n", __func__); | |
| free(devinfo); | |
| return NULL; | |
| } | |
| if (!devinfo->conn_type) { | |
| LIBUSBMUXD_ERROR("%s: Failed to get ConnectionType!\n", __func__); | |
| free(devinfo); | |
| devinfo = NULL; | |
| } else if (devinfo->conn_type == CONNECTION_TYPE_NETWORK && !devinfo->conn_data[0]) { | |
| LIBUSBMUXD_ERROR("%s: Failed to get EscapedFullServiceName!\n", __func__); | |
| free(devinfo); | |
| devinfo = NULL; | |
| } | |
| return devinfo; | |
| } | |
| static usbmuxd_device_info_t *device_info_from_device_record(struct usbmuxd_device_record *dev) | |
| { | |
| if (!dev) { | |
| return NULL; | |
| } | |
| usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)malloc(sizeof(usbmuxd_device_info_t)); | |
| if (!devinfo) { | |
| LIBUSBMUXD_ERROR("%s: Out of memory while allocating device info object\n", __func__); | |
| return NULL; | |
| } | |
| devinfo->handle = dev->device_id; | |
| devinfo->product_id = dev->product_id; | |
| char *t = stpncpy(devinfo->udid, dev->serial_number, sizeof(devinfo->udid)-2); | |
| *t = '\0'; | |
| sanitize_udid(devinfo); | |
| return devinfo; | |
| } | |
| 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 = socket_receive_timeout(sfd, &hdr, sizeof(hdr), 0, timeout); | |
| if (recv_len < 0) { | |
| if (!cancelling) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error receiving packet: %s\n", __func__, strerror(-recv_len)); | |
| } | |
| return recv_len; | |
| } | |
| if ((size_t)recv_len < sizeof(hdr)) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Received packet is too small, got %d bytes!\n", __func__, recv_len); | |
| return recv_len; | |
| } | |
| uint32_t payload_size = hdr.length - sizeof(hdr); | |
| if (payload_size > 0) { | |
| payload_loc = (char*)malloc(payload_size); | |
| uint32_t rsize = 0; | |
| do { | |
| int res = socket_receive_timeout(sfd, payload_loc + rsize, payload_size - rsize, 0, 5000); | |
| if (res < 0) { | |
| break; | |
| } | |
| rsize += res; | |
| } while (rsize < payload_size); | |
| if (rsize != payload_size) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error receiving payload of size %d (bytes received: %d)\n", __func__, payload_size, rsize); | |
| free(payload_loc); | |
| return -EBADMSG; | |
| } | |
| } | |
| 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) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error getting plist from payload!\n", __func__); | |
| return -EBADMSG; | |
| } | |
| plist_t node = plist_dict_get_item(plist, "MessageType"); | |
| if (!node || plist_get_node_type(node) != PLIST_STRING) { | |
| *payload = plist; | |
| hdr.length = sizeof(hdr); | |
| memcpy(header, &hdr, sizeof(hdr)); | |
| return hdr.length; | |
| } | |
| 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 */ | |
| usbmuxd_device_info_t *devinfo = NULL; | |
| plist_t props = plist_dict_get_item(plist, "Properties"); | |
| if (!props) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Could not get properties for message '%s' from plist!\n", __func__, message); | |
| free(message); | |
| plist_free(plist); | |
| return -EBADMSG; | |
| } | |
| devinfo = device_info_from_plist(props); | |
| if (!devinfo) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Could not create device info object from properties!\n", __func__); | |
| free(message); | |
| plist_free(plist); | |
| return -EBADMSG; | |
| } | |
| *payload = (void*)devinfo; | |
| hdr.length = sizeof(hdr) + sizeof(usbmuxd_device_info_t); | |
| 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 if (strcmp(message, "Paired") == 0) { | |
| /* device pair 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_PAIRED; | |
| } | |
| } else { | |
| char *xml = NULL; | |
| uint32_t len = 0; | |
| plist_to_xml(plist, &xml, &len); | |
| LIBUSBMUXD_DEBUG(1, "%s: Unexpected message '%s' in plist:\n%s\n", __func__, message, xml); | |
| free(xml); | |
| free(message); | |
| plist_free(plist); | |
| return -EBADMSG; | |
| } | |
| free(message); | |
| } | |
| plist_free(plist); | |
| } else if (hdr.message == MESSAGE_DEVICE_ADD) { | |
| usbmuxd_device_info_t *devinfo = device_info_from_device_record((struct usbmuxd_device_record*)payload_loc); | |
| free(payload_loc); | |
| *payload = devinfo; | |
| } else { | |
| *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, void **result_plist) | |
| { | |
| struct usbmuxd_header hdr; | |
| int recv_len; | |
| uint32_t *res = NULL; | |
| if (!result) { | |
| return -EINVAL; | |
| } | |
| *result = -1; | |
| if (result_plist) { | |
| *result_plist = NULL; | |
| } | |
| recv_len = receive_packet(sfd, &hdr, (void**)&res, 5000); | |
| if (recv_len < 0 || (size_t)recv_len < sizeof(hdr)) { | |
| free(res); | |
| return (recv_len < 0 ? recv_len : -EPROTO); | |
| } | |
| if (hdr.message == MESSAGE_RESULT) { | |
| int ret = 0; | |
| if (hdr.tag != tag) { | |
| LIBUSBMUXD_DEBUG(1, "%s: WARNING: tag mismatch (%d != %d). Proceeding anyway.\n", __func__, hdr.tag, tag); | |
| } | |
| if (res) { | |
| memcpy(result, res, sizeof(uint32_t)); | |
| ret = 1; | |
| } | |
| free(res); | |
| return ret; | |
| } | |
| if (hdr.message == MESSAGE_PLIST) { | |
| if (!result_plist) { | |
| LIBUSBMUXD_DEBUG(1, "%s: MESSAGE_PLIST result but result_plist pointer is NULL!\n", __func__); | |
| return -1; | |
| } | |
| *result_plist = (plist_t)res; | |
| *result = RESULT_OK; | |
| return 1; | |
| } | |
| LIBUSBMUXD_DEBUG(1, "%s: Unexpected message of type %d received!\n", __func__, hdr.message); | |
| 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 = socket_send(sfd, &header, sizeof(header)); | |
| if (sent != sizeof(header)) { | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: could not send packet header\n", __func__); | |
| return -1; | |
| } | |
| if (payload && (payload_size > 0)) { | |
| uint32_t ssize = 0; | |
| do { | |
| int res = socket_send(sfd, (char*)payload + ssize, payload_size - ssize); | |
| if (res < 0) { | |
| break; | |
| } | |
| ssize += res; | |
| } while (ssize < payload_size); | |
| sent += ssize; | |
| } | |
| if (sent != (int)header.length) { | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: could not send whole packet (sent %d of %d)\n", __func__, sent, header.length); | |
| socket_close(sfd); | |
| return -1; | |
| } | |
| return sent; | |
| } | |
| static int send_plist_packet(int sfd, uint32_t tag, plist_t message) | |
| { | |
| int res; | |
| char *payload = NULL; | |
| uint32_t payload_size = 0; | |
| plist_to_xml(message, &payload, &payload_size); | |
| res = send_packet(sfd, MESSAGE_PLIST, tag, payload, payload_size); | |
| free(payload); | |
| return res; | |
| } | |
| static void get_bundle_id() | |
| { | |
| #if defined (__APPLE__) | |
| char CONTENTS_INFO_PLIST[] = "Contents/Info.plist"; | |
| char* execpath = malloc(1024); | |
| uint32_t size = 1024; | |
| if (_NSGetExecutablePath(execpath, &size) != 0) { | |
| free(execpath); | |
| return; | |
| } | |
| // strip off executable name | |
| char *p = execpath + strlen(execpath) - 1; | |
| while (p > execpath && *p != '/') p--; | |
| if (*p == '/') *p = '\0'; | |
| // now walk back trying to find "/Contents/MacOS", and strip it off | |
| int macos_found = 0; | |
| while (p > execpath) { | |
| p--; | |
| if (*p != '/') continue; | |
| if (strcmp(p, "/.") == 0) { | |
| *p = '\0'; | |
| } else if (!macos_found && strcmp(p, "/MacOS") == 0) { | |
| *p = '\0'; | |
| macos_found++; | |
| } else if (macos_found && strcmp(p, "/Contents") == 0) { | |
| *p = '\0'; | |
| break; | |
| } else { | |
| break; | |
| } | |
| } | |
| // now just append "/Contents/Info.plist" | |
| size_t len = strlen(execpath) + sizeof(CONTENTS_INFO_PLIST) + 1; | |
| char *infopl = malloc(len); | |
| snprintf(infopl, len, "%s/%s", execpath, CONTENTS_INFO_PLIST); | |
| free(execpath); | |
| struct stat fst; | |
| fst.st_size = 0; | |
| if (stat(infopl, &fst) != 0) { | |
| free(infopl); | |
| return; | |
| } | |
| size_t fsize = fst.st_size; | |
| if (fsize < 8) { | |
| free(infopl); | |
| return; | |
| } | |
| FILE *f = fopen(infopl, "r"); | |
| free(infopl); | |
| if (!f) | |
| return; | |
| char *buf = malloc(fsize); | |
| if (!buf) | |
| return; | |
| if (fread(buf, 1, fsize, f) == fsize) { | |
| plist_t pl = NULL; | |
| if (memcmp(buf, "bplist00", 8) == 0) { | |
| plist_from_bin(buf, fst.st_size, &pl); | |
| } else { | |
| plist_from_xml(buf, fst.st_size, &pl); | |
| } | |
| if (pl) { | |
| plist_t bid = plist_dict_get_item(pl, "CFBundleIdentifier"); | |
| if (plist_get_node_type(bid) == PLIST_STRING) { | |
| plist_get_string_val(bid, &bundle_id); | |
| } | |
| plist_free(pl); | |
| } | |
| } | |
| free(buf); | |
| fclose(f); | |
| #endif | |
| } | |
| static void get_prog_name() | |
| { | |
| #if defined(__APPLE__) || defined(__FreeBSD__) | |
| const char *pname = getprogname(); | |
| if (pname) { | |
| prog_name = strdup(pname); | |
| } | |
| #elif defined (_WIN32) | |
| TCHAR *_pname = malloc((MAX_PATH+1) * sizeof(TCHAR)); | |
| if (GetModuleFileName(NULL, _pname, MAX_PATH+1) > 0) { | |
| char* pname = NULL; | |
| #if defined(UNICODE) || defined(_UNICODE) | |
| char* __pname = NULL; | |
| int l = WideCharToMultiByte(CP_UTF8, 0, _pname, -1, NULL, 0, NULL, NULL); | |
| if (l > 0) { | |
| __pname = malloc(l); | |
| if (WideCharToMultiByte(CP_UTF8, 0, _pname, -1, __pname, l, NULL, NULL) > 0) { | |
| pname = __pname; | |
| } | |
| } | |
| #else | |
| pname = _pname; | |
| #endif | |
| if (pname) { | |
| char *p = pname+strlen(pname)-1; | |
| while (p > pname && *p != '\\' && *p != '/') p--; | |
| if (*p == '\\' || *p == '/') p++; | |
| prog_name = strdup(p); | |
| } | |
| #if defined(UNICODE) || defined(_UNICODE) | |
| free(__pname); | |
| #endif | |
| } | |
| free(_pname); | |
| #elif defined (HAVE_PROGRAM_INVOCATION_SHORT_NAME) | |
| char *pname = program_invocation_short_name; | |
| if (pname) { | |
| prog_name = strdup(pname); | |
| } | |
| #elif defined (__linux__) | |
| FILE *f = fopen("/proc/self/stat", "r"); | |
| if (!f) { | |
| return; | |
| } | |
| char *tmpbuf = malloc(512); | |
| size_t r = fread(tmpbuf, 1, 512, f); | |
| if (r > 0) { | |
| char *p = tmpbuf; | |
| while (((size_t)(p-tmpbuf) < r) && (*p != '(') && (*p != '\0')) p++; | |
| if (*p == '(') { | |
| p++; | |
| char *pname = p; | |
| while (((size_t)(p-tmpbuf) < r) && (*p != ')') && (*p != '\0')) p++; | |
| if (*p == ')') { | |
| *p = '\0'; | |
| prog_name = strdup(pname); | |
| } | |
| } | |
| } | |
| free(tmpbuf); | |
| fclose(f); | |
| #else | |
| #warning FIXME: no method to determine program name | |
| #endif | |
| } | |
| static plist_t create_plist_message(const char* message_type) | |
| { | |
| if (!bundle_id) { | |
| get_bundle_id(); | |
| } | |
| if (!prog_name) { | |
| get_prog_name(); | |
| } | |
| plist_t plist = plist_new_dict(); | |
| if (bundle_id) { | |
| plist_dict_set_item(plist, "BundleID", plist_new_string(bundle_id)); | |
| } | |
| plist_dict_set_item(plist, "ClientVersionString", plist_new_string(PLIST_CLIENT_VERSION_STRING)); | |
| plist_dict_set_item(plist, "MessageType", plist_new_string(message_type)); | |
| if (prog_name) { | |
| plist_dict_set_item(plist, "ProgName", plist_new_string(prog_name)); | |
| } | |
| plist_dict_set_item(plist, "kLibUSBMuxVersion", plist_new_uint(PLIST_LIBUSBMUX_VERSION)); | |
| return plist; | |
| } | |
| static int send_listen_packet(int sfd, uint32_t tag) | |
| { | |
| int res = 0; | |
| if (proto_version == 1) { | |
| /* construct message plist */ | |
| plist_t plist = create_plist_message("Listen"); | |
| res = send_plist_packet(sfd, tag, plist); | |
| plist_free(plist); | |
| } else { | |
| /* 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; | |
| if (proto_version == 1) { | |
| /* construct message plist */ | |
| plist_t plist = create_plist_message("Connect"); | |
| plist_dict_set_item(plist, "DeviceID", plist_new_uint(device_id)); | |
| plist_dict_set_item(plist, "PortNumber", plist_new_uint(htons(port))); | |
| res = send_plist_packet(sfd, tag, plist); | |
| plist_free(plist); | |
| } else { | |
| /* 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; | |
| } | |
| static int send_list_devices_packet(int sfd, uint32_t tag) | |
| { | |
| int res = -1; | |
| /* construct message plist */ | |
| plist_t plist = create_plist_message("ListDevices"); | |
| res = send_plist_packet(sfd, tag, plist); | |
| plist_free(plist); | |
| return res; | |
| } | |
| static int send_read_buid_packet(int sfd, uint32_t tag) | |
| { | |
| int res = -1; | |
| /* construct message plist */ | |
| plist_t plist = create_plist_message("ReadBUID"); | |
| res = send_plist_packet(sfd, tag, plist); | |
| plist_free(plist); | |
| return res; | |
| } | |
| static int send_pair_record_packet(int sfd, uint32_t tag, const char* msgtype, const char* pair_record_id, uint32_t device_id, plist_t data) | |
| { | |
| int res = -1; | |
| /* construct message plist */ | |
| plist_t plist = create_plist_message(msgtype); | |
| plist_dict_set_item(plist, "PairRecordID", plist_new_string(pair_record_id)); | |
| if (data) { | |
| plist_dict_set_item(plist, "PairRecordData", plist_copy(data)); | |
| } | |
| if (device_id > 0) { | |
| plist_dict_set_item(plist, "DeviceID", plist_new_uint(device_id)); | |
| } | |
| res = send_plist_packet(sfd, tag, plist); | |
| plist_free(plist); | |
| 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(const usbmuxd_device_info_t *dev, enum usbmuxd_event_type event) | |
| { | |
| usbmuxd_event_t ev; | |
| if (!dev) { | |
| return; | |
| } | |
| ev.event = event; | |
| memcpy(&ev.device, dev, sizeof(usbmuxd_device_info_t)); | |
| mutex_lock(&listener_mutex); | |
| FOREACH(struct usbmuxd_subscription_context* context, &listeners) { | |
| context->callback(&ev, context->user_data); | |
| } ENDFOREACH | |
| mutex_unlock(&listener_mutex); | |
| } | |
| static int usbmuxd_listen_poll() | |
| { | |
| int sfd; | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| while (1) { | |
| mutex_lock(&listener_mutex); | |
| int num = collection_count(&listeners); | |
| mutex_unlock(&listener_mutex); | |
| if (num <= 0) { | |
| break; | |
| } | |
| if ((sfd = connect_usbmuxd_socket()) >= 0) { | |
| break; | |
| } | |
| sleep(1); | |
| } | |
| } | |
| return sfd; | |
| } | |
| #ifdef HAVE_INOTIFY | |
| #ifndef HAVE_PSELECT | |
| static int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask) | |
| { | |
| int ready; | |
| struct timeval tv; | |
| struct timeval *p_timeout; | |
| sigset_t origmask; | |
| if (timeout) { | |
| tv.tv_sec = timeout->tv_sec; | |
| tv.tv_usec = timeout->tv_nsec / 1000; | |
| p_timeout = &tv; | |
| } else { | |
| p_timeout = NULL; | |
| } | |
| pthread_sigmask(SIG_SETMASK, sigmask, &origmask); | |
| ready = select(nfds, readfds, writefds, exceptfds, p_timeout); | |
| pthread_sigmask(SIG_SETMASK, &origmask, NULL); | |
| return ready; | |
| } | |
| #endif | |
| 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_init1(IN_CLOEXEC); | |
| if (inot_fd < 0) { | |
| LIBUSBMUXD_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) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Failed to setup watch descriptor for socket dir\n", __func__); | |
| close (inot_fd); | |
| return -2; | |
| } | |
| while (1) { | |
| fd_set rfds; | |
| struct timespec tv = {1, 0}; | |
| FD_ZERO(&rfds); | |
| FD_SET(inot_fd, &rfds); | |
| int r = pselect(inot_fd+1, &rfds, NULL, NULL, &tv, NULL); | |
| if (r < 0) { | |
| break; | |
| } | |
| if (r == 0) { | |
| continue; | |
| } | |
| 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[0] != 0 && | |
| strcmp(pevent->name, USBMUXD_SOCKET_NAME) == 0) { | |
| /* retry if usbmuxd isn't ready yet */ | |
| int retry = 10; | |
| while (--retry >= 0) { | |
| if ((sfd = connect_usbmuxd_socket ()) >= 0) { | |
| break; | |
| } | |
| sleep(1); | |
| } | |
| 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; | |
| int tag; | |
| retry: | |
| #ifdef HAVE_INOTIFY | |
| sfd = usbmuxd_listen_inotify(); | |
| if (sfd == -2) | |
| sfd = usbmuxd_listen_poll(); | |
| #else | |
| sfd = usbmuxd_listen_poll(); | |
| #endif | |
| if (sfd < 0) { | |
| if (!cancelling) { | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: usbmuxd was supposed to be running here...\n", __func__); | |
| } | |
| return sfd; | |
| } | |
| tag = ++use_tag; | |
| if (send_listen_packet(sfd, tag) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: could not send listen packet\n", __func__); | |
| socket_close(sfd); | |
| return -1; | |
| } | |
| if ((usbmuxd_get_result(sfd, tag, &res, NULL) == 1) && (res != 0)) { | |
| socket_close(sfd); | |
| if ((res == RESULT_BADVERSION) && (proto_version == 1)) { | |
| proto_version = 0; | |
| goto retry; | |
| } | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: did not get OK but %d\n", __func__, res); | |
| return -1; | |
| } | |
| 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) | |
| { | |
| struct usbmuxd_header hdr; | |
| void *payload = NULL; | |
| /* block until we receive something */ | |
| if (receive_packet(sfd, &hdr, &payload, 0) < 0) { | |
| if (!cancelling) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error in usbmuxd connection, disconnecting all devices!\n", __func__); | |
| } | |
| // 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(dev, UE_DEVICE_REMOVE); | |
| collection_remove(&devices, dev); | |
| free(dev); | |
| } ENDFOREACH | |
| return -EIO; | |
| } | |
| if ((hdr.length > sizeof(hdr)) && !payload) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Invalid packet received, payload is missing!\n", __func__); | |
| return -EBADMSG; | |
| } | |
| if (hdr.message == MESSAGE_DEVICE_ADD) { | |
| usbmuxd_device_info_t *devinfo = (usbmuxd_device_info_t*)payload; | |
| collection_add(&devices, devinfo); | |
| generate_event(devinfo, UE_DEVICE_ADD); | |
| payload = NULL; | |
| } 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) { | |
| LIBUSBMUXD_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(devinfo, UE_DEVICE_REMOVE); | |
| collection_remove(&devices, devinfo); | |
| free(devinfo); | |
| } | |
| } else if (hdr.message == MESSAGE_DEVICE_PAIRED) { | |
| uint32_t handle; | |
| usbmuxd_device_info_t *devinfo; | |
| memcpy(&handle, payload, sizeof(uint32_t)); | |
| devinfo = devices_find(handle); | |
| if (!devinfo) { | |
| LIBUSBMUXD_DEBUG(1, "%s: WARNING: got paired message for device handle %d, but couldn't find the corresponding handle in the device list. This event will be ignored.\n", __func__, handle); | |
| } else { | |
| generate_event(devinfo, UE_DEVICE_PAIRED); | |
| } | |
| } else if (hdr.length > 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Unexpected message type %d length %d received!\n", __func__, hdr.message, hdr.length); | |
| } | |
| 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); | |
| socket_close(listenfd); | |
| listenfd = -1; | |
| } | |
| /** | |
| * Device Monitor thread function. | |
| * | |
| * This function sets up a connection to usbmuxd | |
| */ | |
| static void *device_monitor(void *data) | |
| { | |
| running = 1; | |
| collection_init(&devices); | |
| cancelling = 0; | |
| #ifdef HAVE_THREAD_CLEANUP | |
| thread_cleanup_push(device_monitor_cleanup, NULL); | |
| #endif | |
| do { | |
| listenfd = usbmuxd_listen(); | |
| if (listenfd < 0) { | |
| continue; | |
| } | |
| while (running) { | |
| int res = get_next_event(listenfd); | |
| if (res < 0) { | |
| break; | |
| } | |
| } | |
| mutex_lock(&listener_mutex); | |
| if (collection_count(&listeners) == 0) { | |
| running = 0; | |
| } | |
| mutex_unlock(&listener_mutex); | |
| } while (running); | |
| #ifdef HAVE_THREAD_CLEANUP | |
| thread_cleanup_pop(1); | |
| #else | |
| device_monitor_cleanup(NULL); | |
| #endif | |
| return NULL; | |
| } | |
| static void init_listeners(void) | |
| { | |
| collection_init(&listeners); | |
| mutex_init(&listener_mutex); | |
| } | |
| int usbmuxd_events_subscribe(usbmuxd_subscription_context_t *context, usbmuxd_event_cb_t callback, void *user_data) | |
| { | |
| if (!context || !callback) { | |
| return -EINVAL; | |
| } | |
| thread_once(&listener_init_once, init_listeners); | |
| mutex_lock(&listener_mutex); | |
| *context = malloc(sizeof(struct usbmuxd_subscription_context)); | |
| if (!*context) { | |
| mutex_unlock(&listener_mutex); | |
| LIBUSBMUXD_ERROR("ERROR: %s: malloc failed\n", __func__); | |
| return -ENOMEM; | |
| } | |
| (*context)->callback = callback; | |
| (*context)->user_data = user_data; | |
| collection_add(&listeners, *context); | |
| if (devmon == THREAD_T_NULL || !thread_alive(devmon)) { | |
| mutex_unlock(&listener_mutex); | |
| int res = thread_new(&devmon, device_monitor, NULL); | |
| if (res != 0) { | |
| free(*context); | |
| LIBUSBMUXD_DEBUG(1, "%s: ERROR: Could not start device watcher thread!\n", __func__); | |
| return res; | |
| } | |
| } else { | |
| /* we need to submit DEVICE_ADD events to the new listener */ | |
| FOREACH(usbmuxd_device_info_t *dev, &devices) { | |
| if (dev) { | |
| usbmuxd_event_t ev; | |
| ev.event = UE_DEVICE_ADD; | |
| memcpy(&ev.device, dev, sizeof(usbmuxd_device_info_t)); | |
| (*context)->callback(&ev, (*context)->user_data); | |
| } | |
| } ENDFOREACH | |
| mutex_unlock(&listener_mutex); | |
| } | |
| return 0; | |
| } | |
| int usbmuxd_events_unsubscribe(usbmuxd_subscription_context_t context) | |
| { | |
| int ret = 0; | |
| int num = 0; | |
| if (!context) { | |
| return -EINVAL; | |
| } | |
| mutex_lock(&listener_mutex); | |
| if (collection_remove(&listeners, context) == 0) { | |
| FOREACH(usbmuxd_device_info_t *dev, &devices) { | |
| if (dev) { | |
| usbmuxd_event_t ev; | |
| ev.event = UE_DEVICE_REMOVE; | |
| memcpy(&ev.device, dev, sizeof(usbmuxd_device_info_t)); | |
| (context)->callback(&ev, (context)->user_data); | |
| } | |
| } ENDFOREACH | |
| free(context); | |
| } | |
| num = collection_count(&listeners); | |
| mutex_unlock(&listener_mutex); | |
| if (num == 0) { | |
| int res = 0; | |
| cancelling = 1; | |
| socket_shutdown(listenfd, SHUT_RDWR); | |
| if (thread_alive(devmon)) { | |
| if (thread_cancel(devmon) < 0) { | |
| running = 0; | |
| } | |
| #if defined(HAVE_INOTIFY) && !defined(HAVE_PTHREAD_CANCEL) | |
| pthread_kill(devmon, SIGINT); | |
| #endif | |
| res = thread_join(devmon); | |
| thread_free(devmon); | |
| devmon = THREAD_T_NULL; | |
| } | |
| if ((res != 0) && (res != ESRCH)) { | |
| ret = res; | |
| } | |
| } | |
| return ret; | |
| } | |
| int usbmuxd_subscribe(usbmuxd_event_cb_t callback, void *user_data) | |
| { | |
| if (!callback) { | |
| return -EINVAL; | |
| } | |
| if (event_ctx) { | |
| usbmuxd_events_unsubscribe(event_ctx); | |
| event_ctx = NULL; | |
| } | |
| return usbmuxd_events_subscribe(&event_ctx, callback, user_data); | |
| } | |
| int usbmuxd_unsubscribe(void) | |
| { | |
| int res = usbmuxd_events_unsubscribe(event_ctx); | |
| event_ctx = NULL; | |
| return res; | |
| } | |
| int usbmuxd_get_device_list(usbmuxd_device_info_t **device_list) | |
| { | |
| int sfd; | |
| int tag; | |
| int listen_success = 0; | |
| uint32_t res; | |
| struct collection tmpdevs; | |
| usbmuxd_device_info_t *newlist = NULL; | |
| struct usbmuxd_header hdr; | |
| int dev_cnt = 0; | |
| void *payload = NULL; | |
| *device_list = NULL; | |
| retry: | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: error opening socket!\n", __func__); | |
| return sfd; | |
| } | |
| tag = ++use_tag; | |
| if ((proto_version == 1) && (try_list_devices)) { | |
| if (send_list_devices_packet(sfd, tag) > 0) { | |
| plist_t list = NULL; | |
| if ((usbmuxd_get_result(sfd, tag, &res, &list) == 1) && (res == 0)) { | |
| plist_t devlist = plist_dict_get_item(list, "DeviceList"); | |
| if (devlist && plist_get_node_type(devlist) == PLIST_ARRAY) { | |
| collection_init(&tmpdevs); | |
| uint32_t numdevs = plist_array_get_size(devlist); | |
| uint32_t i; | |
| for (i = 0; i < numdevs; i++) { | |
| plist_t pdev = plist_array_get_item(devlist, i); | |
| plist_t props = plist_dict_get_item(pdev, "Properties"); | |
| usbmuxd_device_info_t *devinfo = device_info_from_plist(props); | |
| if (!devinfo) { | |
| socket_close(sfd); | |
| LIBUSBMUXD_DEBUG(1, "%s: Could not create device info object from properties!\n", __func__); | |
| plist_free(list); | |
| return -1; | |
| } | |
| collection_add(&tmpdevs, devinfo); | |
| } | |
| plist_free(list); | |
| goto got_device_list; | |
| } | |
| } else { | |
| if (res == RESULT_BADVERSION) { | |
| proto_version = 0; | |
| } | |
| socket_close(sfd); | |
| try_list_devices = 0; | |
| plist_free(list); | |
| goto retry; | |
| } | |
| plist_free(list); | |
| } | |
| } | |
| tag = ++use_tag; | |
| if (send_listen_packet(sfd, tag) > 0) { | |
| res = -1; | |
| // get response | |
| if ((usbmuxd_get_result(sfd, tag, &res, NULL) == 1) && (res == 0)) { | |
| listen_success = 1; | |
| } else { | |
| socket_close(sfd); | |
| if ((res == RESULT_BADVERSION) && (proto_version == 1)) { | |
| proto_version = 0; | |
| goto retry; | |
| } | |
| LIBUSBMUXD_DEBUG(1, "%s: Did not get response to scan request (with result=0)...\n", __func__); | |
| return res; | |
| } | |
| } | |
| if (!listen_success) { | |
| socket_close(sfd); | |
| LIBUSBMUXD_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, 100) > 0) { | |
| if (hdr.message == MESSAGE_DEVICE_ADD) { | |
| usbmuxd_device_info_t *devinfo = payload; | |
| collection_add(&tmpdevs, devinfo); | |
| payload = NULL; | |
| } 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 { | |
| LIBUSBMUXD_DEBUG(1, "%s: Unexpected message %d\n", __func__, hdr.message); | |
| } | |
| free(payload); | |
| } else { | |
| // we _should_ have all of them now. | |
| // or perhaps an error occurred. | |
| break; | |
| } | |
| } | |
| got_device_list: | |
| // explicitly close connection | |
| socket_close(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; | |
| usbmuxd_device_info_t *dev = NULL; | |
| int result = 0; | |
| int i; | |
| if (!device) { | |
| return -EINVAL; | |
| } | |
| if (usbmuxd_get_device_list(&dev_list) < 0) { | |
| return -ENODEV; | |
| } | |
| for (i = 0; dev_list[i].handle > 0; i++) { | |
| if (!udid) { | |
| if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { | |
| dev = &dev_list[i]; | |
| break; | |
| } | |
| } else if (!strcmp(udid, dev_list[i].udid)) { | |
| if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { | |
| dev = &dev_list[i]; | |
| break; | |
| } | |
| } | |
| } | |
| if (dev) { | |
| device->handle = dev->handle; | |
| device->product_id = dev->product_id; | |
| char *t = stpncpy(device->udid, dev->udid, sizeof(device->udid)-1); | |
| *t = '\0'; | |
| device->conn_type = dev->conn_type; | |
| memcpy(device->conn_data, dev->conn_data, sizeof(device->conn_data)); | |
| result = 1; | |
| } | |
| usbmuxd_device_list_free(&dev_list); | |
| return result; | |
| } | |
| int usbmuxd_get_device(const char *udid, usbmuxd_device_info_t *device, enum usbmux_lookup_options options) | |
| { | |
| usbmuxd_device_info_t *dev_list = NULL; | |
| usbmuxd_device_info_t *dev_network = NULL; | |
| usbmuxd_device_info_t *dev_usbmuxd = NULL; | |
| usbmuxd_device_info_t *dev = NULL; | |
| int result = 0; | |
| int i; | |
| if (!device) { | |
| return -EINVAL; | |
| } | |
| if (usbmuxd_get_device_list(&dev_list) < 0) { | |
| return -ENODEV; | |
| } | |
| if (options == 0) { | |
| options = DEVICE_LOOKUP_USBMUX; | |
| } | |
| for (i = 0; dev_list[i].handle > 0; i++) { | |
| if (!udid) { | |
| if ((options & DEVICE_LOOKUP_USBMUX) && (dev_list[i].conn_type == CONNECTION_TYPE_USB)) { | |
| dev_usbmuxd = &dev_list[i]; | |
| break; | |
| } | |
| if ((options & DEVICE_LOOKUP_NETWORK) && (dev_list[i].conn_type == CONNECTION_TYPE_NETWORK)) { | |
| dev_network = &dev_list[i]; | |
| break; | |
| } | |
| } else if (!strcmp(udid, dev_list[i].udid)) { | |
| if ((options & DEVICE_LOOKUP_USBMUX) && (dev_list[i].conn_type == CONNECTION_TYPE_USB)) { | |
| dev_usbmuxd = &dev_list[i]; | |
| } else if ((options & DEVICE_LOOKUP_NETWORK) && (dev_list[i].conn_type == CONNECTION_TYPE_NETWORK)) { | |
| dev_network = &dev_list[i]; | |
| } | |
| } | |
| if (dev_usbmuxd && dev_network) { | |
| break; | |
| } | |
| } | |
| if (dev_network && dev_usbmuxd) { | |
| dev = (options & DEVICE_LOOKUP_PREFER_NETWORK) ? dev_network : dev_usbmuxd; | |
| } else if (dev_network) { | |
| dev = dev_network; | |
| } else if (dev_usbmuxd) { | |
| dev = dev_usbmuxd; | |
| } | |
| if (dev) { | |
| device->handle = dev->handle; | |
| device->product_id = dev->product_id; | |
| char *t = stpncpy(device->udid, dev->udid, sizeof(device->udid)-1); | |
| *t = '\0'; | |
| device->conn_type = dev->conn_type; | |
| memcpy(device->conn_data, dev->conn_data, sizeof(device->conn_data)); | |
| result = 1; | |
| } | |
| free(dev_list); | |
| return result; | |
| } | |
| int usbmuxd_connect(const uint32_t handle, const unsigned short port) | |
| { | |
| int sfd; | |
| int tag; | |
| int connected = 0; | |
| int result = EBADF; | |
| retry: | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", __func__, strerror(-sfd)); | |
| return sfd; | |
| } | |
| tag = ++use_tag; | |
| if (send_connect_packet(sfd, tag, handle, (uint16_t)port) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error sending connect message!\n", __func__); | |
| } else { | |
| // read ACK | |
| uint32_t res = -1; | |
| LIBUSBMUXD_DEBUG(2, "%s: Reading connect result...\n", __func__); | |
| if (usbmuxd_get_result(sfd, tag, &res, NULL) == 1) { | |
| if (res == 0) { | |
| LIBUSBMUXD_DEBUG(2, "%s: Connect success!\n", __func__); | |
| connected = 1; | |
| } else { | |
| if ((res == RESULT_BADVERSION) && (proto_version == 1)) { | |
| proto_version = 0; | |
| socket_close(sfd); | |
| goto retry; | |
| } | |
| LIBUSBMUXD_DEBUG(1, "%s: Connect failed, Error code=%d\n", __func__, res); | |
| if (res == RESULT_CONNREFUSED) { | |
| result = ECONNREFUSED; | |
| } else if (res == RESULT_BADDEV) { | |
| result = ENODEV; | |
| } else { | |
| result = EBADF; | |
| } | |
| } | |
| } | |
| } | |
| if (connected) { | |
| return sfd; | |
| } | |
| socket_close(sfd); | |
| return -result; | |
| } | |
| int usbmuxd_disconnect(int sfd) | |
| { | |
| return socket_close(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 = socket_send(sfd, (void*)data, len); | |
| if (num_sent < 0) { | |
| *sent_bytes = 0; | |
| LIBUSBMUXD_DEBUG(1, "%s: Error %d when sending: %s\n", __func__, -num_sent, strerror(-num_sent)); | |
| return num_sent; | |
| } | |
| if ((uint32_t)num_sent < len) { | |
| LIBUSBMUXD_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 = socket_receive_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); | |
| } | |
| int usbmuxd_read_buid(char **buid) | |
| { | |
| int sfd; | |
| int tag; | |
| int ret = -1; | |
| if (!buid) { | |
| return -EINVAL; | |
| } | |
| *buid = NULL; | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", __func__, strerror(-sfd)); | |
| return sfd; | |
| } | |
| proto_version = 1; | |
| tag = ++use_tag; | |
| if (send_read_buid_packet(sfd, tag) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error sending ReadBUID message!\n", __func__); | |
| } else { | |
| uint32_t rc = 0; | |
| plist_t pl = NULL; | |
| ret = usbmuxd_get_result(sfd, tag, &rc, &pl); | |
| if ((ret == 1) && (rc == 0)) { | |
| plist_t node = plist_dict_get_item(pl, "BUID"); | |
| if (node && plist_get_node_type(node) == PLIST_STRING) { | |
| plist_get_string_val(node, buid); | |
| } | |
| ret = 0; | |
| } else if (ret == 1) { | |
| ret = -(int)rc; | |
| } | |
| plist_free(pl); | |
| } | |
| socket_close(sfd); | |
| return ret; | |
| } | |
| int usbmuxd_read_pair_record(const char* record_id, char **record_data, uint32_t *record_size) | |
| { | |
| int sfd; | |
| int tag; | |
| int ret = -1; | |
| if (!record_id || !record_data || !record_size) { | |
| return -EINVAL; | |
| } | |
| *record_data = NULL; | |
| *record_size = 0; | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", __func__, strerror(-sfd)); | |
| return sfd; | |
| } | |
| proto_version = 1; | |
| tag = ++use_tag; | |
| if (send_pair_record_packet(sfd, tag, "ReadPairRecord", record_id, 0, NULL) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error sending ReadPairRecord message!\n", __func__); | |
| } else { | |
| uint32_t rc = 0; | |
| plist_t pl = NULL; | |
| ret = usbmuxd_get_result(sfd, tag, &rc, &pl); | |
| if ((ret == 1) && (rc == 0)) { | |
| plist_t node = plist_dict_get_item(pl, "PairRecordData"); | |
| if (node && plist_get_node_type(node) == PLIST_DATA) { | |
| uint64_t int64val = 0; | |
| plist_get_data_val(node, record_data, &int64val); | |
| if (*record_data && int64val > 0) { | |
| *record_size = (uint32_t)int64val; | |
| ret = 0; | |
| } | |
| } | |
| } else if (ret == 1) { | |
| ret = -(int)rc; | |
| } | |
| plist_free(pl); | |
| } | |
| socket_close(sfd); | |
| return ret; | |
| } | |
| int usbmuxd_save_pair_record_with_device_id(const char* record_id, uint32_t device_id, const char *record_data, uint32_t record_size) | |
| { | |
| int sfd; | |
| int tag; | |
| int ret = -1; | |
| if (!record_id || !record_data || !record_size) { | |
| return -EINVAL; | |
| } | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", __func__, strerror(-sfd)); | |
| return sfd; | |
| } | |
| proto_version = 1; | |
| tag = ++use_tag; | |
| plist_t data = plist_new_data(record_data, record_size); | |
| if (send_pair_record_packet(sfd, tag, "SavePairRecord", record_id, device_id, data) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error sending SavePairRecord message!\n", __func__); | |
| } else { | |
| uint32_t rc = 0; | |
| ret = usbmuxd_get_result(sfd, tag, &rc, NULL); | |
| if ((ret == 1) && (rc == 0)) { | |
| ret = 0; | |
| } else if (ret == 1) { | |
| ret = -(int)rc; | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: saving pair record failed: %d\n", __func__, ret); | |
| } | |
| } | |
| plist_free(data); | |
| socket_close(sfd); | |
| return ret; | |
| } | |
| int usbmuxd_save_pair_record(const char* record_id, const char *record_data, uint32_t record_size) | |
| { | |
| return usbmuxd_save_pair_record_with_device_id(record_id, 0, record_data, record_size); | |
| } | |
| int usbmuxd_delete_pair_record(const char* record_id) | |
| { | |
| int sfd; | |
| int tag; | |
| int ret = -1; | |
| if (!record_id) { | |
| return -EINVAL; | |
| } | |
| sfd = connect_usbmuxd_socket(); | |
| if (sfd < 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: Connection to usbmuxd failed: %s\n", __func__, strerror(-sfd)); | |
| return sfd; | |
| } | |
| proto_version = 1; | |
| tag = ++use_tag; | |
| if (send_pair_record_packet(sfd, tag, "DeletePairRecord", record_id, 0, NULL) <= 0) { | |
| LIBUSBMUXD_DEBUG(1, "%s: Error sending DeletePairRecord message!\n", __func__); | |
| } else { | |
| uint32_t rc = 0; | |
| ret = usbmuxd_get_result(sfd, tag, &rc, NULL); | |
| if ((ret == 1) && (rc == 0)) { | |
| ret = 0; | |
| } else if (ret == 1) { | |
| ret = -(int)rc; | |
| LIBUSBMUXD_DEBUG(1, "%s: Error: deleting pair record failed: %d\n", __func__, ret); | |
| } | |
| } | |
| socket_close(sfd); | |
| return ret; | |
| } | |
| void libusbmuxd_set_use_inotify(int set) | |
| { | |
| #ifdef HAVE_INOTIFY | |
| use_inotify = set; | |
| #endif | |
| } | |
| void libusbmuxd_set_debug_level(int level) | |
| { | |
| libusbmuxd_debug = level; | |
| socket_set_verbose(level); | |
| } | |
| const char* libusbmuxd_version() | |
| { | |
| #ifndef PACKAGE_VERSION | |
| #error PACKAGE_VERSION is not defined | |
| #endif | |
| return PACKAGE_VERSION; | |
| } |