diff --git a/README.md b/README.md index 1a4e0b7..2846449 100644 --- a/README.md +++ b/README.md @@ -385,7 +385,7 @@ Example configuration file: ``` # Defaults -baudrate = 9600 +baudrate = 115200 databits = 8 parity = none stopbits = 1 @@ -393,15 +393,15 @@ color = 10 [rpi3] device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0 -baudrate = 115200 -no-reconnect = enable -log = enable +no-reconnect = true +log = true log-file = rpi3.log line-pulse-duration = DTR=200,RTS=150 color = 11 [svf2] device = /dev/ttyUSB0 +baudrate = 9600 script = expect("login: "); send("root\n"); expect("Password: "); send("root\n") color = 12 diff --git a/examples/config/config b/examples/config/config index 4e0de75..df32b8d 100644 --- a/examples/config/config +++ b/examples/config/config @@ -15,22 +15,22 @@ databits = 8 flow = none stopbits = 1 parity = none -prefix-ctrl-key = t output-delay = 0 output-line-delay = 0 auto-connect = direct -no-reconnect = disable +no-reconnect = false +local-echo = false input-mode = normal output-mode = normal -timestamp = disable -log = disable -log-append = disable -log-strip = disable -local-echo = disable +timestamp = false +log = false +log-append = false +log-strip = false color = bold -rs-485 = disable +rs-485 = false alert = none script-run = always +prefix-ctrl-key = t # Configuration profiles @@ -50,9 +50,9 @@ color = 10 [tincan] baudrate = 9600 device = /dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0 -log = enable +log = true log-file = tincan.log -log-strip = enable +log-strip = true color = 11 [usb] @@ -62,7 +62,7 @@ color = 12 [rs-485-device] device = /dev/ttyUSB0 -rs-485 = enable +rs-485 = true rs-485-config = RTS_ON_SEND=1,RTS_AFTER_SEND=1,RTS_DELAY_BEFORE_SEND=60,RTS_DELAY_AFTER_SEND=80,RX_DURING_TX color = 13 @@ -71,4 +71,3 @@ device = /dev/ttyUSB0 color = 14 script = high(DTR); low(RTS); msleep(100); low(DTR); high(RTS); msleep(100); low(RTS) script-run = always - diff --git a/man/tio.1.in b/man/tio.1.in index 7dd7bc2..93840c0 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -623,7 +623,7 @@ regular expressions: .eo [usb device] pattern = usb([0-9]*) -device = /dev/ttyUSB%s +device = /dev/ttyUSB%m1 baudrate = 115200 .ec .fi @@ -635,7 +635,7 @@ Activate the configuration profile by pattern match: $ tio usb12 .TP -Which is equivalent to: +Which becomes equivalent to: $ tio -b 115200 /dev/ttyUSB12 diff --git a/src/alert.c b/src/alert.c index e2f90a4..f9ed3ec 100644 --- a/src/alert.c +++ b/src/alert.c @@ -29,29 +29,6 @@ #include "print.h" #include "options.h" -alert_t alert_option_parse(const char *arg) -{ - alert_t alert = option.alert; // Default - - if (arg != NULL) - { - if (strcmp(arg, "none") == 0) - { - return ALERT_NONE; - } - else if (strcmp(arg, "bell") == 0) - { - return ALERT_BELL; - } - else if (strcmp(arg, "blink") == 0) - { - return ALERT_BLINK; - } - } - - return alert; -} - void blink_background(void) { // Turn on reverse video @@ -109,18 +86,3 @@ void alert_disconnect(void) break; } } - -const char *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"; - } -} diff --git a/src/alert.h b/src/alert.h index d7b66f5..91258c9 100644 --- a/src/alert.h +++ b/src/alert.h @@ -29,7 +29,5 @@ typedef enum ALERT_END, } alert_t; -alert_t alert_option_parse(const char *arg); void alert_connect(void); void alert_disconnect(void); -const char *alert_state_to_string(alert_t state); diff --git a/src/configfile.c b/src/configfile.c index 0357b53..ac66e9b 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -32,10 +32,11 @@ #include #include #include +#include #include #include #include -#include +#include #include "options.h" #include "configfile.h" #include "misc.h" @@ -46,505 +47,555 @@ #include "timestamp.h" #include "alert.h" -struct config_t +#define CONFIG_GROUP_NAME_DEFAULT "default" + +struct config_t config = {}; + +static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...) { - const char *user; + (void)dest; + GError *error = NULL; + bool mismatch = true; - char *path; - char *section_name; - char *match; - - char *target; - char *flow; - char *parity; - char *log_filename; - char *socket; - char *map; - char *script; - char *script_filename; - bool script_run; - char *exclude_devices; - char *exclude_drivers; - char *exclude_tids; -}; - -static struct config_t c; - -static int get_match(const char *input, const char *pattern, char **match) -{ - int ret; - int len = 0; - regex_t re; - regmatch_t m[2]; - char err[128]; - - /* compile a regex with the pattern */ - ret = regcomp(&re, pattern, REG_EXTENDED); - if (ret) + gchar *string = g_key_file_get_string(key_file, group, key, &error); + if (error != NULL) { - regerror(ret, &re, err, sizeof(err)); - tio_error_printf("Regex failure: %s", err); - return ret; - } - - /* try to match on input */ - ret = regexec(&re, input, 2, m, 0); - if (!ret) - { - len = m[1].rm_eo - m[1].rm_so; - } - - regfree(&re); - - if (len) - { - asprintf(match, "%s", &input[m[1].rm_so]); - } - - return len; -} - -static bool read_boolean(const char *value, const char *name) -{ - const char *true_values[] = { "true", "enable", "on", "yes", "1", NULL }; - const char *false_values[] = { "false", "disable", "off", "no", "0", NULL }; - - for (int i = 0; true_values[i] != NULL; i++) - if (strcmp(value, true_values[i]) == 0) - return true; - - for (int i = 0; false_values[i] != NULL; i++) - if (strcmp(value, false_values[i]) == 0) - return false; - - tio_error_printf("Invalid value '%s' for option '%s' in configuration file", - value, name); - exit(EXIT_FAILURE); -} - -static long read_integer(const char *value, const char *name, long min_value, long max_value) -{ - errno = 0; - char *endptr; - long result = strtol(value, &endptr, 10); - - if (errno || endptr == value || *endptr != '\0' || result < min_value || result > max_value) - { - tio_error_printf("Invalid value '%s' for option '%s' in configuration file", - value, name); + if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) + { + // Key not found - ignore key + g_error_free(error); + return; + } + tio_error_print("%s: %s", config.path, error->message); + g_error_free(error); exit(EXIT_FAILURE); } - return result; + va_list args; + const char* current_arg = allowed_string; + va_start(args, allowed_string); + + if (current_arg == NULL) + { + mismatch = false; + } + + // Iterate through variable arguments + while (current_arg != NULL) + { + if (strcmp(string, current_arg) == 0) + { + mismatch = false; + break; + } + current_arg = va_arg(args, const char *); + } + + if (mismatch) + { + tio_error_print("%s: Invalid %s value '%s' in %s profile", config.path, key, string, group); + exit(EXIT_FAILURE); + } + + va_end(args); + + *dest = string; } -/** - * data_handler() - walk config file to load parameters matching user input - * - * INIH handler used to get all parameters from a given section - */ -static int data_handler(void *user, const char *section, const char *name, - const char *value) +static void config_get_integer(GKeyFile *key_file, gchar *group, gchar *key, int *dest, int min, int max) { - UNUSED(user); + (void)dest; + GError *error = NULL; - // If section matches current section being parsed - if (!strcmp(section, c.section_name)) + int value = g_key_file_get_integer(key_file, group, key, &error); + if (error != NULL) { - // Set configuration parameter if found - if (!strcmp(name, "device") || !strcmp(name, "tty")) + if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) { - asprintf(&c.target, value, c.match); - option.target = c.target; + // Key not found - ignore key + g_error_free(error); + return; } - else if (!strcmp(name, "baudrate")) - { - option.baudrate = read_integer(value, name, 0, LONG_MAX); - } - else if (!strcmp(name, "databits")) - { - option.databits = read_integer(value, name, 5, 8); - } - else if (!strcmp(name, "flow")) - { - asprintf(&c.flow, "%s", value); - option.flow = c.flow; - } - else if (!strcmp(name, "stopbits")) - { - option.stopbits = read_integer(value, name, 1, 2); - } - else if (!strcmp(name, "parity")) - { - asprintf(&c.parity, "%s", value); - option.parity = c.parity; - } - else if (!strcmp(name, "output-delay")) - { - option.output_delay = read_integer(value, name, 0, LONG_MAX); - } - else if (!strcmp(name, "output-line-delay")) - { - option.output_line_delay = read_integer(value, name, 0, LONG_MAX); - } - else if (!strcmp(name, "line-pulse-duration")) - { - line_pulse_duration_option_parse(value); - } - else if (!strcmp(name, "no-reconnect")) - { - option.no_reconnect = read_boolean(value, name); - } - else if (!strcmp(name, "auto-connect")) - { - option.auto_connect = auto_connect_option_parse(value); - } - else if (!strcmp(name, "log")) - { - option.log = read_boolean(value, name); - } - else if (!strcmp(name, "log-file")) - { - asprintf(&c.log_filename, "%s", value); - option.log_filename = c.log_filename; - } - else if (!strcmp(name, "log-append")) - { - option.log_append = read_boolean(value, name); - } - else if (!strcmp(name, "log-strip")) - { - option.log_strip = read_boolean(value, name); - } - else if (!strcmp(name, "local-echo")) - { - option.local_echo = read_boolean(value, name); - } - else if (!strcmp(name, "input-mode")) - { - option.input_mode = input_mode_option_parse(value); - } - else if (!strcmp(name, "output-mode")) - { - option.output_mode = output_mode_option_parse(value); - } - else if (!strcmp(name, "timestamp")) - { - option.timestamp = read_boolean(value, name) ? - TIMESTAMP_24HOUR : TIMESTAMP_NONE; - } - else if (!strcmp(name, "timestamp-format")) - { - option.timestamp = timestamp_option_parse(value); - } - else if (!strcmp(name, "timestamp-timeout")) - { - option.timestamp_timeout = read_integer(value, name, 0, LONG_MAX); - } - else if (!strcmp(name, "map")) - { - asprintf(&c.map, "%s", value); - option.map = c.map; - } - else if (!strcmp(name, "color")) - { - if (!strcmp(value, "list")) - { - // Ignore - return 0; - } - else if (!strcmp(value, "none")) - { - option.color = -1; // No color - return 0; - } - else if (!strcmp(value, "bold")) - { - option.color = 256; // Bold - return 0; - } + tio_error_print("%s: %s", config.path, error->message); + g_error_free(error); + exit(EXIT_FAILURE); + } - option.color = atoi(value); - if ((option.color < 0) || (option.color > 255)) - { - option.color = -1; // No color - } - } - else if (!strcmp(name, "socket")) + if ((value < min) || (value > max)) + { + tio_error_print("%s: Invalid %s value '%d' in %s profile", config.path, key, value, group); + exit(EXIT_FAILURE); + } + + *dest = value; +} + +static void config_get_bool(GKeyFile *key_file, gchar *group, gchar *key, bool *dest) +{ + (void)dest; + GError *error = NULL; + + bool value = g_key_file_get_boolean(key_file, group, key, &error); + if (error != NULL) + { + if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) { - asprintf(&c.socket, "%s", value); - option.socket = c.socket; + // Key not found - ignore key + g_error_free(error); + return; } - else if (!strcmp(name, "prefix-ctrl-key")) + tio_error_print("%s: %s", config.path, error->message); + g_error_free(error); + exit(EXIT_FAILURE); + } + + *dest = value; +} + +static void config_parse_keys(GKeyFile *key_file, char *group) +{ + char *string = NULL; + bool boolean = false; + + config_get_string(key_file, group, "device", &config.device, NULL); + config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX); + config_get_integer(key_file, group, "databits", &option.databits, 5, 8); + config_get_string(key_file, group, "flow", &string, "none", "hard", "soft", NULL); + if (string != NULL) + { + option_parse_flow(string, &option.flow); + g_free((void *)string); + string = NULL; + } + config_get_integer(key_file, group, "stopbits", &option.stopbits, 1, 2); + config_get_string(key_file, group, "parity", &string, "odd", "even", "none", "mark", "space", NULL); + if (string != NULL) + { + option_parse_parity(string, &option.parity); + g_free((void *)string); + string = NULL; + } + + config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX); + config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX); + config_get_string(key_file, group, "line-pulse-duration", &string, NULL); + if (string != NULL) + { + option_parse_line_pulse_duration(string); + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "auto-connect", &string, "new", "latest", "direct", NULL); + if (string != NULL) + { + option_parse_auto_connect(string, &option.auto_connect); + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "exclude-devices", &option.exclude_devices, NULL); + config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL); + config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL); + config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect); + config_get_bool(key_file, group, "local-echo", &option.no_reconnect); + config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL); + if (string != NULL) + { + option_parse_input_mode(string, &option.input_mode); + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "output-mode", &string, NULL); + if (string != NULL) + { + option_parse_output_mode(string, &option.output_mode); + g_free((void *)string); + string = NULL; + } + config_get_bool(key_file, group, "timestamp", &boolean); + if (boolean == true) + { + option.timestamp = TIMESTAMP_24HOUR; + } + config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", NULL); + if (string != NULL) + { + option_parse_timestamp(string, &option.timestamp); + g_free((void *)string); + string = NULL; + } + config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX); + config_get_bool(key_file, group, "log", &option.log); + config_get_string(key_file, group, "log-file", &option.log_filename, NULL); + config_get_bool(key_file, group, "log-append", &option.log_append); + config_get_bool(key_file, group, "log-strip", &option.log_strip); + config_get_string(key_file, group, "map", &option.map, NULL); + config_get_string(key_file, group, "color", &string, NULL); + if (string != NULL) + { + if (strcmp(string, "list") == 0) { - if (!strcmp(value, "none")) - { - option.prefix_enabled = false; - } - else if (ctrl_key_code(value[0]) > 0) - { - option.prefix_code = ctrl_key_code(value[0]); - option.prefix_key = value[0]; - } + // Ignore } - else if (!strcmp(name, "rs-485")) + else if (strcmp(string, "none") == 0) { - option.rs485 = read_boolean(value, name); + option.color = -1; // No color } - else if (!strcmp(name, "rs-485-config")) + else if (strcmp(string, "bold") == 0) { - rs485_parse_config(value); - } - else if (!strcmp(name, "alert")) - { - option.alert = alert_option_parse(value); - } - else if (!strcmp(name, "mute")) - { - option.mute = read_boolean(value, name); - } - else if (!strcmp(name, "pattern")) - { - // Do nothing - } - else if (!strcmp(name, "script")) - { - asprintf(&c.script, "%s", value); - option.script = c.script; - } - else if (!strcmp(name, "script-file")) - { - asprintf(&c.script_filename, "%s", value); - option.script_filename = c.script_filename; - } - else if (!strcmp(name, "script-run")) - { - option.script_run = script_run_option_parse(value); - } - else if (!strcmp(name, "exclude-devices")) - { - c.exclude_devices = strdup(value); - option.exclude_devices = c.exclude_devices; - } - else if (!strcmp(name, "exclude-drivers")) - { - c.exclude_drivers = strdup(value); - option.exclude_drivers = c.exclude_drivers; - } - else if (!strcmp(name, "exclude-tids")) - { - c.exclude_tids = strdup(value); - option.exclude_tids = c.exclude_tids; + option.color = 256; // Bold } else { - tio_warning_printf("Unknown option '%s' in configuration file, ignored", name); + option.color = atoi(string); + if ((option.color < 0) || (option.color > 255)) + { + tio_error_print("%s: Invalid color value in %s profile", config.path, group); + exit(EXIT_FAILURE); + } } + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "socket", &option.socket, NULL); + config_get_bool(key_file, group, "rs-485", &option.rs485); + config_get_string(key_file, group, "rs-385-config", &string, NULL); + if (string != NULL) + { + rs485_parse_config(string); + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "alert", &string, "bell", "blink", "none", NULL); + if (string != NULL) + { + option_parse_alert(string, &option.alert); + g_free((void *)string); + string = NULL; + } + config_get_bool(key_file, group, "mute", &option.mute); + config_get_string(key_file, group, "script", &option.script, NULL); + config_get_string(key_file, group, "script-file", &option.script_filename, NULL); + config_get_string(key_file, group, "script-run", &string, NULL); + if (string != NULL) + { + option_parse_script_run(string, &option.script_run); + g_free((void *)string); + string = NULL; + } + config_get_string(key_file, group, "prefix-ctrl-key", &string, NULL); + if (string != NULL) + { + if (strcmp(string, "none") == 0) + { + option.prefix_enabled = false; + } + else if (strlen(string) >= 2) + { + tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group); + exit(EXIT_FAILURE); + } + else if (ctrl_key_code(string[0]) > 0) + { + option.prefix_enabled = true; + option.prefix_code = ctrl_key_code(string[0]); + option.prefix_key = string[0]; + } + else + { + tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group); + exit(EXIT_FAILURE); + } + g_free((void *)string); + string = NULL; } - - return 0; } -/** - * section_pattern_search_handler() - walk config file to find section matching user input - * - * INIH handler used to resolve the section matching the user's input. - * This will look for the pattern element of each section and try to match it - * with the user input. - */ -static int section_pattern_search_handler(void *user, const char *section, const char *varname, - const char *varval) -{ - UNUSED(user); - - if (strcmp(varname, "pattern")) - return 0; - - if (!strcmp(varval, c.user)) - { - /* pattern matches as plain text */ - asprintf(&c.section_name, "%s", section); - } - else if (get_match(c.user, varval, &c.match) > 0) - { - /* pattern matches as regex */ - asprintf(&c.section_name, "%s", section); - } - - return 0; -} - -/** - * section_pattern_search_handler() - walk config file to find section matching user input - * - * INIH handler used to resolve the section matching the user's input. - * This will try to match the user input against a section with the name of the user input. - */ -static int section_name_search_handler(void *user, const char *section, const char *varname, - const char *varval) -{ - UNUSED(user); - UNUSED(varname); - UNUSED(varval); - - if (!strcmp(section, c.user)) - { - /* section name matches as plain text */ - asprintf(&c.section_name, "%s", section); - } - - return 0; -} - -static int section_name_print_handler(void *user, const char *section, const char *varname, - const char *varval) -{ - UNUSED(user); - UNUSED(varname); - UNUSED(varval); - - static char *section_previous = ""; - - if (strcmp(section, section_previous) != 0) - { - printf("%s ", section); - section_previous = strdup(section); - } - - return 0; -} - -static int resolve_config_file(void) +static int config_file_resolve(void) { char *xdg = getenv("XDG_CONFIG_HOME"); if (xdg) { - if (asprintf(&c.path, "%s/tio/config", xdg) != -1) + if (asprintf(&config.path, "%s/tio/config", xdg) != -1) { - if (access(c.path, F_OK) == 0) + if (access(config.path, F_OK) == 0) { return 0; } - free(c.path); + free(config.path); } } char *home = getenv("HOME"); if (home) { - if (asprintf(&c.path, "%s/.config/tio/config", home) != -1) + if (asprintf(&config.path, "%s/.config/tio/config", home) != -1) { - if (access(c.path, F_OK) == 0) + if (access(config.path, F_OK) == 0) { return 0; } - free(c.path); + free(config.path); } - if (asprintf(&c.path, "%s/.tioconfig", home) != -1) + if (asprintf(&config.path, "%s/.tioconfig", home) != -1) { - if (access(c.path, F_OK) == 0) + if (access(config.path, F_OK) == 0) { return 0; } - free(c.path); + free(config.path); } } - c.path = NULL; + config.path = NULL; return -EINVAL; } void config_file_show_profiles(void) { - memset(&c, 0, sizeof(struct config_t)); + memset(&config, 0, sizeof(struct config_t)); // Find config file - if (resolve_config_file() != 0) + if (config_file_resolve() != 0) { // None found - stop parsing return; } - ini_parse(c.path, section_name_print_handler, NULL); + GKeyFile *keyfile; + GError *error = NULL; + + keyfile = g_key_file_new(); + + if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error)) + { + tio_error_print("Failure loading file: %s", error->message); + g_error_free(error); + return; + } + + // Get all group names + gsize num_groups; + gchar **group = g_key_file_get_groups(keyfile, &num_groups); + + for (gsize i = 0; i < num_groups; i++) + { + // Skip default group + if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0) + { + continue; + } + } + + g_strfreev(group); + g_key_file_free(keyfile); +} + +static void replace_substring(char *str, const char *substr, const char *replacement) +{ + char *pos = strstr(str, substr); + if (pos != NULL) + { + int substrLen = strlen(substr); + int replacementLen = strlen(replacement); + memmove(pos + replacementLen, pos + substrLen, strlen(pos + substrLen) + 1); + memcpy(pos, replacement, replacementLen); + } +} + +static char *match_and_replace(const char *str, const char *pattern, char *device) +{ + char replacement_str[PATH_MAX] = {}; + char m_key[13] = {}; + regex_t regex; + + assert(str != NULL); + assert(pattern != NULL); + assert(device != NULL); + + char *string = malloc(strlen(device) + PATH_MAX); + if (string == NULL) + { + return NULL; + } + strcpy(string, device); + + /* Find matches of pattern in str. For each match, replace any '%mN' in the + * copy of the device string with the corresponding match subexpression and + * return the new formed device string. + * + * Note: %m0 = Full match expression. + * %m1 = First subexpression + * %m2 = Second subexpression + * %m3 = etc.. + */ + + if (regcomp(®ex, pattern, REG_EXTENDED) != 0) + { + // Failure to compile regular expression + return NULL; + } + + regmatch_t matches[regex.re_nsub + 1]; + int status = regexec(®ex, str, regex.re_nsub + 1, matches, REG_EXTENDED); + if (status == 0) + { + tio_debug_printf("Full match: "); + int j = 0; + for (int i = matches[0].rm_so; i < matches[0].rm_eo; i++) + { + tio_debug_printf_raw("%c", str[i]); + replacement_str[j++] = str[i]; + } + replacement_str[j] = '\0'; + replace_substring(string, "%m0", replacement_str); + tio_debug_printf_raw("\n"); + + for (int i = 1; i < ((int)regex.re_nsub + 1) && matches[i].rm_so != -1; i++) + { + tio_debug_printf("Subexpression %d match: ", i); + int k = 0; + for (int j = matches[i].rm_so; j < matches[i].rm_eo; j++) + { + tio_debug_printf_raw("%c", str[j]); + replacement_str[k++] = str[j]; + } + replacement_str[k] = '\0'; + sprintf(m_key, "%%m%d", i); + replace_substring(string, m_key, replacement_str); + tio_debug_printf_raw("\n"); + } + } + else if (status == REG_NOMATCH) + { + goto error; + } + else + { + char error_message[100]; + regerror(status, ®ex, error_message, sizeof(error_message)); + tio_error_print("Regex match failed: %s", error_message); + goto error; + } + + regfree(®ex); + return string; + +error: + regfree(®ex); + return NULL; } void config_file_parse(void) { - int ret; - - memset(&c, 0, sizeof(struct config_t)); - // Find config file - if (resolve_config_file() != 0) + if (config_file_resolve() != 0) { // None found - stop parsing return; } - // Set user input which may be tty device or profile or tid - c.user = option.target; - - if (!c.user) + if (option.target == NULL) { return; } - // Parse default (unnamed) settings - asprintf(&c.section_name, "%s", ""); - ret = ini_parse(c.path, data_handler, NULL); - if (ret < 0) + GKeyFile *keyfile = g_key_file_new(); + GError *error = NULL; + + if (g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error) == false) { - tio_error_printf("Unable to parse configuration file (%d)", ret); + tio_error_print("Failure loading file %s: %s", config.path, error->message); + g_error_free(error); exit(EXIT_FAILURE); } - free(c.section_name); - c.section_name = NULL; - // Find matching section - ret = ini_parse(c.path, section_pattern_search_handler, NULL); - if (!c.section_name) + // Parse default group/section + if (g_key_file_has_group(keyfile, CONFIG_GROUP_NAME_DEFAULT)) { - ret = ini_parse(c.path, section_name_search_handler, NULL); - if (!c.section_name) + config_parse_keys(keyfile, CONFIG_GROUP_NAME_DEFAULT); + } + + // Parse target + if (g_key_file_has_group(keyfile, option.target)) + { + config.active_group = strdup(option.target); + config_parse_keys(keyfile, option.target); + } + else + { + // Find group by pattern + gsize num_groups; + gchar **group = g_key_file_get_groups(keyfile, &num_groups); + + for (gsize i = 0; i < num_groups; i++) { - tio_debug_printf("Unable to match user input to configuration section (%d)", ret); - return; + // Skip default group + if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0) + { + continue; + } + + // Lookup 'pattern' key + GError *error = NULL; + gchar *pattern = g_key_file_get_string(keyfile, group[i], "pattern", &error); + if (error != NULL) + { + g_error_free(error); + continue; + } + + // Lookup 'device' key + gchar *device = g_key_file_get_string(keyfile, group[i], "device", &error); + if (error != NULL) + { + g_error_free(error); + continue; + } + + // Match pattern against target and replace any sub expression + // matches (%mN) in device string and return resulting string + // representing the new pattern based string. + config.device = match_and_replace(option.target, pattern, device); + if (config.device != NULL) + { + // Match found - save device + char *device = strdup(config.device); + + // Parse found group + config_parse_keys(keyfile, group[i]); + + // Update configuration + config.active_group = strdup(group[i]); + config.device = device; + + break; + } } + + g_strfreev(group); } - // Parse settings of found section (profile) - ret = ini_parse(c.path, data_handler, NULL); - if (ret < 0) - { - tio_error_printf("Unable to parse configuration file (%d)", ret); - exit(EXIT_FAILURE); - } + g_key_file_free(keyfile); atexit(&config_exit); } void config_exit(void) { - free(c.target); - free(c.flow); - free(c.parity); - free(c.log_filename); - free(c.map); - - free(c.match); - free(c.section_name); - free(c.path); + free(config.active_group); + free(config.path); + free(config.device); } void config_file_print(void) { - if (c.path != NULL) + if (config.path != NULL) { - tio_printf(" Active configuration file: %s", c.path); - if (c.section_name != NULL) + tio_printf(" Active configuration file: %s", config.path); + if (config.active_group != NULL) { - tio_printf(" Active configuration profile: %s", c.section_name); + tio_printf(" Active configuration profile: %s", config.active_group); } } } diff --git a/src/configfile.h b/src/configfile.h index 554df3c..7b7a3a9 100644 --- a/src/configfile.h +++ b/src/configfile.h @@ -22,6 +22,15 @@ #pragma once +struct config_t +{ + char *path; + char *active_group; + char *device; +}; + +extern struct config_t config; + void config_file_print(void); void config_file_parse(void); void config_exit(void); diff --git a/src/error.c b/src/error.c index 9cff900..2973fbb 100644 --- a/src/error.c +++ b/src/error.c @@ -35,6 +35,12 @@ static char error[2][1000]; static bool in_session = false; +bool error_normal = true; + +void switch_error_output_mode(void) +{ + error_normal = false; +} void error_enter_session_mode(void) { diff --git a/src/error.h b/src/error.h index ed81ba3..9a257fc 100644 --- a/src/error.h +++ b/src/error.h @@ -21,6 +21,10 @@ #pragma once +#include + +extern bool error_normal; + #define TIO_SUCCESS 0 #define TIO_ERROR 1 @@ -28,3 +32,4 @@ void tio_error_printf(const char *format, ...); void tio_error_printf_silent(const char *format, ...); void error_exit(void); void error_enter_session_mode(void); +void switch_error_output_mode(void); diff --git a/src/log.c b/src/log.c index 0a2df34..c593213 100644 --- a/src/log.c +++ b/src/log.c @@ -75,7 +75,7 @@ int log_open(const char *filename) // If using 'new' or 'latest' autoconnect strategy we simply use strategy // name to name autogenerated log name as device names may vary asprintf(&automatic_filename, "tio_%s_%s.log", - auto_connect_state_to_string(option.auto_connect), + option_auto_connect_state_to_string(option.auto_connect), date_time()); } diff --git a/src/main.c b/src/main.c index 269995e..304258e 100644 --- a/src/main.c +++ b/src/main.c @@ -71,6 +71,9 @@ int main(int argc, char *argv[]) interactive_mode = false; } + /* Switch error output format */ + switch_error_output_mode(); + /* Configure output terminal */ if (isatty(fileno(stdout))) { diff --git a/src/meson.build b/src/meson.build index 297777e..3b3790b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -37,9 +37,6 @@ endif tio_dep = [ dependency('threads', required: true), dependency('glib-2.0', required: true), - dependency('inih', required: true, - fallback : ['libinih', 'inih_dep'], - default_options: ['default_library=static', 'distro_install=false']), lua_dep ] diff --git a/src/misc.c b/src/misc.c index 091675a..630671e 100644 --- a/src/misc.c +++ b/src/misc.c @@ -54,22 +54,6 @@ void delay(long ms) nanosleep(&ts, NULL); } -long string_to_long(char *string) -{ - long result; - char *end_token; - - errno = 0; - result = strtol(string, &end_token, 10); - if ((errno != 0) || (*end_token != 0)) - { - printf("Error: Invalid digit\n"); - exit(EXIT_FAILURE); - } - - return result; -} - int ctrl_key_code(unsigned char key) { if ((key >= 'a') && (key <= 'z')) diff --git a/src/misc.h b/src/misc.h index 25be622..edbb511 100644 --- a/src/misc.h +++ b/src/misc.h @@ -27,10 +27,7 @@ #define UNUSED(expr) do { (void)(expr); } while (0) void delay(long ms); -long string_to_long(char *string); int ctrl_key_code(unsigned char key); -void alert_connect(void); -void alert_disconnect(void); bool regex_match(const char *string, const char *pattern); unsigned long djb2_hash(const unsigned char *str); char *base62_encode(unsigned long num); diff --git a/src/options.c b/src/options.c index 65082b6..15bacb9 100644 --- a/src/options.c +++ b/src/options.c @@ -20,6 +20,7 @@ */ #include "config.h" +#include #include #include #include @@ -75,9 +76,9 @@ struct option_t option = .target = "", .baudrate = 115200, .databits = 8, - .flow = "none", + .flow = FLOW_NONE, .stopbits = 1, - .parity = "none", + .parity = PARITY_NONE, .output_delay = 0, .output_line_delay = 0, .dtr_pulse_duration = 100, @@ -121,7 +122,7 @@ struct option_t option = .vt100 = false, }; -void print_help(char *argv[]) +void option_print_help(char *argv[]) { UNUSED(argv); @@ -173,7 +174,270 @@ void print_help(char *argv[]) printf("See the man page for more details.\n"); } -const char *auto_connect_state_to_string(auto_connect_t strategy) +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; + + 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 + { + 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) { @@ -184,39 +448,44 @@ const char *auto_connect_state_to_string(auto_connect_t strategy) case AUTO_CONNECT_LATEST: return "latest"; default: - return "Unknown"; + return "unknown"; } } -auto_connect_t auto_connect_option_parse(const char *arg) +void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect) { - auto_connect_t auto_connect = option.auto_connect; // Default + assert(arg != NULL); if (arg != NULL) { if (strcmp(arg, "direct") == 0) { - return AUTO_CONNECT_DIRECT; + *auto_connect = AUTO_CONNECT_DIRECT; } else if (strcmp(arg, "new") == 0) { - return AUTO_CONNECT_NEW; + *auto_connect = AUTO_CONNECT_NEW; } else if (strcmp(arg, "latest") == 0) { - return AUTO_CONNECT_LATEST; + *auto_connect = AUTO_CONNECT_LATEST; + } + else + { + tio_error_print("Invalid auto-connect strategy '%s'", arg); + exit(EXIT_FAILURE); } } - - return auto_connect; } -void line_pulse_duration_option_parse(const char *arg) +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) @@ -259,6 +528,11 @@ void line_pulse_duration_option_parse(const char *arg) { option.ri_pulse_duration = value; } + else + { + tio_error_print("Invalid line '%s'", keyname); + exit(EXIT_FAILURE); + } } else { @@ -274,7 +548,7 @@ void line_pulse_duration_option_parse(const char *arg) } // Function to parse the input string -int parse_hexN_string(const char *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; @@ -285,7 +559,7 @@ int parse_hexN_string(const char *input_string) ret = regcomp(®ex, "^hex([0-9]+)?$", REG_EXTENDED); if (ret) { - tio_error_printf("Could not compile regex\n"); + tio_error_print("Could not compile regex"); exit(EXIT_FAILURE); } @@ -319,7 +593,7 @@ int parse_hexN_string(const char *input_string) { char msgbuf[100]; regerror(ret, ®ex, msgbuf, sizeof(msgbuf)); - tio_error_printf("Regex match failed: %s\n", msgbuf); + tio_error_print("Regex match failed: %s", msgbuf); exit(EXIT_FAILURE); } @@ -328,48 +602,52 @@ int parse_hexN_string(const char *input_string) return n_value; } -input_mode_t input_mode_option_parse(const char *arg) +void option_parse_input_mode(const char *arg, input_mode_t *mode) { + assert(arg != NULL); + if (strcmp("normal", arg) == 0) { - return INPUT_MODE_NORMAL; + *mode = INPUT_MODE_NORMAL; } else if (strcmp("hex", arg) == 0) { - return INPUT_MODE_HEX; + *mode = INPUT_MODE_HEX; } else if (strcmp("line", arg) == 0) { - return INPUT_MODE_LINE; + *mode = INPUT_MODE_LINE; } else { - tio_error_printf("Invalid input mode option"); + tio_error_print("Invalid input mode '%s'", arg); exit(EXIT_FAILURE); } } -output_mode_t output_mode_option_parse(const char *arg) +void option_parse_output_mode(const char *arg, output_mode_t *mode) { int n = 0; + assert(arg != NULL); + if (strcmp("normal", arg) == 0) { - return OUTPUT_MODE_NORMAL; + *mode = OUTPUT_MODE_NORMAL; } - else if ((n = parse_hexN_string(arg)) != -1) + else if ((n = option_parse_hexN_string(arg)) != -1) { option.hex_n_value = n; - return OUTPUT_MODE_HEX; + *mode = OUTPUT_MODE_HEX; } else { - tio_error_printf("Invalid output mode option"); + tio_error_print("Invalid output mode '%s'", arg); exit(EXIT_FAILURE); } } -const char *input_mode_by_string(input_mode_t mode) +const char *option_input_mode_to_string(input_mode_t mode) { switch (mode) { @@ -386,7 +664,7 @@ const char *input_mode_by_string(input_mode_t mode) return NULL; } -const char *output_mode_by_string(output_mode_t mode) +const char *option_output_mode_to_string(output_mode_t mode) { switch (mode) { @@ -401,23 +679,25 @@ const char *output_mode_by_string(output_mode_t mode) return NULL; } -script_run_t script_run_option_parse(const char *arg) +void option_parse_script_run(const char *arg, script_run_t *script_run) { + assert(arg != NULL); + if (strcmp("once", arg) == 0) { - return SCRIPT_RUN_ONCE; + *script_run = SCRIPT_RUN_ONCE; } else if (strcmp("always", arg) == 0) { - return SCRIPT_RUN_ALWAYS; + *script_run = SCRIPT_RUN_ALWAYS; } else if (strcmp("never", arg) == 0) { - return SCRIPT_RUN_NEVER; + *script_run = SCRIPT_RUN_NEVER; } else { - tio_error_printf("Invalid script run option"); + tio_error_print("Invalid script run option '%s'", arg); exit(EXIT_FAILURE); } } @@ -427,25 +707,25 @@ 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); + tio_printf(" Flow: %s", option_flow_to_string(option.flow)); tio_printf(" Stopbits: %d", option.stopbits); - tio_printf(" Parity: %s", option.parity); - tio_printf(" Local echo: %s", option.local_echo ? "enabled" : "disabled"); - tio_printf(" Timestamp: %s", timestamp_state_to_string(option.timestamp)); + 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", auto_connect_state_to_string(option.auto_connect)); - tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "disabled" : "enabled"); + 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", input_mode_by_string(option.input_mode)); - tio_printf(" Output mode: %s", output_mode_by_string(option.output_mode)); - tio_printf(" Alert: %s", alert_state_to_string(option.alert)); + 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.map[0] != 0) { tio_printf(" Map flags: %s", option.map); @@ -457,8 +737,8 @@ void options_print() { tio_printf(" Log file directory: %s", option.log_directory); } - tio_printf(" Log append: %s", option.log_append ? "enabled" : "disabled"); - tio_printf(" Log strip: %s", option.log_strip ? "enabled" : "disabled"); + tio_printf(" Log append: %s", option.log_append ? "true" : "false"); + tio_printf(" Log strip: %s", option.log_strip ? "true" : "false"); } if (option.socket) { @@ -477,7 +757,7 @@ void options_parse(int argc, char *argv[]) if (argc == 1) { - print_help(argv); + option_print_help(argv); exit(EXIT_SUCCESS); } @@ -563,39 +843,39 @@ void options_parse(int argc, char *argv[]) break; case 'b': - option.baudrate = string_to_long(optarg); + option_string_to_integer(optarg, &option.baudrate, "baudrate", 0, INT_MAX); break; case 'd': - option.databits = string_to_long(optarg); + option_string_to_integer(optarg, &option.databits, "databits", 5, 8); break; case 'f': - option.flow = optarg; + option_parse_flow(optarg, &option.flow); break; case 's': - option.stopbits = string_to_long(optarg); + option_string_to_integer(optarg, &option.stopbits, "stopbits", 1, 2); break; case 'p': - option.parity = optarg; + option_parse_parity(optarg, &option.parity); break; case 'o': - option.output_delay = string_to_long(optarg); + option_string_to_integer(optarg, &option.output_delay, "output delay", 0, INT_MAX); break; case 'O': - option.output_line_delay = string_to_long(optarg); + option_string_to_integer(optarg, &option.output_line_delay, "output line delay", 0, INT_MAX); break; case OPT_LINE_PULSE_DURATION: - line_pulse_duration_option_parse(optarg); + option_parse_line_pulse_duration(optarg); break; case 'a': - option.auto_connect = auto_connect_option_parse(optarg); + option_parse_auto_connect(optarg, &option.auto_connect); break; case OPT_EXCLUDE_DEVICES: @@ -623,11 +903,11 @@ void options_parse(int argc, char *argv[]) break; case OPT_TIMESTAMP_FORMAT: - option.timestamp = timestamp_option_parse(optarg); + option_parse_timestamp(optarg, &option.timestamp); break; case OPT_TIMESTAMP_TIMEOUT: - option.timestamp_timeout = string_to_long(optarg); + option_string_to_integer(optarg, &option.timestamp_timeout, "timestamp timeout", 0, INT_MAX); break; case 'L': @@ -664,41 +944,15 @@ void options_parse(int argc, char *argv[]) break; case 'c': - if (!strcmp(optarg, "list")) - { - // 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(optarg, "none")) - { - option.color = -1; // No color - break; - } - else if (!strcmp(optarg, "bold")) - { - option.color = 256; // Bold - break; - } - - option.color = string_to_long(optarg); - if ((option.color < 0) || (option.color > 255)) - { - tio_error_printf("Invalid color code"); - exit(EXIT_FAILURE); - } + option_parse_color(optarg, &option.color); break; case OPT_INPUT_MODE: - option.input_mode = input_mode_option_parse(optarg); + option_parse_input_mode(optarg, &option.input_mode); break; case OPT_OUTPUT_MODE: - option.output_mode = output_mode_option_parse(optarg); + option_parse_output_mode(optarg, &option.output_mode); break; case OPT_RS485: @@ -710,7 +964,7 @@ void options_parse(int argc, char *argv[]) break; case OPT_ALERT: - option.alert = alert_option_parse(optarg); + option_parse_alert(optarg, &option.alert); break; case OPT_MUTE: @@ -726,7 +980,7 @@ void options_parse(int argc, char *argv[]) break; case OPT_SCRIPT_RUN: - option.script_run = script_run_option_parse(optarg); + option_parse_script_run(optarg, &option.script_run); break; case 'v': @@ -735,7 +989,7 @@ void options_parse(int argc, char *argv[]) break; case 'h': - print_help(argv); + option_print_help(argv); exit(EXIT_SUCCESS); break; @@ -775,7 +1029,7 @@ void options_parse(int argc, char *argv[]) if (strlen(option.target) == 0) { - tio_error_printf("Missing tty device, profile or topology ID"); + tio_error_print("Missing tty device, profile or topology ID"); exit(EXIT_FAILURE); } @@ -794,9 +1048,6 @@ void options_parse(int argc, char *argv[]) void options_parse_final(int argc, char *argv[]) { - /* Preserve target which may have been set by configuration file */ - const char *target = option.target; - /* Do 2nd pass to override settings set by configuration file */ optind = 1; // Reset option index to restart scanning of argv options_parse(argc, argv); @@ -804,16 +1055,13 @@ void options_parse_final(int argc, char *argv[]) #ifdef __CYGWIN__ unsigned char portnum; char *tty_win; - if ( ((strncmp("COM", target, 3) == 0) - || (strncmp("com", target, 3) == 0) ) - && (sscanf(target + 3, "%hhu", &portnum) == 1) + 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); - target = tty_win; + option.target = tty_win; } #endif - - /* Restore target */ - option.target = target; } diff --git a/src/options.h b/src/options.h index 016978d..31e807a 100644 --- a/src/options.h +++ b/src/options.h @@ -49,20 +49,20 @@ typedef enum /* Options */ struct option_t { - const char *target; - unsigned int baudrate; + char *target; + int baudrate; int databits; - char *flow; + flow_t flow; int stopbits; - char *parity; + parity_t parity; int output_delay; int output_line_delay; - unsigned int dtr_pulse_duration; - unsigned int rts_pulse_duration; - unsigned int cts_pulse_duration; - unsigned int dsr_pulse_duration; - unsigned int dcd_pulse_duration; - unsigned int ri_pulse_duration; + int dtr_pulse_duration; + int rts_pulse_duration; + int cts_pulse_duration; + int dsr_pulse_duration; + int dcd_pulse_duration; + int ri_pulse_duration; bool no_reconnect; auto_connect_t auto_connect; bool log; @@ -70,15 +70,15 @@ struct option_t bool log_strip; bool local_echo; timestamp_t timestamp; - const char *log_filename; - const char *log_directory; - const char *map; - const char *socket; + char *log_filename; + char *log_directory; + char *map; + char *socket; int color; input_mode_t input_mode; output_mode_t output_mode; - unsigned char prefix_code; - unsigned char prefix_key; + char prefix_code; + char prefix_key; bool prefix_enabled; bool mute; bool rs485; @@ -87,13 +87,13 @@ struct option_t int32_t rs485_delay_rts_after_send; alert_t alert; bool complete_profiles; - const char *script; - const char *script_filename; + char *script; + char *script_filename; script_run_t script_run; - unsigned int timestamp_timeout; - const char *exclude_devices; - const char *exclude_drivers; - const char *exclude_tids; + int timestamp_timeout; + char *exclude_devices; + char *exclude_drivers; + char *exclude_tids; int hex_n_value; bool vt100; }; @@ -104,10 +104,20 @@ void options_print(); void options_parse(int argc, char *argv[]); void options_parse_final(int argc, char *argv[]); -void line_pulse_duration_option_parse(const char *arg); -script_run_t script_run_option_parse(const char *arg); +int option_string_to_integer(const char *string, int *value, const char *desc, int min, int max); -input_mode_t input_mode_option_parse(const char *arg); -output_mode_t output_mode_option_parse(const char *arg); -auto_connect_t auto_connect_option_parse(const char *arg); -const char *auto_connect_state_to_string(auto_connect_t strategy); +void option_parse_flow(const char *arg, flow_t *flow); +void option_parse_parity(const char *arg, parity_t *parity); + +void option_parse_output_mode(const char *arg, output_mode_t *mode); +void option_parse_input_mode(const char *arg, input_mode_t *mode); + +void option_parse_line_pulse_duration(const char *arg); +void option_parse_script_run(const char *arg, script_run_t *script_run); +void option_parse_alert(const char *arg, alert_t *alert); + +void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect); +const char *option_auto_connect_state_to_string(auto_connect_t strategy); + +void option_parse_timestamp(const char *arg, timestamp_t *timestamp); +const char* option_timestamp_format_to_string(timestamp_t timestamp); diff --git a/src/print.h b/src/print.h index dd65393..4a41b32 100644 --- a/src/print.h +++ b/src/print.h @@ -87,10 +87,18 @@ extern char ansi_format[]; { \ if (print_tainted) \ putchar('\n'); \ - if (option.color < 0) \ - fprintf (stdout, "\r[%s] Error: " format "\r\n", timestamp_current_time(), ## args); \ - else \ - ansi_printf("[%s] Error: " format, timestamp_current_time(), ## args); \ + if (option.color < 0) { \ + if (error_normal) \ + fprintf (stdout, "Error: " format "\n", ## args); \ + else \ + fprintf (stdout, "\r[%s] Error: " format "\r\n", timestamp_current_time(), ## args); \ + } \ + else { \ + if (error_normal) \ + { ansi_printf("Error: " format, ## args); }\ + else \ + { ansi_printf("[%s] Error: " format, timestamp_current_time(), ## args); }\ + } \ print_tainted = false; \ } \ } diff --git a/src/rs485.c b/src/rs485.c index c1b06db..30b7dc8 100644 --- a/src/rs485.c +++ b/src/rs485.c @@ -128,7 +128,7 @@ void rs485_print_config(void) tio_printf(" RTS_AFTER_SEND: %s", (rs485_config.flags & SER_RS485_RTS_AFTER_SEND) ? "high" : "low"); tio_printf(" RTS_DELAY_BEFORE_SEND = %d", rs485_config.delay_rts_before_send); tio_printf(" RTS_DELAY_AFTER_SEND = %d", rs485_config.delay_rts_after_send); - tio_printf(" RX_DURING_TX: %s", (rs485_config.flags & SER_RS485_RX_DURING_TX) ? "enabled" : "disabled"); + tio_printf(" RX_DURING_TX: %s", (rs485_config.flags & SER_RS485_RX_DURING_TX) ? "true" : "false"); } int rs485_mode_enable(int fd) diff --git a/src/timestamp.c b/src/timestamp.c index 19ea70e..338ff87 100644 --- a/src/timestamp.c +++ b/src/timestamp.c @@ -93,60 +93,3 @@ char *timestamp_current_time(void) return (len < TIME_STRING_SIZE_MAX) ? time_string : NULL; } - -const char* timestamp_state_to_string(timestamp_t timestamp) -{ - switch (timestamp) - { - case TIMESTAMP_NONE: - return "disabled"; - 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; - - default: - return "unknown"; - break; - } -} - -timestamp_t timestamp_option_parse(const char *arg) -{ - timestamp_t timestamp = TIMESTAMP_24HOUR; // Default - - if (arg != NULL) - { - if (strcmp(arg, "24hour") == 0) - { - return TIMESTAMP_24HOUR; - } - else if (strcmp(arg, "24hour-start") == 0) - { - return TIMESTAMP_24HOUR_START; - } - else if (strcmp(arg, "24hour-delta") == 0) - { - return TIMESTAMP_24HOUR_DELTA; - } - else if (strcmp(arg, "iso8601") == 0) - { - return TIMESTAMP_ISO8601; - } - } - - return timestamp; -} diff --git a/src/timestamp.h b/src/timestamp.h index e36660a..32d129d 100644 --- a/src/timestamp.h +++ b/src/timestamp.h @@ -32,5 +32,4 @@ typedef enum } timestamp_t; char *timestamp_current_time(void); -const char* timestamp_state_to_string(timestamp_t timestamp); -timestamp_t timestamp_option_parse(const char *arg); + diff --git a/src/tty.c b/src/tty.c index c8f7d7b..79bae34 100644 --- a/src/tty.c +++ b/src/tty.c @@ -158,7 +158,7 @@ bool map_ign_cr = false; char key_hit = 0xff; -const char* device_name; +const char* device_name = NULL; GList *device_list = NULL; static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; static unsigned long rx_total = 0, tx_total = 0; @@ -1154,25 +1154,26 @@ void tty_configure(void) } /* Set flow control */ - if (strcmp("hard", option.flow) == 0) + switch (option.flow) { - tio.c_cflag |= CRTSCTS; - tio.c_iflag &= ~(IXON | IXOFF | IXANY); - } - else if (strcmp("soft", option.flow) == 0) - { - tio.c_cflag &= ~CRTSCTS; - tio.c_iflag |= IXON | IXOFF; - } - else if (strcmp("none", option.flow) == 0) - { - tio.c_cflag &= ~CRTSCTS; - tio.c_iflag &= ~(IXON | IXOFF | IXANY); - } - else - { - tio_error_printf("Invalid flow control"); - exit(EXIT_FAILURE); + case FLOW_NONE: + tio.c_cflag &= ~CRTSCTS; + tio.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + + case FLOW_HARD: + tio.c_cflag |= CRTSCTS; + tio.c_iflag &= ~(IXON | IXOFF | IXANY); + break; + + case FLOW_SOFT: + tio.c_cflag &= ~CRTSCTS; + tio.c_iflag |= IXON | IXOFF; + break; + + default: + tio_error_printf("Invalid flow control"); + exit(EXIT_FAILURE); } /* Set stopbits */ @@ -1190,36 +1191,37 @@ void tty_configure(void) } /* Set parity */ - if (strcmp("odd", option.parity) == 0) + switch (option.parity) { - tio.c_cflag |= PARENB; - tio.c_cflag |= PARODD; - } - else if (strcmp("even", option.parity) == 0) - { - tio.c_cflag |= PARENB; - tio.c_cflag &= ~PARODD; - } - else if (strcmp("none", option.parity) == 0) - { - tio.c_cflag &= ~PARENB; - } - else if ( strcmp("mark", option.parity) == 0) - { - tio.c_cflag |= PARENB; - tio.c_cflag |= PARODD; - tio.c_cflag |= CMSPAR; - } - else if ( strcmp("space", option.parity) == 0) - { - tio.c_cflag |= PARENB; - tio.c_cflag &= ~PARODD; - tio.c_cflag |= CMSPAR; - } - else - { - tio_error_printf("Invalid parity"); - exit(EXIT_FAILURE); + case PARITY_NONE: + tio.c_cflag &= ~PARENB; + break; + + case PARITY_ODD: + tio.c_cflag |= PARENB; + tio.c_cflag |= PARODD; + break; + + case PARITY_EVEN: + tio.c_cflag |= PARENB; + tio.c_cflag &= ~PARODD; + break; + + case PARITY_MARK: + tio.c_cflag |= PARENB; + tio.c_cflag |= PARODD; + tio.c_cflag |= CMSPAR; + break; + + case PARITY_SPACE: + tio.c_cflag |= PARENB; + tio.c_cflag &= ~PARODD; + tio.c_cflag |= CMSPAR; + break; + + default: + tio_error_printf("Invalid parity"); + exit(EXIT_FAILURE); } /* Control, input, output, local modes for tty device */ @@ -1809,7 +1811,14 @@ void tty_search(void) return; case AUTO_CONNECT_DIRECT: - if (strlen(option.target) == TOPOLOGY_ID_SIZE) + if (config.device != NULL) + { + // Prioritize any found pattern first + device_name = config.device; + + return; + } + else if (strlen(option.target) == TOPOLOGY_ID_SIZE) { // Potential topology ID detected -> trigger device search tty_search_for_serial_devices(); @@ -1829,6 +1838,12 @@ void tty_search(void) } } + if (config.device != NULL) + { + device_name = config.device; + break; + } + // Fallback to using tty device provided via cmdline target device_name = option.target; break; diff --git a/src/tty.h b/src/tty.h index b142034..4a8619f 100644 --- a/src/tty.h +++ b/src/tty.h @@ -29,6 +29,22 @@ #define TOPOLOGY_ID_SIZE 4 +typedef enum +{ + FLOW_NONE, + FLOW_HARD, + FLOW_SOFT, +} flow_t; + +typedef enum +{ + PARITY_NONE, + PARITY_ODD, + PARITY_EVEN, + PARITY_MARK, + PARITY_SPACE, +} parity_t; + typedef enum { AUTO_CONNECT_DIRECT, diff --git a/subprojects/libinih.wrap b/subprojects/libinih.wrap deleted file mode 100644 index fc5f690..0000000 --- a/subprojects/libinih.wrap +++ /dev/null @@ -1,4 +0,0 @@ -[wrap-git] -directory=libinih -url=https://github.com/benhoyt/inih.git -revision=r58