diff options
author | Didier Raboud <odyx@debian.org> | 2018-07-24 08:21:33 +0200 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2018-07-24 08:21:33 +0200 |
commit | 876ffda4e40b8410f5fe65caf24736558284ba3b (patch) | |
tree | 9bf1fe911d83306a6fabc7a0448b693fdca9e164 | |
parent | 7ceb9c6060461ba4c7fad6999c63fafc28f090d7 (diff) | |
parent | 8782a5cf9a99c876e197d0f26151ba45c6de68f1 (diff) |
record new upstream branch and merge it
-rw-r--r-- | debian/.git-dpm | 14 | ||||
-rw-r--r-- | readme.md | 2 | ||||
-rw-r--r-- | src/http.c | 573 | ||||
-rw-r--r-- | src/http.h | 44 | ||||
-rw-r--r-- | src/ippusbxd.c | 767 | ||||
-rw-r--r-- | src/options.h | 1 | ||||
-rw-r--r-- | src/tcp.c | 133 | ||||
-rw-r--r-- | src/tcp.h | 12 | ||||
-rw-r--r-- | src/usb.c | 307 | ||||
-rw-r--r-- | src/usb.h | 6 |
10 files changed, 699 insertions, 1160 deletions
diff --git a/debian/.git-dpm b/debian/.git-dpm index 861de66..0c7ebde 100644 --- a/debian/.git-dpm +++ b/debian/.git-dpm @@ -1,8 +1,8 @@ # see git-dpm(1) from git-dpm package -1e3fde8c875770a5e906d026f96798c956563137 -1e3fde8c875770a5e906d026f96798c956563137 -1e3fde8c875770a5e906d026f96798c956563137 -1e3fde8c875770a5e906d026f96798c956563137 -ippusbxd_1.32.orig.tar.gz -78ed507f1ba804713de9ab897b8b20950fac28bc -87106 +8782a5cf9a99c876e197d0f26151ba45c6de68f1 +8782a5cf9a99c876e197d0f26151ba45c6de68f1 +8782a5cf9a99c876e197d0f26151ba45c6de68f1 +8782a5cf9a99c876e197d0f26151ba45c6de68f1 +ippusbxd_1.33.orig.tar.gz +06fabfe6e59263aac473d803a89e14bfe11c3c5b +84316 @@ -1,6 +1,6 @@ # IPPUSBXD [![Coverity analysis status](https://scan.coverity.com/projects/2634/badge.svg)](https://scan.coverity.com/projects/2634) -Version 1.31 +Version 1.33 ## About @@ -27,551 +27,28 @@ #define BUFFER_STEP (1 << 12) -struct http_message_t *http_message_new() +struct http_packet_t *packet_new() { - struct http_message_t *msg = calloc(1, sizeof(*msg)); - if (msg == NULL) { - ERR("failed to alloc space for http message"); - return NULL; - } - - msg->spare_capacity = 0; - msg->spare_filled = 0; - msg->spare_buffer = NULL; - - return msg; -} - -void message_free(struct http_message_t *msg) -{ - free(msg->spare_buffer); - free(msg); -} - -static void packet_check_completion(struct http_packet_t *pkt) -{ - struct http_message_t *msg = pkt->parent_message; - /* Msg full */ - if (msg->claimed_size && msg->received_size >= msg->claimed_size) { - msg->is_completed = 1; - NOTE("http: Message completed: Received size >= claimed size"); - - /* Sanity check */ - if (msg->spare_filled > 0) - ERR_AND_EXIT("Msg spare not empty upon completion"); - } - - /* Pkt full */ - if (pkt->expected_size && pkt->filled_size >= pkt->expected_size) { - pkt->is_completed = 1; - NOTE("http: Packet completed: Packet full"); - } - - /* Pkt over capacity */ - if (pkt->filled_size > pkt->buffer_capacity) { - /* Santiy check */ - ERR_AND_EXIT("Overflowed packet buffer"); - } -} - -static int doesMatch(const char *matcher, size_t matcher_len, - const uint8_t *key, size_t key_len) -{ - for (size_t i = 0; i < matcher_len; i++) - if (i >= key_len || matcher[i] != key[i]) - return 0; - return 1; -} - -static int inspect_header_field(struct http_packet_t *pkt, size_t header_size, - char *key, size_t key_size) -{ - /* Find key */ - uint8_t *pos = memmem(pkt->buffer, header_size, key, key_size); - if (pos == NULL) - return -1; - - /* Find first digit */ - size_t number_pos = (size_t) (pos - pkt->buffer) + key_size; - while (number_pos < pkt->filled_size && !isdigit(pkt->buffer[number_pos])) - ++number_pos; - - /* Find next non-digit */ - size_t number_end = number_pos; - while (number_end < pkt->filled_size && isdigit(pkt->buffer[number_end])) - ++number_end; - - /* Failed to find next non-digit - header field might be broken */ - if (number_end >= pkt->filled_size) - return -1; - - /* Temporary stringification of buffer for atoi() */ - uint8_t original_char = pkt->buffer[number_end]; - pkt->buffer[number_end] = '\0'; - int val = atoi((const char *)(pkt->buffer + number_pos)); - - /* Restore buffer */ - pkt->buffer[number_end] = original_char; - return val; -} - -static void packet_store_excess(struct http_packet_t *pkt) -{ - struct http_message_t *msg = pkt->parent_message; - if (msg->spare_buffer != NULL) - ERR_AND_EXIT("Do not store excess to non-empty packet"); - - if (pkt->expected_size >= pkt->filled_size) - ERR_AND_EXIT("Do not call packet_store_excess() unless needed"); - - size_t spare_size = pkt->filled_size - pkt->expected_size; - size_t non_spare = pkt->expected_size; - NOTE("HTTP: Storing %d bytes of excess", spare_size); - - /* Align to BUFFER_STEP */ - size_t needed_size = 0; - needed_size += spare_size / BUFFER_STEP; - needed_size += (spare_size % BUFFER_STEP) > 0 ? BUFFER_STEP : 0; - - if (msg->spare_buffer == NULL) { - uint8_t *buffer = calloc(1, needed_size); - if (buffer == NULL) - ERR_AND_EXIT("Failed to alloc msg spare buffer"); - msg->spare_buffer = buffer; - } - - memcpy(msg->spare_buffer, pkt->buffer + non_spare, spare_size); - pkt->filled_size = non_spare; - - msg->spare_capacity = needed_size; - msg->spare_filled = spare_size; -} - -static void packet_take_spare(struct http_packet_t *pkt) -{ - struct http_message_t *msg = pkt->parent_message; - if (msg->spare_filled == 0) - return; - - if (msg->spare_buffer == NULL) - return; - - if (pkt->filled_size > 0) - ERR_AND_EXIT("pkt should be empty when loading msg spare"); - - /* Take message's buffer */ - size_t msg_size = msg->spare_capacity; - size_t msg_filled = msg->spare_filled; - uint8_t *msg_buffer = msg->spare_buffer; - - pkt->buffer_capacity = msg_size; - pkt->filled_size = msg_filled; - pkt->buffer = msg_buffer; - - msg->spare_capacity = 0; - msg->spare_filled = 0; - msg->spare_buffer = NULL; -} - -static ssize_t packet_find_chunked_size(struct http_packet_t *pkt) -{ - /* NOTE: - chunks can have trailers which are - tacked on http header fields. - NOTE: - chunks may also have extensions. - No one uses or supports them. */ - - /* Find end of size string */ - if (pkt->filled_size >= SSIZE_MAX) - ERR_AND_EXIT("Buffer beyond sane size"); - - ssize_t max = (ssize_t) pkt->filled_size; - ssize_t size_end = -1; - ssize_t miniheader_end = -1; - ssize_t delimiter_start = -1; - for (ssize_t i = 0; i < max; i++) { - - uint8_t *buf = pkt->buffer; - if (size_end < 0) { - /* No extension */ - if (i + 1 < max && - (buf[i] == '\r' && /* CR */ - buf[i + 1] == '\n')) { /* LF */ - size_end = i + 1; - miniheader_end = size_end; - delimiter_start = i; - break; - } - - /* No extension */ - if (buf[i] == '\n') { /* LF */ - size_end = i; - miniheader_end = size_end; - delimiter_start = i; - break; - } - - /* Has extensions */ - if (buf[i] == ';') { - size_end = i; - continue; - } - } - if (miniheader_end < 0) { - if (i + 1 < max && - (buf[i] == '\r' && /* CR */ - buf[i + 1] == '\n')) { /* LF */ - miniheader_end = i + 1; - delimiter_start = i; - break; - } - - if (buf[i] == '\n') { /* LF */ - miniheader_end = i; - delimiter_start = i; - break; - } - } - } - - if (miniheader_end < 0) { - /* NOTE: knowing just the size field - is not enough since the extensions - are not included in the size */ - NOTE("failed to find chunk mini-header so far"); - return -1; - } - - /* Temporary stringification for strtol() */ - uint8_t original_char = *(pkt->buffer + size_end); - *(pkt->buffer + size_end) = '\0'; - size_t size = strtoul((char *)pkt->buffer, NULL, 16); - NOTE("Chunk size raw: %s", pkt->buffer); - *(pkt->buffer + size_end) = original_char; - if (size > SSIZE_MAX) - ERR_AND_EXIT("chunk size is insane"); - - if (size > 0) { - /* Regular chunk */ - ssize_t chunk_size = (ssize_t) size; /* Chunk body */ - chunk_size += miniheader_end + 1; /* Mini-header */ - chunk_size += 2; /* Trailing CRLF */ - NOTE("HTTP: Chunk size: %lu", chunk_size); - return (ssize_t) chunk_size; - } - - /* Terminator chunk - May have trailers in body */ - ssize_t full_size = -1; - for (ssize_t i = delimiter_start; i < max; i++) { - uint8_t *buf = pkt->buffer; - if (i + 3 < max && - (buf[i] == '\r' && /* CR */ - buf[i + 1] == '\n' && /* LF */ - buf[i + 2] == '\r' && /* CR */ - buf[i + 3] == '\n')) { /* LF */ - full_size = i + 4; - break; - } - - if (i + 1 < max && - buf[i] == '\n' && /* LF */ - buf[i + 1] == '\n') { /* LF */ - full_size = i + 2; - break; - } - } - - if (full_size < 0) { - NOTE("Chunk miniheader present but body incomplete"); - return -1; - } - - NOTE("Found end chunked packet"); - pkt->parent_message->is_completed = 1; - pkt->is_completed = 1; - return full_size; -} - -static ssize_t packet_get_header_size(struct http_packet_t *pkt) -{ - if (pkt->header_size != 0) - goto found; - - /* - * RFC2616 recomends we match newline on \n despite full - * complience requires the message to use only \r\n - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.3 - */ - - /* Find header */ - for (size_t i = 0; i < pkt->filled_size && i < SSIZE_MAX; i++) { - /* two \r\n pairs */ - if ((i + 3) < pkt->filled_size && - '\r' == pkt->buffer[i] && - '\n' == pkt->buffer[i + 1] && - '\r' == pkt->buffer[i + 2] && - '\n' == pkt->buffer[i + 3]) { - pkt->header_size = i + 4; - goto found; - } - - /* two \n pairs */ - if ((i + 1) < pkt->filled_size && - '\n' == pkt->buffer[i] && - '\n' == pkt->buffer[i + 1]) { - pkt->header_size = i + 2; - goto found; - } - } - - return -1; - - found: - return (ssize_t) pkt->header_size; -} - -enum http_request_t packet_find_type(struct http_packet_t *pkt) -{ - enum http_request_t type = HTTP_UNSET; - size_t size = 0; - /* - * Valid methods for determining http request - * size are defined by W3 in RFC2616 section 4.4 - * link: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 - */ - - /* - * This function attempts to find what method this - * packet would use. This is only possible in specific case: - * 1. if the request uses method 1 we can check the http - * request type. We must be called on a packet which - * has the full header. - * 2. if the request uses method 2 we need the full header - * but a simple network-byte-order-aware string search - * works. This function does not work if called with - * a chunked transport's sub-packet. - * 3. if the request uses method 3 we again perform the - * string search. - * - * All cases require the packat to contain the full header. - */ - - ssize_t header_size_raw = packet_get_header_size(pkt); - if (header_size_raw < 0) { - /* We don't have the header yet */ - goto do_ret; - } - size_t header_size = (size_t) header_size_raw; - - /* Try Transfer-Encoding Chunked */ - char xfer_encode_str[] = "Transfer-Encoding: chunked"; - size_t xfer_encode_str_size = sizeof(xfer_encode_str) - 1; - uint8_t *xfer_encode_pos = memmem(pkt->buffer, header_size, - xfer_encode_str, - xfer_encode_str_size); - if (xfer_encode_pos != NULL) { - size = 0; - type = HTTP_CHUNKED; - goto do_ret; - } - - /* Try Content-Length */ - char content_length_str[] = "Content-Length: "; - ssize_t contlen_size = - inspect_header_field(pkt, header_size, - content_length_str, sizeof(content_length_str) - 1); - if (contlen_size >= 0) { - size = (size_t) contlen_size + header_size; - type = HTTP_CONTENT_LENGTH; - goto do_ret; - } - - /* GET requests (start with GET) or answers from the server (start - with HTTP) */ - if (doesMatch("GET", 3, pkt->buffer, pkt->filled_size) || - doesMatch("HTTP", 4, pkt->buffer, pkt->filled_size)) { - size = header_size; - type = HTTP_HEADER_ONLY; - goto do_ret; - } - - /* No size was detectable yet header was found */ - type = HTTP_UNKNOWN; - size = 0; - - do_ret: - pkt->parent_message->claimed_size = size; - pkt->parent_message->type = type; - return type; -} - -size_t packet_pending_bytes(struct http_packet_t *pkt) -{ - struct http_message_t *msg = pkt->parent_message; - - /* Check Cache */ - if (pkt->expected_size > 0) - goto pending_known; - - if (HTTP_UNSET == msg->type) { - msg->type = packet_find_type(pkt); - - if (HTTP_CHUNKED == msg->type) { - /* Note: this was the packet with the - header of our chunked message. */ - - /* Save any non-header data we got */ - ssize_t header_size = packet_get_header_size(pkt); - - /* Sanity check */ - if (header_size < 0 || - (size_t)header_size > pkt->filled_size) - ERR_AND_EXIT("HTTP: Could not find header twice"); - - NOTE("HTTP: Chunked header size is %ld bytes", - header_size); - pkt->expected_size = (size_t) header_size; - msg->claimed_size = 0; - goto pending_known; - } - } - - if (HTTP_CHUNKED == msg->type) { - if (pkt->filled_size == 0) { - /* Grab chunk's mini-header */ - goto pending_known; - } - - if (pkt->expected_size == 0) { - /* Check chunk's mini-header */ - ssize_t size = packet_find_chunked_size(pkt); - if (size <= 0) { - ERR("============================================="); - ERR("Malformed chunk-transport http header receivd"); - ERR("Missing chunk's mini-headers in first data"); - ERR("Have %d bytes", pkt->filled_size); - printf("%.*s\n", (int)pkt->filled_size, pkt->buffer); - ERR("Malformed chunk-transport http header receivd"); - ERR("============================================="); - goto pending_known; - } - - pkt->expected_size = (size_t) size; - msg->claimed_size = 0; - } - - goto pending_known; - } - if (HTTP_HEADER_ONLY == msg->type) { - /* Note: we can only know it is header only - when the buffer already contains the header. - So this next call cannot fail. */ - pkt->expected_size = (size_t) packet_get_header_size(pkt); - msg->claimed_size = pkt->expected_size; - goto pending_known; - } - if (HTTP_CONTENT_LENGTH == msg->type) { - /* Note: find_header() has - filled msg's claimed_size */ - msg->claimed_size = msg->claimed_size; - pkt->expected_size = msg->claimed_size; - goto pending_known; - } - - pending_known: - - /* Save excess data */ - if (pkt->expected_size && pkt->filled_size > pkt->expected_size) - packet_store_excess(pkt); - - size_t expected = pkt->expected_size; - if (expected == 0) - expected = msg->claimed_size; - if (expected == 0) - expected = pkt->buffer_capacity; - - /* Sanity check */ - if (expected < pkt->filled_size) - ERR_AND_EXIT("Expected cannot be larger than filled"); - - size_t pending = expected - pkt->filled_size; - - /* Expand buffer as needed */ - while (pending + pkt->filled_size > pkt->buffer_capacity) { - ssize_t size_added = packet_expand(pkt); - if (size_added < 0) { - WARN("packet at max allowed size"); - return 0; - } - if (size_added == 0) { - ERR("Failed to expand packet"); - return 0; - } - } - - packet_check_completion(pkt); - - return pending; -} - -void packet_mark_received(struct http_packet_t *pkt, size_t received) -{ - struct http_message_t *msg = pkt->parent_message; - msg->received_size += received; - - pkt->filled_size += received; - if (received) { - NOTE("HTTP: got %lu bytes so: pkt has %lu bytes, " - "msg has %lu bytes", - received, pkt->filled_size, msg->received_size); - } - - packet_check_completion(pkt); - - if (pkt->filled_size > pkt->buffer_capacity) - ERR_AND_EXIT("Overflowed packet's buffer"); - - if (pkt->expected_size && pkt->filled_size > pkt->expected_size) { - /* Store excess data */ - packet_store_excess(pkt); - } -} - -struct http_packet_t *packet_new(struct http_message_t *parent_msg) -{ - struct http_packet_t *pkt = NULL; - uint8_t *buf = NULL; size_t const capacity = BUFFER_STEP; - assert(parent_msg != NULL); - pkt = calloc(1, sizeof(*pkt)); + struct http_packet_t *pkt = calloc(1, sizeof(*pkt)); if (pkt == NULL) { ERR("failed to alloc packet"); return NULL; } - pkt->parent_message = parent_msg; - pkt->expected_size = 0; - - /* Claim any spare data from prior packets */ - packet_take_spare(pkt); - if (pkt->buffer == NULL) { - buf = calloc(capacity, sizeof(*buf)); - if (buf == NULL) { - ERR("failed to alloc space for packet's buffer or space for packet"); - free(pkt); - return NULL; - } - - /* Assemble packet */ - pkt->buffer = buf; - pkt->buffer_capacity = capacity; - pkt->filled_size = 0; + uint8_t *buf = calloc(capacity, sizeof(*buf)); + if (buf == NULL) { + ERR("failed to alloc space for packet's buffer or space for packet"); + free(pkt); + return NULL; } + /* Assemble packet */ + pkt->buffer = buf; + pkt->buffer_capacity = capacity; + pkt->filled_size = 0; + return pkt; } @@ -580,29 +57,3 @@ void packet_free(struct http_packet_t *pkt) free(pkt->buffer); free(pkt); } - -#define MAX_PACKET_SIZE (1 << 26) /* 64MiB */ -ssize_t packet_expand(struct http_packet_t *pkt) -{ - size_t cur_size = pkt->buffer_capacity; - size_t new_size = cur_size * 2; - if (new_size > MAX_PACKET_SIZE) { - WARN("HTTP: cannot expand packet beyond limit"); - return -1; - } - NOTE("HTTP: doubling packet buffer to %lu", new_size); - - uint8_t *new_buf = realloc(pkt->buffer, new_size); - if (new_buf == NULL) { - /* If realloc fails the original buffer is still valid */ - WARN("Failed to expand packet"); - return 0; - } - pkt->buffer = new_buf; - pkt->buffer_capacity = new_size; - - size_t diff = new_size - cur_size; - if (diff > SSIZE_MAX) - ERR_AND_EXIT("Buffer expanded beyond sane limit"); - return (ssize_t) diff; -} @@ -16,51 +16,11 @@ #include <stdint.h> #include <sys/types.h> -enum http_request_t { - HTTP_UNSET, - HTTP_UNKNOWN, - HTTP_CHUNKED, - HTTP_CONTENT_LENGTH, - HTTP_HEADER_ONLY -}; - -struct http_message_t { - enum http_request_t type; - - size_t spare_filled; - size_t spare_capacity; - uint8_t *spare_buffer; - - size_t unreceived_size; - uint8_t is_completed; - - /* Detected from child packets */ - size_t claimed_size; - size_t received_size; -}; - struct http_packet_t { - /* Cache */ - size_t header_size; - size_t filled_size; - size_t expected_size; - size_t buffer_capacity; uint8_t *buffer; - - struct http_message_t *parent_message; - - uint8_t is_completed; }; -struct http_message_t *http_message_new(void); -void message_free(struct http_message_t *); - -enum http_request_t packet_find_type(struct http_packet_t *pkt); -size_t packet_pending_bytes(struct http_packet_t *); -void packet_mark_received(struct http_packet_t *, size_t); - -struct http_packet_t *packet_new(struct http_message_t *); -void packet_free(struct http_packet_t *); -ssize_t packet_expand(struct http_packet_t *); +struct http_packet_t *packet_new(); +void packet_free(struct http_packet_t *pkt); diff --git a/src/ippusbxd.c b/src/ippusbxd.c index ead0670..b0fae90 100644 --- a/src/ippusbxd.c +++ b/src/ippusbxd.c @@ -22,6 +22,9 @@ #include <unistd.h> #include <getopt.h> #include <pthread.h> +#include <errno.h> + +#include <libusb.h> #include "options.h" #include "logging.h" @@ -33,13 +36,52 @@ struct service_thread_param { struct tcp_conn_t *tcp; struct usb_sock_t *usb_sock; + struct usb_conn_t *usb_conn; pthread_t thread_handle; - int thread_num; + uint32_t thread_num; + pthread_cond_t *cond; +}; + +struct libusb_callback_data { + int *read_inflight; + uint32_t thread_num; + struct tcp_conn_t *tcp; + struct http_packet_t *pkt; + pthread_mutex_t *read_inflight_mutex; + pthread_cond_t *read_inflight_cond; }; +/* Function prototypes */ +static void *service_connection(void *params_void); + +static void service_socket_connection(struct service_thread_param *params); + +static void *service_printer_connection(void *params_void); + +static int allocate_socket_connection(struct service_thread_param *param); + +static int setup_socket_connection(struct service_thread_param *param); + +static int setup_usb_connection(struct usb_sock_t *usb_sock, + struct service_thread_param *param); + +static int setup_communication_thread(void *(*routine)(void *), + struct service_thread_param *param); + +static int get_read_inflight(const int *read_inflight, + pthread_mutex_t *read_inflight_mutex); + +static struct libusb_callback_data *setup_libusb_callback_data( + struct http_packet_t *pkt, int *read_inflight, + struct service_thread_param *thread_param, + pthread_mutex_t *read_inflight_mutex); + +static int is_socket_open(const struct service_thread_param *param); + +/* Global variables */ static pthread_mutex_t thread_register_mutex; static struct service_thread_param **service_threads = NULL; -static int num_service_threads = 0; +static uint32_t num_service_threads = 0; static void sigterm_handler(int sig) { @@ -48,10 +90,11 @@ static void sigterm_handler(int sig) NOTE("Caught signal %d, shutting down ...", sig); } -static void list_service_threads(int num_service_threads, - struct service_thread_param **service_threads) +static void list_service_threads( + uint32_t num_service_threads, + struct service_thread_param **service_threads) { - int i; + uint32_t i; char *p; char buf[10240]; @@ -61,7 +104,7 @@ static void list_service_threads(int num_service_threads, snprintf(p, sizeof(buf) - strlen(buf), "None"); } else { for (i = 0; i < num_service_threads; i ++) { - snprintf(p, sizeof(buf) - strlen(buf), "#%d, ", + snprintf(p, sizeof(buf) - strlen(buf), "#%u, ", service_threads[i]->thread_num); p = buf + strlen(buf); } @@ -72,291 +115,515 @@ static void list_service_threads(int num_service_threads, NOTE("%s", buf); } -static int register_service_thread(int *num_service_threads, - struct service_thread_param ***service_threads, - struct service_thread_param *new_thread) +static int register_service_thread( + uint32_t *num_service_threads, + struct service_thread_param ***service_threads, + struct service_thread_param *new_thread) { - NOTE("Registering thread #%d", new_thread->thread_num); - (*num_service_threads) ++; - *service_threads = realloc(*service_threads, - *num_service_threads * sizeof(void*)); + NOTE("Registering thread #%u", new_thread->thread_num); + (*num_service_threads)++; + *service_threads = + realloc(*service_threads, *num_service_threads * sizeof(void *)); if (*service_threads == NULL) { - ERR("Registering thread #%d: Failed to alloc space for thread registration list", - new_thread->thread_num); + ERR("Registering thread #%u: Failed to alloc space for thread registration " + "list", + new_thread->thread_num); return -1; } (*service_threads)[*num_service_threads - 1] = new_thread; return 0; } -static int unregister_service_thread(int *num_service_threads, - struct service_thread_param ***service_threads, - int thread_num) +static int unregister_service_thread( + uint32_t *num_service_threads, + struct service_thread_param ***service_threads, uint32_t thread_num) { - int i; + uint32_t i; + + NOTE("Unregistering thread #%u", thread_num); + /* Search |service_threads| for an element with a matching thread number. */ + for (i = 0; i < *num_service_threads; i++) { + if ((*service_threads)[i]->thread_num == thread_num) break; + } - NOTE("Unregistering thread #%d", thread_num); - for (i = 0; i < *num_service_threads; i ++) - if ((*service_threads)[i]->thread_num == thread_num) - break; if (i >= *num_service_threads) { - ERR("Unregistering thread #%d: Cannot unregister, not found", thread_num); + ERR("Unregistering thread #%u: Cannot unregister, not found", thread_num); return -1; } - (*num_service_threads) --; - for (; i < *num_service_threads; i ++) + + (*num_service_threads)--; + struct service_thread_param *removed_thread = (*service_threads)[i]; + /* Shift the contents after |removed_thread| down. */ + for (; i < *num_service_threads; i++) { (*service_threads)[i] = (*service_threads)[i + 1]; - *service_threads = realloc(*service_threads, - *num_service_threads * sizeof(void*)); - if (*num_service_threads == 0) + } + free(removed_thread); + + *service_threads = + realloc(*service_threads, *num_service_threads * sizeof(void *)); + + if (*num_service_threads == 0) { *service_threads = NULL; - else if (*service_threads == NULL) { - ERR("Unregistering thread #%d: Failed to alloc space for thread registration list", - thread_num); + } else if (*service_threads == NULL) { + ERR("Unregistering thread #%u: Failed to alloc space for thread " + "registration list", + thread_num); return -1; } + return 0; } static void cleanup_handler(void *arg_void) { - int thread_num = *((int *)(arg_void)); - NOTE("Thread #%d: Called clean-up handler", thread_num); + uint32_t thread_num = *((int *)(arg_void)); + NOTE("Thread #%u: Called clean-up handler", thread_num); pthread_mutex_lock(&thread_register_mutex); unregister_service_thread(&num_service_threads, &service_threads, thread_num); list_service_threads(num_service_threads, service_threads); pthread_mutex_unlock(&thread_register_mutex); } -static void *service_connection(void *arg_void) +static void read_transfer_callback(struct libusb_transfer *transfer) { - struct service_thread_param *arg = - (struct service_thread_param *)arg_void; - int thread_num = arg->thread_num; + struct libusb_callback_data *user_data = + (struct libusb_callback_data *)transfer->user_data; + + uint32_t thread_num = user_data->thread_num; + pthread_mutex_t *read_inflight_mutex = user_data->read_inflight_mutex; + pthread_cond_t *read_inflight_cond = user_data->read_inflight_cond; + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + user_data->pkt->filled_size = transfer->actual_length; + + if (transfer->actual_length) { + NOTE("Thread #%u: Pkt from %s (buffer size: %zu)\n===\n%s===", thread_num, + "usb", user_data->pkt->filled_size, + hexdump(user_data->pkt->buffer, (int)user_data->pkt->filled_size)); + + tcp_packet_send(user_data->tcp, user_data->pkt); + /* Mark the tcp socket as active. */ + set_is_active(user_data->tcp, 1); + } + + break; + case LIBUSB_TRANSFER_ERROR: + ERR("Thread #%u: There was an error completing the transfer", thread_num); + g_options.terminate = 1; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + NOTE( + "Thread #%u: The transfer timed out before it could be completed: " + "Received %u bytes", + thread_num, transfer->actual_length); + break; + case LIBUSB_TRANSFER_CANCELLED: + NOTE("Thread #%u: The transfer was cancelled", thread_num); + break; + case LIBUSB_TRANSFER_STALL: + ERR("Thread #%u: The transfer has stalled", thread_num); + g_options.terminate = 1; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ERR("Thread #%u: The printer was disconnected during the transfer", + thread_num); + g_options.terminate = 1; + break; + case LIBUSB_TRANSFER_OVERFLOW: + ERR("Thread #%u: The printer sent more data than was requested", + thread_num); + g_options.terminate = 1; + break; + default: + ERR("Thread #%u: Something unexpected happened", thread_num); + g_options.terminate = 1; + } - NOTE("Thread #%d: Starting", thread_num); + /* Free the packet used for the transfer. */ + packet_free(user_data->pkt); + + /* Mark the transfer as completed. */ + pthread_mutex_lock(read_inflight_mutex); + *user_data->read_inflight = 0; + pthread_cond_broadcast(read_inflight_cond); + pthread_mutex_unlock(read_inflight_mutex); + + /* Cleanup the data used for the transfer */ + free(user_data); + libusb_free_transfer(transfer); +} + +/* This function is responsible for handling connection requests and + is run in a separate thread. It detaches itself from the main thread and sets + up a USB connection with the printer. This function spawns a partner thread + which is responsible for reading from the printer, and then this function + calls into service_socket_connection() which is responsible for reading from + the socket which made the connection request. Once the socket has closed its + end of communiction, this function notifies its partner thread that the + connection has been closed and then joins on the partner thread before + shutting down. */ +static void *service_connection(void *params_void) +{ + struct service_thread_param *params = + (struct service_thread_param *)params_void; + uint32_t thread_num = params->thread_num; - /* Detach this thread so that the main thread does not need to join this thread - after termination for clean-up */ + /* Detach this thread so that the main thread does not need to join this + thread after termination for clean-up. */ pthread_detach(pthread_self()); - /* Register clean-up handler */ + /* Register clean-up handler. */ pthread_cleanup_push(cleanup_handler, &thread_num); - /* Allow immediate cancelling of this thread */ + /* Allow immediate cancelling of this thread. */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); - /* classify priority */ - struct usb_conn_t *usb = NULL; - int usb_failed = 0; - while (!arg->tcp->is_closed && usb_failed == 0 && !g_options.terminate) { - struct http_message_t *server_msg = NULL; - struct http_message_t *client_msg = NULL; - - /* Client's request */ - client_msg = http_message_new(); - if (client_msg == NULL) { - ERR("Thread #%d: Failed to create client message", thread_num); - break; - } - NOTE("Thread #%d: M %p: Client msg starting", - thread_num, client_msg); - - while (!client_msg->is_completed && !g_options.terminate) { - struct http_packet_t *pkt; - pkt = tcp_packet_get(arg->tcp, client_msg); - if (pkt == NULL) { - if (arg->tcp->is_closed) { - NOTE("Thread #%d: M %p: Client closed connection", - thread_num, client_msg); - goto cleanup_subconn; - } - ERR("Thread #%d: M %p: Got null packet from tcp", - thread_num, client_msg); - goto cleanup_subconn; - } - if (usb == NULL && arg->usb_sock != NULL) { - usb = usb_conn_acquire(arg->usb_sock); - if (usb == NULL) { - ERR("Thread #%d: M %p: Failed to acquire usb interface", - thread_num, client_msg); - packet_free(pkt); - usb_failed = 1; - goto cleanup_subconn; - } - usb_failed = 0; - NOTE("Thread #%d: M %p: Interface #%d: acquired usb conn", - thread_num, client_msg, - usb->interface_index); - } + /* Attempt to establish a connection with the printer. */ + if (setup_usb_connection(params->usb_sock, params)) + goto cleanup; + + /* Condition variable used to broadcast updates to the printer thread. */ + pthread_cond_t cond; + if (pthread_cond_init(&cond, NULL)) + goto cleanup; + params->cond = &cond; + + /* Copy the contents of |params| into |printer_params|. The only + differences between the two are the |thread_num| and |thread_handle|. */ + struct service_thread_param *printer_params = + calloc(1, sizeof(*printer_params)); + memcpy(printer_params, params, sizeof(*printer_params)); + printer_params->thread_num += 1; + + /* Attempt to start the printer's end of the communication. */ + if (setup_communication_thread(&service_printer_connection, printer_params)) + goto cleanup; + + pthread_t printer_params_thread_handle = printer_params->thread_handle; + + /* This function will run until the socket has been closed. When this function + returns it means that the communication has been completed. */ + service_socket_connection(params); + + /* Notify the printer's end that the socket has closed so that it does not + have to wait for any pending asynchronous transfers to complete. */ + pthread_cond_broadcast(params->cond); + + /* Wait for the printer thread to exit. */ + NOTE("Thread #%u: Waiting for thread #%u to complete", thread_num, + thread_num + 1); + if (pthread_join(printer_params_thread_handle, NULL)) + ERR("Thread #%u: Something went wrong trying to join the printer thread", + thread_num); + +cleanup: + if (params->usb_conn != NULL) { + NOTE("Thread #%u: interface #%u: releasing usb conn", thread_num, + params->usb_conn->interface_index); + usb_conn_release(params->usb_conn); + params->usb_conn = NULL; + } - if (g_options.terminate) - goto cleanup_subconn; - - NOTE("Thread #%d: M %p P %p: Pkt from tcp (buffer size: %d)\n===\n%s===", - thread_num, client_msg, pkt, - pkt->filled_size, - hexdump(pkt->buffer, (int)pkt->filled_size)); - /* In no-printer mode we simply ignore passing the - client message on to the printer */ - if (arg->usb_sock != NULL) { - if (usb_conn_packet_send(usb, pkt) != 0) { - ERR("Thread #%d: M %p P %p: Interface #%d: Unable to send client package via USB", - thread_num, - client_msg, pkt, usb->interface_index); - packet_free(pkt); - goto cleanup_subconn; - } - NOTE("Thread #%d: M %p P %p: Interface #%d: Client pkt done", - thread_num, - client_msg, pkt, usb->interface_index); - } - packet_free(pkt); - } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Client msg completed", - thread_num, client_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Client msg completed", - thread_num, client_msg); - message_free(client_msg); - client_msg = NULL; - - if (g_options.terminate) - goto cleanup_subconn; - - - /* Server's response */ - server_msg = http_message_new(); - if (server_msg == NULL) { - ERR("Thread #%d: Failed to create server message", - thread_num); - goto cleanup_subconn; + NOTE("Thread #%u: closing, %s", thread_num, + g_options.terminate ? "shutdown requested" + : "communication thread terminated"); + tcp_conn_close(params->tcp); + + /* Execute clean-up handler. */ + pthread_cleanup_pop(1); + pthread_exit(NULL); +} + +/* Reads from the socket and writes data to the printer. */ +static void service_socket_connection(struct service_thread_param *params) +{ + uint32_t thread_num = params->thread_num; + + while (is_socket_open(params) && !g_options.terminate) { + int result = poll_tcp_socket(params->tcp); + if (result < 0 || !is_socket_open(params)) { + NOTE("Thread #%u: Client closed connection", thread_num); + return; + } else if (result == 0) { + continue; } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Server msg starting", - thread_num, server_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Server msg starting", - thread_num, server_msg); - while (!server_msg->is_completed && !g_options.terminate) { - struct http_packet_t *pkt; - if (arg->usb_sock != NULL) { - pkt = usb_conn_packet_get(usb, server_msg); - if (pkt == NULL) { - usb_failed = 1; - goto cleanup_subconn; - } - } else { - /* In no-printer mode we "invent" the answer - of the printer, a simple HTML message as - a pseudo web interface */ - pkt = packet_new(server_msg); - snprintf((char*)(pkt->buffer), - pkt->buffer_capacity - 1, - "HTTP/1.1 200 OK\r\nContent-Type: text/html; name=ippusbxd.html; charset=UTF-8\r\n\r\n<html><h2>ippusbxd</h2><p>Debug/development mode without connection to IPP-over-USB printer</p></html>\r\n"); - pkt->filled_size = 183; - /* End the TCP connection, so that a - web browser does not wait for more data */ - server_msg->is_completed = 1; - arg->tcp->is_closed = 1; - } - if (g_options.terminate) - goto cleanup_subconn; - - NOTE("Thread #%d: M %p P %p: Pkt from usb (buffer size: %d)\n===\n%s===", - thread_num, server_msg, pkt, pkt->filled_size, - hexdump(pkt->buffer, (int)pkt->filled_size)); - if (tcp_packet_send(arg->tcp, pkt) != 0) { - ERR("Thread #%d: M %p P %p: Unable to send client package via TCP", - thread_num, - client_msg, pkt); - packet_free(pkt); - goto cleanup_subconn; - } - if (usb != NULL) - NOTE("Thread #%d: M %p P %p: Interface #%d: Server pkt done", - thread_num, server_msg, pkt, - usb->interface_index); - else - NOTE("Thread #%d: M %p P %p: Server pkt done", - thread_num, server_msg, pkt); - packet_free(pkt); + struct http_packet_t *pkt = tcp_packet_get(params->tcp); + if (pkt == NULL) { + NOTE("Thread #%u: There was an error reading from the socket", + thread_num); + return; } - if (usb != NULL) - NOTE("Thread #%d: M %p: Interface #%d: Server msg completed", - thread_num, server_msg, - usb->interface_index); - else - NOTE("Thread #%d: M %p: Server msg completed", - thread_num, server_msg); - - cleanup_subconn: - if (usb != NULL && (arg->tcp->is_closed || usb_failed == 1)) { - NOTE("Thread #%d: M %p: Interface #%d: releasing usb conn", - thread_num, server_msg, usb->interface_index); - usb_conn_release(usb); - usb = NULL; + + if (!is_socket_open(params)) { + NOTE("Thread #%u: Client closed connection", thread_num); + return; } - if (client_msg != NULL) - message_free(client_msg); - if (server_msg != NULL) - message_free(server_msg); + + NOTE("Thread #%u: Pkt from tcp (buffer size: %zu)\n===\n%s===", thread_num, + pkt->filled_size, hexdump(pkt->buffer, (int)pkt->filled_size)); + + /* Send pkt to printer. */ + usb_conn_packet_send(params->usb_conn, pkt); + + packet_free(pkt); } +} - NOTE("Thread #%d: Closing, %s", thread_num, - g_options.terminate ? "shutdown requested" : "communication thread terminated"); - tcp_conn_close(arg->tcp); - free(arg); +/* Returns the value of |read_inflight|. Uses a mutex since another thread which + is processing the asynchronous transfer may change the value once the + transfer is complete. */ +static int get_read_inflight(const int *read_inflight, pthread_mutex_t *mtx) +{ + pthread_mutex_lock(mtx); + int val = *read_inflight; + pthread_mutex_unlock(mtx); - /* Execute clean-up handler */ - pthread_cleanup_pop(1); + return val; +} - pthread_exit(NULL); +/* Sets the value of |read_inflight| to |val|. Uses a mutex since another thread + which is processing the asynchronous transfer may change the value once the + transfer is complete. */ +static void set_read_inflight(int val, pthread_mutex_t *mtx, int *read_inflight) +{ + pthread_mutex_lock(mtx); + *read_inflight = val; + pthread_mutex_unlock(mtx); } -static void start_daemon() +/* Reads from the printer and writes to the socket. */ +static void *service_printer_connection(void *params_void) { - /* Capture USB device if not in no-printer mode */ - struct usb_sock_t *usb_sock; + struct service_thread_param *params = + (struct service_thread_param *)params_void; + uint32_t thread_num = params->thread_num; - /* Termination flag */ - g_options.terminate = 0; + /* Register clean-up handler. */ + pthread_cleanup_push(cleanup_handler, &thread_num); - if (g_options.noprinter_mode == 0) { - usb_sock = usb_open(); - if (usb_sock == NULL) - goto cleanup_usb; - } else { - usb_sock = NULL; - g_options.device_id = "MFG:Acme;MDL:LaserStar 2000;CMD:AppleRaster,PWGRaster;CLS:PRINTER;DES:Acme LaserStar 2000;SN:001;"; + int read_inflight = 0; + pthread_mutex_t read_inflight_mutex; + if (pthread_mutex_init(&read_inflight_mutex, NULL)) + goto cleanup; + + struct libusb_transfer *read_transfer = NULL; + + while (is_socket_open(params) && !g_options.terminate) { + /* If there is already a read from the printer underway, block until it has + completed. */ + pthread_mutex_lock(&read_inflight_mutex); + while (is_socket_open(params) && read_inflight) + pthread_cond_wait(params->cond, &read_inflight_mutex); + pthread_mutex_unlock(&read_inflight_mutex); + + /* After waking up due to a completed transfer, verify that the socket is + still open and that the termination flag has not been set before + attempting to start another transfer. */ + if (!is_socket_open(params) || g_options.terminate) + break; + + struct http_packet_t *pkt = packet_new(); + if (pkt == NULL) { + ERR("Thread #%u: Failed to allocate packet", thread_num); + break; + } + + struct libusb_callback_data *user_data = setup_libusb_callback_data( + pkt, &read_inflight, params, &read_inflight_mutex); + + if (user_data == NULL) { + ERR("Thread #%u: Failed to allocate memory for libusb_callback_data", + thread_num); + break; + } + + read_transfer = setup_async_read( + params->usb_conn, pkt, read_transfer_callback, (void *)user_data, 5000); + + if (read_transfer == NULL) { + ERR("Thread #%u: Failed to allocate memory for libusb transfer", + thread_num); + break; + } + + /* Mark that there is a new read in flight. A mutex should not be needed + here since the transfer callback won't be fired until after calling + libusb_submit_transfer() */ + read_inflight = 1; + + if (libusb_submit_transfer(read_transfer)) { + ERR("Thread #%u: Failed to submit asynchronous USB transfer", thread_num); + set_read_inflight(0, &read_inflight_mutex, &read_inflight); + break; + } } - /* Capture a socket */ + /* If the socket used for communication has closed and there is still a + transfer from the printer in flight then we attempt to cancel it. */ + if (get_read_inflight(&read_inflight, &read_inflight_mutex)) { + NOTE( + "Thread #%u: There was a read in flight when the connection was " + "closed, cancelling transfer", thread_num); + int cancel_status = libusb_cancel_transfer(read_transfer); + if (!cancel_status) { + /* Wait until the cancellation has completed. */ + NOTE("Thread #%u: Waiting until the transfer has been cancelled", + thread_num); + pthread_mutex_lock(&read_inflight_mutex); + while (read_inflight) + pthread_cond_wait(params->cond, &read_inflight_mutex); + pthread_mutex_unlock(&read_inflight_mutex); + } else if (cancel_status == LIBUSB_ERROR_NOT_FOUND) { + NOTE("Thread #%u: The transfer has already completed", thread_num); + } else { + NOTE("Thread #%u: Failed to cancel transfer"); + g_options.terminate = 1; + } + } + + pthread_mutex_destroy(&read_inflight_mutex); + +cleanup: + /* Execute clean-up handler. */ + pthread_cleanup_pop(1); + pthread_exit(NULL); +} + +static uint16_t open_tcp_socket(void) +{ uint16_t desired_port = g_options.desired_port; g_options.tcp_socket = NULL; g_options.tcp6_socket = NULL; + for (;;) { g_options.tcp_socket = tcp_open(desired_port, g_options.interface); g_options.tcp6_socket = tcp6_open(desired_port, g_options.interface); - if (g_options.tcp_socket || g_options.tcp6_socket || g_options.only_desired_port) + if (g_options.tcp_socket || g_options.tcp6_socket || + g_options.only_desired_port) break; - /* Search for a free port */ + /* Search for a free port. */ desired_port ++; - /* We failed with 0 as port number or we reached the max - port number */ + /* We failed with 0 as port number or we reached the max port number. */ if (desired_port == 1 || desired_port == 0) - /* IANA recommendation of 49152 to 65535 for ephemeral - ports - https://en.wikipedia.org/wiki/Ephemeral_port */ + /* IANA recommendation of 49152 to 65535 for ephemeral ports. */ desired_port = 49152; - NOTE("Access to desired port failed, trying alternative port %d", desired_port); + NOTE("Access to desired port failed, trying alternative port %d", + desired_port); + } + + return desired_port; +} + +/* Attempts to allocate space for a tcp socket. If the allocation is + successful then a value of 0 is returned, otherwise a non-zero value is + returned. */ +static int allocate_socket_connection(struct service_thread_param *param) +{ + param->tcp = calloc(1, sizeof(*param->tcp)); + + if (param->tcp == NULL) { + ERR("Preparing thread #%u: Failed to allocate space for cups connection", + param->thread_num); + return -1; + } + + return 0; +} + +/* Attempts to setup a connection for to a tcp socket. Returns a 0 value on + success and a non-zero value if something went wrong attempting to establish + the connection. */ +static int setup_socket_connection(struct service_thread_param *param) +{ + param->tcp = tcp_conn_select(g_options.tcp_socket, g_options.tcp6_socket); + if (g_options.terminate || param->tcp == NULL) + return -1; + return 0; +} + +/* Attempt to create a new usb_conn_t and assign it to |param| by acquiring an + available usb interface. Returns 0 if the creating on the connection struct + was successful, and non-zero if there was an error attempting to acquire the + interface. */ +static int setup_usb_connection(struct usb_sock_t *usb_sock, + struct service_thread_param *param) +{ + param->usb_conn = usb_conn_acquire(usb_sock); + if (param->usb_conn == NULL) { + ERR("Thread #%u: Failed to acquire usb interface", param->thread_num); + return -1; } + + return 0; +} + +/* Attempt to register a new communication thread to execute the function + |routine| with the given |params|. If successful a 0 value is returned, + otherwise a non-zero value is returned. */ +static int setup_communication_thread(void *(*routine)(void *), + struct service_thread_param *param) +{ + pthread_mutex_lock(&thread_register_mutex); + register_service_thread(&num_service_threads, &service_threads, param); + list_service_threads(num_service_threads, service_threads); + pthread_mutex_unlock(&thread_register_mutex); + + int status = + pthread_create(¶m->thread_handle, NULL, routine, param); + + if (status) { + ERR("Creating thread #%u: Failed to spawn thread, error %d", + param->thread_num, status); + pthread_mutex_lock(&thread_register_mutex); + unregister_service_thread(&num_service_threads, &service_threads, + param->thread_num); + list_service_threads(num_service_threads, service_threads); + pthread_mutex_unlock(&thread_register_mutex); + return -1; + } + + return 0; +} + +static struct libusb_callback_data *setup_libusb_callback_data( + struct http_packet_t *pkt, int *read_inflight, + struct service_thread_param *thread_param, + pthread_mutex_t *read_inflight_mutex) { + struct libusb_callback_data *data = calloc(1, sizeof(*data)); + if (data == NULL) + return NULL; + + data->pkt = pkt; + data->read_inflight = read_inflight; + data->thread_num = thread_param->thread_num; + data->read_inflight_mutex = read_inflight_mutex; + data->read_inflight_cond = thread_param->cond; + data->tcp = thread_param->tcp; + + return data; +} + +static int is_socket_open(const struct service_thread_param *param) { + return !param->tcp->is_closed; +} + +static void start_daemon() +{ + /* Capture USB device. */ + struct usb_sock_t *usb_sock; + + /* Termination flag */ + g_options.terminate = 0; + + usb_sock = usb_open(); + if (usb_sock == NULL) goto cleanup_usb; + + /* Capture a socket */ + uint16_t desired_port = open_tcp_socket(); if (g_options.tcp_socket == NULL && g_options.tcp6_socket == NULL) goto cleanup_tcp; @@ -419,45 +686,32 @@ static void start_daemon() } /* Main loop */ - int i = 0; + uint32_t i = 1; pthread_mutex_init(&thread_register_mutex, NULL); while (!g_options.terminate) { - i ++; struct service_thread_param *args = calloc(1, sizeof(*args)); if (args == NULL) { - ERR("Preparing thread #%d: Failed to alloc space for thread args", - i); + ERR("Preparing thread #%u: Failed to alloc space for thread args", i); goto cleanup_thread; } args->thread_num = i; args->usb_sock = usb_sock; - /* For each request/response round we use the socket (IPv4 or - IPv6) which receives data first */ - args->tcp = tcp_conn_select(g_options.tcp_socket, g_options.tcp6_socket); - if (g_options.terminate) + /* Allocate space for a tcp socket to be used for communication. */ + if (allocate_socket_connection(args)) goto cleanup_thread; - if (args->tcp == NULL) { - ERR("Preparing thread #%d: Failed to open tcp connection", i); + + /* Attempt to establish a connection to the relevant socket. */ + if (setup_socket_connection(args)) goto cleanup_thread; - } - pthread_mutex_lock(&thread_register_mutex); - register_service_thread(&num_service_threads, &service_threads, args); - list_service_threads(num_service_threads, service_threads); - pthread_mutex_unlock(&thread_register_mutex); - int status = pthread_create(&args->thread_handle, NULL, - &service_connection, args); - if (status) { - ERR("Creating thread #%d: Failed to spawn thread, error %d", - i, status); - pthread_mutex_lock(&thread_register_mutex); - unregister_service_thread(&num_service_threads, &service_threads, i); - list_service_threads(num_service_threads, service_threads); - pthread_mutex_unlock(&thread_register_mutex); + /* Attempt to start up a new thread to handle the socket's end of + communication. */ + if (setup_communication_thread(&service_connection, args)) goto cleanup_thread; - } + + i += 2; continue; @@ -479,7 +733,7 @@ static void start_daemon() stopping ippusbxd, so that no USB communication with the printer can happen after the final reset */ while (num_service_threads) { - NOTE("Thread #%d did not terminate, canceling it now ...", + NOTE("Thread #%u did not terminate, canceling it now ...", service_threads[0]->thread_num); i = num_service_threads; pthread_cancel(service_threads[0]->thread_handle); @@ -488,6 +742,7 @@ static void start_daemon() } /* Wait for USB unplug event observer thread to terminate */ + NOTE("Shutting down usb observer thread"); pthread_join(g_options.usb_event_thread_handle, NULL); /* TCP clean-up */ @@ -538,7 +793,6 @@ int main(int argc, char *argv[]) {"verbose", no_argument, 0, 'q' }, {"no-fork", no_argument, 0, 'n' }, {"no-broadcast", no_argument, 0, 'B' }, - {"no-printer", no_argument, 0, 'N' }, {"help", no_argument, 0, 'h' }, {NULL, 0, 0, 0 } }; @@ -552,7 +806,7 @@ int main(int argc, char *argv[]) g_options.bus = 0; g_options.device = 0; - while ((c = getopt_long(argc, argv, "qnhdp:P:i:s:lv:m:NB", + while ((c = getopt_long(argc, argv, "qnhdp:P:i:s:lv:m:B", long_options, &option_index)) != -1) { switch (c) { case '?': @@ -625,9 +879,6 @@ int main(int argc, char *argv[]) case 's': g_options.serial_num = (unsigned char *)optarg; break; - case 'N': - g_options.noprinter_mode = 1; - break; case 'B': g_options.nobroadcast = 1; break; @@ -673,13 +924,11 @@ int main(int argc, char *argv[]) " -n No-fork mode\n" " --no-broadcast\n" " -B No-broadcast mode, do not DNS-SD-broadcast\n" - " --no-printer\n" - " -N No-printer mode, debug/developer mode which makes ippusbxd\n" - " run without IPP-over-USB printer\n" , argv[0], argv[0], argv[0]); return 0; } start_daemon(); + NOTE("ippusbxd completed successfully"); return 0; } diff --git a/src/options.h b/src/options.h index 2de31a2..e167514 100644 --- a/src/options.h +++ b/src/options.h @@ -35,7 +35,6 @@ struct options { int help_mode; int verbose_mode; int nofork_mode; - int noprinter_mode; int nobroadcast; /* Printer identity */ @@ -17,6 +17,7 @@ #include <stdlib.h> #include <string.h> #include <ctype.h> +#include <pthread.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> @@ -30,11 +31,11 @@ #include <unistd.h> #include <errno.h> -#include "options.h" +#include "http.h" #include "logging.h" +#include "options.h" #include "tcp.h" - struct tcp_sock_t *tcp_open(uint16_t port, char* interface) { struct tcp_sock_t *this = calloc(1, sizeof *this); @@ -221,56 +222,37 @@ uint16_t tcp_port_number_get(struct tcp_sock_t *sock) return 0; } -struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp, - struct http_message_t *msg) +struct http_packet_t *tcp_packet_get(struct tcp_conn_t *tcp) { - /* Alloc packet ==---------------------------------------------------== */ - struct http_packet_t *pkt = packet_new(msg); + /* Allocate packet for incoming message. */ + struct http_packet_t *pkt = packet_new(); if (pkt == NULL) { ERR("failed to create packet for incoming tcp message"); goto error; } - size_t want_size = packet_pending_bytes(pkt); - if (want_size == 0) { - NOTE("TCP: Got %lu from spare buffer", pkt->filled_size); - return pkt; - } - struct timeval tv; tv.tv_sec = 3; tv.tv_usec = 0; - setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, - (char *)&tv, sizeof(struct timeval)); - - while (want_size != 0 && !msg->is_completed && !g_options.terminate) { - NOTE("TCP: Getting %d bytes", want_size); - uint8_t *subbuffer = pkt->buffer + pkt->filled_size; - ssize_t gotten_size = recv(tcp->sd, subbuffer, want_size, 0); - if (gotten_size < 0) { - int errno_saved = errno; - ERR("recv failed with err %d:%s", errno_saved, - strerror(errno_saved)); - tcp->is_closed = 1; - goto error; - } - NOTE("TCP: Got %d bytes", gotten_size); - if (gotten_size == 0) { - tcp->is_closed = 1; - if (pkt->filled_size == 0) { - /* Client closed TCP conn */ - goto error; - } else { - break; - } - } + if (setsockopt(tcp->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))) { + ERR("TCP: Setting options for tcp connection socket failed"); + goto error; + } + + ssize_t gotten_size = recv(tcp->sd, pkt->buffer, pkt->buffer_capacity, 0); + + if (gotten_size < 0) { + int errno_saved = errno; + ERR("recv failed with err %d:%s", errno_saved, strerror(errno_saved)); + tcp->is_closed = 1; + goto error; + } - packet_mark_received(pkt, (unsigned) gotten_size); - want_size = packet_pending_bytes(pkt); - NOTE("TCP: Want more %d bytes; Message %scompleted", want_size, msg->is_completed ? "" : "not "); + if (gotten_size == 0) { + tcp->is_closed = 1; } - NOTE("TCP: Received %lu bytes", pkt->filled_size); + pkt->filled_size = gotten_size; return pkt; error: @@ -283,9 +265,10 @@ int tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) { size_t remaining = pkt->filled_size; size_t total = 0; + while (remaining > 0 && !g_options.terminate) { - ssize_t sent = send(conn->sd, pkt->buffer + total, - remaining, MSG_NOSIGNAL); + ssize_t sent = send(conn->sd, pkt->buffer + total, remaining, MSG_NOSIGNAL); + if (sent < 0) { if (errno == EPIPE) { conn->is_closed = 1; @@ -295,13 +278,14 @@ int tcp_packet_send(struct tcp_conn_t *conn, struct http_packet_t *pkt) return -1; } - size_t sent_ulong = (unsigned) sent; - total += sent_ulong; - if (sent_ulong >= remaining) + size_t sent_unsigned = (size_t)sent; + total += sent_unsigned; + if (sent_unsigned >= remaining) remaining = 0; else - remaining -= sent_ulong; + remaining -= sent_unsigned; } + NOTE("TCP: sent %lu bytes", total); return 0; } @@ -315,6 +299,7 @@ struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, ERR("Calloc for connection struct failed"); goto error; } + fd_set rfds; int retval = 0; int nfds = 0; @@ -354,6 +339,11 @@ struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, ERR("accept failed"); goto error; } + + /* Attempt to initialize the connection's mutex. */ + if (pthread_mutex_init(&conn->mutex, NULL)) + goto error; + return conn; error: @@ -371,5 +361,56 @@ void tcp_conn_close(struct tcp_conn_t *conn) shutdown(conn->sd, SHUT_RDWR); close(conn->sd); + pthread_mutex_destroy(&conn->mutex); free(conn); } + +/* Poll the tcp socket to determine if it is ready to transmit data. */ +int poll_tcp_socket(struct tcp_conn_t *tcp) +{ + struct pollfd poll_fd; + poll_fd.fd = tcp->sd; + poll_fd.events = POLLIN; + const int nfds = 1; + const int timeout = 5000; /* 5 seconds. */ + + int result = poll(&poll_fd, nfds, timeout); + if (result < 0) { + ERR("poll failed with error %d:%s", errno, strerror(errno)); + tcp->is_closed = 1; + } else if (result == 0) { + /* In the case where the poll timed out, check to see whether or not data + * has recently been sent from the printer along the socket. If so, then we + * keep the connection alive an reset the is_active flag. Otherwise, close + * the connection. */ + if (get_is_active(tcp)) { + set_is_active(tcp, 0); + } else { + tcp->is_closed = 1; + } + } else { + if (poll_fd.revents != POLLIN) { + ERR("poll returned an unexpected event"); + tcp->is_closed = 1; + return -1; + } + } + + return result; +} + +int get_is_active(struct tcp_conn_t *tcp) +{ + pthread_mutex_lock(&tcp->mutex); + int val = tcp->is_active; + pthread_mutex_unlock(&tcp->mutex); + + return val; +} + +void set_is_active(struct tcp_conn_t *tcp, int val) +{ + pthread_mutex_lock(&tcp->mutex); + tcp->is_active = val; + pthread_mutex_unlock(&tcp->mutex); +} @@ -15,6 +15,8 @@ #pragma once #include <stdint.h> +#include <pthread.h> + #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> @@ -37,6 +39,8 @@ struct tcp_sock_t { struct tcp_conn_t { int sd; int is_closed; + int is_active; + pthread_mutex_t mutex; }; struct tcp_sock_t *tcp_open(uint16_t, char* interface); @@ -48,6 +52,10 @@ struct tcp_conn_t *tcp_conn_select(struct tcp_sock_t *sock, struct tcp_sock_t *sock6); void tcp_conn_close(struct tcp_conn_t *); -struct http_packet_t *tcp_packet_get(struct tcp_conn_t *, - struct http_message_t *); +struct http_packet_t *tcp_packet_get(struct tcp_conn_t *); int tcp_packet_send(struct tcp_conn_t *, struct http_packet_t *); + +int poll_tcp_socket(struct tcp_conn_t *tcp); + +int get_is_active(struct tcp_conn_t *tcp); +void set_is_active(struct tcp_conn_t *tcp, int val); @@ -168,6 +168,43 @@ int get_device_id(struct libusb_device_handle *handle, return (0); } +static void try_detach_kernel_driver(struct usb_sock_t *usb, + struct usb_interface *uf) { + /* Make kernel release interface */ + if (libusb_kernel_driver_active(usb->printer, uf->libusb_interface_index) == + 1) { + /* Only linux supports this other platforms will fail thus we ignore the + error code it either works or it does not */ + libusb_detach_kernel_driver(usb->printer, uf->libusb_interface_index); + } +} + +static int try_claim_usb_interface(struct usb_sock_t *usb, + struct usb_interface *uf) { + /* Claim the whole interface */ + int status = 0; + do { + /* Spinlock-like Libusb does not offer a blocking call so we're left with a + spinlock. */ + status = libusb_claim_interface(usb->printer, uf->libusb_interface_index); + if (status) + NOTE("Failed to claim interface %d, retrying", + uf->libusb_interface_index); + switch (status) { + case LIBUSB_ERROR_NOT_FOUND: + ERR("USB Interface did not exist"); + return -1; + case LIBUSB_ERROR_NO_DEVICE: + ERR("Printer was removed"); + return -1; + default: + break; + } + } while (status != 0 && !g_options.terminate); + + return 0; +} + struct usb_sock_t *usb_open() { int status_lock; @@ -372,6 +409,23 @@ struct usb_sock_t *usb_open() goto error; } + /* Try to make the kernel release the usb interface. */ + try_detach_kernel_driver(usb, uf); + + /* Try to claim the usb interface. */ + if (try_claim_usb_interface(usb, uf)) { + ERR("Failed to claim usb interface #%d", uf->interface_number); + goto error; + } + + /* Select the IPP-USB alt setting of the interface. */ + if (libusb_set_interface_alt_setting( + usb->printer, uf->libusb_interface_index, uf->interface_alt)) { + ERR("Failed to set alt setting for interface #%d", + uf->interface_number); + goto error; + } + break; } } @@ -435,7 +489,10 @@ void usb_close(struct usb_sock_t *usb) NOTE("Resetting printer ..."); libusb_reset_device(usb->printer); NOTE("Reset completed."); + NOTE("Closing device handle..."); libusb_close(usb->printer); + NOTE("Closed device handle."); + if (usb != NULL) { if (usb->context != NULL) libusb_exit(usb->context); @@ -552,55 +609,6 @@ void usb_register_callback(struct usb_sock_t *usb) ERR("Failed to register unplug callback"); } -static void usb_conn_mark_staled(struct usb_conn_t *conn) -{ - if (conn->is_staled) - return; - - struct usb_sock_t *usb = conn->parent; - - sem_wait(&usb->num_staled_lock); - { - usb->num_staled++; - } - sem_post(&usb->num_staled_lock); - - conn->is_staled = 1; -} - -static void usb_conn_mark_moving(struct usb_conn_t *conn) -{ - if (!conn->is_staled) - return; - - struct usb_sock_t *usb = conn->parent; - - sem_wait(&usb->num_staled_lock); - { - usb->num_staled--; - } - sem_post(&usb->num_staled_lock); - - conn->is_staled = 0; -} - -static int usb_all_conns_staled(struct usb_sock_t *usb) -{ - int staled; - - sem_wait(&usb->num_staled_lock); - { - sem_wait(&usb->pool_manage_lock); - { - staled = usb->num_staled == usb->num_taken; - } - sem_post(&usb->pool_manage_lock); - } - sem_post(&usb->num_staled_lock); - - return staled; -} - struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb) { int i; @@ -616,7 +624,6 @@ struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb) ERR("Timed out waiting for a free USB interface"); return NULL; } - usleep(100000); } struct usb_conn_t *conn = calloc(1, sizeof(*conn)); @@ -643,45 +650,6 @@ struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *usb) goto acquire_error; } - /* Make kernel release interface */ - if (libusb_kernel_driver_active(usb->printer, - uf->libusb_interface_index) == 1) { - /* Only linux supports this - other platforms will fail - thus we ignore the error code - it either works or it does not */ - libusb_detach_kernel_driver(usb->printer, - uf->libusb_interface_index); - } - - /* Claim the whole interface */ - int status = 0; - do { - /* Spinlock-like - Libusb does not offer a blocking call - so we're left with a spinlock */ - status = libusb_claim_interface(usb->printer, uf->libusb_interface_index); - if (status) NOTE("Failed to claim interface %d, retrying", conn->interface_index); - switch (status) { - case LIBUSB_ERROR_NOT_FOUND: - ERR("USB Interface did not exist"); - goto acquire_error; - case LIBUSB_ERROR_NO_DEVICE: - ERR("Printer was removed"); - goto acquire_error; - default: - break; - } - } while (status != 0 && !g_options.terminate); - - if (g_options.terminate) - goto acquire_error; - - /* Select the IPP-USB alt setting of the interface */ - libusb_set_interface_alt_setting(usb->printer, - uf->libusb_interface_index, - uf->interface_alt); - /* Take successfully acquired interface from the pool */ usb->num_taken++; usb->num_avail--; @@ -701,16 +669,6 @@ void usb_conn_release(struct usb_conn_t *conn) struct usb_sock_t *usb = conn->parent; sem_wait(&usb->pool_manage_lock); { - int status = 0; - do { - /* Spinlock-like - libusb does not offer a blocking call - so we're left with a spinlock */ - status = libusb_release_interface(usb->printer, - conn->interface->libusb_interface_index); - if (status) NOTE("Failed to release interface %d, retrying", conn->interface_index); - } while (status != 0 && !g_options.terminate); - /* Return usb interface to pool */ usb->num_taken--; usb->num_avail++; @@ -719,7 +677,6 @@ void usb_conn_release(struct usb_conn_t *conn) /* Release our interface lock */ sem_post(&conn->interface->lock); - free(conn); } sem_post(&usb->pool_manage_lock); @@ -780,148 +737,18 @@ int usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) return 0; } -struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) +struct libusb_transfer *setup_async_read(struct usb_conn_t *conn, + struct http_packet_t *pkt, + libusb_transfer_cb_fn callback, + void *user_data, uint32_t timeout) { - if (msg->is_completed) + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (transfer == NULL) return NULL; - struct http_packet_t *pkt = packet_new(msg); - if (pkt == NULL) { - ERR("failed to create packet for incoming usb message"); - goto cleanup; - } - - /* File packet */ - const int timeout = 1000; /* 1 sec */ - size_t read_size_ulong = packet_pending_bytes(pkt); - if (read_size_ulong == 0) - return pkt; - - uint64_t times_staled = 0; - while (read_size_ulong > 0 && !msg->is_completed && !g_options.terminate) { - if (read_size_ulong >= INT_MAX) - goto cleanup; - int read_size = (int)read_size_ulong; - - /* Pad read_size to multiple of usb's max packet size */ - read_size += (512 - (read_size % 512)) % 512; - - /* Expand buffer if needed */ - if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) - if (packet_expand(pkt) < 0) { - ERR("Failed to ensure room for usb pkt"); - goto cleanup; - } + libusb_fill_bulk_transfer(transfer, conn->parent->printer, + conn->interface->endpoint_in, pkt->buffer, + pkt->buffer_capacity, callback, user_data, timeout); - int gotten_size = 0; - int status = libusb_bulk_transfer(conn->parent->printer, - conn->interface->endpoint_in, - pkt->buffer + pkt->filled_size, - read_size, - &gotten_size, timeout); - - if (status == LIBUSB_ERROR_NO_DEVICE) { - ERR("Printer has been disconnected"); - goto cleanup; - } - - if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { - ERR("bulk xfer failed with error code %d", status); - ERR("tried reading %d bytes", read_size); - goto cleanup; - } else if (status == LIBUSB_ERROR_TIMEOUT) { - ERR("bulk xfer timed out, retrying ..."); - ERR("tried reading %d bytes, actually read %d bytes", - read_size, gotten_size); - } - - if (gotten_size < 0) { - ERR("Negative read size unexpected"); - goto cleanup; - } - - if (gotten_size > 0) { - times_staled = 0; - usb_conn_mark_moving(conn); - } else { - - /* Performance Test --------------- - How long we sleep here has a - dramatic affect on how long it - takes to load a page. - Earlier versions waited a tenth - of a second which resulted in - minute long page loads. - On my HP printer the most obvious - bottleneck is the "Unified.js" file - which weighs 517.87KB. My profiling - looked at how shortening this sleep - could improve this file's load times. - The cycle count is from perf and - covers an entire page load. - - Below are my results: - 1 in 100 == 2447ms, 261M cycles - 1 in 1,000 == 483ms, 500M cycles - 5 in 10,000 == 433ms, 800M cycles - 1 in 10,000 == 320ms, 3000M cycles */ - #define TIMEOUT_RATIO (10000 / 5) - static uint64_t stale_timeout = - CONN_STALE_THRESHHOLD * TIMEOUT_RATIO; - static uint64_t crash_timeout = - PRINTER_CRASH_TIMEOUT_ANSWER * TIMEOUT_RATIO; - static uint64_t skip_timeout = - 1000000000 / TIMEOUT_RATIO; - - struct timespec sleep_dur; - sleep_dur.tv_sec = 0; - sleep_dur.tv_nsec = skip_timeout; - nanosleep(&sleep_dur, NULL); - - if (status == LIBUSB_ERROR_TIMEOUT) - times_staled += TIMEOUT_RATIO * timeout / 1000; - else - times_staled++; - if (times_staled % TIMEOUT_RATIO == 0 || - status == LIBUSB_ERROR_TIMEOUT) { - NOTE("No bytes received for %d sec.", - times_staled / TIMEOUT_RATIO); - if (pkt->filled_size > 0) - NOTE("Packet so far \n===\n%s===\n", - hexdump(pkt->buffer, - pkt->filled_size)); - } - - if (times_staled > stale_timeout) { - usb_conn_mark_staled(conn); - - if (pkt->filled_size > 0 || - usb_all_conns_staled(conn->parent) || - times_staled > crash_timeout) { - ERR("USB timed out, giving up waiting for more data"); - break; - } - } - } - - if (gotten_size) { - NOTE("USB: Getting %d bytes of %d", - read_size, pkt->expected_size); - NOTE("USB: Got %d bytes", gotten_size); - } - packet_mark_received(pkt, (size_t)gotten_size); - read_size_ulong = packet_pending_bytes(pkt); - } - NOTE("USB: Received %d bytes of %d with type %d", - pkt->filled_size, pkt->expected_size, msg->type); - - if (pkt->filled_size == 0) - goto cleanup; - - return pkt; - - cleanup: - if (pkt != NULL) - packet_free(pkt); - return NULL; + return transfer; } @@ -67,4 +67,8 @@ struct usb_conn_t *usb_conn_acquire(struct usb_sock_t *); void usb_conn_release(struct usb_conn_t *); int usb_conn_packet_send(struct usb_conn_t *, struct http_packet_t *); -struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *, struct http_message_t *); + +struct libusb_transfer *setup_async_read(struct usb_conn_t *conn, + struct http_packet_t *pkt, + libusb_transfer_cb_fn callback, + void *user_data, uint32_t timeout); |