/** @file * @brief usbhid-dump - entry point * * Copyright (C) 2010 Nikolai Kondrashov * * This file is part of usbhid-dump. * * Usbhid-dump is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Usbhid-dump is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with usbhid-dump; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * @author Nikolai Kondrashov * * @(#) $Id$ */ #include "uhd/iface_list.h" #include "uhd/str.h" #include "uhd/libusb.h" #include "uhd/misc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #define ERROR(_fmt, _args...) \ fprintf(stderr, _fmt "\n", ##_args) #define IFACE_ERROR(_iface, _fmt, _args...) \ ERROR("%s:" _fmt, _iface->addr_str, ##_args) #define FAILURE(_fmt, _args...) \ ERROR("Failed to " _fmt, ##_args) #define IFACE_FAILURE(_iface, _fmt, _args...) \ IFACE_ERROR(_iface, "Failed to " _fmt, ##_args) #define LIBUSB_FAILURE(_fmt, _args...) \ 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 { \ ERROR(_fmt, ##_args); \ goto cleanup; \ } while (0) #define FAILURE_CLEANUP(_fmt, _args...) \ do { \ 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]; 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) { 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, sizeof(buf), 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 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 */ libusb_set_debug(ctx, 3); /* 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(*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(*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(*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(*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(*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(*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 .\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) { 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", program_invocation_short_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; char 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; #define USAGE_ERROR(_fmt, _args...) \ do { \ fprintf(stderr, _fmt "\n", ##_args); \ usage(stderr); \ 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); 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); 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; }