mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
Add new ways to manage serial devices
* Rename --list-devices to --list
* Rename --no-autoconnect to --no-reconnect
* Switch -l and -L options
* -l now lists available serial devices
* -L enables log to file
* Add option --auto-connect <strategy>
* Supported strategies:
* "new" - Waits to connect first new appearing serial device
* "latest" - Connects to latest registered serial device
* "direct" - Connect directly to specified serial device (default)
* Add options to exclude serial devices from auto connect strategy by
pattern
* Supported exclude options:
* --exclude-devices <pattern>
Example: '--exclude-devices "/dev/ttyUSB2,/dev/ttyS?"'
* --exclude-drivers <pattern>
Example: '--exclude-drivers "cdc_acm"'
* --exclude-tids <pattern>
Example: '--exclude-tids "yW07,bCC2"'
* Patterns support '*' and '?'
* Connect to same port/device combination via unique topology ID (TID)
* Topology ID is a 4 digit base62 encoded hash of a device topology
string coming from the Linux kernel. This means that whenever you
plug in the same e.g. USB serial port device to the same USB hub
port connected via the exact same hub topology all the way to your
computer, you will get the same unique TID.
* Useful for stable reconnections when serial device has no serial
device by ID
* For now, only tested on Linux.
* Reworked and improved listing of serial devices to show serial devices:
* By device
* Including TID, uptime, driver, and description.
* Sorted by uptime (newest device listed last)
* By unique topology ID
* By ID
* By path
* Add script interface 'list = tty_search()' for searching for serial
devices.
This commit is contained in:
parent
ae76f8f58d
commit
d19ba1c492
22 changed files with 1468 additions and 150 deletions
|
|
@ -18,7 +18,11 @@ _tio()
|
|||
-o --output-delay \
|
||||
-o --output-line-delay \
|
||||
--line-pulse-duration \
|
||||
-n --no-autoconnect \
|
||||
-a --auto-connect \
|
||||
--exclude-devices \
|
||||
--exclude-drivers \
|
||||
--exclude-tids \
|
||||
-n --no-reconnect \
|
||||
-e --local-echo \
|
||||
-l --log \
|
||||
--log-file \
|
||||
|
|
@ -29,7 +33,7 @@ _tio()
|
|||
-t --timestamp \
|
||||
--timestamp-format \
|
||||
--timestamp-timeout \
|
||||
-L --list-devices \
|
||||
-L --list \
|
||||
-c --color \
|
||||
-S --socket \
|
||||
--input-mode \
|
||||
|
|
@ -79,7 +83,11 @@ _tio()
|
|||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
-n | --no-autoconnect)
|
||||
-a | --auto-connect)
|
||||
COMPREPLY=( $(compgen -W "new latest none" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
-n | --no-reconnect)
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
|
|
@ -123,7 +131,7 @@ _tio()
|
|||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
-L | --list-devices)
|
||||
-L | --list)
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ struct config_t
|
|||
char *section_name;
|
||||
char *match;
|
||||
|
||||
char *tty;
|
||||
char *target;
|
||||
char *flow;
|
||||
char *parity;
|
||||
char *log_filename;
|
||||
|
|
@ -63,6 +63,9 @@ struct config_t
|
|||
char *script;
|
||||
char *script_filename;
|
||||
bool script_run;
|
||||
char *exclude_devices;
|
||||
char *exclude_drivers;
|
||||
char *exclude_tids;
|
||||
};
|
||||
|
||||
static struct config_t c;
|
||||
|
|
@ -151,8 +154,8 @@ static int data_handler(void *user, const char *section, const char *name,
|
|||
// Set configuration parameter if found
|
||||
if (!strcmp(name, "device") || !strcmp(name, "tty"))
|
||||
{
|
||||
asprintf(&c.tty, value, c.match);
|
||||
option.tty_device = c.tty;
|
||||
asprintf(&c.target, value, c.match);
|
||||
option.target = c.target;
|
||||
}
|
||||
else if (!strcmp(name, "baudrate"))
|
||||
{
|
||||
|
|
@ -188,9 +191,13 @@ static int data_handler(void *user, const char *section, const char *name,
|
|||
{
|
||||
line_pulse_duration_option_parse(value);
|
||||
}
|
||||
else if (!strcmp(name, "no-autoconnect"))
|
||||
else if (!strcmp(name, "no-reconnect"))
|
||||
{
|
||||
option.no_autoconnect = read_boolean(value, name);
|
||||
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"))
|
||||
{
|
||||
|
|
@ -314,6 +321,21 @@ static int data_handler(void *user, const char *section, const char *name,
|
|||
{
|
||||
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
|
||||
{
|
||||
tio_warning_printf("Unknown option '%s' in configuration file, ignored", name);
|
||||
|
|
@ -460,8 +482,8 @@ void config_file_parse(void)
|
|||
return;
|
||||
}
|
||||
|
||||
// Set user input which may be tty device or sub config
|
||||
c.user = option.tty_device;
|
||||
// Set user input which may be tty device or sub config or tid
|
||||
c.user = option.target;
|
||||
|
||||
if (!c.user)
|
||||
{
|
||||
|
|
@ -504,7 +526,7 @@ void config_file_parse(void)
|
|||
|
||||
void config_exit(void)
|
||||
{
|
||||
free(c.tty);
|
||||
free(c.target);
|
||||
free(c.flow);
|
||||
free(c.parity);
|
||||
free(c.log_filename);
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ void error_exit(void)
|
|||
/* Print error */
|
||||
error_printf_("Error: %s", error[0]);
|
||||
}
|
||||
else if ((error[1][0] != 0) && (option.no_autoconnect))
|
||||
else if ((error[1][0] != 0) && (option.no_reconnect))
|
||||
{
|
||||
/* Print silent error */
|
||||
error_printf_("Error: %s", error[1]);
|
||||
|
|
|
|||
205
src/fs.c
Normal file
205
src/fs.c
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* tio - a serial device I/O tool
|
||||
*
|
||||
* Copyright (c) 2014-2024 Martin Lund
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // For statx()
|
||||
#include "config.h"
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <regex.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <sys/poll.h>
|
||||
#include <termios.h>
|
||||
#include "error.h"
|
||||
#include "print.h"
|
||||
#include "options.h"
|
||||
|
||||
bool fs_dir_exists(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(path, &st) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (!S_ISDIR(st.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function to read the content of a file but stripped of newline
|
||||
ssize_t fs_read_file_stripped(char *buf, size_t bufsiz, const char *format, ...)
|
||||
{
|
||||
char filename[PATH_MAX];
|
||||
int bytes_printed = 0;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
|
||||
va_end(args);
|
||||
|
||||
if (bytes_printed < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (!file)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
ssize_t length = fread(buf, 1, bufsiz - 1, file);
|
||||
if (length == -1)
|
||||
{
|
||||
fclose(file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Strip any newline
|
||||
buf[strcspn(buf, "\n")] = 0;
|
||||
buf[length] = '\0'; // Make sure to null-terminate the string
|
||||
|
||||
fclose(file);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
bool fs_file_exists(const char *format, ...)
|
||||
{
|
||||
char filename[PATH_MAX];
|
||||
int bytes_printed = 0;
|
||||
struct stat st;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
|
||||
va_end(args);
|
||||
|
||||
if (bytes_printed < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stat(filename, &st) == 0;
|
||||
}
|
||||
|
||||
char* fs_search_directory(const char *dir_path, const char *dirname)
|
||||
{
|
||||
struct dirent *entry;
|
||||
char path[PATH_MAX];
|
||||
struct stat st;
|
||||
DIR *dir;
|
||||
|
||||
if ((dir = opendir(dir_path)) == NULL)
|
||||
{
|
||||
// Error opening directory
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
snprintf(path, PATH_MAX, "%s/%s", dir_path, entry->d_name);
|
||||
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lstat(path, &st) == -1)
|
||||
{
|
||||
// Error getting directory status
|
||||
closedir(dir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (S_ISLNK(st.st_mode))
|
||||
{
|
||||
// Skip symbolic links
|
||||
continue;
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode))
|
||||
{
|
||||
// If it's a directory, check if it's the one we're looking for
|
||||
if (strcmp(entry->d_name, dirname) == 0)
|
||||
{
|
||||
char* result = malloc(strlen(path) + 1);
|
||||
if (result == NULL)
|
||||
{
|
||||
// Error allocating memory
|
||||
closedir(dir);
|
||||
return NULL;
|
||||
}
|
||||
strcpy(result, path);
|
||||
closedir(dir);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recursively search within directories
|
||||
char* result = fs_search_directory(path, dirname);
|
||||
if (result != NULL)
|
||||
{
|
||||
closedir(dir);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Function to return creation time of file
|
||||
double fs_get_creation_time(const char *path)
|
||||
{
|
||||
struct statx stx;
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1)
|
||||
{
|
||||
// Error
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = statx(fd, "", AT_EMPTY_PATH, STATX_ALL, &stx);
|
||||
if (ret == -1)
|
||||
{
|
||||
// Error
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Close the file
|
||||
close(fd);
|
||||
|
||||
return stx.stx_btime.tv_sec + stx.stx_btime.tv_nsec / 1e9;
|
||||
}
|
||||
31
src/fs.h
Normal file
31
src/fs.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* tio - a serial device I/O tool
|
||||
*
|
||||
* Copyright (c) 2014-2024 Martin Lund
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool fs_dir_exists(const char *path);
|
||||
bool fs_file_exists(const char *format, ...);
|
||||
char* fs_search_directory(const char *dir_path, const char *dirname);
|
||||
ssize_t fs_read_file_stripped(char *buf, size_t bufsiz, const char *format, ...);
|
||||
double fs_get_creation_time(const char *path);
|
||||
18
src/log.c
18
src/log.c
|
|
@ -33,7 +33,7 @@
|
|||
#include "options.h"
|
||||
#include "print.h"
|
||||
#include "error.h"
|
||||
#include "misc.h"
|
||||
#include "fs.h"
|
||||
|
||||
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F))
|
||||
#define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E))
|
||||
|
|
@ -64,8 +64,20 @@ int log_open(const char *filename)
|
|||
|
||||
if (filename == NULL)
|
||||
{
|
||||
// Generate filename if none provided ("tio_DEVICE_YYYY-MM-DDTHH:MM:SS.log")
|
||||
asprintf(&automatic_filename, "tio_%s_%s.log", basename((char *)option.tty_device), date_time());
|
||||
// Generate filename if none provided
|
||||
if (option.auto_connect == AUTO_CONNECT_DIRECT)
|
||||
{
|
||||
// File name format ("tio_TARGET_YYYY-MM-DDTHH:MM:SS.log")
|
||||
asprintf(&automatic_filename, "tio_%s_%s.log", basename((char *)option.target), date_time());
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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),
|
||||
date_time());
|
||||
}
|
||||
|
||||
if (option.log_directory != NULL)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -121,8 +121,9 @@ int main(int argc, char *argv[])
|
|||
tty_input_thread_wait_ready();
|
||||
|
||||
/* Connect to tty device */
|
||||
if (option.no_autoconnect)
|
||||
if (option.no_reconnect)
|
||||
{
|
||||
tty_search();
|
||||
status = tty_connect();
|
||||
}
|
||||
else
|
||||
|
|
@ -131,7 +132,7 @@ int main(int argc, char *argv[])
|
|||
while (true)
|
||||
{
|
||||
tty_wait_for_device();
|
||||
status = tty_connect();
|
||||
tty_connect();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ tio_sources = [
|
|||
'timestamp.c',
|
||||
'alert.c',
|
||||
'xymodem.c',
|
||||
'script.c'
|
||||
'script.c',
|
||||
'fs.c'
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ 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']),
|
||||
|
|
|
|||
133
src/misc.c
133
src/misc.c
|
|
@ -20,7 +20,10 @@
|
|||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <regex.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -29,6 +32,7 @@
|
|||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <sys/poll.h>
|
||||
#include <termios.h>
|
||||
#include "error.h"
|
||||
#include "print.h"
|
||||
#include "options.h"
|
||||
|
|
@ -74,22 +78,6 @@ int ctrl_key_code(unsigned char key)
|
|||
return -1;
|
||||
}
|
||||
|
||||
bool fs_dir_exists(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(path, &st) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (!S_ISDIR(st.st_mode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool regex_match(const char *string, const char *pattern)
|
||||
{
|
||||
regex_t regex;
|
||||
|
|
@ -141,3 +129,116 @@ int read_poll(int fd, void *data, size_t len, int timeout)
|
|||
/* Timeout */
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Function to calculate djb2 hash of string
|
||||
unsigned long djb2_hash(const unsigned char *str)
|
||||
{
|
||||
unsigned long hash = 5381;
|
||||
int c;
|
||||
|
||||
while ((c = *str++))
|
||||
{
|
||||
hash = ((hash << 5) + hash) + c; // hash * 33 + c
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Function to encode a number to base62
|
||||
char *base62_encode(unsigned long num)
|
||||
{
|
||||
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
char *output = (char *) malloc(5); // 4 characters + null terminator
|
||||
if (output == NULL)
|
||||
{
|
||||
tio_error_print("Memory allocation failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
output[i] = base62_chars[num % 62];
|
||||
num /= 62;
|
||||
}
|
||||
output[4] = '\0';
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
// Function to return current time
|
||||
double get_current_time(void)
|
||||
{
|
||||
struct timespec current_time_ts;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, ¤t_time_ts) == -1)
|
||||
{
|
||||
// Error
|
||||
return -1;
|
||||
}
|
||||
|
||||
return current_time_ts.tv_sec + current_time_ts.tv_nsec / 1e9;
|
||||
}
|
||||
|
||||
// Function to match string with comma separated patterns which supports '*' and '?'
|
||||
static bool is_match(const char *str, const char *pattern)
|
||||
{
|
||||
// If both string and pattern reach end, they match
|
||||
if (*str == '\0' && *pattern == '\0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If pattern reaches end but string has characters left, no match
|
||||
if (*pattern == '\0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If current characters match or pattern has '?', move to the next character in both
|
||||
if (*str == *pattern || *pattern == '?')
|
||||
{
|
||||
return is_match(str + 1, pattern + 1);
|
||||
}
|
||||
|
||||
// If current pattern character is '*', check for matches by moving string or pattern
|
||||
if (*pattern == '*')
|
||||
{
|
||||
// '*' matches zero or more characters, so try all possibilities
|
||||
// Move pattern to the next character and check if remaining pattern matches remaining string
|
||||
// Move string to the next character and check if current pattern matches remaining string
|
||||
return is_match(str, pattern + 1) || is_match(str + 1, pattern);
|
||||
}
|
||||
|
||||
// No match
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match_any_pattern(const char *str, const char *patterns)
|
||||
{
|
||||
if ((str == NULL) || (patterns == NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char *patterns_copy = strdup(patterns);
|
||||
if (patterns_copy == NULL)
|
||||
{
|
||||
tio_error_print("Memory allocation failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *token = strtok(patterns_copy, ",");
|
||||
while (token != NULL)
|
||||
{
|
||||
if (is_match(str, token))
|
||||
{
|
||||
free(patterns_copy);
|
||||
return true;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
free(patterns_copy);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,12 +26,14 @@
|
|||
|
||||
#define UNUSED(expr) do { (void)(expr); } while (0)
|
||||
|
||||
char * current_time(void);
|
||||
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 fs_dir_exists(const char *path);
|
||||
bool regex_match(const char *string, const char *pattern);
|
||||
unsigned long djb2_hash(const unsigned char *str);
|
||||
char *base62_encode(unsigned long num);
|
||||
int read_poll(int fd, void *data, size_t len, int timeout);
|
||||
double get_current_time(void);
|
||||
bool match_any_pattern(const char *str, const char *patterns);
|
||||
|
|
|
|||
141
src/options.c
141
src/options.c
|
|
@ -61,12 +61,15 @@ enum opt_t
|
|||
OPT_SCRIPT_RUN,
|
||||
OPT_INPUT_MODE,
|
||||
OPT_OUTPUT_MODE,
|
||||
OPT_EXCLUDE_DEVICES,
|
||||
OPT_EXCLUDE_DRIVERS,
|
||||
OPT_EXCLUDE_TIDS,
|
||||
};
|
||||
|
||||
/* Default options */
|
||||
struct option_t option =
|
||||
{
|
||||
.tty_device = "",
|
||||
.target = "",
|
||||
.baudrate = 115200,
|
||||
.databits = 8,
|
||||
.flow = "none",
|
||||
|
|
@ -80,7 +83,8 @@ struct option_t option =
|
|||
.dsr_pulse_duration = 100,
|
||||
.dcd_pulse_duration = 100,
|
||||
.ri_pulse_duration = 100,
|
||||
.no_autoconnect = false,
|
||||
.no_reconnect = false,
|
||||
.auto_connect = AUTO_CONNECT_DIRECT,
|
||||
.log = false,
|
||||
.log_append = false,
|
||||
.log_filename = NULL,
|
||||
|
|
@ -107,15 +111,18 @@ struct option_t option =
|
|||
.script_filename = NULL,
|
||||
.script_run = SCRIPT_RUN_ALWAYS,
|
||||
.timestamp_timeout = 200,
|
||||
.exclude_devices = NULL,
|
||||
.exclude_drivers = NULL,
|
||||
.exclude_tids = NULL,
|
||||
};
|
||||
|
||||
void print_help(char *argv[])
|
||||
{
|
||||
UNUSED(argv);
|
||||
|
||||
printf("Usage: tio [<options>] <tty-device|sub-config>\n");
|
||||
printf("Usage: tio [<options>] <tty-device|sub-config|tid>\n");
|
||||
printf("\n");
|
||||
printf("Connect to TTY device directly or via sub-configuration.\n");
|
||||
printf("Connect to TTY device directly or via sub-configuration or topology ID.\n");
|
||||
printf("\n");
|
||||
printf("Options:\n");
|
||||
printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n");
|
||||
|
|
@ -126,15 +133,19 @@ void print_help(char *argv[])
|
|||
printf(" -o, --output-delay <ms> Output character delay (default: 0)\n");
|
||||
printf(" -O, --output-line-delay <ms> Output line delay (default: 0)\n");
|
||||
printf(" --line-pulse-duration <duration> Set line pulse duration\n");
|
||||
printf(" -n, --no-autoconnect Disable automatic connect\n");
|
||||
printf(" -a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)\n");
|
||||
printf(" --exclude-devices <pattern> Exclude devices by pattern\n");
|
||||
printf(" --exclude-drivers <pattern> Exclude drivers by pattern\n");
|
||||
printf(" --exclude-tids <pattern> Exclude topology IDs by pattern\n");
|
||||
printf(" -n, --no-reconnect Do not reconnect\n");
|
||||
printf(" -e, --local-echo Enable local echo\n");
|
||||
printf(" --input-mode normal|hex|line Select input mode (default: normal)\n");
|
||||
printf(" --output-mode normal|hex Select output mode (default: normal)\n");
|
||||
printf(" -t, --timestamp Enable line timestamp\n");
|
||||
printf(" --timestamp-format <format> Set timestamp format (default: 24hour)\n");
|
||||
printf(" --timestamp-timeout <ms> Set timestamp timeout (default: 200)\n");
|
||||
printf(" -L, --list-devices List available serial devices by ID\n");
|
||||
printf(" -l, --log Enable log to file\n");
|
||||
printf(" -l, --list List available serial devices\n");
|
||||
printf(" -L, --log Enable log to file\n");
|
||||
printf(" --log-file <filename> Set log filename\n");
|
||||
printf(" --log-directory <path> Set log directory path for automatic named logs\n");
|
||||
printf(" --log-append Append to log file\n");
|
||||
|
|
@ -157,6 +168,44 @@ 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)
|
||||
{
|
||||
switch (strategy)
|
||||
{
|
||||
case AUTO_CONNECT_DIRECT:
|
||||
return "direct";
|
||||
case AUTO_CONNECT_NEW:
|
||||
return "new";
|
||||
case AUTO_CONNECT_LATEST:
|
||||
return "latest";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
auto_connect_t auto_connect_option_parse(const char *arg)
|
||||
{
|
||||
auto_connect_t auto_connect = option.auto_connect; // Default
|
||||
|
||||
if (arg != NULL)
|
||||
{
|
||||
if (strcmp(arg, "direct") == 0)
|
||||
{
|
||||
return AUTO_CONNECT_DIRECT;
|
||||
}
|
||||
else if (strcmp(arg, "new") == 0)
|
||||
{
|
||||
return AUTO_CONNECT_NEW;
|
||||
}
|
||||
else if (strcmp(arg, "latest") == 0)
|
||||
{
|
||||
return AUTO_CONNECT_LATEST;
|
||||
}
|
||||
}
|
||||
|
||||
return auto_connect;
|
||||
}
|
||||
|
||||
void line_pulse_duration_option_parse(const char *arg)
|
||||
{
|
||||
bool token_found = true;
|
||||
|
|
@ -312,7 +361,7 @@ script_run_t script_run_option_parse(const char *arg)
|
|||
|
||||
void options_print()
|
||||
{
|
||||
tio_printf(" Device: %s", option.tty_device);
|
||||
tio_printf(" Device: %s", device_name);
|
||||
tio_printf(" Baudrate: %u", option.baudrate);
|
||||
tio_printf(" Databits: %d", option.databits);
|
||||
tio_printf(" Flow: %s", option.flow);
|
||||
|
|
@ -323,7 +372,8 @@ void options_print()
|
|||
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(" Auto connect: %s", option.no_autoconnect ? "disabled" : "enabled");
|
||||
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(" 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,
|
||||
|
|
@ -385,13 +435,17 @@ void options_parse(int argc, char *argv[])
|
|||
{"output-delay", required_argument, 0, 'o' },
|
||||
{"output-line-delay" , required_argument, 0, 'O' },
|
||||
{"line-pulse-duration", required_argument, 0, OPT_LINE_PULSE_DURATION },
|
||||
{"no-autoconnect", no_argument, 0, 'n' },
|
||||
{"auto-connect", required_argument, 0, 'a' },
|
||||
{"exclude-devices", required_argument, 0, OPT_EXCLUDE_DEVICES },
|
||||
{"exclude-drivers", required_argument, 0, OPT_EXCLUDE_DRIVERS },
|
||||
{"exclude-tids", required_argument, 0, OPT_EXCLUDE_TIDS },
|
||||
{"no-reconnect", no_argument, 0, 'n' },
|
||||
{"local-echo", no_argument, 0, 'e' },
|
||||
{"timestamp", no_argument, 0, 't' },
|
||||
{"timestamp-format", required_argument, 0, OPT_TIMESTAMP_FORMAT },
|
||||
{"timestamp-timeout", required_argument, 0, OPT_TIMESTAMP_TIMEOUT },
|
||||
{"list-devices", no_argument, 0, 'L' },
|
||||
{"log", no_argument, 0, 'l' },
|
||||
{"list", no_argument, 0, 'l' },
|
||||
{"log", no_argument, 0, 'L' },
|
||||
{"log-file", required_argument, 0, OPT_LOG_FILE },
|
||||
{"log-directory", required_argument, 0, OPT_LOG_DIRECTORY },
|
||||
{"log-append", no_argument, 0, OPT_LOG_APPEND },
|
||||
|
|
@ -418,7 +472,7 @@ void options_parse(int argc, char *argv[])
|
|||
int option_index = 0;
|
||||
|
||||
/* Parse argument using getopt_long */
|
||||
c = getopt_long(argc, argv, "b:d:f:s:p:o:O:netLlS:m:c:xrvh", long_options, &option_index);
|
||||
c = getopt_long(argc, argv, "b:d:f:s:p:o:O:a:netLlS:m:c:xrvh", long_options, &option_index);
|
||||
|
||||
/* Detect the end of the options */
|
||||
if (c == -1)
|
||||
|
|
@ -468,8 +522,24 @@ void options_parse(int argc, char *argv[])
|
|||
line_pulse_duration_option_parse(optarg);
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
option.auto_connect = auto_connect_option_parse(optarg);
|
||||
break;
|
||||
|
||||
case OPT_EXCLUDE_DEVICES:
|
||||
option.exclude_devices = optarg;
|
||||
break;
|
||||
|
||||
case OPT_EXCLUDE_DRIVERS:
|
||||
option.exclude_drivers = optarg;
|
||||
break;
|
||||
|
||||
case OPT_EXCLUDE_TIDS:
|
||||
option.exclude_tids = optarg;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
option.no_autoconnect = true;
|
||||
option.no_reconnect = true;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
|
|
@ -489,12 +559,12 @@ void options_parse(int argc, char *argv[])
|
|||
break;
|
||||
|
||||
case 'L':
|
||||
list_serial_devices();
|
||||
exit(EXIT_SUCCESS);
|
||||
option.log = true;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
option.log = true;
|
||||
list_serial_devices();
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
case OPT_LOG_FILE:
|
||||
|
|
@ -611,24 +681,33 @@ void options_parse(int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
|
||||
/* Assume first non-option is the tty device name */
|
||||
if (strcmp(option.tty_device, ""))
|
||||
/* Assume first non-option is the target (tty device, sub-config, tid) */
|
||||
if (strcmp(option.target, ""))
|
||||
{
|
||||
optind++;
|
||||
}
|
||||
else if (optind < argc)
|
||||
option.tty_device = argv[optind++];
|
||||
{
|
||||
option.target = argv[optind++];
|
||||
}
|
||||
|
||||
if (option.complete_sub_configs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(option.tty_device) == 0)
|
||||
if (option.auto_connect != AUTO_CONNECT_DIRECT)
|
||||
{
|
||||
tio_error_printf("Missing tty device or sub-configuration name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen(option.target) == 0)
|
||||
{
|
||||
tio_error_printf("Missing tty device, sub-configuration or topology ID");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Print any remaining command line arguments (unknown options) */
|
||||
/* Print any remaining command line arguments as unknown */
|
||||
if (optind < argc)
|
||||
{
|
||||
fprintf(stderr, "Error: Unknown argument ");
|
||||
|
|
@ -643,8 +722,8 @@ void options_parse(int argc, char *argv[])
|
|||
|
||||
void options_parse_final(int argc, char *argv[])
|
||||
{
|
||||
/* Preserve tty device which may have been set by configuration file */
|
||||
const char *tty_device = option.tty_device;
|
||||
/* 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
|
||||
|
|
@ -653,16 +732,16 @@ void options_parse_final(int argc, char *argv[])
|
|||
#ifdef __CYGWIN__
|
||||
unsigned char portnum;
|
||||
char *tty_win;
|
||||
if ( ((strncmp("COM", tty_device, 3) == 0)
|
||||
|| (strncmp("com", tty_device, 3) == 0) )
|
||||
&& (sscanf(tty_device + 3, "%hhu", &portnum) == 1)
|
||||
if ( ((strncmp("COM", target, 3) == 0)
|
||||
|| (strncmp("com", target, 3) == 0) )
|
||||
&& (sscanf(target + 3, "%hhu", &portnum) == 1)
|
||||
&& (portnum > 0) )
|
||||
{
|
||||
asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1);
|
||||
tty_device = tty_win;
|
||||
target = tty_win;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Restore tty device */
|
||||
option.tty_device = tty_device;
|
||||
/* Restore target */
|
||||
option.target = target;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "script.h"
|
||||
#include "timestamp.h"
|
||||
#include "alert.h"
|
||||
#include "tty.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
|
|
@ -48,7 +49,7 @@ typedef enum
|
|||
/* Options */
|
||||
struct option_t
|
||||
{
|
||||
const char *tty_device;
|
||||
const char *target;
|
||||
unsigned int baudrate;
|
||||
int databits;
|
||||
char *flow;
|
||||
|
|
@ -62,7 +63,8 @@ struct option_t
|
|||
unsigned int dsr_pulse_duration;
|
||||
unsigned int dcd_pulse_duration;
|
||||
unsigned int ri_pulse_duration;
|
||||
bool no_autoconnect;
|
||||
bool no_reconnect;
|
||||
auto_connect_t auto_connect;
|
||||
bool log;
|
||||
bool log_append;
|
||||
bool log_strip;
|
||||
|
|
@ -89,6 +91,9 @@ struct option_t
|
|||
const char *script_filename;
|
||||
script_run_t script_run;
|
||||
unsigned int timestamp_timeout;
|
||||
const char *exclude_devices;
|
||||
const char *exclude_drivers;
|
||||
const char *exclude_tids;
|
||||
};
|
||||
|
||||
extern struct option_t option;
|
||||
|
|
@ -102,3 +107,5 @@ script_run_t script_run_option_parse(const char *arg);
|
|||
|
||||
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);
|
||||
|
|
|
|||
75
src/script.c
75
src/script.c
|
|
@ -36,6 +36,7 @@
|
|||
#include "xymodem.h"
|
||||
#include "log.h"
|
||||
#include "script.h"
|
||||
#include "fs.h"
|
||||
|
||||
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
|
||||
|
||||
|
|
@ -44,6 +45,9 @@ static char circular_buffer[MAX_BUFFER_SIZE];
|
|||
static char match_string[MAX_BUFFER_SIZE];
|
||||
static int buffer_size = 0;
|
||||
|
||||
static char init_script[] =
|
||||
"\n";
|
||||
|
||||
// lua: sleep(seconds)
|
||||
static int sleep_(lua_State *L)
|
||||
{
|
||||
|
|
@ -401,6 +405,60 @@ static int exit_(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// lua: list = tty_search()
|
||||
static int tty_search_(lua_State *L)
|
||||
{
|
||||
UNUSED(L);
|
||||
GList *iter;
|
||||
int i = 1;
|
||||
|
||||
GList *device_list = tty_search_for_serial_devices();
|
||||
|
||||
if (device_list == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a new table
|
||||
lua_newtable(L);
|
||||
|
||||
// Iterate through found devices
|
||||
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
|
||||
{
|
||||
device_t *device = (device_t *) iter->data;
|
||||
|
||||
// Create a new sub-table for each serial device
|
||||
lua_newtable(L);
|
||||
|
||||
// Add elements to the table
|
||||
lua_pushstring(L, "path");
|
||||
lua_pushstring(L, device->path);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushstring(L, "tid");
|
||||
lua_pushstring(L, device->tid);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushstring(L, "uptime");
|
||||
lua_pushnumber(L, device->uptime);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushstring(L, "driver");
|
||||
lua_pushstring(L, device->driver);
|
||||
lua_settable(L, -3);
|
||||
|
||||
lua_pushstring(L, "description");
|
||||
lua_pushstring(L, device->description);
|
||||
lua_settable(L, -3);
|
||||
|
||||
// Set the sub-table as a row in the main table
|
||||
lua_rawseti(L, -2, i++);
|
||||
}
|
||||
|
||||
// Return table
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void script_buffer_run(lua_State *L, const char *script_buffer)
|
||||
{
|
||||
int error;
|
||||
|
|
@ -410,7 +468,7 @@ static void script_buffer_run(lua_State *L, const char *script_buffer)
|
|||
if (error)
|
||||
{
|
||||
tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
|
||||
lua_pop(L, 1); /* pop error message from the stack */
|
||||
lua_pop(L, 1); /* Pop error message from the stack */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +487,7 @@ static const struct luaL_Reg tio_lib[] =
|
|||
{ "read", read_string},
|
||||
{ "expect", expect},
|
||||
{ "exit", exit_},
|
||||
{ "tty_search", tty_search_},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -451,6 +510,18 @@ static void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup)
|
|||
}
|
||||
#endif
|
||||
|
||||
static void load_init_script(lua_State *L)
|
||||
{
|
||||
int error;
|
||||
|
||||
error = luaL_loadbuffer(L, init_script, strlen(init_script), "tio") || lua_pcall(L, 0, 0, 0);
|
||||
if (error)
|
||||
{
|
||||
tio_error_print("%s\n", lua_tostring(L, -1));
|
||||
lua_pop(L, 1); // Pop error message from the stack
|
||||
}
|
||||
}
|
||||
|
||||
int lua_register_tio(lua_State *L)
|
||||
{
|
||||
// Register lxi functions
|
||||
|
|
@ -458,6 +529,8 @@ int lua_register_tio(lua_State *L)
|
|||
luaL_setfuncs(L, tio_lib, 0);
|
||||
lua_pop(L, 1);
|
||||
|
||||
load_init_script(L);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
569
src/tty.c
569
src/tty.c
|
|
@ -19,7 +19,11 @@
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <linux/serial.h>
|
||||
#endif
|
||||
#include "config.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/time.h>
|
||||
|
|
@ -42,6 +46,7 @@
|
|||
#include <time.h>
|
||||
#include <dirent.h>
|
||||
#include <pthread.h>
|
||||
#include <glib.h>
|
||||
#include "config.h"
|
||||
#include "configfile.h"
|
||||
#include "tty.h"
|
||||
|
|
@ -58,11 +63,13 @@
|
|||
#include "misc.h"
|
||||
#include "script.h"
|
||||
#include "xymodem.h"
|
||||
#include "fs.h"
|
||||
|
||||
/* tty device listing configuration */
|
||||
|
||||
#if defined(__linux__)
|
||||
#define PATH_SERIAL_DEVICES "/dev/serial/by-id/"
|
||||
#define PATH_SERIAL_DEVICES_BY_PATH "/dev/serial/by-path/"
|
||||
#define PREFIX_TTY_DEVICES ""
|
||||
#elif defined(__FreeBSD__)
|
||||
#define PATH_SERIAL_DEVICES "/dev/"
|
||||
|
|
@ -158,6 +165,8 @@ bool map_ign_cr = false;
|
|||
|
||||
char key_hit = 0xff;
|
||||
|
||||
const char* device_name;
|
||||
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;
|
||||
static bool connected = false;
|
||||
|
|
@ -1336,6 +1345,539 @@ void tty_configure(void)
|
|||
free(buffer);
|
||||
}
|
||||
|
||||
static bool is_serial_device(const char *format, ...)
|
||||
{
|
||||
char filename[PATH_MAX];
|
||||
struct winsize ws;
|
||||
int bytes_printed;
|
||||
int status = true;
|
||||
struct stat st;
|
||||
va_list args;
|
||||
int fd = -1;
|
||||
|
||||
va_start(args, format);
|
||||
bytes_printed = vsnprintf(filename, sizeof(filename), format, args);
|
||||
va_end(args);
|
||||
|
||||
if (bytes_printed < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stat(filename, &st) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure it is a character device
|
||||
if ((st.st_mode & S_IFMT) != S_IFCHR)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fd = open(filename, O_RDONLY | O_NONBLOCK | O_NOCTTY);
|
||||
if (fd == -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure it is a tty
|
||||
status = isatty(fd);
|
||||
if (status == 0)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Serial devices do not have rows and columns
|
||||
status = ioctl(fd, TIOCGWINSZ, &ws);
|
||||
if (status == 0)
|
||||
{
|
||||
status = true;
|
||||
if (ws.ws_row && ws.ws_col)
|
||||
{
|
||||
status = false;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
close(fd);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void list_serial_devices_by_id(void)
|
||||
{
|
||||
DIR *d = opendir(PATH_SERIAL_DEVICES);
|
||||
if (d)
|
||||
{
|
||||
struct dirent *dir;
|
||||
|
||||
printf("By-id\n");
|
||||
printf("--------------------------------------------------------------------------------\n");
|
||||
|
||||
while ((dir = readdir(d)) != NULL)
|
||||
{
|
||||
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
|
||||
{
|
||||
if (!strncmp(dir->d_name, PREFIX_TTY_DEVICES, sizeof(PREFIX_TTY_DEVICES) - 1))
|
||||
{
|
||||
if (is_serial_device("%s%s", PATH_SERIAL_DEVICES, dir->d_name))
|
||||
{
|
||||
printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
}
|
||||
|
||||
static void list_serial_devices_by_path(void)
|
||||
{
|
||||
#ifdef PATH_SERIAL_DEVICES_BY_PATH
|
||||
|
||||
DIR *d = opendir(PATH_SERIAL_DEVICES_BY_PATH);
|
||||
if (d)
|
||||
{
|
||||
struct dirent *dir;
|
||||
|
||||
printf("\nBy-path\n");
|
||||
printf("--------------------------------------------------------------------------------\n");
|
||||
|
||||
while ((dir = readdir(d)) != NULL)
|
||||
{
|
||||
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
|
||||
{
|
||||
if (!strncmp(dir->d_name, "", sizeof("") - 1))
|
||||
{
|
||||
if (is_serial_device("%s%s", PATH_SERIAL_DEVICES_BY_PATH, dir->d_name))
|
||||
{
|
||||
printf("%s%s\n", PATH_SERIAL_DEVICES_BY_PATH, dir->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static gint compare_uptime(gconstpointer a, gconstpointer b)
|
||||
{
|
||||
device_t *device_a = (device_t *) a;
|
||||
device_t *device_b = (device_t *) b;
|
||||
|
||||
// Make sure we end up with device with smallest uptime last in list
|
||||
if (device_a->uptime > device_b->uptime)
|
||||
return -1;
|
||||
else if (device_a->uptime < device_b->uptime)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
// Function to get serial port type as a string
|
||||
const char* get_serial_port_type(const char* port_name)
|
||||
{
|
||||
int fd;
|
||||
static struct serial_struct serial_info;
|
||||
|
||||
// Open the serial port
|
||||
fd = open(port_name, O_RDWR);
|
||||
if (fd == -1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Get serial port information
|
||||
if (ioctl(fd, TIOCGSERIAL, &serial_info) == -1)
|
||||
{
|
||||
close(fd);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Close the serial port
|
||||
close(fd);
|
||||
|
||||
// Return the serial port type as a string
|
||||
switch (serial_info.type)
|
||||
{
|
||||
case PORT_UNKNOWN:
|
||||
return "Unknown";
|
||||
|
||||
case PORT_8250:
|
||||
return "8250 UART";
|
||||
|
||||
case PORT_16450:
|
||||
return "16450 UART";
|
||||
|
||||
case PORT_16550:
|
||||
return "16550 UART";
|
||||
|
||||
case PORT_16550A:
|
||||
return "16550A UART";
|
||||
|
||||
case PORT_16650:
|
||||
return "16650 UART";
|
||||
|
||||
case PORT_16650V2:
|
||||
return "16650V2 UART";
|
||||
|
||||
case PORT_16750:
|
||||
return "16750 UART";
|
||||
|
||||
case PORT_STARTECH:
|
||||
return "Startech UART";
|
||||
|
||||
case PORT_16850:
|
||||
return "16850 UART";
|
||||
|
||||
case PORT_16C950:
|
||||
return "16C950 UART";
|
||||
|
||||
case PORT_16654:
|
||||
return "16654 UART";
|
||||
|
||||
case PORT_RSA:
|
||||
return "RSA UART";
|
||||
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
const char* get_serial_port_type(const char* port_name)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void search_reset(void)
|
||||
{
|
||||
GList *iter;
|
||||
|
||||
if (g_list_length(device_list) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Free data of all list elements
|
||||
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
|
||||
{
|
||||
device_t *device = (device_t *) iter->data;
|
||||
g_free(device->tid);
|
||||
g_free(device->path);
|
||||
g_free(device->driver);
|
||||
g_free(device->description);
|
||||
}
|
||||
|
||||
// Free all list elements
|
||||
g_list_free_full(device_list, g_free);
|
||||
|
||||
// Indicate an empty list
|
||||
device_list = NULL;
|
||||
}
|
||||
|
||||
GList *tty_search_for_serial_devices(void)
|
||||
{
|
||||
DIR *dir;
|
||||
char path[PATH_MAX] = {};
|
||||
char device_path[PATH_MAX] = {};
|
||||
char driver_path[PATH_MAX] = {};
|
||||
double current_time, creation_time;
|
||||
ssize_t length;
|
||||
|
||||
search_reset();
|
||||
|
||||
// Open the sysfs directory for the tty subsystem
|
||||
dir = opendir("/sys/class/tty");
|
||||
if (!dir)
|
||||
{
|
||||
// Error
|
||||
return NULL;
|
||||
}
|
||||
|
||||
current_time = get_current_time();
|
||||
|
||||
// Iterate through each device in the subsystem directory
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL)
|
||||
{
|
||||
// Skip . and .. entries
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip non serial devices
|
||||
if (is_serial_device("/dev/%s", entry->d_name) == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct the path to the device's device symlink
|
||||
snprintf(path, sizeof(path), "/sys/class/tty/%s/device", entry->d_name);
|
||||
|
||||
// Read the device symlink to get the device path
|
||||
// Example symlinks:
|
||||
// /sys/class/tty/ttyUSB0/device -> ../../../ttyUSB0
|
||||
// /sys/class/tty/ttyACM0/device -> ../../../3-6.4:1.2
|
||||
length = readlink(path, device_path, sizeof(device_path) - 1);
|
||||
if (length == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Null-terminate the string
|
||||
device_path[length] = '\0';
|
||||
|
||||
// Extract last part of device path (string after last '/')
|
||||
// Example resulting device_name:
|
||||
// "ttyUSB0"
|
||||
// "3-6.4:1.2"
|
||||
char *device_name = strrchr(device_path, '/');
|
||||
device_name++; // Move past the '/'
|
||||
|
||||
// Find that part in /sys/devices and return first result string
|
||||
// Example devices_path:
|
||||
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.3/3-6.3:1.0/ttyUSB0"
|
||||
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.4/3-6.4:1.2"
|
||||
char *devices_path = fs_search_directory("/sys/devices", device_name);
|
||||
if (devices_path == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove last part if it contains device short name (e.g ttyUSB0)
|
||||
// Example resulting devices_path:
|
||||
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.3/3-6.3:1.0"
|
||||
// "/sys/devices/pci0000:00/0000:00:14.0/usb3/3-6/3-6.4/3-6.4:1.2"
|
||||
char *last_part = strrchr(devices_path, '/');
|
||||
last_part++;
|
||||
if (strcmp(last_part, entry->d_name) == 0)
|
||||
{
|
||||
// Remove last part (string after last '/')
|
||||
char *slash = strrchr(devices_path, '/');
|
||||
int index = (int) (slash - devices_path);
|
||||
devices_path[index] = '\0';
|
||||
}
|
||||
|
||||
// Hash remaining string to get unique topology ID
|
||||
unsigned long hash = djb2_hash((const unsigned char *)devices_path);
|
||||
char *tid = base62_encode(hash);
|
||||
free(devices_path);
|
||||
|
||||
// Construct the path to the device's driver symlink
|
||||
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/driver", entry->d_name);
|
||||
|
||||
// Read the symlink to get the driver's path
|
||||
length = readlink(path, driver_path, sizeof(driver_path) - 1);
|
||||
if (length == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Null-terminate the string
|
||||
driver_path[length] = '\0';
|
||||
|
||||
// Extract the driver name from the path
|
||||
char *driver = strrchr(driver_path, '/');
|
||||
if (driver == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
driver++; // Move past the last '/'
|
||||
|
||||
// Construct the path to the TTY device file
|
||||
snprintf(path, sizeof(path), "/dev/%s", entry->d_name);
|
||||
|
||||
// Calculate uptime
|
||||
creation_time = fs_get_creation_time(path);
|
||||
double uptime = current_time - creation_time;
|
||||
|
||||
// Read sysfs files to get best possible description of the driver
|
||||
char description[50] = {};
|
||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/interface", entry->d_name);
|
||||
if (length == -1)
|
||||
{
|
||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../interface", entry->d_name);
|
||||
}
|
||||
if (length == -1)
|
||||
{
|
||||
length = fs_read_file_stripped(description, sizeof(description), "/sys/class/tty/%s/device/../../product", entry->d_name);
|
||||
}
|
||||
if (length == -1)
|
||||
{
|
||||
snprintf(description, sizeof(description), "%s", get_serial_port_type(path));
|
||||
}
|
||||
|
||||
// Do not add devices excluded by exclude patterns
|
||||
if (match_any_pattern(path, option.exclude_devices))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (match_any_pattern(driver, option.exclude_drivers))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (match_any_pattern(tid, option.exclude_tids))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allocate new device item for device list
|
||||
device_t *device = g_new0(device_t, 1);
|
||||
if (device == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fill in device information
|
||||
device->path = g_strdup(path);
|
||||
device->tid = g_strdup(tid);
|
||||
device->uptime = uptime;
|
||||
device->driver = g_strdup(driver);
|
||||
device->description = g_strdup(description);
|
||||
|
||||
// Add device information to device list
|
||||
device_list = g_list_append(device_list, device);
|
||||
}
|
||||
|
||||
if (g_list_length(device_list) == 0)
|
||||
{
|
||||
// Return NULL if no serial devices found
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Sort device list device with respect to uptime
|
||||
device_list = g_list_sort(device_list, compare_uptime);
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return device_list;
|
||||
}
|
||||
|
||||
void list_serial_devices(void)
|
||||
{
|
||||
tty_search_for_serial_devices();
|
||||
|
||||
if (g_list_length(device_list) > 0)
|
||||
{
|
||||
printf("Device TID Uptime [s] Driver Description\n");
|
||||
printf("----------------- ---- ------------- ---------------- --------------------------\n");
|
||||
|
||||
// Iterate through the device list
|
||||
GList *iter;
|
||||
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
|
||||
{
|
||||
device_t *device = (device_t *) iter->data;
|
||||
|
||||
// Print device information
|
||||
printf("%-17s %4s %13.3f %-16s %s\n", device->path, device->tid, device->uptime, device->driver, device->description);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
list_serial_devices_by_id();
|
||||
list_serial_devices_by_path();
|
||||
}
|
||||
|
||||
void tty_search(void)
|
||||
{
|
||||
GList *iter;
|
||||
device_t *device = NULL;
|
||||
double uptime_minimum = 0;
|
||||
bool no_new = true;
|
||||
|
||||
switch (option.auto_connect)
|
||||
{
|
||||
case AUTO_CONNECT_NEW:
|
||||
tty_search_for_serial_devices();
|
||||
|
||||
// Save smallest uptime
|
||||
if (g_list_length(device_list) > 0)
|
||||
{
|
||||
// Get latest registered device (smallest uptime)
|
||||
GList *last = g_list_last(device_list);
|
||||
device = last->data;
|
||||
uptime_minimum = device->uptime;
|
||||
}
|
||||
|
||||
tio_printf("Waiting for tty device..");
|
||||
|
||||
while (no_new)
|
||||
{
|
||||
tty_search_for_serial_devices();
|
||||
|
||||
// Iterate through the device list generated by search
|
||||
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
|
||||
{
|
||||
device = (device_t *) iter->data;
|
||||
|
||||
// Find first new device
|
||||
if (device->uptime < uptime_minimum)
|
||||
{
|
||||
// Match found -> update device
|
||||
device_name = device->path;
|
||||
no_new = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (no_new)
|
||||
{
|
||||
usleep(500*1000); // Sleep 0.5 s
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case AUTO_CONNECT_LATEST:
|
||||
tty_search_for_serial_devices();
|
||||
if (g_list_length(device_list) > 0)
|
||||
{
|
||||
// Get latest registered device (smallest uptime)
|
||||
GList *last = g_list_last(device_list);
|
||||
device = last->data;
|
||||
device_name = device->path;
|
||||
}
|
||||
return;
|
||||
|
||||
case AUTO_CONNECT_DIRECT:
|
||||
if (strlen(option.target) == TOPOLOGY_ID_SIZE)
|
||||
{
|
||||
// Potential topology ID detected -> trigger device search
|
||||
tty_search_for_serial_devices();
|
||||
|
||||
// Iterate through the device list generated by search
|
||||
for (iter = device_list; iter != NULL; iter = g_list_next(iter))
|
||||
{
|
||||
device = (device_t *) iter->data;
|
||||
|
||||
if (strcmp(device->tid, option.target) == 0)
|
||||
{
|
||||
// Topology ID match found -> use corresponding device name
|
||||
device_name = device->path;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to using tty device provided via cmdline target
|
||||
device_name = option.target;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Should never be reached
|
||||
tio_printf("Unknown connection strategy");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
void tty_wait_for_device(void)
|
||||
{
|
||||
fd_set rdfs;
|
||||
|
|
@ -1349,6 +1891,8 @@ void tty_wait_for_device(void)
|
|||
/* Loop until device pops up */
|
||||
while (true)
|
||||
{
|
||||
tty_search();
|
||||
|
||||
if (interactive_mode)
|
||||
{
|
||||
/* In interactive mode, while waiting for tty device, we need to
|
||||
|
|
@ -1400,7 +1944,7 @@ void tty_wait_for_device(void)
|
|||
}
|
||||
|
||||
/* Test for accessible device file */
|
||||
status = access(option.tty_device, R_OK);
|
||||
status = access(device_name, R_OK);
|
||||
if (status == 0)
|
||||
{
|
||||
last_errno = 0;
|
||||
|
|
@ -1549,7 +2093,7 @@ int tty_connect(void)
|
|||
struct timeval tval_before = {}, tval_now, tval_result;
|
||||
|
||||
/* Open tty device */
|
||||
device_fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
device_fd = open(device_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
if (device_fd < 0)
|
||||
{
|
||||
tio_error_printf_silent("Could not open tty device (%s)", strerror(errno));
|
||||
|
|
@ -1575,7 +2119,7 @@ int tty_connect(void)
|
|||
tcflush(device_fd, TCIOFLUSH);
|
||||
|
||||
/* Print connect status */
|
||||
tio_printf("Connected");
|
||||
tio_printf("Connected to %s", device_name);
|
||||
connected = true;
|
||||
print_tainted = false;
|
||||
|
||||
|
|
@ -2019,22 +2563,3 @@ error_open:
|
|||
return TIO_ERROR;
|
||||
}
|
||||
|
||||
void list_serial_devices(void)
|
||||
{
|
||||
DIR *d = opendir(PATH_SERIAL_DEVICES);
|
||||
if (d)
|
||||
{
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL)
|
||||
{
|
||||
if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
|
||||
{
|
||||
if (!strncmp(dir->d_name, PREFIX_TTY_DEVICES, sizeof(PREFIX_TTY_DEVICES) - 1))
|
||||
{
|
||||
printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
src/tty.h
23
src/tty.h
|
|
@ -22,10 +22,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <glib.h>
|
||||
|
||||
#define LINE_HIGH true
|
||||
#define LINE_LOW false
|
||||
|
||||
#define TOPOLOGY_ID_SIZE 4
|
||||
|
||||
typedef enum
|
||||
{
|
||||
AUTO_CONNECT_DIRECT,
|
||||
AUTO_CONNECT_NEW,
|
||||
AUTO_CONNECT_LATEST,
|
||||
AUTO_CONNECT_END,
|
||||
} auto_connect_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *tid;
|
||||
double uptime;
|
||||
char *path;
|
||||
char *driver;
|
||||
char *description;
|
||||
} device_t;
|
||||
|
||||
extern const char *device_name;
|
||||
extern bool interactive_mode;
|
||||
extern bool map_i_nl_cr;
|
||||
extern bool map_i_cr_nl;
|
||||
|
|
@ -43,3 +64,5 @@ void tty_line_set(int fd, int mask, bool value);
|
|||
void tty_line_toggle(int fd, int mask);
|
||||
void tty_line_config(int mask, bool value);
|
||||
void tty_line_config_apply(void);
|
||||
void tty_search(void);
|
||||
GList *tty_search_for_serial_devices(void);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue