Replace inih with glib key file parser

After including the use of glib we might as well replace inih
with the glib key file parser.

All configuraiton file parsing has been reworked and also the options
parsing has been cleaned up, resulting in better and stricter
configuration file and option value checks.

Compared to old, configuration files now requires any default
configurations to be put in a group/section named [default].

Configuration file keywords such as "enable", "disable", "on",
"off", "yes", "no", "0", "1" have been retired. Now only "true" and
"false" apply to boolean configuration options. This is done to simplify
things and avoid any confusion.

The pattern option feature has been reworked so now the user can now
access the full match string and any matching subexpression using the
%mN syntax.

For example:

[usb devices]
pattern = usb([0-9]*)
device = /dev/ttyUSB%m1

Then when using tio:
$ tio usb12

   %m0 = 'usb12'  // Full match string
   %m1 = 12       // First match subexpression

Which results in device = /dev/ttyUSB12
This commit is contained in:
Martin Lund 2024-05-02 17:21:10 +02:00
parent 68d3b845b2
commit 65c5a068d8
23 changed files with 969 additions and 723 deletions

View file

@ -385,7 +385,7 @@ Example configuration file:
``` ```
# Defaults # Defaults
baudrate = 9600 baudrate = 115200
databits = 8 databits = 8
parity = none parity = none
stopbits = 1 stopbits = 1
@ -393,15 +393,15 @@ color = 10
[rpi3] [rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0 device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200 no-reconnect = true
no-reconnect = enable log = true
log = enable
log-file = rpi3.log log-file = rpi3.log
line-pulse-duration = DTR=200,RTS=150 line-pulse-duration = DTR=200,RTS=150
color = 11 color = 11
[svf2] [svf2]
device = /dev/ttyUSB0 device = /dev/ttyUSB0
baudrate = 9600
script = expect("login: "); send("root\n"); expect("Password: "); send("root\n") script = expect("login: "); send("root\n"); expect("Password: "); send("root\n")
color = 12 color = 12

View file

@ -15,22 +15,22 @@ databits = 8
flow = none flow = none
stopbits = 1 stopbits = 1
parity = none parity = none
prefix-ctrl-key = t
output-delay = 0 output-delay = 0
output-line-delay = 0 output-line-delay = 0
auto-connect = direct auto-connect = direct
no-reconnect = disable no-reconnect = false
local-echo = false
input-mode = normal input-mode = normal
output-mode = normal output-mode = normal
timestamp = disable timestamp = false
log = disable log = false
log-append = disable log-append = false
log-strip = disable log-strip = false
local-echo = disable
color = bold color = bold
rs-485 = disable rs-485 = false
alert = none alert = none
script-run = always script-run = always
prefix-ctrl-key = t
# Configuration profiles # Configuration profiles
@ -50,9 +50,9 @@ color = 10
[tincan] [tincan]
baudrate = 9600 baudrate = 9600
device = /dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0 device = /dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0
log = enable log = true
log-file = tincan.log log-file = tincan.log
log-strip = enable log-strip = true
color = 11 color = 11
[usb] [usb]
@ -62,7 +62,7 @@ color = 12
[rs-485-device] [rs-485-device]
device = /dev/ttyUSB0 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 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 color = 13
@ -71,4 +71,3 @@ device = /dev/ttyUSB0
color = 14 color = 14
script = high(DTR); low(RTS); msleep(100); low(DTR); high(RTS); msleep(100); low(RTS) script = high(DTR); low(RTS); msleep(100); low(DTR); high(RTS); msleep(100); low(RTS)
script-run = always script-run = always

View file

@ -623,7 +623,7 @@ regular expressions:
.eo .eo
[usb device] [usb device]
pattern = usb([0-9]*) pattern = usb([0-9]*)
device = /dev/ttyUSB%s device = /dev/ttyUSB%m1
baudrate = 115200 baudrate = 115200
.ec .ec
.fi .fi
@ -635,7 +635,7 @@ Activate the configuration profile by pattern match:
$ tio usb12 $ tio usb12
.TP .TP
Which is equivalent to: Which becomes equivalent to:
$ tio -b 115200 /dev/ttyUSB12 $ tio -b 115200 /dev/ttyUSB12

View file

@ -29,29 +29,6 @@
#include "print.h" #include "print.h"
#include "options.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) void blink_background(void)
{ {
// Turn on reverse video // Turn on reverse video
@ -109,18 +86,3 @@ void alert_disconnect(void)
break; 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";
}
}

View file

@ -29,7 +29,5 @@ typedef enum
ALERT_END, ALERT_END,
} alert_t; } alert_t;
alert_t alert_option_parse(const char *arg);
void alert_connect(void); void alert_connect(void);
void alert_disconnect(void); void alert_disconnect(void);
const char *alert_state_to_string(alert_t state);

View file

@ -32,10 +32,11 @@
#include <errno.h> #include <errno.h>
#include <getopt.h> #include <getopt.h>
#include <termios.h> #include <termios.h>
#include <assert.h>
#include <limits.h> #include <limits.h>
#include <unistd.h> #include <unistd.h>
#include <regex.h> #include <regex.h>
#include <ini.h> #include <glib.h>
#include "options.h" #include "options.h"
#include "configfile.h" #include "configfile.h"
#include "misc.h" #include "misc.h"
@ -46,505 +47,555 @@
#include "timestamp.h" #include "timestamp.h"
#include "alert.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; gchar *string = g_key_file_get_string(key_file, group, key, &error);
char *section_name; if (error != NULL)
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)
{ {
regerror(ret, &re, err, sizeof(err)); if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
tio_error_printf("Regex failure: %s", err); {
return ret; // Key not found - ignore key
g_error_free(error);
return;
} }
tio_error_print("%s: %s", config.path, error->message);
/* try to match on input */ g_error_free(error);
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);
exit(EXIT_FAILURE); 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;
} }
/** static void config_get_integer(GKeyFile *key_file, gchar *group, gchar *key, int *dest, int min, int max)
* 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)
{ {
UNUSED(user); (void)dest;
GError *error = NULL;
// If section matches current section being parsed int value = g_key_file_get_integer(key_file, group, key, &error);
if (!strcmp(section, c.section_name)) if (error != NULL)
{ {
// Set configuration parameter if found if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
if (!strcmp(name, "device") || !strcmp(name, "tty"))
{ {
asprintf(&c.target, value, c.match); // Key not found - ignore key
option.target = c.target; g_error_free(error);
return;
} }
else if (!strcmp(name, "baudrate")) tio_error_print("%s: %s", config.path, error->message);
{ g_error_free(error);
option.baudrate = read_integer(value, name, 0, LONG_MAX); exit(EXIT_FAILURE);
} }
else if (!strcmp(name, "databits"))
if ((value < min) || (value > max))
{ {
option.databits = read_integer(value, name, 5, 8); tio_error_print("%s: Invalid %s value '%d' in %s profile", config.path, key, value, group);
exit(EXIT_FAILURE);
} }
else if (!strcmp(name, "flow"))
*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)
{ {
asprintf(&c.flow, "%s", value); if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
option.flow = c.flow; {
// Key not found - ignore key
g_error_free(error);
return;
} }
else if (!strcmp(name, "stopbits")) tio_error_print("%s: %s", config.path, error->message);
{ g_error_free(error);
option.stopbits = read_integer(value, name, 1, 2); exit(EXIT_FAILURE);
} }
else if (!strcmp(name, "parity"))
*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)
{ {
asprintf(&c.parity, "%s", value); option_parse_flow(string, &option.flow);
option.parity = c.parity; g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "output-delay")) 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.output_delay = read_integer(value, name, 0, LONG_MAX); option_parse_parity(string, &option.parity);
g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "output-line-delay"))
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.output_line_delay = read_integer(value, name, 0, LONG_MAX); option_parse_line_pulse_duration(string);
g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "line-pulse-duration")) config_get_string(key_file, group, "auto-connect", &string, "new", "latest", "direct", NULL);
if (string != NULL)
{ {
line_pulse_duration_option_parse(value); option_parse_auto_connect(string, &option.auto_connect);
g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "no-reconnect")) 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.no_reconnect = read_boolean(value, name); option_parse_input_mode(string, &option.input_mode);
g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "auto-connect")) config_get_string(key_file, group, "output-mode", &string, NULL);
if (string != NULL)
{ {
option.auto_connect = auto_connect_option_parse(value); option_parse_output_mode(string, &option.output_mode);
g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "log")) config_get_bool(key_file, group, "timestamp", &boolean);
if (boolean == true)
{ {
option.log = read_boolean(value, name); option.timestamp = TIMESTAMP_24HOUR;
} }
else if (!strcmp(name, "log-file")) config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", NULL);
if (string != NULL)
{ {
asprintf(&c.log_filename, "%s", value); option_parse_timestamp(string, &option.timestamp);
option.log_filename = c.log_filename; g_free((void *)string);
string = NULL;
} }
else if (!strcmp(name, "log-append")) 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)
{ {
option.log_append = read_boolean(value, name); if (strcmp(string, "list") == 0)
}
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 // Ignore
return 0;
} }
else if (!strcmp(value, "none")) else if (strcmp(string, "none") == 0)
{ {
option.color = -1; // No color option.color = -1; // No color
return 0;
} }
else if (!strcmp(value, "bold")) else if (strcmp(string, "bold") == 0)
{ {
option.color = 256; // Bold option.color = 256; // Bold
return 0;
}
option.color = atoi(value);
if ((option.color < 0) || (option.color > 255))
{
option.color = -1; // No color
}
}
else if (!strcmp(name, "socket"))
{
asprintf(&c.socket, "%s", value);
option.socket = c.socket;
}
else if (!strcmp(name, "prefix-ctrl-key"))
{
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];
}
}
else if (!strcmp(name, "rs-485"))
{
option.rs485 = read_boolean(value, name);
}
else if (!strcmp(name, "rs-485-config"))
{
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;
} }
else 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);
return 0; 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;
}
} }
/** static int config_file_resolve(void)
* 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)
{ {
char *xdg = getenv("XDG_CONFIG_HOME"); char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg) 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; return 0;
} }
free(c.path); free(config.path);
} }
} }
char *home = getenv("HOME"); char *home = getenv("HOME");
if (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; 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; return 0;
} }
free(c.path); free(config.path);
} }
} }
c.path = NULL; config.path = NULL;
return -EINVAL; return -EINVAL;
} }
void config_file_show_profiles(void) void config_file_show_profiles(void)
{ {
memset(&c, 0, sizeof(struct config_t)); memset(&config, 0, sizeof(struct config_t));
// Find config file // Find config file
if (resolve_config_file() != 0) if (config_file_resolve() != 0)
{ {
// None found - stop parsing // None found - stop parsing
return; 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(&regex, pattern, REG_EXTENDED) != 0)
{
// Failure to compile regular expression
return NULL;
}
regmatch_t matches[regex.re_nsub + 1];
int status = regexec(&regex, 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, &regex, error_message, sizeof(error_message));
tio_error_print("Regex match failed: %s", error_message);
goto error;
}
regfree(&regex);
return string;
error:
regfree(&regex);
return NULL;
} }
void config_file_parse(void) void config_file_parse(void)
{ {
int ret;
memset(&c, 0, sizeof(struct config_t));
// Find config file // Find config file
if (resolve_config_file() != 0) if (config_file_resolve() != 0)
{ {
// None found - stop parsing // None found - stop parsing
return; return;
} }
// Set user input which may be tty device or profile or tid if (option.target == NULL)
c.user = option.target;
if (!c.user)
{ {
return; return;
} }
// Parse default (unnamed) settings GKeyFile *keyfile = g_key_file_new();
asprintf(&c.section_name, "%s", ""); GError *error = NULL;
ret = ini_parse(c.path, data_handler, NULL);
if (ret < 0) 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); exit(EXIT_FAILURE);
} }
free(c.section_name);
c.section_name = NULL;
// Find matching section // Parse default group/section
ret = ini_parse(c.path, section_pattern_search_handler, NULL); if (g_key_file_has_group(keyfile, CONFIG_GROUP_NAME_DEFAULT))
if (!c.section_name)
{ {
ret = ini_parse(c.path, section_name_search_handler, NULL); config_parse_keys(keyfile, CONFIG_GROUP_NAME_DEFAULT);
if (!c.section_name) }
// Parse target
if (g_key_file_has_group(keyfile, option.target))
{ {
tio_debug_printf("Unable to match user input to configuration section (%d)", ret); config.active_group = strdup(option.target);
return; 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++)
{
// 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;
} }
} }
// Parse settings of found section (profile) g_strfreev(group);
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); atexit(&config_exit);
} }
void config_exit(void) void config_exit(void)
{ {
free(c.target); free(config.active_group);
free(c.flow); free(config.path);
free(c.parity); free(config.device);
free(c.log_filename);
free(c.map);
free(c.match);
free(c.section_name);
free(c.path);
} }
void config_file_print(void) void config_file_print(void)
{ {
if (c.path != NULL) if (config.path != NULL)
{ {
tio_printf(" Active configuration file: %s", c.path); tio_printf(" Active configuration file: %s", config.path);
if (c.section_name != NULL) if (config.active_group != NULL)
{ {
tio_printf(" Active configuration profile: %s", c.section_name); tio_printf(" Active configuration profile: %s", config.active_group);
} }
} }
} }

View file

@ -22,6 +22,15 @@
#pragma once #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_print(void);
void config_file_parse(void); void config_file_parse(void);
void config_exit(void); void config_exit(void);

View file

@ -35,6 +35,12 @@
static char error[2][1000]; static char error[2][1000];
static bool in_session = false; static bool in_session = false;
bool error_normal = true;
void switch_error_output_mode(void)
{
error_normal = false;
}
void error_enter_session_mode(void) void error_enter_session_mode(void)
{ {

View file

@ -21,6 +21,10 @@
#pragma once #pragma once
#include <stdbool.h>
extern bool error_normal;
#define TIO_SUCCESS 0 #define TIO_SUCCESS 0
#define TIO_ERROR 1 #define TIO_ERROR 1
@ -28,3 +32,4 @@ void tio_error_printf(const char *format, ...);
void tio_error_printf_silent(const char *format, ...); void tio_error_printf_silent(const char *format, ...);
void error_exit(void); void error_exit(void);
void error_enter_session_mode(void); void error_enter_session_mode(void);
void switch_error_output_mode(void);

View file

@ -75,7 +75,7 @@ int log_open(const char *filename)
// If using 'new' or 'latest' autoconnect strategy we simply use strategy // If using 'new' or 'latest' autoconnect strategy we simply use strategy
// name to name autogenerated log name as device names may vary // name to name autogenerated log name as device names may vary
asprintf(&automatic_filename, "tio_%s_%s.log", 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()); date_time());
} }

View file

@ -71,6 +71,9 @@ int main(int argc, char *argv[])
interactive_mode = false; interactive_mode = false;
} }
/* Switch error output format */
switch_error_output_mode();
/* Configure output terminal */ /* Configure output terminal */
if (isatty(fileno(stdout))) if (isatty(fileno(stdout)))
{ {

View file

@ -37,9 +37,6 @@ endif
tio_dep = [ tio_dep = [
dependency('threads', required: true), dependency('threads', required: true),
dependency('glib-2.0', 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 lua_dep
] ]

View file

@ -54,22 +54,6 @@ void delay(long ms)
nanosleep(&ts, NULL); 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) int ctrl_key_code(unsigned char key)
{ {
if ((key >= 'a') && (key <= 'z')) if ((key >= 'a') && (key <= 'z'))

View file

@ -27,10 +27,7 @@
#define UNUSED(expr) do { (void)(expr); } while (0) #define UNUSED(expr) do { (void)(expr); } while (0)
void delay(long ms); void delay(long ms);
long string_to_long(char *string);
int ctrl_key_code(unsigned char key); int ctrl_key_code(unsigned char key);
void alert_connect(void);
void alert_disconnect(void);
bool regex_match(const char *string, const char *pattern); bool regex_match(const char *string, const char *pattern);
unsigned long djb2_hash(const unsigned char *str); unsigned long djb2_hash(const unsigned char *str);
char *base62_encode(unsigned long num); char *base62_encode(unsigned long num);

View file

@ -20,6 +20,7 @@
*/ */
#include "config.h" #include "config.h"
#include <assert.h>
#include <regex.h> #include <regex.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -75,9 +76,9 @@ struct option_t option =
.target = "", .target = "",
.baudrate = 115200, .baudrate = 115200,
.databits = 8, .databits = 8,
.flow = "none", .flow = FLOW_NONE,
.stopbits = 1, .stopbits = 1,
.parity = "none", .parity = PARITY_NONE,
.output_delay = 0, .output_delay = 0,
.output_line_delay = 0, .output_line_delay = 0,
.dtr_pulse_duration = 100, .dtr_pulse_duration = 100,
@ -121,7 +122,7 @@ struct option_t option =
.vt100 = false, .vt100 = false,
}; };
void print_help(char *argv[]) void option_print_help(char *argv[])
{ {
UNUSED(argv); UNUSED(argv);
@ -173,7 +174,270 @@ void print_help(char *argv[])
printf("See the man page for more details.\n"); 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) switch (strategy)
{ {
@ -184,39 +448,44 @@ const char *auto_connect_state_to_string(auto_connect_t strategy)
case AUTO_CONNECT_LATEST: case AUTO_CONNECT_LATEST:
return "latest"; return "latest";
default: 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 (arg != NULL)
{ {
if (strcmp(arg, "direct") == 0) if (strcmp(arg, "direct") == 0)
{ {
return AUTO_CONNECT_DIRECT; *auto_connect = AUTO_CONNECT_DIRECT;
} }
else if (strcmp(arg, "new") == 0) else if (strcmp(arg, "new") == 0)
{ {
return AUTO_CONNECT_NEW; *auto_connect = AUTO_CONNECT_NEW;
} }
else if (strcmp(arg, "latest") == 0) 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; bool token_found = true;
char *token = NULL; char *token = NULL;
char *buffer = strdup(arg); char *buffer = strdup(arg);
assert(arg != NULL);
while (token_found == true) while (token_found == true)
{ {
if (token == NULL) if (token == NULL)
@ -259,6 +528,11 @@ void line_pulse_duration_option_parse(const char *arg)
{ {
option.ri_pulse_duration = value; option.ri_pulse_duration = value;
} }
else
{
tio_error_print("Invalid line '%s'", keyname);
exit(EXIT_FAILURE);
}
} }
else else
{ {
@ -274,7 +548,7 @@ void line_pulse_duration_option_parse(const char *arg)
} }
// Function to parse the input string // 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 regmatch_t match[2]; // One for entire match, one for the optional N
int n_value = 0; int n_value = 0;
@ -285,7 +559,7 @@ int parse_hexN_string(const char *input_string)
ret = regcomp(&regex, "^hex([0-9]+)?$", REG_EXTENDED); ret = regcomp(&regex, "^hex([0-9]+)?$", REG_EXTENDED);
if (ret) if (ret)
{ {
tio_error_printf("Could not compile regex\n"); tio_error_print("Could not compile regex");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -319,7 +593,7 @@ int parse_hexN_string(const char *input_string)
{ {
char msgbuf[100]; char msgbuf[100];
regerror(ret, &regex, msgbuf, sizeof(msgbuf)); regerror(ret, &regex, msgbuf, sizeof(msgbuf));
tio_error_printf("Regex match failed: %s\n", msgbuf); tio_error_print("Regex match failed: %s", msgbuf);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -328,48 +602,52 @@ int parse_hexN_string(const char *input_string)
return n_value; 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) if (strcmp("normal", arg) == 0)
{ {
return INPUT_MODE_NORMAL; *mode = INPUT_MODE_NORMAL;
} }
else if (strcmp("hex", arg) == 0) else if (strcmp("hex", arg) == 0)
{ {
return INPUT_MODE_HEX; *mode = INPUT_MODE_HEX;
} }
else if (strcmp("line", arg) == 0) else if (strcmp("line", arg) == 0)
{ {
return INPUT_MODE_LINE; *mode = INPUT_MODE_LINE;
} }
else else
{ {
tio_error_printf("Invalid input mode option"); tio_error_print("Invalid input mode '%s'", arg);
exit(EXIT_FAILURE); 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; int n = 0;
assert(arg != NULL);
if (strcmp("normal", arg) == 0) 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; option.hex_n_value = n;
return OUTPUT_MODE_HEX; *mode = OUTPUT_MODE_HEX;
} }
else else
{ {
tio_error_printf("Invalid output mode option"); tio_error_print("Invalid output mode '%s'", arg);
exit(EXIT_FAILURE); 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) switch (mode)
{ {
@ -386,7 +664,7 @@ const char *input_mode_by_string(input_mode_t mode)
return NULL; 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) switch (mode)
{ {
@ -401,23 +679,25 @@ const char *output_mode_by_string(output_mode_t mode)
return NULL; 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) if (strcmp("once", arg) == 0)
{ {
return SCRIPT_RUN_ONCE; *script_run = SCRIPT_RUN_ONCE;
} }
else if (strcmp("always", arg) == 0) else if (strcmp("always", arg) == 0)
{ {
return SCRIPT_RUN_ALWAYS; *script_run = SCRIPT_RUN_ALWAYS;
} }
else if (strcmp("never", arg) == 0) else if (strcmp("never", arg) == 0)
{ {
return SCRIPT_RUN_NEVER; *script_run = SCRIPT_RUN_NEVER;
} }
else else
{ {
tio_error_printf("Invalid script run option"); tio_error_print("Invalid script run option '%s'", arg);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
@ -427,25 +707,25 @@ void options_print()
tio_printf(" Device: %s", device_name); tio_printf(" Device: %s", device_name);
tio_printf(" Baudrate: %u", option.baudrate); tio_printf(" Baudrate: %u", option.baudrate);
tio_printf(" Databits: %d", option.databits); 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(" Stopbits: %d", option.stopbits);
tio_printf(" Parity: %s", option.parity); tio_printf(" Parity: %s", option_parity_to_string(option.parity));
tio_printf(" Local echo: %s", option.local_echo ? "enabled" : "disabled"); tio_printf(" Local echo: %s", option.local_echo ? "true" : "false");
tio_printf(" Timestamp: %s", timestamp_state_to_string(option.timestamp)); tio_printf(" Timestamp: %s", option_timestamp_format_to_string(option.timestamp));
tio_printf(" Timestamp timeout: %u", option.timestamp_timeout); tio_printf(" Timestamp timeout: %u", option.timestamp_timeout);
tio_printf(" Output delay: %d", option.output_delay); tio_printf(" Output delay: %d", option.output_delay);
tio_printf(" Output line delay: %d", option.output_line_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 connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect));
tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "disabled" : "enabled"); 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, 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.rts_pulse_duration,
option.cts_pulse_duration, option.cts_pulse_duration,
option.dsr_pulse_duration, option.dsr_pulse_duration,
option.dcd_pulse_duration, option.dcd_pulse_duration,
option.ri_pulse_duration); option.ri_pulse_duration);
tio_printf(" Input mode: %s", input_mode_by_string(option.input_mode)); tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode));
tio_printf(" Output mode: %s", output_mode_by_string(option.output_mode)); tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode));
tio_printf(" Alert: %s", alert_state_to_string(option.alert)); tio_printf(" Alert: %s", option_alert_state_to_string(option.alert));
if (option.map[0] != 0) if (option.map[0] != 0)
{ {
tio_printf(" Map flags: %s", option.map); 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 file directory: %s", option.log_directory);
} }
tio_printf(" Log append: %s", option.log_append ? "enabled" : "disabled"); tio_printf(" Log append: %s", option.log_append ? "true" : "false");
tio_printf(" Log strip: %s", option.log_strip ? "enabled" : "disabled"); tio_printf(" Log strip: %s", option.log_strip ? "true" : "false");
} }
if (option.socket) if (option.socket)
{ {
@ -477,7 +757,7 @@ void options_parse(int argc, char *argv[])
if (argc == 1) if (argc == 1)
{ {
print_help(argv); option_print_help(argv);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
@ -563,39 +843,39 @@ void options_parse(int argc, char *argv[])
break; break;
case 'b': case 'b':
option.baudrate = string_to_long(optarg); option_string_to_integer(optarg, &option.baudrate, "baudrate", 0, INT_MAX);
break; break;
case 'd': case 'd':
option.databits = string_to_long(optarg); option_string_to_integer(optarg, &option.databits, "databits", 5, 8);
break; break;
case 'f': case 'f':
option.flow = optarg; option_parse_flow(optarg, &option.flow);
break; break;
case 's': case 's':
option.stopbits = string_to_long(optarg); option_string_to_integer(optarg, &option.stopbits, "stopbits", 1, 2);
break; break;
case 'p': case 'p':
option.parity = optarg; option_parse_parity(optarg, &option.parity);
break; break;
case 'o': case 'o':
option.output_delay = string_to_long(optarg); option_string_to_integer(optarg, &option.output_delay, "output delay", 0, INT_MAX);
break; break;
case 'O': 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; break;
case OPT_LINE_PULSE_DURATION: case OPT_LINE_PULSE_DURATION:
line_pulse_duration_option_parse(optarg); option_parse_line_pulse_duration(optarg);
break; break;
case 'a': case 'a':
option.auto_connect = auto_connect_option_parse(optarg); option_parse_auto_connect(optarg, &option.auto_connect);
break; break;
case OPT_EXCLUDE_DEVICES: case OPT_EXCLUDE_DEVICES:
@ -623,11 +903,11 @@ void options_parse(int argc, char *argv[])
break; break;
case OPT_TIMESTAMP_FORMAT: case OPT_TIMESTAMP_FORMAT:
option.timestamp = timestamp_option_parse(optarg); option_parse_timestamp(optarg, &option.timestamp);
break; break;
case OPT_TIMESTAMP_TIMEOUT: 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; break;
case 'L': case 'L':
@ -664,41 +944,15 @@ void options_parse(int argc, char *argv[])
break; break;
case 'c': case 'c':
if (!strcmp(optarg, "list")) option_parse_color(optarg, &option.color);
{
// 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);
}
break; break;
case OPT_INPUT_MODE: case OPT_INPUT_MODE:
option.input_mode = input_mode_option_parse(optarg); option_parse_input_mode(optarg, &option.input_mode);
break; break;
case OPT_OUTPUT_MODE: case OPT_OUTPUT_MODE:
option.output_mode = output_mode_option_parse(optarg); option_parse_output_mode(optarg, &option.output_mode);
break; break;
case OPT_RS485: case OPT_RS485:
@ -710,7 +964,7 @@ void options_parse(int argc, char *argv[])
break; break;
case OPT_ALERT: case OPT_ALERT:
option.alert = alert_option_parse(optarg); option_parse_alert(optarg, &option.alert);
break; break;
case OPT_MUTE: case OPT_MUTE:
@ -726,7 +980,7 @@ void options_parse(int argc, char *argv[])
break; break;
case OPT_SCRIPT_RUN: case OPT_SCRIPT_RUN:
option.script_run = script_run_option_parse(optarg); option_parse_script_run(optarg, &option.script_run);
break; break;
case 'v': case 'v':
@ -735,7 +989,7 @@ void options_parse(int argc, char *argv[])
break; break;
case 'h': case 'h':
print_help(argv); option_print_help(argv);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
@ -775,7 +1029,7 @@ void options_parse(int argc, char *argv[])
if (strlen(option.target) == 0) 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); exit(EXIT_FAILURE);
} }
@ -794,9 +1048,6 @@ void options_parse(int argc, char *argv[])
void options_parse_final(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 */ /* Do 2nd pass to override settings set by configuration file */
optind = 1; // Reset option index to restart scanning of argv optind = 1; // Reset option index to restart scanning of argv
options_parse(argc, argv); options_parse(argc, argv);
@ -804,16 +1055,13 @@ void options_parse_final(int argc, char *argv[])
#ifdef __CYGWIN__ #ifdef __CYGWIN__
unsigned char portnum; unsigned char portnum;
char *tty_win; char *tty_win;
if ( ((strncmp("COM", target, 3) == 0) if ( ((strncmp("COM", option.target, 3) == 0)
|| (strncmp("com", target, 3) == 0) ) || (strncmp("com", option.target, 3) == 0) )
&& (sscanf(target + 3, "%hhu", &portnum) == 1) && (sscanf(option.target + 3, "%hhu", &portnum) == 1)
&& (portnum > 0) ) && (portnum > 0) )
{ {
asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1);
target = tty_win; option.target = tty_win;
} }
#endif #endif
/* Restore target */
option.target = target;
} }

View file

@ -49,20 +49,20 @@ typedef enum
/* Options */ /* Options */
struct option_t struct option_t
{ {
const char *target; char *target;
unsigned int baudrate; int baudrate;
int databits; int databits;
char *flow; flow_t flow;
int stopbits; int stopbits;
char *parity; parity_t parity;
int output_delay; int output_delay;
int output_line_delay; int output_line_delay;
unsigned int dtr_pulse_duration; int dtr_pulse_duration;
unsigned int rts_pulse_duration; int rts_pulse_duration;
unsigned int cts_pulse_duration; int cts_pulse_duration;
unsigned int dsr_pulse_duration; int dsr_pulse_duration;
unsigned int dcd_pulse_duration; int dcd_pulse_duration;
unsigned int ri_pulse_duration; int ri_pulse_duration;
bool no_reconnect; bool no_reconnect;
auto_connect_t auto_connect; auto_connect_t auto_connect;
bool log; bool log;
@ -70,15 +70,15 @@ struct option_t
bool log_strip; bool log_strip;
bool local_echo; bool local_echo;
timestamp_t timestamp; timestamp_t timestamp;
const char *log_filename; char *log_filename;
const char *log_directory; char *log_directory;
const char *map; char *map;
const char *socket; char *socket;
int color; int color;
input_mode_t input_mode; input_mode_t input_mode;
output_mode_t output_mode; output_mode_t output_mode;
unsigned char prefix_code; char prefix_code;
unsigned char prefix_key; char prefix_key;
bool prefix_enabled; bool prefix_enabled;
bool mute; bool mute;
bool rs485; bool rs485;
@ -87,13 +87,13 @@ struct option_t
int32_t rs485_delay_rts_after_send; int32_t rs485_delay_rts_after_send;
alert_t alert; alert_t alert;
bool complete_profiles; bool complete_profiles;
const char *script; char *script;
const char *script_filename; char *script_filename;
script_run_t script_run; script_run_t script_run;
unsigned int timestamp_timeout; int timestamp_timeout;
const char *exclude_devices; char *exclude_devices;
const char *exclude_drivers; char *exclude_drivers;
const char *exclude_tids; char *exclude_tids;
int hex_n_value; int hex_n_value;
bool vt100; bool vt100;
}; };
@ -104,10 +104,20 @@ void options_print();
void options_parse(int argc, char *argv[]); void options_parse(int argc, char *argv[]);
void options_parse_final(int argc, char *argv[]); void options_parse_final(int argc, char *argv[]);
void line_pulse_duration_option_parse(const char *arg); int option_string_to_integer(const char *string, int *value, const char *desc, int min, int max);
script_run_t script_run_option_parse(const char *arg);
input_mode_t input_mode_option_parse(const char *arg); void option_parse_flow(const char *arg, flow_t *flow);
output_mode_t output_mode_option_parse(const char *arg); void option_parse_parity(const char *arg, parity_t *parity);
auto_connect_t auto_connect_option_parse(const char *arg);
const char *auto_connect_state_to_string(auto_connect_t strategy); 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);

View file

@ -87,10 +87,18 @@ extern char ansi_format[];
{ \ { \
if (print_tainted) \ if (print_tainted) \
putchar('\n'); \ putchar('\n'); \
if (option.color < 0) \ if (option.color < 0) { \
fprintf (stdout, "\r[%s] Error: " format "\r\n", timestamp_current_time(), ## args); \ if (error_normal) \
fprintf (stdout, "Error: " format "\n", ## args); \
else \ else \
ansi_printf("[%s] Error: " format, timestamp_current_time(), ## args); \ 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; \ print_tainted = false; \
} \ } \
} }

View file

@ -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_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_BEFORE_SEND = %d", rs485_config.delay_rts_before_send);
tio_printf(" RTS_DELAY_AFTER_SEND = %d", rs485_config.delay_rts_after_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) int rs485_mode_enable(int fd)

View file

@ -93,60 +93,3 @@ char *timestamp_current_time(void)
return (len < TIME_STRING_SIZE_MAX) ? time_string : NULL; 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;
}

View file

@ -32,5 +32,4 @@ typedef enum
} timestamp_t; } timestamp_t;
char *timestamp_current_time(void); char *timestamp_current_time(void);
const char* timestamp_state_to_string(timestamp_t timestamp);
timestamp_t timestamp_option_parse(const char *arg);

View file

@ -158,7 +158,7 @@ bool map_ign_cr = false;
char key_hit = 0xff; char key_hit = 0xff;
const char* device_name; const char* device_name = NULL;
GList *device_list = NULL; GList *device_list = NULL;
static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
static unsigned long rx_total = 0, tx_total = 0; static unsigned long rx_total = 0, tx_total = 0;
@ -1154,23 +1154,24 @@ void tty_configure(void)
} }
/* Set flow control */ /* Set flow control */
if (strcmp("hard", option.flow) == 0) switch (option.flow)
{ {
case FLOW_NONE:
tio.c_cflag &= ~CRTSCTS;
tio.c_iflag &= ~(IXON | IXOFF | IXANY);
break;
case FLOW_HARD:
tio.c_cflag |= CRTSCTS; tio.c_cflag |= CRTSCTS;
tio.c_iflag &= ~(IXON | IXOFF | IXANY); tio.c_iflag &= ~(IXON | IXOFF | IXANY);
} break;
else if (strcmp("soft", option.flow) == 0)
{ case FLOW_SOFT:
tio.c_cflag &= ~CRTSCTS; tio.c_cflag &= ~CRTSCTS;
tio.c_iflag |= IXON | IXOFF; tio.c_iflag |= IXON | IXOFF;
} break;
else if (strcmp("none", option.flow) == 0)
{ default:
tio.c_cflag &= ~CRTSCTS;
tio.c_iflag &= ~(IXON | IXOFF | IXANY);
}
else
{
tio_error_printf("Invalid flow control"); tio_error_printf("Invalid flow control");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -1190,34 +1191,35 @@ void tty_configure(void)
} }
/* Set parity */ /* 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)
{ {
case PARITY_NONE:
tio.c_cflag &= ~PARENB; tio.c_cflag &= ~PARENB;
} break;
else if ( strcmp("mark", option.parity) == 0)
{ 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 |= PARENB;
tio.c_cflag |= PARODD; tio.c_cflag |= PARODD;
tio.c_cflag |= CMSPAR; tio.c_cflag |= CMSPAR;
} break;
else if ( strcmp("space", option.parity) == 0)
{ case PARITY_SPACE:
tio.c_cflag |= PARENB; tio.c_cflag |= PARENB;
tio.c_cflag &= ~PARODD; tio.c_cflag &= ~PARODD;
tio.c_cflag |= CMSPAR; tio.c_cflag |= CMSPAR;
} break;
else
{ default:
tio_error_printf("Invalid parity"); tio_error_printf("Invalid parity");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -1809,7 +1811,14 @@ void tty_search(void)
return; return;
case AUTO_CONNECT_DIRECT: 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 // Potential topology ID detected -> trigger device search
tty_search_for_serial_devices(); 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 // Fallback to using tty device provided via cmdline target
device_name = option.target; device_name = option.target;
break; break;

View file

@ -29,6 +29,22 @@
#define TOPOLOGY_ID_SIZE 4 #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 typedef enum
{ {
AUTO_CONNECT_DIRECT, AUTO_CONNECT_DIRECT,

View file

@ -1,4 +0,0 @@
[wrap-git]
directory=libinih
url=https://github.com/benhoyt/inih.git
revision=r58