diff options
Diffstat (limited to 'usbhid-dump/src/usbhid-dump.c')
-rw-r--r-- | usbhid-dump/src/usbhid-dump.c | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/usbhid-dump/src/usbhid-dump.c b/usbhid-dump/src/usbhid-dump.c new file mode 100644 index 0000000..bd669e2 --- /dev/null +++ b/usbhid-dump/src/usbhid-dump.c @@ -0,0 +1,1067 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbhid-dump - entry point * + * + * Copyright (C) 2010-2011 Nikolai Kondrashov <spbnick@gmail.com> + */ + +#include "config.h" + +#include "iface_list.h" +#include "misc.h" +#include <libusb.h> + +#include <assert.h> +#include <stdbool.h> +#include <ctype.h> +#include <limits.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <stdio.h> +#include <stdint.h> + +/* Define LIBUSB_CALL for libusb <= 1.0.8 */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +#define GENERIC_ERROR(_fmt, _args...) \ + fprintf(stderr, _fmt "\n", ##_args) + +#define IFACE_ERROR(_iface, _fmt, _args...) \ + GENERIC_ERROR("%s:" _fmt, _iface->addr_str, ##_args) + +#define GENERIC_FAILURE(_fmt, _args...) \ + GENERIC_ERROR("Failed to " _fmt, ##_args) + +#define IFACE_FAILURE(_iface, _fmt, _args...) \ + IFACE_ERROR(_iface, "Failed to " _fmt, ##_args) + +#define LIBUSB_FAILURE(_fmt, _args...) \ + GENERIC_FAILURE(_fmt ": %s", ##_args, libusb_strerror(err)) + +#define LIBUSB_IFACE_FAILURE(_iface, _fmt, _args...) \ + IFACE_FAILURE(_iface, _fmt ": %s", ##_args, libusb_strerror(err)) + +#define ERROR_CLEANUP(_fmt, _args...) \ + do { \ + GENERIC_ERROR(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define FAILURE_CLEANUP(_fmt, _args...) \ + do { \ + GENERIC_FAILURE(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_FAILURE_CLEANUP(_fmt, _args...) \ + do { \ + LIBUSB_FAILURE(_fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_IFACE_FAILURE_CLEANUP(_iface, _fmt, _args...) \ + do { \ + LIBUSB_IFACE_FAILURE(_iface, _fmt, ##_args); \ + goto cleanup; \ + } while (0) + +#define LIBUSB_GUARD(_expr, _fmt, _args...) \ + do { \ + err = _expr; \ + if (err != LIBUSB_SUCCESS) \ + LIBUSB_FAILURE_CLEANUP(_fmt, ##_args); \ + } while (0) + +#define LIBUSB_IFACE_GUARD(_expr, _iface, _fmt, _args...) \ + do { \ + err = _expr; \ + if (err != LIBUSB_SUCCESS) \ + LIBUSB_IFACE_FAILURE_CLEANUP(_iface, _fmt, ##_args); \ + } while (0) + +/**< Number of the signal causing the exit */ +static volatile sig_atomic_t exit_signum = 0; + +static void +exit_sighandler(int signum) +{ + if (exit_signum == 0) + exit_signum = signum; +} + +/**< "Stream paused" flag - non-zero if paused */ +static volatile sig_atomic_t stream_paused = 0; + +static void +stream_pause_sighandler(int signum) +{ + (void)signum; + stream_paused = 1; +} + +static void +stream_resume_sighandler(int signum) +{ + (void)signum; + stream_paused = 0; +} + +/**< "Stream feedback" flag - non-zero if feedback is enabled */ +static volatile sig_atomic_t stream_feedback = 0; + +static void +dump(const uhd_iface *iface, + const char *entity, + const uint8_t *ptr, + size_t len) +{ + static const char xd[] = "0123456789ABCDEF"; + static char buf[] = " XX\n"; + size_t pos; + uint8_t b; + struct timeval tv; + + gettimeofday(&tv, NULL); + + fprintf(stdout, "%s:%-16s %12llu.%.6u\n", + iface->addr_str, entity, + (unsigned long long int)tv.tv_sec, + (unsigned int)tv.tv_usec); + + for (pos = 1; len > 0; len--, ptr++, pos++) + { + b = *ptr; + buf[1] = xd[b >> 4]; + buf[2] = xd[b & 0xF]; + + (void)fwrite(buf, ((pos % 16 == 0) ? 4 : 3), 1, stdout); + } + + if (pos % 16 != 1) + fputc('\n', stdout); + fputc('\n', stdout); + + fflush(stdout); +} + + +static bool +dump_iface_list_descriptor(const uhd_iface *list) +{ + const uhd_iface *iface; + uint8_t buf[UHD_MAX_DESCRIPTOR_SIZE]; + int rc; + enum libusb_error err; + + UHD_IFACE_LIST_FOR_EACH(iface, list) + { + if (iface->rd_len > sizeof(buf)) + { + err = LIBUSB_ERROR_NO_MEM; + LIBUSB_IFACE_FAILURE(iface, "report descriptor too long: %hu", + iface->rd_len); + return false; + } + + rc = libusb_control_transfer(iface->dev->handle, + /* See HID spec, 7.1.1 */ + 0x81, + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_REPORT << 8), iface->number, + buf, iface->rd_len, UHD_IO_TIMEOUT); + if (rc < 0) + { + err = rc; + LIBUSB_IFACE_FAILURE(iface, "retrieve report descriptor"); + return false; + } + dump(iface, "DESCRIPTOR", buf, rc); + } + + return true; +} + + +static void LIBUSB_CALL +dump_iface_list_stream_cb(struct libusb_transfer *transfer) +{ + enum libusb_error err; + uhd_iface *iface; + + assert(transfer != NULL); + + iface = (uhd_iface *)transfer->user_data; + assert(uhd_iface_valid(iface)); + + /* Clear interface "has transfer submitted" flag */ + iface->submitted = false; + + switch (transfer->status) + { + case LIBUSB_TRANSFER_COMPLETED: + /* Dump the result */ + if (!stream_paused) + { + dump(iface, "STREAM", + transfer->buffer, transfer->actual_length); + if (stream_feedback) + fputc('.', stderr); + } + /* Resubmit the transfer */ + err = libusb_submit_transfer(transfer); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "resubmit a transfer"); + else + { + /* Set interface "has transfer submitted" flag */ + iface->submitted = true; + } + break; + +#define MAP(_name, _desc) \ + case LIBUSB_TRANSFER_##_name: \ + IFACE_ERROR(iface, _desc); \ + break + + MAP(ERROR, "Interrupt transfer failed"); + MAP(TIMED_OUT, "Interrupt transfer timed out"); + MAP(STALL, "Interrupt transfer halted (endpoint stalled)"); + MAP(NO_DEVICE, "Device was disconnected"); + MAP(OVERFLOW, "Interrupt transfer overflowed " + "(device sent more data than requested)"); +#undef MAP + + case LIBUSB_TRANSFER_CANCELLED: + break; + } +} + + +static const char * +format_time_interval(unsigned int i) +{ + static char buf[128]; + char *p = buf; + unsigned int h = i / (60 * 60 * 1000); + unsigned int m = (i % (60 * 60 * 1000)) / (60 * 1000); + unsigned int s = (i % (60 * 1000)) / 1000; + unsigned int ms = i % 1000; + +#define FRACTION(_prev_sum, _name, _val) \ + do { \ + if ((_val) > 0) \ + p += snprintf(p, sizeof(buf) - (p - buf), \ + "%s%u " _name "%s", \ + ((_prev_sum) > 0 ? " " : ""), \ + _val, \ + (((_val) == 1) ? "" : "s")); \ + if (p >= (buf + sizeof(buf))) \ + return buf; \ + } while (0) + + FRACTION(0, "hour", h); + FRACTION(h, "minute", m); + FRACTION(h + m, "second", s); + FRACTION(h + m + s, "millisecond", ms); + +#undef FRACTION + + return buf; +} + + +static const char * +format_timeout(unsigned int i) +{ + return (i == 0) ? "infinite" : format_time_interval(i); +} + + +static bool +dump_iface_list_stream(libusb_context *ctx, + uhd_iface *list, + unsigned int timeout) +{ + bool result = false; + enum libusb_error err; + size_t transfer_num = 0; + struct libusb_transfer **transfer_list = NULL; + struct libusb_transfer **ptransfer; + uhd_iface *iface; + bool submitted = false; + + fprintf(stderr, + "Starting dumping interrupt transfer stream\n" + "with %s timeout.\n\n", + format_timeout(timeout)); + + UHD_IFACE_LIST_FOR_EACH(iface, list) + { + /* Set report protocol */ + LIBUSB_IFACE_GUARD(uhd_iface_set_protocol(iface, true, + UHD_IO_TIMEOUT), + iface, "set report protocol"); + /* Set infinite idle duration */ + LIBUSB_IFACE_GUARD(uhd_iface_set_idle(iface, 0, UHD_IO_TIMEOUT), + iface, "set infinite idle duration"); + } + + /* Calculate number of interfaces and thus transfers */ + transfer_num = uhd_iface_list_len(list); + + /* Allocate transfer list */ + transfer_list = malloc(sizeof(*transfer_list) * transfer_num); + if (transfer_list == NULL) + FAILURE_CLEANUP("allocate transfer list"); + + /* Zero transfer list */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + *ptransfer = NULL; + + /* Allocate transfers */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + *ptransfer = libusb_alloc_transfer(0); + if (*ptransfer == NULL) + FAILURE_CLEANUP("allocate a transfer"); + /* + * Set user_data to NULL explicitly, since libusb_alloc_transfer + * does memset to zero only and zero is not NULL, strictly speaking. + */ + (*ptransfer)->user_data = NULL; + } + + /* Initialize the transfers as interrupt transfers */ + for (ptransfer = transfer_list, iface = list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++, iface = iface->next) + { + void *buf; + const size_t len = iface->int_in_ep_maxp; + + /* Allocate the transfer buffer */ + buf = malloc(len); + if (len > 0 && buf == NULL) + FAILURE_CLEANUP("allocate a transfer buffer"); + + /* Initialize the transfer */ + libusb_fill_interrupt_transfer(*ptransfer, + iface->dev->handle, iface->int_in_ep_addr, + buf, len, + dump_iface_list_stream_cb, + (void *)iface, + timeout); + + /* Ask to free the buffer when the transfer is freed */ + (*ptransfer)->flags |= LIBUSB_TRANSFER_FREE_BUFFER; + } + + /* Submit first transfer requests */ + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + LIBUSB_GUARD(libusb_submit_transfer(*ptransfer), + "submit a transfer"); + /* Set interface "has transfer submitted" flag */ + ((uhd_iface *)(*ptransfer)->user_data)->submitted = true; + /* Set "have any submitted transfers" flag */ + submitted = true; + } + + /* Run the event machine */ + while (submitted && exit_signum == 0) + { + /* Handle the transfer events */ + err = libusb_handle_events(ctx); + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_INTERRUPTED) + LIBUSB_FAILURE_CLEANUP("handle transfer events"); + + /* Check if there are any submitted transfers left */ + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + submitted = true; + } + } + + /* If all the transfers were terminated unexpectedly */ + if (transfer_num > 0 && !submitted) + ERROR_CLEANUP("No more interfaces to dump"); + + result = true; + +cleanup: + + /* Cancel the transfers */ + if (submitted) + { + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + { + err = libusb_cancel_transfer(*ptransfer); + if (err == LIBUSB_SUCCESS) + submitted = true; + else + { + LIBUSB_FAILURE("cancel a transfer, ignoring"); + /* + * XXX are we really sure + * the transfer won't be finished? + */ + iface->submitted = false; + } + } + } + } + + /* Wait for transfer cancellation */ + while (submitted) + { + /* Handle cancellation events */ + err = libusb_handle_events(ctx); + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_INTERRUPTED) + { + LIBUSB_FAILURE("handle transfer cancellation events, " + "aborting transfer cancellation"); + break; + } + + /* Check if there are any submitted transfers left */ + submitted = false; + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + if (iface != NULL && iface->submitted) + submitted = true; + } + } + + /* + * Free transfer list along with non-submitted transfers and their + * buffers. + */ + if (transfer_list != NULL) + { + for (ptransfer = transfer_list; + (size_t)(ptransfer - transfer_list) < transfer_num; + ptransfer++) + { + iface = (uhd_iface *)(*ptransfer)->user_data; + + /* + * Only free a transfer if it is not submitted. Better leak some + * memory than have some important memory overwritten. + */ + if (iface == NULL || !iface->submitted) + libusb_free_transfer(*ptransfer); + } + + free(transfer_list); + } + + return result; +} + + +static int +run(bool dump_descriptor, + bool dump_stream, + unsigned int stream_timeout, + uint8_t bus_num, + uint8_t dev_addr, + uint16_t vid, + uint16_t pid, + int iface_num) +{ + int result = 1; + enum libusb_error err; + libusb_context *ctx = NULL; + uhd_dev *dev_list = NULL; + uhd_iface *iface_list = NULL; + uhd_iface *iface; + + /* Create libusb context */ + LIBUSB_GUARD(libusb_init(&ctx), "create libusb context"); + + /* Set libusb debug level to informational only */ +#if HAVE_LIBUSB_SET_OPTION + libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); +#else + libusb_set_debug(ctx, LIBUSB_LOG_LEVEL_INFO); +#endif + + /* Open device list */ + LIBUSB_GUARD(uhd_dev_list_open(ctx, bus_num, dev_addr, + vid, pid, &dev_list), + "find and open the devices"); + + /* Retrieve the list of HID interfaces from the device list */ + LIBUSB_GUARD(uhd_iface_list_new(dev_list, &iface_list), + "find HID interfaces"); + + /* Filter the interface list by specified interface number */ + if (iface_num != UHD_IFACE_NUM_ANY) + iface_list = uhd_iface_list_fltr_by_num(iface_list, iface_num); + + /* Check if there are any interfaces left */ + if (uhd_iface_list_empty(iface_list)) + ERROR_CLEANUP("No matching HID interfaces"); + + /* Detach and claim the interfaces */ + UHD_IFACE_LIST_FOR_EACH(iface, iface_list) + { + LIBUSB_IFACE_GUARD(uhd_iface_detach(iface), + iface, "detach from the kernel driver"); + LIBUSB_IFACE_GUARD(uhd_iface_claim(iface), + iface, "claim"); + } + + /* Run with the prepared interface list */ + result = (!dump_descriptor || dump_iface_list_descriptor(iface_list)) && + (!dump_stream || dump_iface_list_stream(ctx, iface_list, + stream_timeout)) + ? 0 + : 1; + +cleanup: + + /* Release and attach the interfaces back */ + UHD_IFACE_LIST_FOR_EACH(iface, iface_list) + { + err = uhd_iface_release(iface); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "release"); + + err = uhd_iface_attach(iface); + if (err != LIBUSB_SUCCESS) + LIBUSB_IFACE_FAILURE(iface, "attach to the kernel driver"); + } + + /* Free the interface list */ + uhd_iface_list_free(iface_list); + + /* Close the device list */ + uhd_dev_list_close(dev_list); + + /* Destroy the libusb context */ + if (ctx != NULL) + libusb_exit(ctx); + + return result; +} + + +static bool +parse_number_pair(const char *str, + int base, + long *pn1, + long *pn2) +{ + const char *p; + char *end; + long n1; + long n2; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtol doing so) */ + while (isspace((int)*p)) + p++; + + /* Extract the first number */ + errno = 0; + n1 = strtol(p, &end, base); + if (errno != 0) + return false; + + /* If nothing was read */ + if (end == p) + return false; + + /* Move on */ + p = end; + + /* Skip space */ + while (isspace((int)*p)) + p++; + + /* If it is the end of string */ + if (*p == '\0') + n2 = 0; + else + { + /* If it is not the number separator */ + if (*p != ':') + return false; + + /* Skip the number separator */ + p++; + + /* Skip space (prevent strtol doing so) */ + while (isspace((int)*p)) + p++; + + /* Extract the second number */ + errno = 0; + n2 = strtol(p, &end, base); + if (errno != 0) + return false; + /* If nothing was read */ + if (end == p) + return false; + + /* Move on */ + p = end; + + /* Skip space */ + while (isspace((int)*p)) + p++; + + /* If it is not the end of string */ + if (*p != '\0') + return false; + } + + /* Output the numbers */ + if (pn1 != NULL) + *pn1 = n1; + if (pn2 != NULL) + *pn2 = n2; + + return true; +} + + +static bool +parse_address(const char *str, + uint8_t *pbus_num, + uint8_t *pdev_addr) +{ + long bus_num; + long dev_addr; + + assert(str != NULL); + + if (!parse_number_pair(str, 10, &bus_num, &dev_addr)) + return false; + + if (bus_num < 0 || bus_num > UINT8_MAX || + dev_addr < 0 || dev_addr > UINT8_MAX) + return false; + + if (pbus_num != NULL) + *pbus_num = bus_num; + if (pdev_addr != NULL) + *pdev_addr = dev_addr; + + return true; +} + + +static bool +parse_model(const char *str, + uint16_t *pvid, + uint16_t *ppid) +{ + long vid; + long pid; + + assert(str != NULL); + + if (!parse_number_pair(str, 16, &vid, &pid)) + return false; + + if (vid < 0 || vid > UINT16_MAX || + pid < 0 || pid > UINT16_MAX) + return false; + + if (pvid != NULL) + *pvid = vid; + if (ppid != NULL) + *ppid = pid; + + return true; +} + + +static bool +parse_iface_num(const char *str, + uint8_t *piface_num) +{ + long iface_num; + const char *p; + char *end; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtol doing so) */ + while (isspace((int)*p)) + p++; + + /* Extract interface number */ + errno = 0; + iface_num = strtol(p, &end, 10); + if (errno != 0 || end == p || iface_num < 0 || iface_num > UINT8_MAX) + return false; + + /* Output interface number */ + if (piface_num != NULL) + *piface_num = iface_num; + + return true; +} + + +static bool +parse_timeout(const char *str, + unsigned int *ptimeout) +{ + long long timeout; + const char *p; + char *end; + + assert(str != NULL); + + p = str; + + /* Skip space (prevent strtoll doing so) */ + while (isspace((int)*p)) + p++; + + /* Extract timeout */ + errno = 0; + timeout = strtoll(p, &end, 10); + if (errno != 0 || end == p || timeout < 0 || timeout > UINT_MAX) + return false; + + /* Output timeout */ + if (ptimeout != NULL) + *ptimeout = timeout; + + return true; +} + + +static bool +version(FILE *stream) +{ + return + fprintf( + stream, +PACKAGE_STRING "\n" +"Copyright (C) 2010 Nikolai Kondrashov\n" +"License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.\n" +"\n" +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n") >= 0; +} + + +static bool +usage(FILE *stream, const char *name) +{ + return + fprintf( + stream, +"Usage: %s [OPTION]...\n" +"Dump USB device HID report descriptor(s) and/or stream(s).\n" +"\n" +"Options:\n" +" -h, --help output this help message and exit\n" +" -v, --version output version information and exit\n" +"\n" +" -s, -a, --address=bus[:dev] limit interfaces by bus number\n" +" (1-255) and device address (1-255),\n" +" decimal; zeroes match any\n" +" -d, -m, --model=vid[:pid] limit interfaces by vendor and\n" +" product IDs (0001-ffff), hexadecimal;\n" +" zeroes match any\n" +" -i, --interface=NUMBER limit interfaces by number (0-254),\n" +" decimal; 255 matches any\n" +"\n" +" -e, --entity=STRING what to dump: either \"descriptor\",\n" +" \"stream\" or \"all\"; value can be\n" +" abbreviated\n" +"\n" +" -t, --stream-timeout=NUMBER stream interrupt transfer timeout, ms;\n" +" zero means infinity\n" +" -p, --stream-paused start with the stream dump output\n" +" paused\n" +" -f, --stream-feedback enable stream dumping feedback: for\n" +" every transfer dumped a dot is\n" +" printed to stderr\n" +"\n" +"Default options: --stream-timeout=60000 --entity=descriptor\n" +"\n" +"Signals:\n" +" USR1/USR2 pause/resume the stream dump output\n" +"\n", + name) >= 0; +} + + +typedef enum opt_val { + OPT_VAL_HELP = 'h', + OPT_VAL_VERSION = 'v', + OPT_VAL_ADDRESS = 'a', + OPT_VAL_ADDRESS_COMP = 's', + OPT_VAL_MODEL = 'm', + OPT_VAL_MODEL_COMP = 'd', + OPT_VAL_INTERFACE = 'i', + OPT_VAL_ENTITY = 'e', + OPT_VAL_STREAM_TIMEOUT = 't', + OPT_VAL_STREAM_PAUSED = 'p', + OPT_VAL_STREAM_FEEDBACK = 'f', +} opt_val; + + +static const struct option long_opt_list[] = { + {.val = OPT_VAL_HELP, + .name = "help", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_VERSION, + .name = "version", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_ADDRESS, + .name = "address", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_MODEL, + .name = "model", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_INTERFACE, + .name = "interface", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_ENTITY, + .name = "entity", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_STREAM_TIMEOUT, + .name = "stream-timeout", + .has_arg = required_argument, + .flag = NULL}, + {.val = OPT_VAL_STREAM_PAUSED, + .name = "stream-paused", + .has_arg = no_argument, + .flag = NULL}, + {.val = OPT_VAL_STREAM_FEEDBACK, + .name = "stream-feedback", + .has_arg = no_argument, + .flag = NULL}, + {.val = 0, + .name = NULL, + .has_arg = 0, + .flag = NULL} +}; + + +static const char *short_opt_list = "hvs:a:d:m:i:e:t:pf"; + + +int +main(int argc, char **argv) +{ + int result; + + const char *name; + + int c; + + uint8_t bus_num = UHD_BUS_NUM_ANY; + uint8_t dev_addr = UHD_DEV_ADDR_ANY; + + uint16_t vid = UHD_VID_ANY; + uint16_t pid = UHD_PID_ANY; + + uint8_t iface_num = UHD_IFACE_NUM_ANY; + + bool dump_descriptor = true; + bool dump_stream = false; + unsigned int stream_timeout = 60000; + + struct sigaction sa; + + /* + * Extract program invocation name + */ + name = strrchr(argv[0], '/'); + if (name == NULL) + name = argv[0]; + else + name++; + +#define USAGE_ERROR(_fmt, _args...) \ + do { \ + fprintf(stderr, _fmt "\n", ##_args); \ + usage(stderr, name); \ + return 1; \ + } while (0) + + /* + * Parse command line arguments + */ + while ((c = getopt_long(argc, argv, + short_opt_list, long_opt_list, NULL)) >= 0) + { + switch (c) + { + case OPT_VAL_HELP: + usage(stdout, name); + return 0; + break; + case OPT_VAL_VERSION: + version(stdout); + return 0; + break; + case OPT_VAL_ADDRESS: + case OPT_VAL_ADDRESS_COMP: + if (!parse_address(optarg, &bus_num, &dev_addr)) + USAGE_ERROR("Invalid device address \"%s\"", optarg); + break; + case OPT_VAL_MODEL: + case OPT_VAL_MODEL_COMP: + if (!parse_model(optarg, &vid, &pid)) + USAGE_ERROR("Invalid model \"%s\"", optarg); + break; + case OPT_VAL_INTERFACE: + if (!parse_iface_num(optarg, &iface_num)) + USAGE_ERROR("Invalid interface number \"%s\"", optarg); + break; + case OPT_VAL_ENTITY: + if (strncmp(optarg, "descriptor", strlen(optarg)) == 0) + { + dump_descriptor = true; + dump_stream = false; + } + else if (strncmp(optarg, "stream", strlen(optarg)) == 0) + { + dump_descriptor = false; + dump_stream = true; + } + else if (strncmp(optarg, "all", strlen(optarg)) == 0) + { + dump_descriptor = true; + dump_stream = true; + } + else + USAGE_ERROR("Unknown entity \"%s\"", optarg); + + break; + case OPT_VAL_STREAM_TIMEOUT: + if (!parse_timeout(optarg, &stream_timeout)) + USAGE_ERROR("Invalid stream timeout \"%s\"", optarg); + break; + case OPT_VAL_STREAM_PAUSED: + stream_paused = 1; + break; + case OPT_VAL_STREAM_FEEDBACK: + stream_feedback = 1; + break; + case '?': + usage(stderr, name); + return 1; + break; + } + } + + /* + * Verify positional arguments + */ + if (optind < argc) + USAGE_ERROR("Positional arguments are not accepted"); + + /* + * Setup signal handlers + */ + /* Setup SIGINT to terminate gracefully */ + sigaction(SIGINT, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + { + sa.sa_handler = exit_sighandler; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGTERM); + sa.sa_flags = 0; /* NOTE: no SA_RESTART on purpose */ + sigaction(SIGINT, &sa, NULL); + } + + /* Setup SIGTERM to terminate gracefully */ + sigaction(SIGTERM, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + { + sa.sa_handler = exit_sighandler; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGINT); + sa.sa_flags = 0; /* NOTE: no SA_RESTART on purpose */ + sigaction(SIGTERM, &sa, NULL); + } + + /* Setup SIGUSR1/SIGUSR2 to pause/resume the stream output */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = stream_pause_sighandler; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = stream_resume_sighandler; + sigaction(SIGUSR2, &sa, NULL); + + /* Make stdout buffered - we will flush it explicitly */ + setbuf(stdout, NULL); + + /* Run! */ + result = run(dump_descriptor, dump_stream, stream_timeout, + bus_num, dev_addr, vid, pid, iface_num); + + /* + * Restore signal handlers + */ + sigaction(SIGINT, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + signal(SIGINT, SIG_DFL); + + sigaction(SIGTERM, NULL, &sa); + if (sa.sa_handler != SIG_IGN) + signal(SIGTERM, SIG_DFL); + + /* + * Reproduce the signal used to stop the program to get proper exit + * status. + */ + if (exit_signum != 0) + raise(exit_signum); + + return result; +} + + |