/* * tio - a serial device I/O tool * * Copyright (c) 2014-2022 Martin Lund * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #define _GNU_SOURCE // To access vasprintf #include #include #include #include #include "version.h" #include "config.h" #include "misc.h" #include "print.h" #include "rs485.h" #include "log.h" #include "configfile.h" #define HEX_N_VALUE_MAX 4096 enum opt_t { OPT_NONE, OPT_TIMESTAMP_FORMAT, OPT_TIMESTAMP_TIMEOUT, OPT_LOG_FILE, OPT_LOG_DIRECTORY, OPT_LOG_STRIP, OPT_LOG_APPEND, OPT_LINE_PULSE_DURATION, OPT_RS485, OPT_RS485_CONFIG, OPT_ALERT, OPT_COMPLETE_PROFILES, OPT_MUTE, OPT_SCRIPT, OPT_SCRIPT_FILE, OPT_SCRIPT_RUN, OPT_INPUT_MODE, OPT_OUTPUT_MODE, OPT_EXCLUDE_DEVICES, OPT_EXCLUDE_DRIVERS, OPT_EXCLUDE_TIDS, OPT_EXEC, }; /* Default options */ struct option_t option = { .target = "", .baudrate = 115200, .databits = 8, .flow = FLOW_NONE, .stopbits = 1, .parity = PARITY_NONE, .output_delay = 0, .output_line_delay = 0, .dtr_pulse_duration = 100, .rts_pulse_duration = 100, .cts_pulse_duration = 100, .dsr_pulse_duration = 100, .dcd_pulse_duration = 100, .ri_pulse_duration = 100, .no_reconnect = false, .auto_connect = AUTO_CONNECT_DIRECT, .log = false, .log_append = false, .log_filename = NULL, .log_directory = NULL, .log_strip = false, .local_echo = false, .timestamp = TIMESTAMP_NONE, .socket = NULL, .color = 256, // Bold .input_mode = INPUT_MODE_NORMAL, .output_mode = OUTPUT_MODE_NORMAL, .prefix_code = 20, // ctrl-t .prefix_key = 't', .prefix_enabled = true, .mute = false, .rs485 = false, .rs485_config_flags = 0, .rs485_delay_rts_before_send = -1, .rs485_delay_rts_after_send = -1, .alert = ALERT_NONE, .complete_profiles = false, .script = NULL, .script_filename = NULL, .script_run = SCRIPT_RUN_ALWAYS, .timestamp_timeout = 200, .exclude_devices = NULL, .exclude_drivers = NULL, .exclude_tids = NULL, .hex_n_value = 0, .vt100 = false, .exec = NULL, .map_i_nl_cr = false, .map_i_cr_nl = false, .map_ign_cr = false, .map_i_ff_escc = false, .map_i_nl_crnl = false, .map_i_cr_crnl = false, .map_o_cr_nl = false, .map_o_nl_crnl = false, .map_o_del_bs = false, .map_o_ltu = false, .map_o_nulbrk = false, .map_i_msb2lsb = false, .map_o_ign_cr = false, }; void option_print_help(char *argv[]) { UNUSED(argv); printf("Usage: tio [] \n"); printf("\n"); printf("Connect to TTY device directly or via configuration profile or topology ID.\n"); printf("\n"); printf("Options:\n"); printf(" -b, --baudrate Baud rate (default: 115200)\n"); printf(" -d, --databits 5|6|7|8 Data bits (default: 8)\n"); printf(" -f, --flow hard|soft|none Flow control (default: none)\n"); printf(" -s, --stopbits 1|2 Stop bits (default: 1)\n"); printf(" -p, --parity odd|even|none|mark|space Parity (default: none)\n"); printf(" -o, --output-delay Output character delay (default: 0)\n"); printf(" -O, --output-line-delay Output line delay (default: 0)\n"); printf(" --line-pulse-duration Set line pulse duration\n"); printf(" -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)\n"); printf(" --exclude-devices Exclude devices by pattern\n"); printf(" --exclude-drivers Exclude drivers by pattern\n"); printf(" --exclude-tids Exclude topology IDs by pattern\n"); printf(" -n, --no-reconnect Do not reconnect\n"); printf(" -e, --local-echo Enable local echo\n"); printf(" --input-mode normal|hex|line Select input mode (default: normal)\n"); printf(" --output-mode normal|hex|hexN Select output mode (default: normal)\n"); printf(" -t, --timestamp Enable line timestamp\n"); printf(" --timestamp-format Set timestamp format (default: 24hour)\n"); printf(" --timestamp-timeout Set timestamp timeout (default: 200)\n"); printf(" -l, --list List available serial devices, TIDs, and profiles\n"); printf(" -L, --log Enable log to file\n"); printf(" --log-file Set log filename\n"); printf(" --log-directory Set log directory path for automatic named logs\n"); printf(" --log-append Append to log file\n"); printf(" --log-strip Strip control characters and escape sequences\n"); printf(" -m, --map Map characters\n"); printf(" -c, --color 0..255|bold|none|list Colorize tio text (default: bold)\n"); printf(" -S, --socket Redirect I/O to socket\n"); printf(" --rs-485 Enable RS-485 mode\n"); printf(" --rs-485-config Set RS-485 configuration\n"); printf(" --alert bell|blink|none Alert on connect/disconnect (default: none)\n"); printf(" --mute Mute tio messages\n"); printf(" --script Run script from string\n"); printf(" --script-file Run script from file\n"); printf(" --script-run once|always|never Run script on connect (default: always)\n"); printf(" --exec Execute shell command with I/O redirected to device\n"); printf(" --complete-profiles Prints profiles (for shell completion)\n"); printf(" -v, --version Display version\n"); printf(" -h, --help Display help\n"); printf("\n"); printf("Options and profiles may be set via configuration file.\n"); printf("\n"); printf("In session you can press ctrl-%c ? to list available key commands.\n", option.prefix_key); printf("\n"); printf("See the man page for more details.\n"); } int option_string_to_integer(const char *string, int *value, const char *desc, int min, int max) { int val; char *end_token; errno = 0; val = strtol(string, &end_token, 10); if ((errno != 0) || (*end_token != 0)) { if (desc != NULL) { tio_error_print("Invalid %s '%s'", desc, string); exit(EXIT_FAILURE); } else { tio_error_print("Invalid digit '%s'", string); exit(EXIT_FAILURE); } return -1; } else { if ((val < min) || (val > max)) { if (desc != NULL) { tio_error_print("Invalid %s '%s'", desc, string); exit(EXIT_FAILURE); } else { tio_error_print("Invalid digit '%s'", string); exit(EXIT_FAILURE); } return -1; } else { *value = val; } } return 0; } void option_parse_flow(const char *arg, flow_t *flow) { assert(arg != NULL); /* Parse flow control */ if (strcmp("hard", arg) == 0) { *flow = FLOW_HARD; } else if (strcmp("soft", arg) == 0) { *flow = FLOW_SOFT; } else if (strcmp("none", arg) == 0) { *flow = FLOW_NONE; } else { tio_error_print("Invalid flow control '%s'", arg); exit(EXIT_FAILURE); } } const char *option_flow_to_string(flow_t flow) { switch (flow) { case FLOW_NONE: return "none"; case FLOW_HARD: return "hard"; case FLOW_SOFT: return "soft"; default: return "unknown"; } } void option_parse_parity(const char *arg, parity_t *parity) { assert(arg != NULL); if (strcmp("none", arg) == 0) { *parity = PARITY_NONE; } else if (strcmp("odd", arg) == 0) { *parity = PARITY_ODD; } else if (strcmp("even", arg) == 0) { *parity = PARITY_EVEN; } else if (strcmp("mark", arg) == 0) { *parity = PARITY_MARK; } else if (strcmp("space", arg) == 0) { *parity = PARITY_SPACE; } else { tio_error_print("Invalid parity '%s'", arg); exit(EXIT_FAILURE); } } const char *option_parity_to_string(parity_t parity) { switch (parity) { case PARITY_NONE: return "none"; case PARITY_ODD: return "odd"; case PARITY_EVEN: return "even"; case PARITY_MARK: return "mark"; case PARITY_SPACE: return "space"; default: return "unknown"; } } void option_parse_color(const char *arg, int *color) { int value; assert(arg != NULL); if (strcmp(optarg, "list") == 0) { // Print available color codes printf("Available color codes:\n"); for (int i=0; i<=255; i++) { printf(" \e[1;38;5;%dmThis is color code %d\e[0m\n", i, i); } exit(EXIT_SUCCESS); } else if (strcmp(arg, "none") == 0) { *color = -1; // No color } else if (strcmp(arg, "bold") == 0) { *color = 256; // Bold } else { if (option_string_to_integer(arg, &value, "color code", 0, 255) == 0) { *color = value; } } } void option_parse_alert(const char *arg, alert_t *alert) { assert(arg != NULL); if (strcmp(arg, "none") == 0) { *alert = ALERT_NONE; } else if (strcmp(arg, "bell") == 0) { *alert = ALERT_BELL; } else if (strcmp(arg, "blink") == 0) { *alert = ALERT_BLINK; } else { tio_error_print("Invalid alert '%s'", arg); exit(EXIT_FAILURE); } } const char* option_timestamp_format_to_string(timestamp_t timestamp) { switch (timestamp) { case TIMESTAMP_NONE: return "none"; break; case TIMESTAMP_24HOUR: return "24hour"; break; case TIMESTAMP_24HOUR_START: return "24hour-start"; break; case TIMESTAMP_24HOUR_DELTA: return "24hour-delta"; break; case TIMESTAMP_ISO8601: return "iso8601"; break; case TIMESTAMP_EPOCH: return "epoch"; break; case TIMESTAMP_EPOCH_USEC: return "epoch-usec"; break; default: return "unknown"; break; } } void option_parse_timestamp(const char *arg, timestamp_t *timestamp) { assert(arg != NULL); if (strcmp(arg, "24hour") == 0) { *timestamp = TIMESTAMP_24HOUR; } else if (strcmp(arg, "24hour-start") == 0) { *timestamp = TIMESTAMP_24HOUR_START; } else if (strcmp(arg, "24hour-delta") == 0) { *timestamp = TIMESTAMP_24HOUR_DELTA; } else if (strcmp(arg, "iso8601") == 0) { *timestamp = TIMESTAMP_ISO8601; } else if (strcmp(arg, "epoch") == 0) { *timestamp = TIMESTAMP_EPOCH; } else if (strcmp(arg, "epoch-usec") == 0) { *timestamp = TIMESTAMP_EPOCH_USEC; } else { tio_error_print("Invalid timestamp '%s'", arg); exit(EXIT_FAILURE); } } const char *option_alert_state_to_string(alert_t state) { switch (state) { case ALERT_NONE: return "none"; case ALERT_BELL: return "bell"; case ALERT_BLINK: return "blink"; default: return "unknown"; } } const char *option_auto_connect_state_to_string(auto_connect_t strategy) { switch (strategy) { case AUTO_CONNECT_DIRECT: return "direct"; case AUTO_CONNECT_NEW: return "new"; case AUTO_CONNECT_LATEST: return "latest"; default: return "unknown"; } } void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect) { assert(arg != NULL); if (arg != NULL) { if (strcmp(arg, "direct") == 0) { *auto_connect = AUTO_CONNECT_DIRECT; } else if (strcmp(arg, "new") == 0) { *auto_connect = AUTO_CONNECT_NEW; } else if (strcmp(arg, "latest") == 0) { *auto_connect = AUTO_CONNECT_LATEST; } else { tio_error_print("Invalid auto-connect strategy '%s'", arg); exit(EXIT_FAILURE); } } } void option_parse_line_pulse_duration(const char *arg) { bool token_found = true; char *token = NULL; char *buffer = strdup(arg); assert(arg != NULL); while (token_found == true) { if (token == NULL) { token = strtok(buffer,","); } else { token = strtok(NULL, ","); } if (token != NULL) { char keyname[11]; unsigned int value; if (sscanf(token, "%10[^=]=%d", keyname, &value) == 2) { if (!strcmp(keyname, "DTR")) { option.dtr_pulse_duration = value; } else if (!strcmp(keyname, "RTS")) { option.rts_pulse_duration = value; } else if (!strcmp(keyname, "CTS")) { option.cts_pulse_duration = value; } else if (!strcmp(keyname, "DSR")) { option.dsr_pulse_duration = value; } else if (!strcmp(keyname, "DCD")) { option.dcd_pulse_duration = value; } else if (!strcmp(keyname, "RI")) { option.ri_pulse_duration = value; } else { tio_error_print("Invalid line '%s'", keyname); exit(EXIT_FAILURE); } } else { token_found = false; } } else { token_found = false; } } free(buffer); } // Function to parse the input string int option_parse_hexN_string(const char *input_string) { regmatch_t match[2]; // One for entire match, one for the optional N int n_value = 0; regex_t regex; int ret; // Compile the regular expression to match "hex" and optionally capture N ret = regcomp(®ex, "^hex([0-9]+)?$", REG_EXTENDED); if (ret) { tio_error_print("Could not compile regex"); exit(EXIT_FAILURE); } // Execute the regular expression ret = regexec(®ex, input_string, 2, match, 0); if (!ret) { // If there is a match, extract the N value if present if (match[1].rm_so != -1) { char n_value_str[32]; // Assume max 32 digits for the numerical value strncpy(n_value_str, input_string + match[1].rm_so, match[1].rm_eo - match[1].rm_so); n_value_str[match[1].rm_eo - match[1].rm_so] = '\0'; // Null-terminate the string n_value = atoi(n_value_str); if ((n_value > HEX_N_VALUE_MAX) || (n_value == 0)) { n_value = -1; } } else { n_value = 0; } } else if (ret == REG_NOMATCH) { n_value = -1; } else { char msgbuf[100]; regerror(ret, ®ex, msgbuf, sizeof(msgbuf)); tio_error_print("Regex match failed: %s", msgbuf); exit(EXIT_FAILURE); } regfree(®ex); return n_value; } void option_parse_input_mode(const char *arg, input_mode_t *mode) { assert(arg != NULL); if (strcmp("normal", arg) == 0) { *mode = INPUT_MODE_NORMAL; } else if (strcmp("hex", arg) == 0) { *mode = INPUT_MODE_HEX; } else if (strcmp("line", arg) == 0) { *mode = INPUT_MODE_LINE; } else { tio_error_print("Invalid input mode '%s'", arg); exit(EXIT_FAILURE); } } void option_parse_output_mode(const char *arg, output_mode_t *mode) { int n = 0; assert(arg != NULL); if (strcmp("normal", arg) == 0) { *mode = OUTPUT_MODE_NORMAL; } else if ((n = option_parse_hexN_string(arg)) != -1) { option.hex_n_value = n; *mode = OUTPUT_MODE_HEX; } else { tio_error_print("Invalid output mode '%s'", arg); exit(EXIT_FAILURE); } } const char *option_input_mode_to_string(input_mode_t mode) { switch (mode) { case INPUT_MODE_NORMAL: return "normal"; case INPUT_MODE_HEX: return "hex"; case INPUT_MODE_LINE: return "line"; case INPUT_MODE_END: break; } return NULL; } const char *option_output_mode_to_string(output_mode_t mode) { switch (mode) { case OUTPUT_MODE_NORMAL: return "normal"; case OUTPUT_MODE_HEX: return "hex"; case OUTPUT_MODE_END: break; } return NULL; } void option_parse_script_run(const char *arg, script_run_t *script_run) { assert(arg != NULL); if (strcmp("once", arg) == 0) { *script_run = SCRIPT_RUN_ONCE; } else if (strcmp("always", arg) == 0) { *script_run = SCRIPT_RUN_ALWAYS; } else if (strcmp("never", arg) == 0) { *script_run = SCRIPT_RUN_NEVER; } else { tio_error_print("Invalid script run option '%s'", arg); exit(EXIT_FAILURE); } } void option_parse_mappings(const char *map) { bool token_found = true; char *token = NULL; char *buffer; if (map == NULL) { return; } /* Parse any specified input or output mappings */ buffer = strdup(map); while (token_found == true) { if (token == NULL) { token = strtok(buffer,","); } else { token = strtok(NULL, ","); } if (token != NULL) { if (strcmp(token,"INLCR") == 0) { option.map_i_nl_cr = true; } else if (strcmp(token,"IGNCR") == 0) { option.map_ign_cr = true; } else if (strcmp(token,"ICRNL") == 0) { option.map_i_cr_nl = true; } else if (strcmp(token,"OCRNL") == 0) { option.map_o_cr_nl = true; } else if (strcmp(token,"ODELBS") == 0) { option.map_o_del_bs = true; } else if (strcmp(token,"IFFESCC") == 0) { option.map_i_ff_escc = true; } else if (strcmp(token,"INLCRNL") == 0) { option.map_i_nl_crnl = true; } else if (strcmp(token,"ICRCRNL") == 0) { option.map_i_cr_crnl = true; } else if (strcmp(token, "ONLCRNL") == 0) { option.map_o_nl_crnl = true; } else if (strcmp(token, "OLTU") == 0) { option.map_o_ltu = true; } else if (strcmp(token, "ONULBRK") == 0) { option.map_o_nulbrk = true; } else if (strcmp(token, "OIGNCR") == 0) { option.map_o_ign_cr = true; } else if (strcmp(token, "IMSB2LSB") == 0) { option.map_i_msb2lsb = true; } else { printf("Error: Unknown mapping flag %s\n", token); exit(EXIT_FAILURE); } } else { token_found = false; } } free(buffer); } void options_print() { tio_printf(" Device: %s", device_name); tio_printf(" Baudrate: %u", option.baudrate); tio_printf(" Databits: %d", option.databits); tio_printf(" Flow: %s", option_flow_to_string(option.flow)); tio_printf(" Stopbits: %d", option.stopbits); tio_printf(" Parity: %s", option_parity_to_string(option.parity)); tio_printf(" Local echo: %s", option.local_echo ? "true" : "false"); tio_printf(" Timestamp: %s", option_timestamp_format_to_string(option.timestamp)); tio_printf(" Timestamp timeout: %u", option.timestamp_timeout); tio_printf(" Output delay: %d", option.output_delay); tio_printf(" Output line delay: %d", option.output_line_delay); tio_printf(" Automatic connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect)); tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "true" : "false"); tio_printf(" Pulse duration: DTR=%d RTS=%d CTS=%d DSR=%d DCD=%d RI=%d", option.dtr_pulse_duration, option.rts_pulse_duration, option.cts_pulse_duration, option.dsr_pulse_duration, option.dcd_pulse_duration, option.ri_pulse_duration); tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode)); tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode)); tio_printf(" Alert: %s", option_alert_state_to_string(option.alert)); if (option.log) { tio_printf(" Log file: %s", log_get_filename()); if (option.log_directory != NULL) { tio_printf(" Log file directory: %s", option.log_directory); } tio_printf(" Log append: %s", option.log_append ? "true" : "false"); tio_printf(" Log strip: %s", option.log_strip ? "true" : "false"); } if (option.socket) { tio_printf(" Socket: %s", option.socket); } if (option.script_filename != NULL) { tio_printf(" Script file: %s", option.script_filename); tio_printf(" Script run: %s", script_run_state_to_string(option.script_run)); } } void options_parse(int argc, char *argv[]) { int c; if (argc == 1) { option_print_help(argv); exit(EXIT_SUCCESS); } // Support no-color.org informal spec char *no_color = getenv("NO_COLOR"); if (no_color != NULL && no_color[0] != '\0') { option.color = -1; } // Check for vt100 terminal char *term = getenv("TERM"); if ((term != NULL) && (!strcmp(term, "vt100"))) { option.vt100 = true; } while (1) { static struct option long_options[] = { {"baudrate", required_argument, 0, 'b' }, {"databits", required_argument, 0, 'd' }, {"flow", required_argument, 0, 'f' }, {"stopbits", required_argument, 0, 's' }, {"parity", required_argument, 0, 'p' }, {"output-delay", required_argument, 0, 'o' }, {"output-line-delay" , required_argument, 0, 'O' }, {"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION }, {"auto-connect", required_argument, 0, 'a' }, {"exclude-devices", required_argument, 0, OPT_EXCLUDE_DEVICES }, {"exclude-drivers", required_argument, 0, OPT_EXCLUDE_DRIVERS }, {"exclude-tids", required_argument, 0, OPT_EXCLUDE_TIDS }, {"no-reconnect", no_argument, 0, 'n' }, {"local-echo", no_argument, 0, 'e' }, {"timestamp", no_argument, 0, 't' }, {"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT }, {"timestamp-timeout", required_argument, 0, OPT_TIMESTAMP_TIMEOUT }, {"list", no_argument, 0, 'l' }, {"log", no_argument, 0, 'L' }, {"log-file", required_argument, 0, OPT_LOG_FILE }, {"log-directory", required_argument, 0, OPT_LOG_DIRECTORY }, {"log-append", no_argument, 0, OPT_LOG_APPEND }, {"log-strip", no_argument, 0, OPT_LOG_STRIP }, {"socket", required_argument, 0, 'S' }, {"map", required_argument, 0, 'm' }, {"color", required_argument, 0, 'c' }, {"input-mode", required_argument, 0, OPT_INPUT_MODE }, {"output-mode", required_argument, 0, OPT_OUTPUT_MODE }, {"rs-485", no_argument, 0, OPT_RS485 }, {"rs-485-config", required_argument, 0, OPT_RS485_CONFIG }, {"alert", required_argument, 0, OPT_ALERT }, {"mute", no_argument, 0, OPT_MUTE }, {"script", required_argument, 0, OPT_SCRIPT }, {"script-file", required_argument, 0, OPT_SCRIPT_FILE }, {"script-run", required_argument, 0, OPT_SCRIPT_RUN }, {"exec", required_argument, 0, OPT_EXEC }, {"version", no_argument, 0, 'v' }, {"help", no_argument, 0, 'h' }, {"complete-profiles", no_argument, 0, OPT_COMPLETE_PROFILES }, {0, 0, 0, 0 } }; /* getopt_long stores the option index here */ int option_index = 0; /* Parse argument using getopt_long */ c = getopt_long(argc, argv, "b:d:f:s:p:o:O:a:netLlS:m:c:xrvh", long_options, &option_index); /* Detect the end of the options */ if (c == -1) break; switch (c) { case 0: /* If this option sets a flag, do nothing else now */ if (long_options[option_index].flag != 0) break; printf("option %s", long_options[option_index].name); if (optarg) printf(" with arg %s", optarg); printf("\n"); break; case 'b': option_string_to_integer(optarg, &option.baudrate, "baudrate", 0, INT_MAX); break; case 'd': option_string_to_integer(optarg, &option.databits, "databits", 5, 8); break; case 'f': option_parse_flow(optarg, &option.flow); break; case 's': option_string_to_integer(optarg, &option.stopbits, "stopbits", 1, 2); break; case 'p': option_parse_parity(optarg, &option.parity); break; case 'o': option_string_to_integer(optarg, &option.output_delay, "output delay", 0, INT_MAX); break; case 'O': option_string_to_integer(optarg, &option.output_line_delay, "output line delay", 0, INT_MAX); break; case OPT_LINE_PULSE_DURATION: option_parse_line_pulse_duration(optarg); break; case 'a': option_parse_auto_connect(optarg, &option.auto_connect); break; case OPT_EXCLUDE_DEVICES: option.exclude_devices = optarg; break; case OPT_EXCLUDE_DRIVERS: option.exclude_drivers = optarg; break; case OPT_EXCLUDE_TIDS: option.exclude_tids = optarg; break; case 'n': option.no_reconnect = true; break; case 'e': option.local_echo = true; break; case 't': if (option.timestamp == TIMESTAMP_NONE) { option.timestamp = TIMESTAMP_24HOUR; } break; case OPT_TIMESTAMP_FORMAT: option_parse_timestamp(optarg, &option.timestamp); break; case OPT_TIMESTAMP_TIMEOUT: option_string_to_integer(optarg, &option.timestamp_timeout, "timestamp timeout", 0, INT_MAX); break; case 'L': option.log = true; break; case 'l': list_serial_devices(); config_list_targets(); exit(EXIT_SUCCESS); break; case OPT_LOG_FILE: option.log_filename = optarg; break; case OPT_LOG_DIRECTORY: option.log_directory = optarg; break; case OPT_LOG_STRIP: option.log_strip = true; break; case OPT_LOG_APPEND: option.log_append = true; break; case 'S': option.socket = optarg; break; case 'm': option_parse_mappings(optarg); break; case 'c': option_parse_color(optarg, &option.color); break; case OPT_INPUT_MODE: option_parse_input_mode(optarg, &option.input_mode); break; case OPT_OUTPUT_MODE: option_parse_output_mode(optarg, &option.output_mode); break; case OPT_RS485: option.rs485 = true; break; case OPT_RS485_CONFIG: rs485_parse_config(optarg); break; case OPT_ALERT: option_parse_alert(optarg, &option.alert); break; case OPT_MUTE: option.mute = true; break; case OPT_SCRIPT: option.script = optarg; break; case OPT_SCRIPT_FILE: option.script_filename = optarg; break; case OPT_SCRIPT_RUN: option_parse_script_run(optarg, &option.script_run); break; case OPT_EXEC: option.exec = optarg; break; case 'v': printf("tio %s\n", VERSION); exit(EXIT_SUCCESS); break; case 'h': option_print_help(argv); exit(EXIT_SUCCESS); break; case OPT_COMPLETE_PROFILES: option.complete_profiles = true; break; case '?': /* getopt_long already printed an error message */ exit(EXIT_FAILURE); break; default: exit(EXIT_FAILURE); } } /* Assume first non-option is the target (tty device, profile, tid) */ if (strcmp(option.target, "")) { optind++; } else if (optind < argc) { option.target = argv[optind++]; } if (option.complete_profiles) { return; } if (option.auto_connect != AUTO_CONNECT_DIRECT) { return; } if (strlen(option.target) == 0) { tio_error_print("Missing tty device, profile or topology ID"); exit(EXIT_FAILURE); } /* Print any remaining command line arguments as unknown */ if (optind < argc) { fprintf(stderr, "Error: Unknown argument "); while (optind < argc) { fprintf(stderr, "%s ", argv[optind++]); } fprintf(stderr, "\n"); exit(EXIT_FAILURE); } } void options_parse_final(int argc, char *argv[]) { /* Do 2nd pass to override settings set by configuration file */ optind = 1; // Reset option index to restart scanning of argv options_parse(argc, argv); #ifdef __CYGWIN__ unsigned char portnum; char *tty_win; if ( ((strncmp("COM", option.target, 3) == 0) || (strncmp("com", option.target, 3) == 0) ) && (sscanf(option.target + 3, "%hhu", &portnum) == 1) && (portnum > 0) ) { asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); option.target = tty_win; } #endif }