Compare commits

..

No commits in common. "master" and "v1.9" have entirely different histories.
master ... v1.9

88 changed files with 1841 additions and 14240 deletions

View file

@ -1,5 +0,0 @@
BasedOnStyle: llvm
IndentWidth: 4
AllowShortFunctionsOnASingleLine: None
KeepEmptyLinesAtTheStartOfBlocks: false
BreakBeforeBraces: Allman

1
.github/FUNDING.yml vendored
View file

@ -1 +0,0 @@
custom: ["https://www.paypal.me/lundmar"]

View file

@ -1,7 +0,0 @@
#!/usr/bin/env bash
pip3 install meson -U
pip3 install ninja -U
sudo apt-get install -y liblua5.2-dev libglib2.0-dev
meson setup build
meson compile -C build

View file

@ -1,126 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main", "master" ]
schedule:
- cron: '0 0 * * *'
pull_request:
branches: '*'
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-22.04' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
queries: security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
#- name: Autobuild
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- run: |
./.github/workflows/codeql-buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
upload: false
id: step1
# Filter out rules with low severity or high false positve rate
# Also filter out warnings in third-party code
- name: Filter out unwanted errors and warnings
uses: advanced-security/filter-sarif@v1
with:
patterns: |
-**:cpp/path-injection
-**:cpp/world-writable-file-creation
-**:cpp/poorly-documented-function
-**:cpp/potentially-dangerous-function
-**:cpp/use-of-goto
-**:cpp/integer-multiplication-cast-to-long
-**:cpp/comparison-with-wider-type
-**:cpp/leap-year/*
-**:cpp/ambiguously-signed-bit-field
-**:cpp/suspicious-pointer-scaling
-**:cpp/suspicious-pointer-scaling-void
-**:cpp/unsigned-comparison-zero
-**/cmake*/Modules/**
input: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
output: ${{ steps.step1.outputs.sarif-output }}/cpp.sarif
- name: Upload CodeQL results to code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.step1.outputs.sarif-output }}
category: "/language:${{matrix.language}}"
- name: Upload CodeQL results as an artifact
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: codeql-results
path: ${{ steps.step1.outputs.sarif-output }}
retention-days: 5
- name: Fail if an error is found
run: |
./.github/workflows/fail_on_error.py \
${{ steps.step1.outputs.sarif-output }}/cpp.sarif

View file

@ -1,34 +0,0 @@
#!/usr/bin/env python3
import json
import sys
# Return whether SARIF file contains error-level results
def codeql_sarif_contain_error(filename):
with open(filename, 'r') as f:
s = json.load(f)
for run in s.get('runs', []):
rules_metadata = run['tool']['driver']['rules']
if not rules_metadata:
rules_metadata = run['tool']['extensions'][0]['rules']
for res in run.get('results', []):
if 'ruleIndex' in res:
rule_index = res['ruleIndex']
elif 'rule' in res and 'index' in res['rule']:
rule_index = res['rule']['index']
else:
continue
try:
rule_level = rules_metadata[rule_index]['defaultConfiguration']['level']
except IndexError as e:
print(e, rule_index, len(rules_metadata))
else:
if rule_level == 'error':
return True
return False
if __name__ == "__main__":
if codeql_sarif_contain_error(sys.argv[1]):
sys.exit(1)

View file

@ -1,30 +0,0 @@
name: MacOS build
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
brew install meson ninja lua
- name: Build
run: |
meson setup build
meson compile -C build --verbose
meson install -C build

View file

@ -1,31 +0,0 @@
name: Ubuntu build
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y bash-completion git meson liblua5.2-dev libglib2.0-dev
- name: Build
run: |
meson setup build --prefix $HOME/opt/tio
meson compile -C build --verbose
meson install -C build

25
.gitignore vendored
View file

@ -1,4 +1,23 @@
/build *.tar.xz
/subprojects/libinih src/*.o
src/tio
Makefile.in
Makefile
aclocal.m4
autom4te.cache/
configure
compile
depcomp
install-sh
missing
src/Makefile.in
src/Makefile
config.log
config.status
src/.deps/
src/include/config.h
src/include/config.h.in
src/include/config.h.in~
src/include/stamp-h1
*.swp *.swp
.cache /tio-*

View file

@ -1,2 +0,0 @@
[default]
extend-ignore-words-re = ["tio"]

60
AUTHORS
View file

@ -1,70 +1,10 @@
Maintainer: Maintainer:
Martin Lund <martin.lund@keep-it-simple.com> Martin Lund <martin.lund@keep-it-simple.com>
Co-maintainer:
Sylvain LAFRASSE <slymacdev@free.fr>
Contributors: Contributors:
Jesper Larsen <knorr.jesper@gmail.com> Jesper Larsen <knorr.jesper@gmail.com>
Jeppe Ledet-Pedersen <jlp@gomspace.com> Jeppe Ledet-Pedersen <jlp@gomspace.com>
Jakob Haufe <sur5r@sur5r.net> Jakob Haufe <sur5r@sur5r.net>
Jakub Wilk <jwilk@jwilk.net> Jakub Wilk <jwilk@jwilk.net>
Martin Hundeboll <martin@hundeboll.net>
Nick Østergaard <oe.nick@gmail.com>
Adam Borowski <kilobyte@angband.pl>
Robert Scheck <robert@fedoraproject.org>
Dmitri Goutnik <dg@syrec.org>
Daniel Engberg <daniel.engberg.lists@pyret.net>
Petr Vaněk <pv@excello.cz>
qianfan Zhao <qianfanguijin@163.com>
Henner Zeller <h.zeller@acm.org>
Robey Pointer <robey@afero.io>
Lars Kellogg-Stedman <lars@oddbit.com>
arichi <sergey.korabanov@gmail.com>
George Stark <george-u@yandex.com>
Erik Moqvist <erik.moqvist@gmail.com>
Fabrice Fontaine <fontaine.fabrice@gmail.com>
Alban Bedel <alban.bedel@aerq.com>
Björn Stenberg <bjorn@haxx.se>
Henner Zeller <h.zeller@acm.org>
Henrik Brix Andersen <henrik@brixandersen.dk>
Mariusz Midor <dexlab@o2.pl>
attila-v <attila_v@index.hu>
Yin Fengwei <fengwei.yin@intel.com>
Liam Beguin <liambeguin@gmail.com>
Peter Collingbourne <pcc@google.com>
g0mb4 <gomba007@gmail.com>
ZeroMemoryEx on GitHub
George Joseph <g.devel@wxy78.net>
Robert Snell <rcsnell@ncf.ca>
Rui Chen <rui@chenrui.dev>
Ralph Siemsen <ralphs@netwinder.org>
Victor Oliveira <rhapsodyv@gmail.com>
Attila Veghelyi <aveghelyi@dension.com>
Vyacheslav Patkov <slava@patkov.ru>
Bill Hass <billhass@umich.edu>
Peter van Dijk <peter@7bits.nl>
Braden Young <braden@somewearlabs.com>
Wes Koerber <wkoerber@acsd4u.com>
HiFiPhile <admin@hifiphile.com>
Paul Ruizendaal <pnr@planet.nl>
Fredrik Svedberg <fredrik@svedberg.us>
Sebastian <sebastian.krahmer@gmail.com>
Mingjie Shen <shen497@purdue.edu>
Brian <bayuan@purdue.edu>
Davis C <davisclaib@gmail.com>
KhazAkar <damianzrb@zohomail.eu>
Eliot Alan Foss <eliotfoss@gmail.com>
Robert Lipe <robertlipe@gpsbabel.org>
Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
Tomka Gergely <tomkatudor@gmail.com>
Steve Marple <stevemarple@googlemail.com>
konosubakonoakua <ailike_meow@qq.com>
Keith Hill <k_hill@unitronlp.com>
Lubov66 <radolevanja@gmail.com>
V <v@anomalous.eu>
Samuel Holland <samuel@sholland.org>
David Ordnung <david.ordnung@googlemail.com>
Thanks to everyone who has contributed to this project. Thanks to everyone who has contributed to this project.

339
COPYING Normal file
View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

124
ChangeLog Normal file
View file

@ -0,0 +1,124 @@
=== tio v1.9 ===
Changes since tio v1.8:
* Cleanup of error handling
Introduced consistent way of handling errors and printing error messages.
Also upgraded some warnings to errors.
* Updated localtime() error message
* Cleanup
Jakub Wilk:
* Fix error handling for select()
Previously the error handling code for select() was unreachable.
* Removed unneeded quotes from AM_CFLAGS
* Expanded tabs
* Fixed setting "tainted"
Set "tainted" if and only if any character was read from the device.
Ctrl-t is no longer sent to the device on exit, so the trick to avoid
its echo is not necessary.
Characters read from stdin don't directly affect output, so they
shouldn't enable "tainted".
* Used \r in color_printf()
\033[300D is an unusual way to move the cursor back to column 1.
Use straightforward \r instead.
* Added missing \r\n to warning messages
\n alone is not enough, because the terminal is in raw mode.
Changes since tio v1.7:
* Fixed enablement of compiler warnings
* Fixed log_open() prototype
* Fixed index error wrt ctrl-t detection
* Fixed handling of ctrl-t
Before, when exercising the quit key sequence (ctrl-t + q) the ctrl-t code
(0x14) would be sent.
This is now fixed so that it is not sent.
However, in case it is needed to send ctrl-t to the device it is possible by
simply repeating the ctrl-t.
Meaning, ctrl-t + ctrl-t = ctrl-t sent to device.
* Improved error handling
Fixes a memory leak and avoids aggressive busy looping when problems
accessing tty device.
* Removed redundant log_close() call
* Enabled compiler warnings
Jakub Wilk:
* Stopped copying arguments to fixed-size buffers
Don't needlessly copy command-line arguments into fixed-size buffers.
Previously the program crashed if an overlong pathname was provided on
the command line. Also, some systems (such as GNU Hurd) don't define
MAXPATHLEN at all.
* Added const to log_open() prototype
* Completed the ^g to ^t transition
In 72a287f18995 the escape key was changed from ^g to ^t, but some
code and comments still referred to the old key.
* Used HTTPS for tio.github.io
* Man page beautification
* Bumped date in man page
* Improve man page formatting
Use regular font for metacharacters such as "[]", "," or "|";
use italic font for metavariables.
* Fixed hyphen vs minus vs em-dash confusion in man page
- prints as hyphen;
\- prints as minus sign;
\em prints as em-dash.
Changes since tio v1.6:
* Changed escape key from ^g to ^t
After renaming to "tio" it makes sense to change the escape key
accordingly. Hence, the new escape key is ^t.
Meaning, in session, its now ctrl-t + q to quit.
Jakub Wilk:
* Fixed silly "tio or tio" in man page
* Fixed typo

370
INSTALL Normal file
View file

@ -0,0 +1,370 @@
Installation Instructions
*************************
Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation,
Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
Basic Installation
==================
Briefly, the shell command `./configure && make && make install'
should configure, build, and install this package. The following
more-detailed instructions are generic; see the `README' file for
instructions specific to this package. Some packages provide this
`INSTALL' file but do not implement all of the features documented
below. The lack of an optional feature in a given package is not
necessarily a bug. More recommendations for GNU packages can be found
in *note Makefile Conventions: (standards)Makefile Conventions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, and a
file `config.log' containing compiler output (useful mainly for
debugging `configure').
It can also use an optional file (typically called `config.cache'
and enabled with `--cache-file=config.cache' or simply `-C') that saves
the results of its tests to speed up reconfiguring. Caching is
disabled by default to prevent problems with accidental use of stale
cache files.
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If you are using the cache, and at
some point `config.cache' contains results you don't want to keep, you
may remove or edit it.
The file `configure.ac' (or `configure.in') is used to create
`configure' by a program called `autoconf'. You need `configure.ac' if
you want to change it or regenerate `configure' using a newer version
of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system.
Running `configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.
4. Type `make install' to install the programs and any data files and
documentation. When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the `make install' phase executed with root
privileges.
5. Optionally, type `make installcheck' to repeat any self-tests, but
this time using the binaries in their final installed location.
This target does not install anything. Running this target as a
regular user, particularly if the prior `make install' required
root privileges, verifies that the installation completed
correctly.
6. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
7. Often, you can also type `make uninstall' to remove the installed
files again. In practice, not all packages have tested that
uninstallation works correctly, even though it is required by the
GNU Coding Standards.
8. Some packages, particularly those that use Automake, provide `make
distcheck', which can by used by developers to test that all other
targets like `make install' and `make uninstall' work correctly.
This target is generally not run by end users.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. Run `./configure --help'
for details on some of the pertinent environment variables.
You can give `configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here
is an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
*Note Defining Variables::, for more details.
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you can use GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'. This
is known as a "VPATH" build.
With a non-GNU `make', it is safer to compile the package for one
architecture at a time in the source code directory. After you have
installed the package for one architecture, use `make distclean' before
reconfiguring for another architecture.
On MacOS X 10.5 and later systems, you can create libraries and
executables that work on multiple system types--known as "fat" or
"universal" binaries--by specifying multiple `-arch' options to the
compiler but only a single `-arch' option to the preprocessor. Like
this:
./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CPP="gcc -E" CXXCPP="g++ -E"
This is not guaranteed to produce working output in all cases, you
may have to build one architecture at a time and combine the results
using the `lipo' tool if you have problems.
Installation Names
==================
By default, `make install' installs the package's commands under
`/usr/local/bin', include files under `/usr/local/include', etc. You
can specify an installation prefix other than `/usr/local' by giving
`configure' the option `--prefix=PREFIX', where PREFIX must be an
absolute file name.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=DIR' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them. In general, the
default for these options is expressed in terms of `${prefix}', so that
specifying just `--prefix' will affect all of the other directory
specifications that were not explicitly provided.
The most portable way to affect installation locations is to pass the
correct locations to `configure'; however, many packages provide one or
both of the following shortcuts of passing variable assignments to the
`make install' command line to change installation locations without
having to reconfigure or recompile.
The first method involves providing an override variable for each
affected directory. For example, `make install
prefix=/alternate/directory' will choose an alternate location for all
directory configuration variables that were expressed in terms of
`${prefix}'. Any directories that were specified during `configure',
but not in terms of `${prefix}', must each be overridden at install
time for the entire installation to be relocated. The approach of
makefile variable overrides for each directory variable is required by
the GNU Coding Standards, and ideally causes no recompilation.
However, some platforms have known limitations with the semantics of
shared libraries that end up requiring recompilation when using this
method, particularly noticeable in packages that use GNU Libtool.
The second method involves providing the `DESTDIR' variable. For
example, `make install DESTDIR=/alternate/directory' will prepend
`/alternate/directory' before all installation names. The approach of
`DESTDIR' overrides is not required by the GNU Coding Standards, and
does not work on platforms that have drive letters. On the other hand,
it does better at avoiding recompilation issues, and works well even
when some directory options were not specified in terms of `${prefix}'
at `configure' time.
Optional Features
=================
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Some packages offer the ability to configure how verbose the
execution of `make' will be. For these packages, running `./configure
--enable-silent-rules' sets the default to minimal output, which can be
overridden with `make V=1'; while running `./configure
--disable-silent-rules' sets the default to verbose, which can be
overridden with `make V=0'.
Particular systems
==================
On HP-UX, the default C compiler is not ANSI C compatible. If GNU
CC is not installed, it is recommended to use the following options in
order to use an ANSI C compiler:
./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
and if that doesn't work, install pre-built binaries of GCC for HP-UX.
HP-UX `make' updates targets which have the same time stamps as
their prerequisites, which makes it generally unusable when shipped
generated files such as `configure' are involved. Use GNU `make'
instead.
On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
parse its `<wchar.h>' header file. The option `-nodtk' can be used as
a workaround. If GNU CC is not installed, it is therefore recommended
to try
./configure CC="cc"
and if that doesn't work, try
./configure CC="cc -nodtk"
On Solaris, don't put `/usr/ucb' early in your `PATH'. This
directory contains several dysfunctional programs; working variants of
these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
in your `PATH', put it _after_ `/usr/bin'.
On Haiku, software installed for all users goes in `/boot/common',
not `/usr/local'. It is recommended to use the following options:
./configure --prefix=/boot/common
Specifying the System Type
==========================
There may be some features `configure' cannot figure out
automatically, but needs to determine by the type of machine the package
will run on. Usually, assuming the package is built to be run on the
_same_ architectures, `configure' can figure that out, but if it prints
a message saying it cannot guess the machine type, give it the
`--build=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS
KERNEL-OS
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option `--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with `--host=TYPE'.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to `configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the `configure' command line, using `VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified `gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for `CONFIG_SHELL' due to
an Autoconf limitation. Until the limitation is lifted, you can use
this workaround:
CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
`configure' Invocation
======================
`configure' recognizes the following options to control how it
operates.
`--help'
`-h'
Print a summary of all of the options to `configure', and exit.
`--help=short'
`--help=recursive'
Print a summary of the options unique to this package's
`configure', and exit. The `short' variant lists options used
only in the top level, while the `recursive' variant lists options
also present in any nested packages.
`--version'
`-V'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally `config.cache'. FILE defaults to `/dev/null' to
disable caching.
`--config-cache'
`-C'
Alias for `--cache-file=config.cache'.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--prefix=DIR'
Use DIR as the installation prefix. *note Installation Names::
for more details, including other options available for fine-tuning
the installation locations.
`--no-create'
`-n'
Run the configure checks, but stop before creating any output
files.
`configure' also accepts some other, not widely useful, options. Run
`configure --help' for more details.

View file

@ -1,4 +1,4 @@
Copyright (c) 2014-2025 Martin Lund <martin.lund@keep-it-simple.com> Copyright (c) 2014-2016 Martin Lund <martin.lund@keep-it-simple.com>
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License

1
Makefile.am Normal file
View file

@ -0,0 +1 @@
SUBDIRS = src man

2510
NEWS

File diff suppressed because it is too large Load diff

89
README Normal file
View file

@ -0,0 +1,89 @@
=== tio - The simple TTY terminal I/O application ===
1. Introduction
"tio" is a simple TTY terminal application which features a straightforward
commandline interface to easily connect to TTY devices for basic
input/output.
It was created because the author needed a simple no-nonsense TTY
terminal application to easily connect to various terminal TTY devices.
2. Usage
The commandline interface is straightforward as reflected in the output
from 'tio --help':
Usage: tio [<options>] <tty device>
Options:
-b, --baudrate <bps> Baud rate (default: 115200)
-d, --databits 5|6|7|8 Data bits (default: 8)
-f, --flow hard|soft|none Flow control (default: none)
-s, --stopbits 1|2 Stop bits (default: 1)
-p, --parity even|odd|none Parity (default: none)
-o, --output-delay <ms> Output delay (default: 0)
-n, --no-autoconnect Disable automatic connect
-l, --log <filename> Log to file
-v, --version Display version
-h, --help Display help
In session, press ctrl-t + q to quit.
The only option which requires a bit of elaboration is perhaps the
--no-autoconnect option.
By default tio automatically connects to the provided device if present.
If the device is not present it will wait for it to appear and then
connect. If the connection is lost (eg. device disconnects) it will wait
for the device to reappear and then reconnect.
However, if the --no-autoconnect option is provided tio will exit if the
device is not present or exit if an established connection is lost.
Tio features full bash autocompletion support.
3. Download
Find the latest release tarball at https://tio.github.io
The latest source is available on github: https://github.com/tio/tio
4. Installation
Install steps:
$ ./configure
$ make
$ make install
See INSTALL file for installation details.
5. Contributing
Tio is open source. Any contributions (bug fixes, doc, ideas, etc.) are
welcome.
6. Support
Submit bug reports on github: https://github.com/tio/tio/issues
7. License
Tio is GPLv2+. See COPYING file for license details.
8. Authors
Created by Martin Lund <martin.lund@keep-it-simple.com>
See the AUTHORS file for full list of authors.

574
README.md
View file

@ -1,574 +0,0 @@
[![tio](images/tio-icon.png)]()
# tio - a serial device I/O tool
[![](https://img.shields.io/github/actions/workflow/status/tio/tio/ubuntu.yml?label=Ubuntu)](https://github.com/tio/tio/actions/workflows/ubuntu.yml)
[![](https://img.shields.io/github/actions/workflow/status/tio/tio/macos.yml?label=MacOS)](https://github.com/tio/tio/actions/workflows/macos.yml)
[![](https://github.com/tio/tio/actions/workflows/codeql.yml/badge.svg)](https://github.com/tio/tio/actions/workflows/codeql.yml)
[![](https://img.shields.io/codefactor/grade/github/tio/tio)](https://www.codefactor.io/repository/github/tio/tio)
[![](https://img.shields.io/github/v/release/tio/tio?sort=semver)](https://github.com/tio/tio/releases)
[![](https://img.shields.io/repology/repositories/tio)](https://repology.org/project/tio/versions)
## 1. Introduction
tio is a serial device tool which features a straightforward command-line and
configuration file interface to easily connect to serial TTY devices for basic
I/O operations.
<p align="center">
<img src="images/tio-demo.gif">
</p>
### 1.1 Motivation
To make a simpler serial device tool for working with serial TTY devices with
less focus on classic terminal/modem features and more focus on the needs of
embedded developers and hackers.
tio was originally created as an alternative to
[screen](https://www.gnu.org/software/screen) for connecting to serial devices
when used in combination with [tmux](https://tmux.github.io).
## 2. Features
* Easily connect to serial TTY devices
* Sensible defaults (115200 8n1)
* Automatic connection management
* Automatic detection of serial ports
* Automatic reconnect
* Automatically connect to first new appearing serial device
* Automatically connect to latest registered serial device
* Connect to same port/device combination via unique topology ID (TID)
* Useful for reconnecting when serial device has no serial device by ID
* Support for non-standard baud rates
* Support for mark and space parity
* X-modem (1K/CRC) and Y-modem file upload
* Support for RS-485 mode
* List available serial devices
* By device
* Including topology ID, uptime, driver, description
* Sorted by uptime (newest device listed last)
* By ID
* By path
* Show RX/TX statistics
* Toggle serial lines
* Pulse serial lines with configurable pulse duration
* Local echo support
* Remapping of characters (nl, cr-nl, bs, lowercase to uppercase, etc.)
* Switchable independent input and output
* Normal mode
* Hex mode (output supports variable width)
* Line mode (input only)
* Timestamp support
* Per line in normal output mode
* Output timeout timestamps in hex output mode
* Support for delayed output
* Per character
* Per line
* Log to file
* Automatic naming of log file (default)
* Configurable directory for saving automatic named log files
* Manual naming of log file
* Overwrite (default) or append to log file
* Strip control characters and escape sequences
* Configuration file support
* Support for configuration profiles
* Activate configuration profiles by name or pattern
* Support for including other configuration files
* Redirect I/O of shell command to serial device
* Redirect I/O to UNIX socket or IPv4/v6 network socket
* Useful for scripting or TTY sharing
* Pipe input and/or output
* Bash completion on options, serial device names, and profile names
* Configurable tio message text color
* Supports NO_COLOR env variable as per [no-color.org](https://no-color.org)
* Visual or audible alert on connect/disconnect
* Remapping of prefix key
* Lua scripting support for automation
* Run script manually or automatically at connect (once/always/never)
* Simple expect/send like functionality with support for regular expressions
* Manipulate port modem lines (useful for microcontroller reset/boot etc.)
* Send files via x/y-modem protocol
* Search for serial devices
* Man page documentation
* Plays nicely with [tmux](https://tmux.github.io) and similar terminal multiplexers
## 3. Usage
For more usage details please see the man page documentation
[here](https://raw.githubusercontent.com/tio/tio/master/man/tio.1.txt).
### 3.1 Command-line
The command-line interface is straightforward as reflected in the output from
```tio --help```:
```
Usage: tio [<options>] <tty-device|profile|tid>
Connect to TTY device directly or via configuration profile or topology ID.
Options:
-b, --baudrate <bps> Baud rate (default: 115200)
-d, --databits 5|6|7|8 Data bits (default: 8)
-f, --flow hard|soft|none Flow control (default: none)
-s, --stopbits 1|2 Stop bits (default: 1)
-p, --parity odd|even|none|mark|space Parity (default: none)
-o, --output-delay <ms> Output character delay (default: 0)
-O, --output-line-delay <ms> Output line delay (default: 0)
--line-pulse-duration <duration> Set line pulse duration
-a, --auto-connect new|latest|direct Automatic connect strategy (default: direct)
--exclude-devices <pattern> Exclude devices by pattern
--exclude-drivers <pattern> Exclude drivers by pattern
--exclude-tids <pattern> Exclude topology IDs by pattern
-n, --no-reconnect Do not reconnect
-e, --local-echo Enable local echo
--input-mode normal|hex|line Select input mode (default: normal)
--output-mode normal|hex|hexN Select output mode (default: normal)
-t, --timestamp Enable line timestamp
--timestamp-format <format> Set timestamp format (default: 24hour)
--timestamp-timeout <ms> Set timestamp timeout (default: 200)
-l, --list List available serial devices, TIDs, and profiles
-L, --log Enable log to file
--log-file <filename> Set log filename
--log-directory <path> Set log directory path for automatic named logs
--log-append Append to log file
--log-strip Strip control characters and escape sequences
-m, --map <flags> Map characters
-c, --color 0..255|bold|none|list Colorize tio text (default: bold)
-S, --socket <socket> Redirect I/O to socket
--rs-485 Enable RS-485 mode
--rs-485-config <config> Set RS-485 configuration
--alert bell|blink|none Alert on connect/disconnect (default: none)
--mute Mute tio messages
--script <string> Run script from string
--script-file <filename> Run script from file
--script-run once|always|never Run script on connect (default: always)
--exec <command> Execute shell command with I/O redirected to device
-v, --version Display version
-h, --help Display help
Options and profiles may be set via configuration file.
In session you can press ctrl-t ? to list available key commands.
See the man page for more details.
```
By default tio automatically connects to the provided TTY device. If the device
is not present, tio will wait for it to appear and then connect. If the
connection is lost (e.g. device is unplugged), it will wait for the device to
reappear and then reconnect. However, if the `--no-reconnect` option is
provided, tio will exit if the device is not present or an established
connection is lost.
#### 3.1.1 Examples
Typical use is without options:
```
$ tio /dev/ttyUSB0
```
Which corresponds to the commonly used default options:
```
$ tio --baudrate 115200 --databits 8 --flow none --stopbits 1 --parity none /dev/ttyUSB0
```
List available serial devices:
```
$ tio --list
Device TID Uptime [s] Driver Description
----------------- ---- ------------- ---------------- --------------------------
/dev/ttyS4 BaaB 19526.576 port 16550A UART
/dev/ttyS5 eV0Z 19525.845 port 16550A UART
/dev/ttyUSB1 bCC2 1023.274 ftdi_sio TTL232R-3V3
/dev/ttyUSB0 SPpw 978.527 ftdi_sio TTL232RG-VREG3V3
/dev/ttyACM0 i5q4 2.079 cdc_acm ST-Link VCP Ctrl
By-id
--------------------------------------------------------------------------------
/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0
/dev/serial/by-id/usb-FTDI_TTL232RG-VREG3V3_FT1NELUB-if00-port0
/dev/serial/by-id/usb-STMicroelectronics_STLINK-V3_004900343438510234313939-if02
By-path
--------------------------------------------------------------------------------
/dev/serial/by-path/pci-0000:00:14.0-usb-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:8.1.3.1.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.4:1.0-port0
/dev/serial/by-path/pci-0000:00:14.0-usbv2-0:6.3:1.2
/dev/serial/by-path/pci-0000:00:14.0-usb-0:6.3:1.2
Configuration profiles (/home/lundmar/.config/tio/config)
--------------------------------------------------------------------------------
rpi3 stm32 esp32 am64-evm
imx8mp-evk nucleo-h743zi2 usb-devices
```
It is recommended to connect serial TTY devices by ID:
```
$ tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTCHUV56-if00-port0
```
Note: Using serial devices by ID helps ensure that tio automatically reconnects
to the same serial device when reattached, even when it enumerates differently.
If no serial device by ID is available it is recommended to connect via
topology ID (TID):
```
$ tio bCC2
```
Note: The TID is unique and will stay the same as long as your USB serial port
device plugs into the same USB topology (same ports, same hubs, same
connections, etc.). This way it is possible for tio to successfully reconnect
to the same device.
Connect automatically to first new appearing serial device:
```
$ tio --auto-connect new
```
Connect automatically to latest registered serial device:
```
$ tio --auto-connect latest
```
It is possible to use exclude options to affect which serial devices are
involved in the automatic connection strategy:
```
$ tio --auto-connect new --exclude-devices "/dev/ttyACM?,/dev/ttyUSB2"
```
And to exclude drivers by pattern:
```
$ tio --auto-connect new --exclude-drivers "cdc_acm,ftdi_sio"
```
Note: Pattern matching supports '*' and '?'. Use comma separation to define
multiple patterns.
To include drivers by specific pattern simply negate the exclude option:
```
$ tio --auto-connect new --exclude-drivers !("cp2102")
```
Log to file with autogenerated filename:
```
$ tio --log /dev/ttyUSB0
```
Log to file with specific filename:
```
$ tio --log --log-file my-log.txt
```
Enable ISO8601 timestamps per line:
```
$ tio --timestamp --timestamp-format iso8601 /dev/ttyUSB0
```
Output to hex with width 16:
```
$ tio --output-mode hex16 /dev/ttyUSB0
```
Redirect I/O to IPv4 network socket on port 4242:
```
$ tio --socket inet:4242 /dev/ttyUSB0
```
Map NL to CR-NL on input from device and DEL to BS on output to device:
```
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
```
Pipe data to the serial device:
```
$ cat data.bin | tio /dev/ttyUSB0
```
Manipulate modem lines on connect:
```
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=toggle,RTS=toggle}" /dev/ttyUSB0
```
Pipe command to serial device and wait for line response within 1 second:
```
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\r\n', 1000)" --mute
KORAD KD3305P V4.2 SN:32475045
```
### 3.2 Key commands
Various in session key commands are supported. When tio is started, press
ctrl-t ? to list the available key commands.
```
[15:02:53.269] Key commands:
[15:02:53.269] ctrl-t ? List available key commands
[15:02:53.269] ctrl-t b Send break
[15:02:53.269] ctrl-t c Show configuration
[15:02:53.269] ctrl-t e Toggle local echo mode
[15:02:53.269] ctrl-t f Toggle log to file
[15:02:53.269] ctrl-t F Flush data I/O buffers
[15:02:53.269] ctrl-t g Toggle serial port line
[15:02:53.269] ctrl-t i Toggle input mode
[15:02:53.269] ctrl-t l Clear screen
[15:02:53.269] ctrl-t L Show line states
[15:02:53.269] ctrl-t m Change mapping of characters on input or output
[15:02:53.269] ctrl-t o Toggle output mode
[15:02:53.269] ctrl-t p Pulse serial port line
[15:02:53.269] ctrl-t q Quit
[15:02:53.269] ctrl-t r Run script
[15:02:53.269] ctrl-t R Execute shell command with I/O redirected to device
[15:02:53.269] ctrl-t s Show statistics
[15:02:53.269] ctrl-t t Toggle line timestamp mode
[15:02:53.269] ctrl-t v Show version
[15:02:53.269] ctrl-t x Send file via Xmodem
[15:02:53.269] ctrl-t y Send file via Ymodem
[15:02:53.269] ctrl-t ctrl-t Send ctrl-t character
```
If needed, the prefix key (ctrl-t) can be remapped via configuration file.
### 3.3 Configuration file
Options can be set via the configuration file first found in any of the
following locations in the order listed:
- $XDG_CONFIG_HOME/tio/config
- $HOME/.config/tio/config
- $HOME/.tioconfig
The configuration file supports profiles using named sections which can be
activated via the command-line by name or pattern. A profile specifies which
TTY device to connect to and other options.
### 3.3.1 Example
Example configuration file:
```
[default]
baudrate = 115200
databits = 8
parity = none
stopbits = 1
color = 10
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
no-reconnect = true
log = true
log-file = rpi3.log
line-pulse-duration = DTR=200,RTS=150
color = 11
[svf2]
device = /dev/ttyUSB0
baudrate = 9600
script = tio.expect("login: "); tio.write("root\n"); tio.expect("Password: "); tio.write("root\n")
color = 12
[esp32]
device = /dev/serial/by-id/usb-0403_6014-if00-port0
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
script-run = once
color = 13
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
color = 14
```
To use a specific profile by name simply start tio like so:
```
$ tio rpi3
```
Or by pattern match:
```
$ tio usb12
```
Another more elaborate configuration file example is available [here](examples/config/config).
### 3.4 Lua script API
Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions
and variables available:
#### `tio.expect(pattern, timeout)`
Waits for the Lua pattern to match or timeout before continuing.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
Returns the captures from the pattern or `nil` on timeout.
#### `tio.read(size, timeout)`
Read up to `size` bytes from serial device. If timeout is 0 or not provided it
will wait forever until data is ready to read.
Returns a string up to `size` bytes long on success and `nil` on timeout.
#### `tio.readline(timeout)`
Read line from serial device. If timeout is 0 or not provided it will wait
forever until data is ready to read.
Returns a string on success and `nil` on timeout. On timeout a partially read
line may be returned as a second return value.
#### `tio.write(string)`
Write string to serial device.
Returns the `tio` table.
#### `tio.send(file, protocol)`
Send file using x/y-modem protocol.
Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `YMODEM`.
#### `tio.ttysearch()`
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found.
Each of these tables contains the serial device information accessible via the
following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns `nil` if no serial devices are found.
#### `tio.set{line=state, ...}`
Set state of one or multiple tty modem lines.
Line can be any of `DTR`, `RTS`, `CTS`, `DSR`, `CD`, `RI`
State is `high`, `low`, or `toggle`.
#### `tio.sleep(seconds)`
Sleep for seconds.
#### `tio.msleep(ms)`
Sleep for milliseconds.
#### `tio.alwaysecho`
A boolean value, defaults to `true`.
If `tio.alwaysecho` is `false`, the result of `tio.read`, `tio.readline` or
`tio.expect` will only be returned from the function and not logged or printed.
If `tio.alwaysecho` is set to `true`, reading functions also emit a single
timestamp to stdout and log file per `options.timestamp` and `options.log`.
## 4. Installation
### 4.1 Installation using package manager (Linux)
Packages for various GNU/Linux distributions are available. Please consult your
package manager tool to find and install tio.
If you would like to see tio included in your favorite distribution, please
reach out to its package maintainers team.
### 4.2 Installation using snap (Linux)
Install latest stable version:
```
$ snap install tio --classic
```
Note: Classic confinement is currently required due to limitations of the snapcraft framework.
See [Issue #187](https://github.com/tio/tio/issues/187) for discussion.
### 4.3 Installation using brew (MacOS, Linux)
If you have [brew](http://brew.sh) installed:
```
$ brew install tio
```
### 4.4 Installation using MSYS2 (Windows)
If you have [MSYS2](https://www.msys2.org) installed:
```
$ pacman -S tio
```
### 4.5 Installation from source
The latest source releases can be found [here](https://github.com/tio/tio/releases).
Before running the install steps make sure you have glib and lua libraries installed. For example:
```
$ sudo apt install libglib2.0-dev liblua5.2-dev
```
Install steps:
```
$ meson setup build
$ meson compile -C build
$ meson install -C build
```
See meson\_options.txt for tio specific build options.
Note: The meson install steps may differ depending on your specific system.
### 4.6 Known issues
Getting permission access errors trying to open your serial device?
Add your user to the group which allows serial device access permanently. For example, to add your user to the 'dialout' group do:
```bash
sudo usermod -a -G dialout <username>
```
Switch to the "dialout" group, temporary but immediately for this session.
```bash
newgrp dialout
```
## 5. Contributing
This is an open source project - all contributions (bug reports, code, doc,
ideas, etc.) are welcome.
Please use the github issue tracker and pull request features.
Also, if you find this free open source software useful please feel free to
consider making a donation of your choice:
[![Donate](images/paypal.png)](https://www.paypal.me/lundmar)
## 6. Support
Submit bug reports via GitHub: https://github.com/tio/tio/issues
## 7. Website
Visit [tio.github.io](https://tio.github.io)
## 8. License
tio is GPLv2+. See LICENSE file for more details.
## 9. Authors
Maintained by Martin Lund \<martin.lund@keep-it-simple.com>
See the AUTHORS file for full list of contributors.

111
TODO
View file

@ -1,111 +0,0 @@
* Add release support for arm and x86 binary tarballs
* Support input and input mapping from lua scripts
* Add option to send file raw (no modem protocol)
* Add loopback option
Send received serial input back to output (for testing etc.)
* Add loopback support between two serial ports
Useful for traffic monitoring
* Add mapping feature for printing non-printable characters
* Porting layer to support native win32 builds.
Some of the work that needs to be done:
All posix functions need to be platform independent, go though file by file:
termios.h
unistd.h has very limited functions
ENV different in config_file_resolve
errno
sys/ioctl.h
sys/poll.h
socket, may need a new thread
Serial, RS485, character mapping
Communication pipe
Port enumerate, all devices of the same type have the same name (eg. USB
Serial Device for ttyACM) -> which makes regex not meaningful (kind of a
good thing since libtre in Mingw has too much dependencies makes binary too
big)
* Support traditional hex output format such as:
00000000 74 65 73 74 20 74 65 73 74 20 74 65 73 74 20 74 |test test test t|
00000010 65 73 74 64 66 0a 61 0a 66 61 0a 66 0a 61 73 66 |estdf.a.fa.f.asf|
00000020 64 61 64 73 66 61 73 66 64 61 73 64 66 61 64 73 |dadsfasfdasdfads|
00000030 66 0a 61 73 64 66 61 64 73 66 61 73 64 66 61 73 |f.asdfadsfasdfas|
00000040 64 66 0a 61 73 64 66 61 64 73 66 61 73 64 66 61 |df.asdfadsfasdfa|
00000050 73 64 66 66 64 61 73 64 66 0a 0a 31 32 33 31 0a |sdffdasdf..1231.|
00000060 65 32 31 64 73 77 65 64 0a 0a |e21dswed..|
0000006a
* Add support for activity based time stamping in normal output mode
Already supported in hex output mode.
* Allow tio to connect to socket
After some more consideration I think it makes sense to support connecting to a
socket as that will make tio be able to both serve a serial port via a socket
and connect to it - it will be an end to end solution. In short we will be able
to do the following:
Host serial port on socket (existing feature):
$ tio --socket unix:/tmp/tio-socket-0 /dev/ttyUSB0
Connect to same socket (new feature):
$ tio unix:/tmp/tio-socket-0
Besides a bit of refactoring the following required changes spring to mind:
* Socket mode and type of socket should be activated via device name prefix. For example:
* UNIX socket: tio unix:<filename>
* TCPv4 socket: tio inet:<ip>:<port>
* TCPv6 socket: tio inet6:<ip>:<port>
* If no port number defined default to 3333
* Mapping flags INLCR, IGNCR, ICRNL needs implementation for socket mode
* Error messages should just say "device" instead of "tty device" etc.
* Remove other tty'isms (tty_write() should be device_write() etc.)
* In session key commands that do not work in socket mode should either not be listed or print an error messages if used.
* All non-tty features should continue work (auto-connect etc.)
* Shell completion script update
* Man page update
* Split I/O feature
Allow to split input and output so that it is possible to manage these
independently.
The general idea is to redirect the output stream on the socket port number
specified but then redirect the input stream on the same port number + 1.
Example:
$ tio /dev/ttyUSB0 --socket inet:4444,split-io
Will result in output stream being hosted on port 4444 and input stream
hosted on port 4445.
For file sockets something similar can be arranged:
$ tio /dev/ttyUSB0 --socket unix:/tmp/tio-socket-0,split-io
Will result in output stream being hosted via /tmp/tio-socket-0 and input
stream hosted via /tmp/tio-socket-0_input
* Websocket support
Extend the socket feature to redirect serial I/O to websocket on e.g. port
1234 like so:
$ tio --socket ws:1234
Use libwesockets to implement feature.

1
autogen.sh Executable file
View file

@ -0,0 +1 @@
autoreconf --force -v --install

30
configure.ac Normal file
View file

@ -0,0 +1,30 @@
AC_PREREQ([2.68])
AC_INIT([tio], [1.9], [], [tio], [https://tio.github.io])
AC_CONFIG_HEADERS([src/include/config.h])
AM_INIT_AUTOMAKE([1.11 foreign dist-xz no-dist-gzip -Wall -Werror])
AM_SILENT_RULES([yes])
AC_PROG_CC
AC_LANG([C])
AC_PROG_INSTALL
PKG_PROG_PKG_CONFIG
AC_ARG_WITH([bash-completion-dir],
AS_HELP_STRING([--with-bash-completion-dir[=PATH]],
[Install the bash auto-completion script in this directory. @<:@default=yes@:>@]),
[],
[with_bash_completion_dir=yes])
if test "x$with_bash_completion_dir" = "xyes"; then
PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
[BASH_COMPLETION_DIR="`pkg-config --variable=completionsdir bash-completion`"],
[BASH_COMPLETION_DIR="${sysconfdir}/bash_completion.d"])
else
BASH_COMPLETION_DIR="$with_bash_completion_dir"
fi
AC_SUBST([BASH_COMPLETION_DIR])
AM_CONDITIONAL([ENABLE_BASH_COMPLETION],[test "x$with_bash_completion_dir" != "xno"])
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([man/Makefile])
AC_OUTPUT

View file

@ -1,78 +0,0 @@
###############################
# tio - https://tio.github.io #
###############################
# Example tio configuration file
#
# Place file in any of the following locations:
# $XDG_CONFIG_HOME/tio/config
# $HOME/.config/tio/config
# $HOME/.tioconfig
[default]
baudrate = 115200
databits = 8
flow = none
stopbits = 1
parity = none
output-delay = 0
output-line-delay = 0
auto-connect = direct
no-reconnect = false
local-echo = false
input-mode = normal
output-mode = normal
timestamp = false
log = false
log-append = false
log-strip = false
color = bold
rs-485 = false
alert = none
script-run = always
prefix-ctrl-key = t
# Configuration profiles
[rpi3]
baudrate = 115200
device = /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A6009HU3-if00-port0
socket = unix:/tmp/tio-socket-0
color = 9
[am64-evm]
baudrate = 115200
device = /dev/serial/by-id/usb-Silicon_Labs_CP2105_Dual_USB_to_UART_Bridge_Controller_01093176-if01-port0
line-pulse-duration = DTR=200,RTS=300,RI=50
alert = bell
color = 10
[tincan]
baudrate = 9600
device = /dev/serial/by-id/usb-TinCanTools_Flyswatter2_FS20000-if00-port0
log = true
log-file = tincan.log
log-strip = true
color = 11
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
color = 12
[rs-485-device]
device = /dev/ttyUSB0
rs-485 = true
rs-485-config = RTS_ON_SEND=1,RTS_AFTER_SEND=1,RTS_DELAY_BEFORE_SEND=60,RTS_DELAY_AFTER_SEND=80,RX_DURING_TX
color = 13
[esp32]
device = /dev/ttyUSB0
color = 14
script = tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{DTR=low,RTS=high}; tio.msleep(100); tio.set{RTS=low}
script-run = always
[buspirate]
device = /dev/ttyACM0
map = INLCRNL,ODELBS
color = 15

View file

@ -1,28 +0,0 @@
local logins = {
["foo"] = {
username = "foouser",
password = "foopass",
},
["bar"] = {
username = "baruser",
password = "barpass",
},
["baz"] = {
username = "bazuser",
password = "bazpass",
},
}
local hostname = tio.expect("^(%g+) login:", 10)
if hostname then
local login = logins[hostname]
if (nil ~= login) then
tio.write(login.username .. "\n")
tio.expect("Password:")
tio.write(login.password .. "\n")
else
io.write("\r\nDon't know login info for " .. hostname .. "\r\n")
end
else
io.write("\r\nDidn't find a login prompt\r\n")
end

View file

@ -1,5 +0,0 @@
tio.set{DTR=high, RTS=low}
tio.msleep(100)
tio.set{DTR=low, RTS=high}
tio.msleep(100)
tio.set{RTS=toggle}

View file

@ -1,14 +0,0 @@
tio.read(1000, 6000) -- initial config
tio.write("\n")
tio.msleep(100)
tio.read(650, 60) -- main menu
tio.write("S") -- S menu
tio.msleep(30)
tio.read(650, 60)
tio.write("t") -- Parallel Value Table
tio.read(650, 60)
while true do
tio.msleep(1000)
tio.write("t")
tio.read(650, 50) -- repeat PVT forever
end

View file

@ -1,15 +0,0 @@
tio.read(1000, 8000) -- read initial config
tio.write("\n")
tio.read(650, 100) -- main menu
tio.write("S") -- S menu
repeat
str = tio.readline(25)
until str == nil
while true do
tio.write("t") -- query PV table
tio.msleep(880)
repeat
str = tio.readline(60)
tio.msleep(60)
until str == nil
end

View file

@ -1,13 +0,0 @@
io.write("Searching... ")
local device = tio.ttysearch()
io.write("done\r\n")
for i in ipairs(device) do
io.write("\r\n" .. device[i]["path"] .. "\r\n")
io.write(" tid = " .. device[i]["tid"] .. "\r\n")
io.write(" uptime = " .. device[i]["uptime"] .. "\r\n")
io.write(" driver = " .. device[i]["driver"] .. "\r\n")
io.write(" description = " .. device[i]["description"] .. "\r\n")
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

1
man/Makefile.am Normal file
View file

@ -0,0 +1 @@
dist_man_MANS = tio.1

View file

@ -1,15 +0,0 @@
mandir = join_paths(get_option('prefix'), get_option('mandir'))
man1dir = join_paths(mandir, 'man1')
conf = configuration_data()
conf.set('version', meson.project_version())
conf.set('version_date', version_date)
manpage = configure_file(
input: files('tio.1.in'),
output: 'tio.1',
configuration: conf)
install_man(
manpage,
install_dir: man1dir)

83
man/tio.1 Normal file
View file

@ -0,0 +1,83 @@
.TH "tio" "1" "7 May 2016"
.SH "NAME"
tio \- The simple TTY terminal I/O application
.SH "SYNOPSIS"
.PP
.B tio
.RI "[" <options> "] " "<tty device>"
.SH "DESCRIPTION"
.PP
.B tio
is a simple TTY terminal application which features a straightforward
commandline interface to easily connect to TTY devices for basic input/output.
.SH "OPTIONS"
.TP
.BR \-b ", " "\-\-baudrate " \fI<bps>
Set baud rate [bps] (default: 115200).
.TP
.BR \-d ", " "\-\-databits 5" | 6 | 7 | 8
Set data bits (default: 8).
.TP
.BR \-f ", " "\-\-flow hard" | soft | none
Set flow control (default: none).
.TP
.BR \-s ", " "\-\-stopbits 1" | 2
Set stop bits (default: 1).
.TP
.BR \-p ", " "\-\-parity odd" | even | none
Set parity (default: none).
.TP
.BR \-o ", " "\-\-output\-delay " \fI<ms>
Set output delay [ms] \(em delay inserted between each transmitted character (default: 0).
.TP
.BR \-n ", " \-\-no\-autoconnect
Disable automatic connect.
.TP
.BR \-l ", " "\-\-log " \fI<filename>
Log to file.
.TP
.BR \-v ", " \-\-version
Display program version.
.TP
.BR \-h ", " \-\-help
Display help.
.SH "KEYS"
.TP
In session, press ctrl-t + q to quit.
.SH "EXAMPLES"
.TP
The most common use is without options. For example:
tio /dev/ttyUSB0
.TP
Which corresponds to:
tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
.TP
It is recommended to connect serial tty devices by id. For example:
tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.PP
Using serial devices by id ensures that tio automatically reconnects to the
correct serial device if the device is disconnected and then reconnected.
.SH "AUTHOR"
.PP
Written by Martin Lund <martin.lund@keep\-it\-simple.com>.

View file

@ -1,781 +0,0 @@
.TH "tio" "1" "@version_date@" "tio @version@" "User Commands"
.SH "NAME"
tio \- a serial device I/O tool
.SH "SYNOPSIS"
.PP
.B tio
.RI "[" <options> "] " "<tty-device|profile|tid>"
.SH "DESCRIPTION"
.PP
\fBtio\fR is a serial device tool which features a straightforward command-line
and configuration file interface to easily connect to serial TTY devices for
basic I/O operations.
.SH "OPTIONS"
.TP
.BR \-b ", " "\-\-baudrate " \fI<bps>
Set baud rate [bps] (default: 115200).
.TP
.BR \-d ", " "\-\-databits " 5 | 6 | 7 | 8
Set data bits (default: 8).
.TP
.BR \-f ", " "\-\-flow " hard | soft | none
Set flow control (default: none).
.TP
.BR \-s ", " "\-\-stopbits " 1 | 2
Set stop bits (default: 1).
.TP
.BR \-p ", " "\-\-parity " odd | even | none | mark | space
Set parity (default: none).
Note: With \fBmark\fR parity the parity bit is always 0. With \fBspace\fR
parity the parity bit is always 1. Not all platforms support \fBmark\fR and
\fBspace\fR parity.
.TP
.BR \-o ", " "\-\-output\-delay " \fI<ms>
Set output delay [ms] inserted between each sent character (default: 0).
.TP
.BR \-O ", " "\-\-output\-line\-delay " \fI<ms>
Set output delay [ms] inserted between each sent line (default: 0).
.TP
.BR " \-\-line\-pulse\-duration " \fI<duration>
Set the pulse duration [ms] of each serial port line using the following key
value pair format in the duration field: <key>=<value>
Each key represents a serial line. The following keys are available:
.RS
.TP 8n
.IP \fBDTR
Data Terminal Ready
.IP \fBRTS
Request To Send
.IP \fBCTS
Clear To Send
.IP \fBDSR
Data Set Ready
.IP \fBDCD
Data Carrier Detect
.IP \fBRI
Ring Indicator
.P
If defining more than one key value pair, the pairs must be comma separated.
The default pulse duration for each line is 100 ms.
.RE
.TP
.BR "\-a, \-\-auto\-connect new|latest|direct"
Automatically connect to serial device according to one of the following
strategies:
.RS
.TP 10n
.IP "\fBnew"
Automatically connect to first new appearing serial device.
.IP "\fBlatest"
Automatically connect to latest registered serial device.
.IP "\fBdirect"
Connect directly to specified TTY device.
.P
All the listed strategies automatically reconnects according to strategy if
device is not available or connection is lost.
.P
Default value is "direct".
.RE
.TP
.BR " \-\-exclude\-devices \fI<pattern>"
Exclude devices by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-drivers \fI<pattern>"
Exclude drivers by pattern ('*' and '?' supported).
.TP
.BR " \-\-exclude\-tids \fI<pattern>"
Exclude topology IDs by pattern ('*' and '?' supported).
.TP
.BR \-n ", " \-\-no\-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an
established connection is lost.
.TP
.BR \-e ", " "\-\-local\-echo
Enable local echo.
.TP
.BR \-t ", " \-\-timestamp
Enable line timestamp.
.TP
.BR " \-\-timestamp\-format \fI<format>"
Set timestamp format to any of the following timestamp formats:
.RS
.TP 16n
.IP "\fB24hour"
24-hour format ("hh:mm:ss.sss")
.IP "\fB24hour-start"
24-hour format relative to start time
.IP "\fB24hour-delta"
24-hour format relative to previous timestamp
.IP "\fBiso8601"
ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
.IP "\fBepoch"
Seconds since Unix epoch (1970-01-01)
.IP "\fBepoch-usec"
Seconds since Unix epoch (1970-01-01) with subdivision in microseconds
.PP
Default format is \fB24hour\fR
.RE
.TP
.BR " \-\-timestamp\-timeout \fI<ms>"
Set timestamp timeout value in milliseconds.
This value only takes effect in hex output mode where timestamps are only
printed after elapsed timeout time of no output activity from tty device.
Default value is 200.
.TP
.BR \-l ", " \-\-list
List available targets (serial devices, TIDs, configuration profiles).
.TP
.BR \-L ", " \-\-log
Enable log to file.
The log file will be automatically named using the following format
tio_TARGET_YYYY-MM-DDTHH:MM:SS.log. Target being the command line target such
as tty-device, tid, or configuration profile.
The filename can be manually set using the \-\-log-file option.
.TP
.BR " \-\-log\-file \fI<filename>
Set log filename.
.TP
.BR " \-\-log\-directory \fI<path>
Set log directory path in which to save automatically named log files.
.TP
.BR " \-\-log\-append
Append to log file.
.TP
.BR " \-\-log-strip
Strip control characters and escape sequences from log.
.TP
.BR \-m ", " "\-\-map " \fI<flags>
Map (replace, translate) characters on input to the serial device or output
from the serial device. The following mapping flags are supported:
.RS
.TP 12n
.IP "\fBICRNL"
Map CR to NL on input (unless IGNCR is set)
.IP "\fBIGNCR"
Ignore CR on input
.IP "\fBIFFESCC"
Map FF to ESC-c on input
.IP "\fBINLCR"
Map NL to CR on input
.IP "\fBINLCRNL"
Map NL to CR-NL on input
.IP "\fBICRCRNL"
Map CR to CR-NL on input
.IP "\fBIMSB2LSB"
Map MSB bit order to LSB on input
.IP "\fBOCRNL"
Map CR to NL on output
.IP "\fBODELBS"
Map DEL to BS on output
.IP "\fBONLCRNL"
Map NL to CR-NL on output
.IP "\fBOLTU"
Map lowercase characters to uppercase on output
.IP "\fBONULBRK"
Map nul (zero) to send break signal on output
.IP "\fBOIGNCR"
Ignore CR on output
.P
If defining more than one flag, the flags must be comma separated.
.RE
.TP
.BR " \-\-input\-mode " normal|hex|line
Set input mode.
In normal mode input characters are sent immediately as they are typed.
In hex input mode bytes can be sent by typing the \fBtwo-character
hexadecimal\fR representation of the 1 byte value, e.g.: to send \fI0xA\fR you
must type \fI0a\fR or \fI0A\fR.
In line input mode input characters are sent when you press enter. The only
editing feature supported in this mode is backspace.
Default value is "normal".
.TP
.BR " \-\-output\-mode " normal|hex|hexN
Set output mode.
In hex mode each incoming byte is printed out as a 1 byte hex value.
In hexN mode, N is a number less than or equal to 4096 which defines how many
hex values will be printed before a line break.
Default value is "normal".
.TP
.BR \-c ", " "\-\-color " 0..255|bold|none|list
Colorize tio text using ANSI color code value ranging from 0 to 255 or use
"none" for no color or use "bold" to apply bold formatting to existing system
color.
Use "list" to print a list of available ANSI color codes.
Default value is "bold".
.TP
.BR \-S ", " "\-\-socket \fI<socket>\fR\fB
Redirect I/O to socket.
Any input from clients connected to the socket is sent on the serial port as if
entered at the terminal where tio is running (except that \fBctrl-t\fR sequences
are not recognized), and any input from the serial port is multiplexed to the
terminal and all connected clients.
Sockets remain open while the serial port is disconnected, and writes will block.
Various socket types are supported using the following prefixes in the socket field:
.RS
.TP 20n
.IP "\fBunix:<filename>"
Unix Domain Socket (file)
.IP "\fBinet:<port>"
Internet Socket (network)
.IP "\fBinet6:<port>"
Internet IPv6 Socket (network)
.P
If port is 0 or no port is provided default port 3333 is used.
.P
At present there is a hardcoded limit of 16 clients connected at one time.
.RE
.TP
.BR " \-\-rs\-485"
Enable RS-485 mode.
.TP
.BR " \-\-rs\-485\-config " \fI<config>
Set the RS-485 configuration using the following key or key value pair format in
the configuration field:
.RS
.TP 30n
.IP \fBRTS_ON_SEND=value
Set logical level (0 or 1) for RTS pin when sending
.IP \fBRTS_AFTER_SEND=value
Set logical level (0 or 1) for RTS pin after sending
.IP \fBRTS_DELAY_BEFORE_SEND=value
Set RTS delay (ms) before sending
.IP \fBRTS_DELAY_AFTER_SEND=value
Set RTS delay (ms) after sending
.IP \fBRX_DURING_TX
Receive data even while sending data
.P
If defining more than one key or key value pair, they must be comma separated.
.RE
.TP
.BR "\-\-alert none|bell|blink"
Set alert action on connect/disconnect.
It will sound the bell once or blink once on successful connect. Likewise it
will sound the bell twice or blink twice on disconnect.
Default value is "none".
.TP
.BR "\-\-mute"
Mute tio messages.
.TP
.BR "\-\-script \fI<string>
Run script from string.
.TP
.BR "\-\-script\-file \fI<filename>
Run script from file with filename.
.TP
.BR "\-\-script\-run once|always|never"
Run script on connect once, always, or never.
Default value is "always".
.TP
.BR "\-\-exec \fI<command>
Execute shell command with I/O redirected to device
.TP
.BR "\-\-complete-profiles
Prints profiles (for shell completion)
.TP
.BR \-v ", " \-\-version
Display program version.
.TP
.BR \-h ", " \-\-help
Display help.
.SH "KEY COMMANDS"
.PP
.TP 16n
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as key commands:
.IP "\fBctrl-t ?"
List available key commands
.IP "\fBctrl-t b"
Send serial break (triggers SysRq on Linux, etc.)
.IP "\fBctrl-t c"
Show configuration (baudrate, databits, etc.)
.IP "\fBctrl-t e"
Toggle local echo mode
.IP "\fBctrl-t f"
Toggle log to file
.IP "\fBctrl-t F"
Flush data I/O buffers (discard data written but not transmitted and data received but not read)
.IP "\fBctrl-t g"
Toggle serial port line
.IP "\fBctrl-t i"
Toggle input mode
.IP "\fBctrl-t l"
Clear screen
.IP "\fBctrl-t L"
Show line states (DTR, RTS, CTS, DSR, DCD, RI)
.IP "\fBctrl-t m"
Change mapping of characters on input or output
.IP "\fBctrl-t o"
Toggle output mode
.IP "\fBctrl-t p"
Pulse serial port line
.IP "\fBctrl-t q"
Quit
.IP "\fBctrl-t r"
Run script
.IP "\fBctrl-t R"
Execute shell command with I/O redirected to device
.IP "\fBctrl-t s"
Show TX/RX statistics
.IP "\fBctrl-t t"
Toggle line timestamp mode
.IP "\fBctrl-t v"
Show version
.IP "\fBctrl-t x"
Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol)
.IP "\fBctrl-t y"
Send file using the YMODEM protocol (prompts for file name)
.IP "\fBctrl-t ctrl-t"
Send ctrl-t character
.SH "SCRIPT API"
.PP
Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions
and variables available:
.TP 6n
.IP "\fBtio.expect(pattern, timeout)"
Waits for the Lua pattern to match or timeout before continuing.
Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
Returns the captures from the pattern or nil on timeout.
.IP "\fBtio.read(size, timeout)"
Read up to size bytes from serial device. If timeout is 0 or not provided it
will wait forever until data is ready to read.
Returns a string up to size bytes long on success and nil on timeout.
.IP "\fBtio.readline(timeout)"
Read line from serial device. If timeout is 0 or not provided it will wait
forever until data is ready to read.
Returns a string on success and nil on timeout. On timeout a partially read
line may be returned as a second return value.
.IP "\fBtio.write(string)"
Write string to serial device.
Returns the tio table.
.IP "\fBtio.send(file, protocol)"
Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
.IP "\fBtio.ttysearch()"
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found.
Each of these tables contains the serial device information accessible via the
following string indexed elements "path", "tid", "uptime", "driver",
"description".
Returns nil if no serial devices are found.
.IP "\fBtio.set{line=state, ...}"
Set state of one or multiple tty modem lines.
Line can be any of DTR, RTS, CTS, DSR, CD, RI
State is high, low, or toggle.
.IP "\fBtio.sleep(seconds)"
Sleep for seconds.
.IP "\fBtio.msleep(ms)"
Sleep for milliseconds.
.IP "\fBtio.alwaysecho"
A boolean value, defaults to true.
If tio.alwaysecho is false, the result of tio.read, tio.readline or tio.expect
will only be returned from the function and not logged or printed.
If tio.alwaysecho is set to true, reading functions also emit a single
timestamp to stdout and log file per options.timestamp and options.log.
.SH "CONFIGURATION FILE"
.PP
Options can be set via configuration file using the INI format. \fBtio\fR uses
the configuration file first found in the following locations in the order
listed:
.PP
.I $XDG_CONFIG_HOME/tio/config
.PP
.I $HOME/.config/tio/config
.PP
.I $HOME/.tioconfig
.PP
Labels can be used to group settings into named configuration profiles which
can be activated from the command-line when starting tio.
.PP
\fBtio\fR will try to match the user input to a configuration profile by name or by
pattern to get the TTY device and other options.
.PP
Options without any label change the default options.
.PP
Any options set via command-line will override options set in the configuration file.
.PP
The following configuration file options are available:
.TP 25n
.IP "\fBpattern"
Pattern matching user input. This pattern can be an extended regular expression with a single group.
.IP "\fBdevice"
TTY device to open. If it contains a "%s" it is substituted with the first group match.
.IP "\fBbaudrate"
Set baud rate
.IP "\fBdatabits"
Set data bits
.IP "\fBflow"
Set flow control
.IP "\fBstopbits"
Set stop bits
.IP "\fBparity"
Set parity
.IP "\fBoutput-delay"
Set output character delay
.IP "\fBoutput-line-delay"
Set output line delay
.IP "\fBline-pulse-duration"
Set line pulse duration
.IP "\fBno-reconnect"
Do not reconnect
.IP "\fBlog"
Enable log to file
.IP "\fBlog-file"
Set log filename
.IP "\fBlog-directory"
Set log directory path in which to save automatically named log files.
.IP "\fBlog-append"
Append to log file
.IP "\fBlog-strip"
Enable strip of control and escape sequences from log
.IP "\fBlocal-echo"
Enable local echo
.IP "\fBtimestamp"
Enable line timestamp
.IP "\fBtimestamp-format"
Set timestamp format
.IP "\fBtimestamp-timeout"
Set timestamp timeout
.IP "\fBmap"
Map characters on input or output
.IP "\fBcolor"
Colorize tio text using ANSI color code ranging from 0 to 255
.IP "\fBinput-mode"
Set input mode
.IP "\fBoutput-mode"
Set output mode
.IP "\fBsocket"
Set socket to redirect I/O to
.IP "\fBprefix-ctrl-key"
Set prefix ctrl key (a..z or 'none', default: t)
.IP "\fBrs-485"
Enable RS-485 mode
.IP "\fBrs-485-config"
Set RS-485 configuration
.IP "\fBalert"
Set alert action on connect/disconnect
.IP "\fBmute"
Mute tio messages
.IP "\fBscript"
Run script from string
.IP "\fBscript-file"
Run script from file
.IP "\fBscript-run"
Run script on connect
.IP "\fBexec"
Execute shell command with I/O redirected to device
.PP
It is possible to include the content of other configuration files using the
include directive like so: "[include <file>]".
.SH "CONFIGURATION FILE EXAMPLES"
.TP
To change the default configuration simply set options like so:
.RS
.nf
.eo
[default]
baudrate = 9600
databits = 8
parity = none
stopbits = 1
color = 10
line-pulse-duration = DTR=200,RTS=400
.ec
.fi
.RE
.TP
Named configuration profiles can be added via labels:
.RS
.nf
.eo
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200
color = 11
.ec
.fi
.RE
.TP
Activate the configuration profile by name:
$ tio rpi3
.TP
Which is equivalent to:
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
.TP
A configuration profile can also be activated by its pattern which supports regular expressions:
.RS
.nf
.eo
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
baudrate = 115200
.ec
.fi
.RE
.TP
Activate the configuration profile by pattern match:
$ tio usb12
.TP
Which becomes equivalent to:
$ tio -b 115200 /dev/ttyUSB12
.TP
It is also possible to combine use of configuration profile and command-line options. For example:
$ tio -l -t usb12
.SH "EXAMPLES"
.TP
Typical use is without options:
$ tio /dev/ttyUSB0
.TP
Which corresponds to the commonly used default options:
$ tio \-b 115200 \-d 8 \-f none \-s 1 \-p none /dev/ttyUSB0
.TP
It is recommended to connect serial TTY devices by ID:
$ tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.PP
Using serial devices by ID ensures that tio automatically reconnects to the
correct serial device if it is disconnected and then reconnected.
.TP
Redirect serial device I/O to Unix file socket for scripting:
$ tio -S unix:/tmp/tio-socket0 /dev/ttyUSB0
.TP
Then, to issue a command via the file socket simply do:
$ echo "ls -la" | nc -UN /tmp/tio-socket0 > /dev/null
.TP
Or use the expect command to script an interaction:
.RS
.nf
.eo
#!/usr/bin/expect -f
.sp
set timeout -1
log_user 0
.sp
spawn nc -UN /tmp/tio-socket0
set uart $spawn_id
.sp
send -i $uart "date\n"
expect -i $uart "prompt> "
send -i $uart "ls -la\n"
expect -i $uart "prompt> "
.ec
.fi
.RE
.TP
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
$ tio --script 'tio.expect("login: "); tio.write("root\\n"); tio.expect("Password: "); tio.write("root\\n")' /dev/ttyUSB0
.TP
Redirect device I/O to network file socket for remote TTY sharing:
$ tio --socket inet:4444 /dev/ttyUSB0
.TP
Then, use netcat to connect to the shared TTY session over network (assuming tio is hosted on IP 10.0.0.42):
$ nc -N 10.0.0.42 4444
.TP
Pipe command to the serial device:
$ echo "ls -la" | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.TP
Pipe command to serial device and wait for line response within 1 second:
$ echo "*IDN?" | tio /dev/ttyACM0 --script "tio.expect('\\r\\n', 1000)" --mute
.TP
.TP
Likewise, to pipe data from file to the serial device:
$ cat data.bin | tio /dev/serial/by\-id/usb\-FTDI_TTL232R-3V3_FTGQVXBL\-if00\-port0
.TP
Map NL to CR-NL on input from device and DEL to BS on output to device:
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
.TP
Enable RS-485 mode:
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
.TP
Manipulate DTR and RTS lines upon first connect to reset connected microcontroller:
$ tio --script "tio.set{DTR=high,RTS=low}; tio.msleep(100); tio.set{RTS=toggle}" --script-run once /dev/ttyUSB0
.SH "WEBSITE"
.PP
Visit https://tio.github.io
.SH "AUTHOR"
.PP
Maintained by Martin Lund <martin.lund@keep\-it\-simple.com>.

View file

@ -1,625 +0,0 @@
tio(1) User Commands tio(1)
NAME
tio - a serial device I/O tool
SYNOPSIS
tio [<options>] <tty-device|profile|tid>
DESCRIPTION
tio is a serial device tool which features a straightforward command-line and configuration file interface to easily connect to serial TTY devices for basic I/O operations.
OPTIONS
-b, --baudrate <bps>
Set baud rate [bps] (default: 115200).
-d, --databits 5|6|7|8
Set data bits (default: 8).
-f, --flow hard|soft|none
Set flow control (default: none).
-s, --stopbits 1|2
Set stop bits (default: 1).
-p, --parity odd|even|none|mark|space
Set parity (default: none).
Note: With mark parity the parity bit is always 0. With space parity the parity bit is always 1. Not all platforms support mark and space parity.
-o, --output-delay <ms>
Set output delay [ms] inserted between each sent character (default: 0).
-O, --output-line-delay <ms>
Set output delay [ms] inserted between each sent line (default: 0).
--line-pulse-duration <duration>
Set the pulse duration [ms] of each serial port line using the following key value pair format in the duration field: <key>=<value>
Each key represents a serial line. The following keys are available:
DTR Data Terminal Ready
RTS Request To Send
CTS Clear To Send
DSR Data Set Ready
DCD Data Carrier Detect
RI Ring Indicator
If defining more than one key value pair, the pairs must be comma separated.
The default pulse duration for each line is 100 ms.
-a, --auto-connect new|latest|direct
Automatically connect to serial device according to one of the following strategies:
new Automatically connect to first new appearing serial device.
latest Automatically connect to latest registered serial device.
direct Connect directly to specified TTY device.
All the listed strategies automatically reconnects according to strategy if device is not available or connection is lost.
Default value is "direct".
--exclude-devices <pattern>
Exclude devices by pattern ('*' and '?' supported).
--exclude-drivers <pattern>
Exclude drivers by pattern ('*' and '?' supported).
--exclude-tids <pattern>
Exclude topology IDs by pattern ('*' and '?' supported).
-n, --no-reconnect
Do not reconnect.
This means that tio will exit if it fails to connect to device or an established connection is lost.
-e, --local-echo
Enable local echo.
-t, --timestamp
Enable line timestamp.
--timestamp-format <format>
Set timestamp format to any of the following timestamp formats:
24hour 24-hour format ("hh:mm:ss.sss")
24hour-start 24-hour format relative to start time
24hour-delta 24-hour format relative to previous timestamp
iso8601 ISO8601 format ("YYYY-MM-DDThh:mm:ss.sss")
epoch Seconds since Unix epoch (1970-01-01)
epoch-usec Seconds since Unix epoch (1970-01-01) with subdivision microseconds
Default format is 24hour
--timestamp-timeout <ms>
Set timestamp timeout value in milliseconds.
This value only takes effect in hex output mode where timestamps are only printed after elapsed timeout time of no output activity from tty device.
Default value is 200.
-l, --list
List available targets (serial devices, TIDs, configuration profiles).
-L, --log
Enable log to file.
The log file will be automatically named using the following format tio_TARGET_YYYY-MM-DDTHH:MM:SS.log. Target being the command line target such as tty-device, tid, or configuration profile.
The filename can be manually set using the --log-file option.
--log-file <filename>
Set log filename.
--log-directory <path>
Set log directory path in which to save automatically named log files.
--log-append
Append to log file.
--log-strip
Strip control characters and escape sequences from log.
-m, --map <flags>
Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported:
ICRNL Map CR to NL on input (unless IGNCR is set)
IGNCR Ignore CR on input
IFFESCC Map FF to ESC-c on input
INLCR Map NL to CR on input
INLCRNL Map NL to CR-NL on input
ICRCRNL Map CR to CR-NL on input
IMSB2LSB Map MSB bit order to LSB on input
OCRNL Map CR to NL on output
ODELBS Map DEL to BS on output
ONLCRNL Map NL to CR-NL on output
OLTU Map lowercase characters to uppercase on output
ONULBRK Map nul (zero) to send break signal on output
OIGNCR Ignore CR on output
If defining more than one flag, the flags must be comma separated.
--input-mode normal|hex|line
Set input mode.
In normal mode input characters are sent immediately as they are typed.
In hex input mode bytes can be sent by typing the two-character hexadecimal representation of the 1 byte value, e.g.: to send 0xA you must type 0a or 0A.
In line input mode input characters are sent when you press enter. The only editing feature supported in this mode is backspace.
Default value is "normal".
--output-mode normal|hex|hexN
Set output mode.
In hex mode each incoming byte is printed out as a 1 byte hex value.
In hexN mode, N is a number less than or equal to 4096 which defines how many hex values will be printed before a line break.
Default value is "normal".
-c, --color 0..255|bold|none|list
Colorize tio text using ANSI color code value ranging from 0 to 255 or use "none" for no color or use "bold" to apply bold formatting to existing system color.
Use "list" to print a list of available ANSI color codes.
Default value is "bold".
-S, --socket <socket>
Redirect I/O to socket.
Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that ctrl-t sequences are not recognized), and any input from the serial port is multi
plexed to the terminal and all connected clients.
Sockets remain open while the serial port is disconnected, and writes will block.
Various socket types are supported using the following prefixes in the socket field:
unix:<filename> Unix Domain Socket (file)
inet:<port> Internet Socket (network)
inet6:<port> Internet IPv6 Socket (network)
If port is 0 or no port is provided default port 3333 is used.
At present there is a hardcoded limit of 16 clients connected at one time.
--rs-485
Enable RS-485 mode.
--rs-485-config <config>
Set the RS-485 configuration using the following key or key value pair format in the configuration field:
RTS_ON_SEND=value Set logical level (0 or 1) for RTS pin when sending
RTS_AFTER_SEND=value Set logical level (0 or 1) for RTS pin after sending
RTS_DELAY_BEFORE_SEND=value Set RTS delay (ms) before sending
RTS_DELAY_AFTER_SEND=value Set RTS delay (ms) after sending
RX_DURING_TX Receive data even while sending data
If defining more than one key or key value pair, they must be comma separated.
--alert none|bell|blink
Set alert action on connect/disconnect.
It will sound the bell once or blink once on successful connect. Likewise it will sound the bell twice or blink twice on disconnect.
Default value is "none".
--mute
Mute tio messages.
--script <string>
Run script from string.
--script-file <filename>
Run script from file with filename.
--script-run once|always|never
Run script on connect once, always, or never.
Default value is "always".
--exec <command>
Execute shell command with I/O redirected to device
--complete-profiles
Prints profiles (for shell completion)
-v, --version
Display program version.
-h, --help
Display help.
KEY COMMANDS
In session, all key strokes are forwarded to the serial device except the following key sequence: a prefix key (default: ctrl-t) followed by a command key. These sequences are intercepted as key commands:
ctrl-t ? List available key commands
ctrl-t b Send serial break (triggers SysRq on Linux, etc.)
ctrl-t c Show configuration (baudrate, databits, etc.)
ctrl-t e Toggle local echo mode
ctrl-t f Toggle log to file
ctrl-t F Flush data I/O buffers (discard data written but not transmitted and data received but not read)
ctrl-t g Toggle serial port line
ctrl-t i Toggle input mode
ctrl-t l Clear screen
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
ctrl-t m Change mapping of characters on input or output
ctrl-t o Toggle output mode
ctrl-t p Pulse serial port line
ctrl-t q Quit
ctrl-t r Run script
ctrl-t R Execute shell command with I/O redirected to device
ctrl-t s Show TX/RX statistics
ctrl-t t Toggle line timestamp mode
ctrl-t v Show version
ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol)
ctrl-t y Send file using the YMODEM protocol (prompts for file name)
ctrl-t ctrl-t Send ctrl-t character
SCRIPT API
Tio suppots Lua scripting to easily automate interaction with the tty device.
In addition to the standard Lua API tio makes the following functions available:
expect(string, timeout)
Expect string - waits for string to match or timeout before continuing. Supports regular expressions. Special characters must be escaped with '\\'. Timeout is in milliseconds, defaults to 0 meaning it will wait forever.
Returns 1 on successful match, 0 on timeout, or -1 on error.
On successful match it also returns the match string as second return value.
read(size, timeout)
Read up to size bytes from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read.
Returns number of bytes read on success, 0 on timeout, or -1 on error.
On success, returns read string as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
read_line(timeout)
Read line from serial device. If timeout is 0 or not provided it will wait forever until data is ready to read.
Returns number of bytes read on success, 0 on timeout, or -1 on error.
On success, returns the string that was read as second return value. Also emits a single timestamp to stdout and log file per options.timestamp and options.log.
write(string)
Write string to serial device.
Returns number of bytes written on success or -1 on error.
send(file, protocol)
Send file using x/y-modem protocol.
Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM.
tty_search()
Search for serial devices.
Returns a table of number indexed tables, one for each serial device found. Each of these tables contains the serial device information accessible via the following string indexed elements "path", "tid", "uptime", "dri
ver", "description".
Returns nil if no serial devices are found.
set{line=state, ...}
Set state of one or multiple tty modem lines.
Line can be any of DTR, RTS, CTS, DSR, CD, RI
State is high, low, or toggle.
sleep(seconds)
Sleep for seconds.
msleep(ms)
Sleep for milliseconds.
exit(code)
Exit with exit code.
CONFIGURATION FILE
Options can be set via configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed:
$XDG_CONFIG_HOME/tio/config
$HOME/.config/tio/config
$HOME/.tioconfig
Labels can be used to group settings into named configuration profiles which can be activated from the command-line when starting tio.
tio will try to match the user input to a configuration profile by name or by pattern to get the TTY device and other options.
Options without any label change the default options.
Any options set via command-line will override options set in the configuration file.
The following configuration file options are available:
pattern Pattern matching user input. This pattern can be an extended regular expression with a single group.
device TTY device to open. If it contains a "%s" it is substituted with the first group match.
baudrate Set baud rate
databits Set data bits
flow Set flow control
stopbits Set stop bits
parity Set parity
output-delay Set output character delay
output-line-delay Set output line delay
line-pulse-duration Set line pulse duration
no-reconnect Do not reconnect
log Enable log to file
log-file Set log filename
log-directory Set log directory path in which to save automatically named log files.
log-append Append to log file
log-strip Enable strip of control and escape sequences from log
local-echo Enable local echo
timestamp Enable line timestamp
timestamp-format Set timestamp format
timestamp-timeout Set timestamp timeout
map Map characters on input or output
color Colorize tio text using ANSI color code ranging from 0 to 255
input-mode Set input mode
output-mode Set output mode
socket Set socket to redirect I/O to
prefix-ctrl-key Set prefix ctrl key (a..z or 'none', default: t)
rs-485 Enable RS-485 mode
rs-485-config Set RS-485 configuration
alert Set alert action on connect/disconnect
mute Mute tio messages
script Run script from string
script-file Run script from file
script-run Run script on connect
exec Execute shell command with I/O redirected to device
It is possible to include the content of other configuration files using the include directive like so: "[include <file>]".
CONFIGURATION FILE EXAMPLES
To change the default configuration simply set options like so:
[default]
baudrate = 9600
databits = 8
parity = none
stopbits = 1
color = 10
line-pulse-duration = DTR=200,RTS=400
Named configuration profiles can be added via labels:
[rpi3]
device = /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
baudrate = 115200
color = 11
Activate the configuration profile by name:
$ tio rpi3
Which is equivalent to:
$ tio -b 115200 -c 11 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
A configuration profile can also be activated by its pattern which supports regular expressions:
[usb-devices]
pattern = ^usb([0-9]*)
device = /dev/ttyUSB%m1
baudrate = 115200
Activate the configuration profile by pattern match:
$ tio usb12
Which becomes equivalent to:
$ tio -b 115200 /dev/ttyUSB12
It is also possible to combine use of configuration profile and command-line options. For example:
$ tio -l -t usb12
EXAMPLES
Typical use is without options:
$ tio /dev/ttyUSB0
Which corresponds to the commonly used default options:
$ tio -b 115200 -d 8 -f none -s 1 -p none /dev/ttyUSB0
It is recommended to connect serial TTY devices by ID:
$ tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Using serial devices by ID ensures that tio automatically reconnects to the correct serial device if it is disconnected and then reconnected.
Redirect serial device I/O to Unix file socket for scripting:
$ tio -S unix:/tmp/tio-socket0 /dev/ttyUSB0
Then, to issue a command via the file socket simply do:
$ echo "ls -la" | nc -UN /tmp/tio-socket0 > /dev/null
Or use the expect command to script an interaction:
#!/usr/bin/expect -f
set timeout -1
log_user 0
spawn nc -UN /tmp/tio-socket0
set uart $spawn_id
send -i $uart "date\n"
expect -i $uart "prompt> "
send -i $uart "ls -la\n"
expect -i $uart "prompt> "
It is also possible to use tio's own simpler expect/send script functionality to e.g. automate logins:
$ tio --script 'expect("login: "); write("root\n"); expect("Password: "); write("root\n")' /dev/ttyUSB0
Redirect device I/O to network file socket for remote TTY sharing:
$ tio --socket inet:4444 /dev/ttyUSB0
Then, use netcat to connect to the shared TTY session over network (assuming tio is hosted on IP 10.0.0.42):
$ nc -N 10.0.0.42 4444
Pipe command to the serial device:
$ echo "ls -la" | tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Pipe command to serial device and wait for line response within 1 second:
$ echo "*IDN?" | tio /dev/ttyACM0 --script "expect('\r\n', 1000)" --mute
Likewise, to pipe data from file to the serial device:
$ cat data.bin | tio /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
Map NL to CR-NL on input from device and DEL to BS on output to device:
$ tio --map INLCRNL,ODELBS /dev/ttyUSB0
Enable RS-485 mode:
$ tio --rs-485 --rs-485-config=RTS_ON_SEND=1,RX_DURING_TX /dev/ttyUSB0
Manipulate DTR and RTS lines upon first connect to reset connected microcontroller:
$ tio --script "set{DTR=high,RTS=low}; msleep(100); set{RTS=toggle}" --script-run once /dev/ttyUSB0
WEBSITE
Visit https://tio.github.io
AUTHOR
Maintained by Martin Lund <martin.lund@keep-it-simple.com>.
tio 3.9 2025-04-13 tio(1)

View file

@ -1,87 +0,0 @@
project('tio', 'c',
version : '3.9',
license : 'GPL-2.0-or-later',
meson_version : '>= 0.53.2',
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
)
# The tag date of the project_version(), update when the version bumps.
version_date = '2025-04-13'
# Test for dynamic baudrate configuration interface
compiler = meson.get_compiler('c')
enable_setspeed2 = false
enable_iossiospeed = false
if host_machine.system() != 'darwin'
if compiler.check_header('asm-generic/ioctls.h')
enable_setspeed2 = compiler.has_header_symbol('asm-generic/ioctls.h', 'TCGETS2')
endif
else
if compiler.check_header('IOKit/serial/ioss.h')
enable_iossiospeed = compiler.has_header_symbol('IOKit/serial/ioss.h', 'IOSSIOSPEED')
endif
endif
# Test for supported baudrates
test_baudrates = [
0,
50,
75,
110,
134,
150,
200,
300,
600,
1200,
1800,
2400,
4800,
7200,
9600,
14400,
19200,
28800,
38400,
57600,
76800,
115200,
230400,
460800,
500000,
576000,
921600,
1000000,
1152000,
1500000,
2000000,
2500000,
3000000,
3500000,
4000000 ]
baudrates = ''
baudrate_cases = ''
foreach rate : test_baudrates
baudrate = rate.to_string()
value = compiler.get_define('B' + baudrate, prefix: '#include <termios.h>')
if value != ''
baudrates = baudrates + baudrate + ' '
baudrate_cases = baudrate_cases + ' case ' + baudrate + ': baudrate = B' + baudrate + '; break;'
endif
endforeach
# Test for RS-485 support on Linux
enable_rs485 = false
if host_machine.system() == 'linux'
if compiler.check_header('linux/serial.h')
enable_rs485 = compiler.has_header_symbol('sys/ioctl.h', 'TIOCSRS485')
endif
endif
subdir('src')
install_man_pages = get_option('install_man_pages')
if install_man_pages
subdir('man')
endif

View file

@ -1,6 +0,0 @@
option('bashcompletiondir',
type : 'string',
description : 'Directory for bash completion scripts ["no" disables]')
option('install_man_pages',
type : 'boolean', value: true,
description : 'Install man pages')

View file

@ -0,0 +1,5 @@
gotty (1.1-1~utopic1) utopic; urgency=low
* Initial release
-- Jesper Larsen <knorr.jesper@gmail.com> Thu, 23 Oct 2014 16:38:40 +0200

View file

@ -0,0 +1 @@
9

View file

@ -0,0 +1,16 @@
Source: gotty
Section: comm
Priority: optional
Maintainer: Jesper Larsen <knorr.jesper@gmail.com>
Build-Depends: debhelper (>= 9.0.0), autotools-dev,
bash-completion
Standards-Version: 3.9.5
Homepage: http://gotty.io
Package: gotty
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Simple TTY terminal application
Go TTY or "gotty" is a really simple TTY terminal application
which features a simple commandline interface to easily connect
to TTY devices for basic input/output.

View file

@ -0,0 +1,30 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: gotty
Source: <http://gotty.io>
Files: *
Copyright: 2014 Martin Lund <martin.lund@keep-it-simple.com>
License: GPL-2
Files: debian/*
Copyright: 2014 Jesper Larsen <knorr.jesper@gmail.com>
License: GPL-2
License: GPL-2
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.
.
On Debian systems, the full text of the GNU General Public
License version 2 can be found in the file
`/usr/share/common-licenses/GPL-2'.

View file

@ -0,0 +1 @@
README

View file

@ -0,0 +1 @@
src/bash-completion/gotty

8
packaging/ubuntu/debian/rules Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
%:
dh $@ --with autotools-dev --with bash-completion

View file

@ -0,0 +1 @@
3.0 (quilt)

View file

@ -0,0 +1,3 @@
version=3
https://github.com/gotty/gotty/releases/ download/v(?:.*)/gotty-(.*).tar.xz

19
src/Makefile.am Normal file
View file

@ -0,0 +1,19 @@
AM_CFLAGS=-Wall
bin_PROGRAMS = tio
tio_SOURCES = tty.c \
options.c \
time.c \
main.c \
log.c \
error.c \
include/tio/tty.h \
include/tio/options.h \
include/tio/time.h \
include/tio/print.h \
include/tio/log.h \
include/tio/error.h
if ENABLE_BASH_COMPLETION
bashcompletiondir=@BASH_COMPLETION_DIR@
dist_bashcompletion_DATA=bash-completion/tio
endif

View file

@ -1,83 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <stdio.h>
#include <unistd.h>
#include "options.h"
#include "alert.h"
void blink_background(void)
{
// Turn on reverse video
printf("\e[?5h");
fflush(stdout);
usleep(200*1000);
// Turn on normal video
printf("\e[?5l");
fflush(stdout);
}
void sound_bell(void)
{
// Audio bell
printf("\a");
fflush(stdout);
}
void alert_connect(void)
{
switch (option.alert)
{
case ALERT_NONE:
break;
case ALERT_BELL:
sound_bell();
break;
case ALERT_BLINK:
blink_background();
break;
default:
break;
}
}
void alert_disconnect(void)
{
switch (option.alert)
{
case ALERT_NONE:
break;
case ALERT_BELL:
sound_bell();
usleep(200*1000);
sound_bell();
break;
case ALERT_BLINK:
blink_background();
usleep(200*1000);
blink_background();
break;
default:
break;
}
}

View file

@ -1,18 +0,0 @@
conf = configuration_data()
conf.set('baudrates', baudrates)
completion_file = configure_file( input: files('tio.in'),
output: 'tio',
configuration: conf )
bashcompletiondir = get_option('bashcompletiondir')
if bashcompletiondir == ''
bash_completion_dep = dependency('bash-completion', required: false)
if bash_completion_dep.found()
bashcompletiondir = join_paths(get_option('datadir'), 'bash-completion', 'completions')
endif
endif
if (bashcompletiondir != 'no') and (bashcompletiondir != '')
install_data(completion_file, install_dir: bashcompletiondir)
endif

104
src/bash-completion/tio Normal file
View file

@ -0,0 +1,104 @@
#
# Bash completion script for tio.
#
_tio()
{
local cur prev opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# The options we'll complete.
opts="-b --baudrate \
-d --databits \
-f --flow \
-s --stopbits \
-p --parity \
-o --output-delay \
-n --no-autoconnect \
-l --log \
-v --version \
-h --help"
# Complete the arguments to the options.
case "${prev}" in
-b | --baudrate)
local baudrates="0 \
50 \
75 \
110 \
134 \
150 \
300 \
600 \
1200 \
2400 \
4800 \
9600 \
19200 \
38400 \
57600 \
115200 \
230400 \
460800 \
500000 \
576000 \
921600 \
1000000 \
1152000 \
1500000 \
2000000 \
2500000 \
3000000 \
3500000 \
4000000"
COMPREPLY=( $(compgen -W "$baudrates" -- ${cur}) )
return 0
;;
-d | --databits)
COMPREPLY=( $(compgen -W "5 6 7 8" -- ${cur}) )
return 0
;;
-f | --flow)
COMPREPLY=( $(compgen -W "hard soft none" -- ${cur}) )
return 0
;;
-s | --stopbits)
COMPREPLY=( $(compgen -W "1 2" -- ${cur}) )
return 0
;;
-p | --parity)
COMPREPLY=( $(compgen -W "even odd none" -- ${cur}) )
return 0
;;
-o | --output-delay)
COMPREPLY=( $(compgen -W "0 1 10 100" -- ${cur}) )
return 0
;;
-n | --no-autoconnect)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-l | --log)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-v | --version)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
-h | --help)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
;;
*)
;;
esac
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
}
# Bind completion to tio command
complete -o default -F _tio tio

View file

@ -1,146 +0,0 @@
#
# Bash completion script for tio.
#
_tio()
{
local cur prev opts base ttys
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
# The options we'll complete.
opts="-b --baudrate \
-d --databits \
-f --flow \
-s --stopbits \
-p --parity \
-o --output-delay \
-o --output-line-delay \
--line-pulse-duration \
-a --auto-connect \
--exclude-devices \
--exclude-drivers \
--exclude-tids \
-n --no-reconnect \
-e --local-echo \
-l --log \
--log-file \
--log-directory \
--log-append \
--log-strip \
-m --map \
-t --timestamp \
--timestamp-format \
--timestamp-timeout \
-L --list \
-c --color \
-S --socket \
--input-mode \
--output-mode \
--rs-485 \
--rs-485-config \
--alert \
--mute \
--script \
--script-file \
--script-run \
--exec \
--complete-profiles \
-v --version \
-h --help"
# Complete the arguments to the options.
case "${prev}" in
-b | --baudrate)
local baudrates="@baudrates@"
COMPREPLY=( $(compgen -W "$baudrates" -- ${cur}) )
return 0
;;
-d | --databits)
COMPREPLY=( $(compgen -W "5 6 7 8" -- ${cur}) )
return 0
;;
-f | --flow)
COMPREPLY=( $(compgen -W "hard soft none" -- ${cur}) )
return 0
;;
-s | --stopbits)
COMPREPLY=( $(compgen -W "1 2" -- ${cur}) )
return 0
;;
-p | --parity)
COMPREPLY=( $(compgen -W "even odd none" -- ${cur}) )
return 0
;;
-o | --output-delay)
COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) )
return 0
;;
-O | --output-line-delay)
COMPREPLY=( $(compgen -W "1 10 100" -- ${cur}) )
return 0
;;
-a | --auto-connect)
COMPREPLY=( $(compgen -W "new latest none" -- ${cur}) )
return 0
;;
-m | --map)
COMPREPLY=( $(compgen -W "ICRNL IGNCR INLCR IFFESCC INLCRNL ICRCRNL IMSB2LSB OCRNL ODELBS ONLCRNL OLTU ONULBRK OIGNCR" -- ${cur}) )
return 0
;;
--timestamp-format)
COMPREPLY=( $(compgen -W "24hour 24hour-start 24hour-delta iso8601 epoch epoch-usec" -- ${cur}) )
return 0
;;
-c | --color)
COMPREPLY=( $(compgen -W "$(seq 0 255) none list" -- ${cur}) )
return 0
;;
-S | --socket)
COMPREPLY=( $(compgen -W "unix: inet: inet6:" -- ${cur}) )
return 0
;;
--input-mode)
COMPREPLY=( $(compgen -W "normal hex line" -- ${cur}) )
return 0
;;
--output-mode)
COMPREPLY=( $(compgen -W "normal hex" -- ${cur}) )
return 0
;;
--rs-485-config)
COMPREPLY=( $(compgen -W "RTS_ON_SEND RTS_AFTER_SEND RTS_DELAY_BEFORE_SEND RTS_DELAY_AFTER_SEND RX_DURING_TX" -- ${cur}) )
return 0
;;
--alert)
COMPREPLY=( $(compgen -W "none bell blink" -- ${cur}) )
return 0
;;
--script-run)
COMPREPLY=( $(compgen -W "once always never" -- ${cur}) )
return 0
;;
*)
;;
esac
case "${cur}" in
-*)
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0
;;
esac
profiles="`tio --complete-profiles`"
if [ -d /dev/serial/by-id ]; then
ttys=$(printf '%s\n' /dev/tty* /dev/serial/by-id/*)
else
ttys=$(printf '%s\n' /dev/tty*)
fi
COMPREPLY=( $(compgen -W "${ttys} ${profiles}" -- ${cur}) )
return 0
}
# Bind completion to tio command
complete -o default -F _tio tio

View file

@ -1,788 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2020-2022 Liam Beguin
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#define _GNU_SOURCE
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <libgen.h>
#include <errno.h>
#include <regex.h>
#include <glib.h>
#include "configfile.h"
#include "timestamp.h"
#include "print.h"
#include "rs485.h"
#include "misc.h"
#define CONFIG_GROUP_NAME_DEFAULT "default"
#define CONFIG_GROUP_INCLUDE_PREFIX "include "
#define MAX_LINE_LENGTH 1024
struct config_t config = {};
static void config_file_load(const char *filename, GString *buffer, bool test);
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test);
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
{
(void)dest;
GError *error = NULL;
bool mismatch = true;
gchar *string = g_key_file_get_string(key_file, group, key, &error);
if (error != NULL)
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
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)
{
(void)dest;
GError *error = NULL;
int value = g_key_file_get_integer(key_file, group, key, &error);
if (error != NULL)
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
if ((value < min) || (value > max))
{
tio_error_print("%s: Invalid %s value '%d' in %s profile", config.path, key, value, group);
exit(EXIT_FAILURE);
}
*dest = value;
}
static void config_get_bool(GKeyFile *key_file, gchar *group, gchar *key, bool *dest)
{
(void)dest;
GError *error = NULL;
bool value = g_key_file_get_boolean(key_file, group, key, &error);
if (error != NULL)
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
*dest = value;
}
static void config_parse_keys(GKeyFile *key_file, char *group)
{
char *string = NULL;
config_get_string(key_file, group, "device", &config.device, NULL);
config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX);
config_get_integer(key_file, group, "databits", &option.databits, 5, 8);
config_get_string(key_file, group, "flow", &string, "none", "hard", "soft", NULL);
if (string != NULL)
{
option_parse_flow(string, &option.flow);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "stopbits", &option.stopbits, 1, 2);
config_get_string(key_file, group, "parity", &string, "odd", "even", "none", "mark", "space", NULL);
if (string != NULL)
{
option_parse_parity(string, &option.parity);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX);
config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX);
config_get_string(key_file, group, "line-pulse-duration", &string, NULL);
if (string != NULL)
{
option_parse_line_pulse_duration(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "auto-connect", &string, "new", "latest", "direct", NULL);
if (string != NULL)
{
option_parse_auto_connect(string, &option.auto_connect);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "exclude-devices", &option.exclude_devices, NULL);
config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL);
config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL);
config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect);
config_get_bool(key_file, group, "local-echo", &option.local_echo);
config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL);
if (string != NULL)
{
option_parse_input_mode(string, &option.input_mode);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "output-mode", &string, NULL);
if (string != NULL)
{
option_parse_output_mode(string, &option.output_mode);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "timestamp", (bool*) &option.timestamp);
if (option.timestamp != TIMESTAMP_NONE)
{
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", "epoch", "epoch-usec", NULL);
if (string != NULL)
{
option_parse_timestamp(string, &option.timestamp);
g_free((void *)string);
string = NULL;
}
}
config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX);
config_get_bool(key_file, group, "log", &option.log);
config_get_string(key_file, group, "log-file", &option.log_filename, NULL);
config_get_string(key_file, group, "log-directory", &option.log_directory, 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", &string, NULL);
if (string != NULL)
{
option_parse_mappings(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "color", &string, NULL);
if (string != NULL)
{
if (strcmp(string, "list") == 0)
{
// Ignore
}
else if (strcmp(string, "none") == 0)
{
option.color = -1; // No color
}
else if (strcmp(string, "bold") == 0)
{
option.color = 256; // Bold
}
else
{
option.color = atoi(string);
if ((option.color < 0) || (option.color > 255))
{
tio_error_print("%s: Invalid color value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
}
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "socket", &option.socket, NULL);
config_get_bool(key_file, group, "rs-485", &option.rs485);
config_get_string(key_file, group, "rs-385-config", &string, NULL);
if (string != NULL)
{
rs485_parse_config(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "alert", &string, "bell", "blink", "none", NULL);
if (string != NULL)
{
option_parse_alert(string, &option.alert);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "mute", &option.mute);
config_get_string(key_file, group, "script", &option.script, NULL);
config_get_string(key_file, group, "script-file", &option.script_filename, NULL);
config_get_string(key_file, group, "script-run", &string, NULL);
if (string != NULL)
{
option_parse_script_run(string, &option.script_run);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "exec", &option.exec, 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)
{
char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg)
{
if (asprintf(&config.path, "%s/tio/config", xdg) != -1)
{
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(config.path);
}
}
char *home = getenv("HOME");
if (home)
{
if (asprintf(&config.path, "%s/.config/tio/config", home) != -1)
{
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(config.path);
}
if (asprintf(&config.path, "%s/.tioconfig", home) != -1)
{
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(config.path);
}
}
config.path = NULL;
return -EINVAL;
}
void config_file_show_profiles(void)
{
GString *config_buffer;
GError *error = NULL;
GKeyFile *keyfile;
// Reset configuration
memset(&config, 0, sizeof(struct config_t));
// Find config file
if (config_file_resolve() != 0)
{
// None found - stop parsing
return;
}
// Load content of configuration file into buffer
config_buffer = g_string_new(NULL);
config_file_load(config.path, config_buffer, false);
// Load configuration
keyfile = g_key_file_new();
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
g_error_free(error);
goto error_load;
}
// 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;
}
// Skip group with include directive
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
{
continue;
}
printf("%s ", group[i]);
}
g_strfreev(group);
error_load:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}
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[14] = {};
regex_t regex;
assert(str != NULL);
assert(pattern != NULL);
assert(device != NULL);
char *string = calloc(PATH_MAX, 1);
if (string == NULL)
{
tio_debug_printf("Failure allocating string memory\n");
return NULL;
}
strncpy(string, device, PATH_MAX - 1);
/* 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
tio_error_print("Failure compiling regular expression '%s'\n", pattern);
exit(EXIT_FAILURE);
}
regmatch_t matches[regex.re_nsub + 1];
int status = regexec(&regex, str, regex.re_nsub + 1, matches, 0);
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 l = matches[i].rm_so; l < matches[i].rm_eo; l++)
{
tio_debug_printf_raw("%c", str[l]);
replacement_str[k++] = str[l];
}
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)
{
tio_debug_printf("No regex match\n");
goto error;
}
else
{
char error_message[100];
regerror(status, &regex, error_message, sizeof(error_message));
tio_debug_printf("Regex match failed: %s", error_message);
goto error;
}
regfree(&regex);
return string;
error:
regfree(&regex);
return NULL;
}
static void config_file_process_line(const char *line, GString *buffer, GList **included_files, bool test)
{
if (strncmp(line, "[include ", 9) == 0 && line[strlen(line) - 2] == ']')
{
char include_filename[MAX_LINE_LENGTH];
// Construct the format string safely
char format_string[50];
snprintf(format_string, sizeof(format_string), "[include %%%ds]", MAX_LINE_LENGTH - 1);
int ret = sscanf(line, format_string, include_filename);
if (ret != 1)
{
return;
}
// Remove the trailing ']' character
include_filename[strlen(include_filename) - 1] = '\0';
if (g_list_find_custom(*included_files, include_filename, (GCompareFunc)strcmp) != NULL)
{
// Already included, avoid recursion
return;
}
// Add to included files list
*included_files = g_list_append(*included_files, g_strdup(include_filename));
// Process the included file
config_file_process(include_filename, buffer, included_files, test);
}
else
{
// Normal line, add to buffer
g_string_append(buffer, line);
}
}
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test)
{
if (test)
{
// Test that configuration file can be parsed
GError *error = NULL;
GKeyFile *keyfile = g_key_file_new();
if (g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error) == false)
{
tio_error_print("Failure loading file %s: %s", filename, error->message);
g_key_file_free(keyfile);
g_error_free(error);
exit(EXIT_FAILURE);
}
}
FILE *file = fopen(filename, "r");
if (file)
{
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), file))
{
config_file_process_line(line, buffer, included_files, test);
}
fclose(file);
}
}
static void config_file_load(const char *filename, GString *buffer, bool test)
{
char current_dir[PATH_MAX] = ".";
char *config_file_dir = dirname(strdup(config.path));
GList *included_files = NULL;
getcwd(current_dir, PATH_MAX);
// Change to the directory of the configuration file
chdir(config_file_dir);
config_file_process(filename, buffer, &included_files, test);
// Restore current directory
chdir(current_dir);
// Free memory
g_list_free_full(included_files, g_free);
free(config_file_dir);
}
void config_file_parse(void)
{
// Find config file
if (config_file_resolve() != 0)
{
// None found - stop parsing
return;
}
if (option.target == NULL)
{
return;
}
GString *config_buffer = g_string_new(NULL);
GKeyFile *keyfile = g_key_file_new();
GList *included_files = NULL;
GError *error = NULL;
config_file_load(config.path, config_buffer, true);
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
tio_error_print("Failure loading file %s: %s", config.path, error->message);
g_string_free(config_buffer, TRUE);
g_key_file_free(keyfile);
g_error_free(error);
exit(EXIT_FAILURE);
}
// Parse default group/section
if (g_key_file_has_group(keyfile, CONFIG_GROUP_NAME_DEFAULT))
{
config_parse_keys(keyfile, CONFIG_GROUP_NAME_DEFAULT);
}
// Parse target
if (g_key_file_has_group(keyfile, option.target))
{
config.active_group = strdup(option.target);
config_parse_keys(keyfile, option.target);
}
else
{
// Find group by pattern
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
continue;
}
// Lookup 'pattern' key
gchar *pattern = g_key_file_get_string(keyfile, group[i], "pattern", &error);
if (error != NULL)
{
g_error_free(error);
error = NULL;
continue;
}
// Lookup 'device' key
gchar *device = g_key_file_get_string(keyfile, group[i], "device", &error);
if (error != NULL)
{
g_error_free(error);
error = NULL;
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
device = strdup(config.device);
// Parse found group (may replace config.device)
config_parse_keys(keyfile, group[i]);
// Update configuration
config.active_group = strdup(group[i]);
config.device = device; // Restore new device string
break;
}
}
g_strfreev(group);
}
// Cleanup
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
g_list_free_full(included_files, g_free);
atexit(&config_exit);
}
void config_exit(void)
{
free(config.active_group);
free(config.path);
free(config.device);
}
void config_file_print(void)
{
if (config.path != NULL)
{
tio_printf(" Active configuration file: %s", config.path);
if (config.active_group != NULL)
{
tio_printf(" Active configuration profile: %s", config.active_group);
}
}
}
void config_list_targets(void)
{
memset(&config, 0, sizeof(struct config_t));
// Find config file
if (config_file_resolve() != 0)
{
// None found
return;
}
GKeyFile *keyfile;
GError *error = NULL;
keyfile = g_key_file_new();
GString *config_buffer = g_string_new(NULL);
config_file_load(config.path, config_buffer, false);
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
g_error_free(error);
goto cleanup;
}
// Get all group names
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
if (num_groups == 0)
{
goto cleanup;
}
printf("\nConfiguration profiles (%s)\n", config.path);
printf("--------------------------------------------------------------------------------\n");
int j = 1;
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
continue;
}
// Skip group with include directive
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
{
continue;
}
printf("%-19s ", group[i]);
if (j++ % 4 == 0)
{
putchar('\n');
}
}
if ((j-1) % 4 != 0)
{
putchar('\n');
}
g_strfreev(group);
cleanup:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}

View file

@ -1,38 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2020 Liam Beguin
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
struct config_t
{
char *path;
char *active_group;
char *device;
};
extern struct config_t config;
void config_file_print(void);
void config_file_parse(void);
void config_exit(void);
void config_file_show_profiles(void);
void config_list_targets(void);

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,79 +19,18 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#define _GNU_SOURCE // To access vasprintf
#include <stdio.h> #include <stdio.h>
#include "print.h" #include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "tio/options.h"
#include "tio/print.h"
#include "tio/error.h"
static char error[2][1000]; char error[1000];
static bool in_session = false;
bool error_normal = true;
void switch_error_output_mode(void)
{
error_normal = false;
}
void error_enter_session_mode(void)
{
in_session = true;
}
void error_printf_(const char *format, ...)
{
va_list args;
char *line;
va_start(args, format);
vasprintf(&line, format, args);
if (in_session)
{
if (print_tainted)
{
putchar('\n');
}
ansi_error_printf("[%s] %s", timestamp_current_time(), line);
}
else
{
fprintf(stderr, "%s\n", line);
}
va_end(args);
print_tainted = false;
free(line);
}
void tio_error_printf(const char *format, ...)
{
va_list args;
va_start(args, format);
vsnprintf(error[0], 1000, format, args);
va_end(args);
}
void tio_error_printf_silent(const char *format, ...)
{
va_list args;
va_start(args, format);
vsnprintf(error[1], 1000, format, args);
va_end(args);
}
void error_exit(void) void error_exit(void)
{ {
if (error[0][0] != 0) if ((error[0] != 0) && (option.no_autoconnect))
{ printf("\rError: %s\r\n", error);
/* Print error */
error_printf_("Error: %s", error[0]);
}
else if ((error[1][0] != 0) && (option.no_reconnect))
{
/* Print silent error */
error_printf_("Error: %s", error[1]);
}
} }

226
src/fs.c
View file

@ -1,226 +0,0 @@
/*
* 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 <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 = strndup(path, PATH_MAX);
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;
}
#if defined(__linux__) && defined(STATX_BTIME)
// 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)
{
return -1;
}
if (statx(fd, "", AT_EMPTY_PATH, STATX_ALL, &stx) != 0)
{
close(fd);
return -1;
}
// Close the file
close(fd);
return stx.stx_btime.tv_sec + stx.stx_btime.tv_nsec / 1e9;
}
#elif defined(__APPLE__) || defined(__MACH__)
double fs_get_creation_time(const char *path)
{
struct stat st;
if (stat(path, &st) != 0)
{
return -1;
}
return st.st_mtimespec.tv_sec + st.st_mtimespec.tv_nsec / 1e9;
}
#else
double fs_get_creation_time(const char *path)
{
struct stat st;
if (stat(path, &st) != 0)
{
return -1;
}
return (double) st.st_ctime;
}
#endif

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,17 +19,17 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #ifndef ERROR_H
#define ERROR_H
#include <stdbool.h>
extern bool error_normal;
#define TIO_SUCCESS 0 #define TIO_SUCCESS 0
#define TIO_ERROR 1 #define TIO_ERROR 1
void tio_error_printf(const char *format, ...); extern char error[1000];
void tio_error_printf_silent(const char *format, ...);
#define error_printf(format, args...) \
snprintf (error, 1000, format, ## args);
void error_exit(void); void error_exit(void);
void error_enter_session_mode(void);
void switch_error_output_mode(void); #endif

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,11 +19,12 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #ifndef LOG_H
#define LOG_H
int log_open(const char *filename); void log_open(const char *filename);
void log_printf(const char *format, ...); void log_write(char c);
void log_putc(char c);
void log_close(void); void log_close(void);
void log_exit(void); void log_exit(void);
const char * log_get_filename(void);
#endif

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2024 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,13 +19,27 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #ifndef OPTIONS_H
#define OPTIONS_H
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <limits.h>
#include <termios.h>
#include <sys/param.h>
bool fs_dir_exists(const char *path); /* Options */
bool fs_file_exists(const char *format, ...); struct option_t
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, ...); const char *tty_device;
double fs_get_creation_time(const char *path); bool log;
const char *log_filename;
bool no_autoconnect;
int output_delay;
struct termios tio;
};
extern struct option_t option;
void parse_options(int argc, char *argv[]);
#endif

49
src/include/tio/print.h Normal file
View file

@ -0,0 +1,49 @@
/*
* tio - the simple TTY terminal I/O application
*
* Copyright (c) 2014-2016 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.
*/
#ifndef PRINT_H
#define PRINT_H
#define ANSI_COLOR_GRAY "\x1b[1;30m"
#define ANSI_COLOR_RED "\x1b[1;31m"
#define ANSI_COLOR_GREEN "\x1b[1;32m"
#define ANSI_COLOR_YELLOW "\x1b[1;33m"
#define ANSI_COLOR_BLUE "\x1b[1;34m"
#define ANSI_COLOR_PINK "\x1b[1;35m"
#define ANSI_COLOR_CYAN "\x1b[1;36m"
#define ANSI_COLOR_WHITE "\x1b[1;37m"
#define ANSI_COLOR_RESET "\x1b[0m"
#define color_printf(format, args...) \
fprintf (stdout, "\r" ANSI_COLOR_YELLOW format ANSI_COLOR_RESET "\r\n", ## args); \
fflush(stdout);
#ifdef DEBUG
#define debug_printf(format, args...) \
fprintf (stdout, "[debug] " format, ## args)
#define debug_printf_raw(format, args...) \
fprintf (stdout, "" format, ## args)
#else
#define debug_printf(format, args...)
#define debug_printf_raw(format, args...)
#endif
#endif

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,6 +19,9 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #ifndef TIME_H
#define TIME_H
int setspeed(int fd, int baudrate); char * current_time(void);
#endif

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,15 +19,15 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #ifndef TTY_H
#define TTY_H
typedef enum #define KEY_CTRL_T 0x14
{ #define KEY_Q 0x71
ALERT_NONE,
ALERT_BELL,
ALERT_BLINK,
ALERT_END,
} alert_t;
void alert_connect(void); void configure_stdout(void);
void alert_disconnect(void); void restore_stdout(void);
int connect_tty(void);
void wait_for_tty_device(void);
#endif

218
src/log.c
View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,224 +19,44 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#define _GNU_SOURCE // To access vasprintf #include <stdio.h>
#include <sys/time.h> #include <stdlib.h>
#include <libgen.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include "print.h" #include "tio/options.h"
#include "fs.h" #include "tio/print.h"
#define IS_ESC_CSI_INTERMEDIATE_CHAR(c) ((c >= 0x20) && (c <= 0x3F)) static FILE *fp;
#define IS_ESC_END_CHAR(c) ((c >= 0x30) && (c <= 0x7E)) static bool error = false;
#define IS_CTRL_CHAR(c) ((c >= 0x00) && (c <= 0x1F))
static FILE *fp = NULL; void log_open(const char *filename)
static char file_buffer[BUFSIZ];
static const char *log_filename = NULL;
static char *date_time(void)
{ {
static char date_time_string[50]; fp = fopen(filename, "w+");
struct tm *tm;
struct timeval tv;
gettimeofday(&tv, NULL);
tm = localtime(&tv.tv_sec);
strftime(date_time_string, sizeof(date_time_string), "%Y-%m-%dT%H:%M:%S", tm);
return date_time_string;
}
int log_open(const char *filename)
{
char *automatic_filename;
char *dir_plus_automatic_filename;
if (filename == NULL)
{
// 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",
option_auto_connect_state_to_string(option.auto_connect),
date_time());
}
if (option.log_directory != NULL)
{
if (fs_dir_exists(option.log_directory) == false)
{
tio_error_printf("Log directory not found");
exit(EXIT_FAILURE);
}
asprintf(&dir_plus_automatic_filename, "%s/%s", option.log_directory, automatic_filename);
filename = dir_plus_automatic_filename;
}
else
{
filename = automatic_filename;
}
}
log_filename = filename;
// Open log file
if (option.log_append)
{
// Append to existing log file
fp = fopen(filename, "a+");
}
else
{
// Truncate existing log file
fp = fopen(filename, "w+");
}
if (fp == NULL) if (fp == NULL)
{ {
tio_warning_printf("Could not open log file %s (%s)", filename, strerror(errno)); error = true;
return -1; exit(EXIT_FAILURE);
} }
// Enable line buffering
setvbuf(fp, file_buffer, _IOLBF, BUFSIZ);
return 0;
} }
bool log_strip(char c) void log_write(char c)
{ {
static char previous_char = 0; if (fp != NULL)
static bool esc_sequence = false; fputc(c, fp);
bool strip = false;
/* Detect if character should be stripped or not */
switch (c)
{
case 0xa:
/* Line feed / new line */
/* Reset ESC sequence just in case something went wrong with the
* escape sequence parsing. */
esc_sequence = false;
break;
case 0x1b:
/* Escape */
strip = true;
break;
case 0x5b:
/* Left bracket */
if (previous_char == 0x1b)
{
// Start of ESC sequence
esc_sequence = true;
strip = true;
}
break;
default:
if (IS_CTRL_CHAR(c))
{
/* Strip ASCII control characters */
strip = true;
break;
}
else
if ((esc_sequence) && (IS_ESC_CSI_INTERMEDIATE_CHAR(c)))
{
strip = true;
break;
}
else
if ((esc_sequence) && (IS_ESC_END_CHAR(c)))
{
esc_sequence = false;
strip = true;
break;
}
break;
}
previous_char = c;
return strip;
}
void log_printf(const char *format, ...)
{
if (fp == NULL)
{
return;
}
char *line;
va_list(args);
va_start(args, format);
vasprintf(&line, format, args);
va_end(args);
fwrite(line, strlen(line), 1, fp);
free(line);
}
void log_putc(char c)
{
if (fp == NULL)
{
return;
}
if (option.output_mode == OUTPUT_MODE_HEX)
{
fprintf(fp, "%02x ", (unsigned char) c);
}
else
{
if (option.log_strip)
{
if (log_strip(c) == false)
{
fputc(c, fp);
}
}
else
{
fputc(c, fp);
}
}
} }
void log_close(void) void log_close(void)
{ {
if (fp != NULL) if (fp != NULL)
{
fclose(fp); fclose(fp);
tio_printf("Saved log to file %s", log_filename);
fp = NULL;
log_filename = NULL;
}
} }
void log_exit(void) void log_exit(void)
{ {
if ((option.log) && (log_filename != NULL)) if (option.log)
{
log_close(); log_close();
}
}
const char *log_get_filename(void) if (error)
{ printf("Error: Could not open log file %s (%s)\n", option.log_filename, strerror(errno));
return log_filename;
} }

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -22,125 +22,42 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include "version.h" #include "tio/options.h"
#include "config.h" #include "tio/tty.h"
#include "options.h" #include "tio/log.h"
#include "configfile.h" #include "tio/error.h"
#include "tty.h" #include "tio/print.h"
#include "log.h"
#include "error.h"
#include "print.h"
#include "signals.h"
#include "socket.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
int status = 0; int status;
/* Handle received signals */ /* Install error exit handler */
signal_handlers_install();
/* Add error exit handler */
atexit(&error_exit); atexit(&error_exit);
/* Parse command-line options (1st pass) */ /* Parse options */
options_parse(argc, argv); parse_options(argc, argv);
if (option.complete_profiles)
{
config_file_show_profiles();
return status;
}
/* Parse configuration file */
config_file_parse();
/* Parse command-line options (2nd pass) */
options_parse_final(argc, argv);
/* Configure tty device */
tty_configure();
/* Disable line buffering in stdout. This is necessary if we
* want things like local echo to work correctly. */
setvbuf(stdout, NULL, _IONBF, 0);
/* Configure input terminal */
if (isatty(fileno(stdin)))
{
stdin_configure();
}
else
{
// Enter non interactive mode
interactive_mode = false;
}
/* Switch error output format */
switch_error_output_mode();
/* Configure output terminal */ /* Configure output terminal */
if (isatty(fileno(stdout))) configure_stdout();
{
stdout_configure();
}
else
{
// No color when piping
option.color = -1;
}
/* Add log exit handler */ /* Install log exit handler */
atexit(&log_exit); atexit(&log_exit);
/* Create log file */ /* Create log file */
if (option.log) if (option.log)
{
log_open(option.log_filename); log_open(option.log_filename);
}
/* Initialize ANSI text formatting (colors etc.) */
print_init_ansi_formatting();
/* Change error printing mode */
error_enter_session_mode();
/* Print launch hints */
tio_printf("tio %s", VERSION);
if (interactive_mode)
{
tio_printf("Press ctrl-%c q to quit", option.prefix_key);
} else
{
tio_printf("Non-interactive mode enabled");
tio_printf("Press ctrl-c to quit");
}
/* Open socket */
if (option.socket)
{
socket_configure();
}
/* Spawn input handling into separate thread */
tty_input_thread_create();
/* Wait for input to be ready */
tty_input_thread_wait_ready();
/* Connect to tty device */ /* Connect to tty device */
if (option.no_reconnect) if (option.no_autoconnect)
{ status = connect_tty();
tty_search();
status = tty_connect();
}
else else
{ {
/* Enter connect loop */ /* Enter connect loop */
while (true) while (true)
{ {
tty_wait_for_device(); wait_for_tty_device();
tty_connect(); status = connect_tty();
} }
} }

View file

@ -1,76 +0,0 @@
# Generate version header
version_h = vcs_tag(command : ['git', 'describe', '--tags', '--always', '--dirty'],
input : 'version.h.in',
output :'version.h',
replace_string:'@VERSION@')
config_h = configuration_data()
config_h.set('BAUDRATE_CASES', baudrate_cases)
configure_file(output: 'config.h', configuration: config_h)
tio_sources = [
'error.c',
'log.c',
'main.c',
'options.c',
'misc.c',
'tty.c',
'print.c',
'configfile.c',
'signals.c',
'socket.c',
'setspeed.c',
'rs485.c',
'timestamp.c',
'alert.c',
'xymodem.c',
'script.c',
'fs.c',
'readline.c',
version_h
]
foreach name: ['lua-5.4', 'lua-5.3', 'lua-5.2', 'lua-5.1', 'lua']
lua_dep = dependency(name, version: '>=5.1', required: false)
if lua_dep.found()
break
endif
endforeach
if not lua_dep.found()
error('Lua could not be found!')
endif
tio_dep = [
dependency('threads', required: true),
dependency('glib-2.0', required: true),
lua_dep
]
if host_machine.system() == 'darwin'
iokit_dep = dependency('appleframeworks', modules: ['IOKit'], required: true)
corefoundation_dep = dependency('appleframeworks', modules: ['CoreFoundation'], required: true)
tio_dep += [iokit_dep, corefoundation_dep]
endif
tio_c_args = ['-Wshadow','-Wno-unused-result']
if enable_setspeed2
tio_c_args += '-DHAVE_TERMIOS2'
endif
if enable_iossiospeed
tio_c_args += '-DHAVE_IOSSIOSPEED'
endif
if enable_rs485
tio_c_args += '-DHAVE_RS485'
endif
executable('tio',
tio_sources,
c_args: tio_c_args,
dependencies: tio_dep,
install: true )
subdir('bash-completion')

View file

@ -1,253 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#define _GNU_SOURCE // For FNM_EXTMATCH
#include <unistd.h>
#include <string.h>
#include <poll.h>
#include <sys/wait.h>
#include <fnmatch.h>
#include <regex.h>
#include <errno.h>
#include "print.h"
void delay(long ms)
{
struct timespec ts;
if (ms <= 0)
{
return;
}
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000;
nanosleep(&ts, NULL);
}
int ctrl_key_code(unsigned char key)
{
if ((key >= 'a') && (key <= 'z'))
{
return key & ~0x60;
}
return -1;
}
bool regex_match(const char *string, const char *pattern)
{
regex_t regex;
int status;
if (regcomp(&regex, pattern, REG_EXTENDED | REG_NOSUB) != 0)
{
// No match
return false;
}
status = regexec(&regex, string, (size_t) 0, NULL, 0);
regfree(&regex);
if (status != 0)
{
// No match
return false;
}
// Match
return true;
}
int read_poll(int fd, void *data, size_t len, int timeout)
{
struct pollfd fds;
int ret = 0;
fds.events = POLLIN;
fds.fd = fd;
/* Wait data available */
ret = poll(&fds, 1, timeout);
if (ret < 0)
{
tio_error_print("%s", strerror(errno));
return ret;
}
else if (ret > 0)
{
if (fds.revents & POLLIN)
{
// Read ready data
return read(fd, data, len);
}
}
/* 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
void *base62_encode(unsigned long num, char *output)
{
const char base62_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
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, &current_time_ts) == -1)
{
// Error
return -1;
}
return current_time_ts.tv_sec + current_time_ts.tv_nsec / 1e9;
}
bool match_patterns(const char *string, const char *patterns)
{
char *pattern;
char *patterns_copy;
if ((string == NULL) || (patterns == NULL))
{
return false;
}
patterns_copy = strdup(patterns);
// Tokenize the patterns string using strtok
pattern = strtok(patterns_copy, ",");
while (pattern != NULL)
{
// Check if the string matches the current pattern
#ifdef FNM_EXTMATCH
if (fnmatch(pattern, string, FNM_EXTMATCH) == 0)
#else
if (fnmatch(pattern, string, 0) == 0)
#endif
{
free(patterns_copy);
return true;
}
// Move to the next pattern
pattern = strtok(NULL, ",");
}
free(patterns_copy);
return false;
}
// Function that forks subprocess, redirects its stdin and stdout to the
// specified filedescriptor, and runs command.
int execute_shell_command(int fd, const char *command)
{
pid_t pid;
int status;
// Fork a child process
pid = fork();
if (pid == -1)
{
// Error occurred
tio_error_print("fork() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
// Child process
tio_printf("Executing shell command '%s'", command);
// Redirect stdout and stderr to the file descriptor
if (dup2(fd, STDOUT_FILENO) == -1 || dup2(fd, STDERR_FILENO) == -1)
{
tio_error_print("dup2() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
// Execute the shell command
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
// If execlp() returns, it means an error occurred
perror("execlp");
tio_error_print("execlp() failed (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
else
{
// Parent process
// Wait for the child process to finish
waitpid(pid, &status, 0);
if (WIFEXITED(status))
{
tio_printf("Command exited with status %d", WEXITSTATUS(status));
return WEXITSTATUS(status);
}
else
{
tio_error_printf("Child process exited abnormally\n");
return -1;
}
}
return 0;
}
void clear_line()
{
print("\r\033[K");
}

View file

@ -1,38 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdbool.h>
#include <stdio.h>
#define UNUSED(expr) do { (void)(expr); } while (0)
void delay(long ms);
int ctrl_key_code(unsigned char key);
bool regex_match(const char *string, const char *pattern);
unsigned long djb2_hash(const unsigned char *str);
void *base62_encode(unsigned long num, char *output);
int read_poll(int fd, void *data, size_t len, int timeout);
double get_current_time(void);
bool match_patterns(const char *string, const char *patterns);
int execute_shell_command(int fd, const char *command);
void clear_line();

File diff suppressed because it is too large Load diff

View file

@ -1,135 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "script.h"
#include "timestamp.h"
#include "alert.h"
#include "tty.h"
typedef enum
{
INPUT_MODE_NORMAL,
INPUT_MODE_HEX,
INPUT_MODE_LINE,
INPUT_MODE_END,
} input_mode_t;
typedef enum
{
OUTPUT_MODE_NORMAL,
OUTPUT_MODE_HEX,
OUTPUT_MODE_END,
} output_mode_t;
/* Options */
struct option_t
{
char *target;
int baudrate;
int databits;
flow_t flow;
int stopbits;
parity_t parity;
int output_delay;
int output_line_delay;
int dtr_pulse_duration;
int rts_pulse_duration;
int cts_pulse_duration;
int dsr_pulse_duration;
int dcd_pulse_duration;
int ri_pulse_duration;
bool no_reconnect;
auto_connect_t auto_connect;
bool log;
bool log_append;
bool log_strip;
bool local_echo;
timestamp_t timestamp;
char *log_filename;
char *log_directory;
char *socket;
int color;
input_mode_t input_mode;
output_mode_t output_mode;
char prefix_code;
char prefix_key;
bool prefix_enabled;
bool mute;
bool rs485;
uint32_t rs485_config_flags;
int32_t rs485_delay_rts_before_send;
int32_t rs485_delay_rts_after_send;
alert_t alert;
bool complete_profiles;
char *script;
char *script_filename;
script_run_t script_run;
int timestamp_timeout;
char *exclude_devices;
char *exclude_drivers;
char *exclude_tids;
int hex_n_value;
bool vt100;
char *exec;
bool map_i_nl_cr;
bool map_i_cr_nl;
bool map_ign_cr;
bool map_i_ff_escc;
bool map_i_nl_crnl;
bool map_i_cr_crnl;
bool map_o_cr_nl;
bool map_o_nl_crnl;
bool map_o_del_bs;
bool map_o_ltu;
bool map_o_nulbrk;
bool map_i_msb2lsb;
bool map_o_ign_cr;
};
extern struct option_t option;
void options_print();
void options_parse(int argc, char *argv[]);
void options_parse_final(int argc, char *argv[]);
int option_string_to_integer(const char *string, int *value, const char *desc, int min, int max);
void option_parse_flow(const char *arg, flow_t *flow);
void option_parse_parity(const char *arg, parity_t *parity);
void option_parse_output_mode(const char *arg, output_mode_t *mode);
void option_parse_input_mode(const char *arg, input_mode_t *mode);
void option_parse_line_pulse_duration(const char *arg);
void option_parse_script_run(const char *arg, script_run_t *script_run);
void option_parse_alert(const char *arg, alert_t *alert);
void option_parse_auto_connect(const char *arg, auto_connect_t *auto_connect);
const char *option_auto_connect_state_to_string(auto_connect_t strategy);
void option_parse_timestamp(const char *arg, timestamp_t *timestamp);
const char* option_timestamp_format_to_string(timestamp_t timestamp);
void option_parse_mappings(const char *map);

View file

@ -1,112 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "print.h"
bool print_tainted = false;
char ansi_format[30];
void print_hex(char c)
{
print_tainted = true;
printf("%02x ", (unsigned char) c);
}
void print_normal(char c)
{
print_tainted = true;
putchar(c);
}
void print_init_ansi_formatting()
{
if (option.color == 256)
{
// Set bold text with no color changes
sprintf(ansi_format, "\e[1m");
}
else
{
// Set bold text with user defined ANSI color
sprintf(ansi_format, "\e[1;38;5;%dm", option.color);
}
}
void tio_printf_array(const char *array)
{
int i = 0, j = 0;
tio_printf("");
while (array[i])
{
if (array[i] == '\n')
{
const char *line = &array[j];
char *line_copy = strndup(line, i-j);
tio_printf_raw("%s\r", line_copy);
free(line_copy);
j = i;
}
i++;
}
tio_printf("");
}
void print_tainted_set()
{
print_tainted = true;
}
void print(const char *format, ...)
{
va_list args;
va_start(args, format);
vprintf(format, args);
fflush(stdout);
va_end(args);
print_tainted = true;
}
void print_padded(char *string, size_t length, char pad_char)
{
size_t padding = 0;
size_t string_length = 0;
size_t i;
string_length = strlen(string);
if (string_length < length)
{
padding += length - string_length;
printf("%s", string);
for (i=0; i<padding; i++)
{
putchar(pad_char);
}
}
else
{
printf("%s", string);
}
}

View file

@ -1,143 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#include "error.h"
#include "options.h"
#include "timestamp.h"
extern bool print_tainted;
extern char ansi_format[];
#define ANSI_RESET "\e[0m"
#define ansi_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stdout, "\r" format "\r\n", ## args); \
else \
fprintf (stdout, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
} \
}
#define ansi_error_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stderr, "\r" format "\r\n", ## args); \
else \
fprintf (stderr, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
fflush(stderr); \
} \
}
#define ansi_printf_raw(format, args...) \
{ \
if (!option.mute) \
{ \
if (option.color < 0) \
fprintf (stdout, format, ## args); \
else \
fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \
} \
}
#define tio_warning_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
if (option.color < 0) \
fprintf (stdout, "\r[%s] Warning: " format "\r\n", timestamp_current_time(), ## args); \
else \
ansi_printf("[%s] Warning: " format, timestamp_current_time(), ## args); \
print_tainted = false; \
} \
}
#define tio_error_print(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
if (option.color < 0) { \
if (error_normal) \
fprintf (stderr, "Error: " format "\n", ## args); \
else \
fprintf (stderr, "\r[%s] Error: " format "\r\n", timestamp_current_time(), ## args); \
} \
else { \
if (error_normal) \
{ ansi_error_printf("Error: " format, ## args); }\
else \
{ ansi_error_printf("[%s] Error: " format, timestamp_current_time(), ## args); }\
} \
print_tainted = false; \
} \
}
#define tio_printf(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
ansi_printf("[%s] " format, timestamp_current_time(), ## args); \
print_tainted = false; \
} \
}
#define tio_printf_raw(format, args...) \
{ \
if (!option.mute) \
{ \
if (print_tainted) \
putchar('\n'); \
ansi_printf_raw("[%s] " format, timestamp_current_time(), ## args); \
print_tainted = false; \
} \
}
#ifdef DEBUG
#define tio_debug_printf(format, args...) \
fprintf(stdout, "[debug] " format, ## args)
#define tio_debug_printf_raw(format, args...) \
fprintf(stdout, "" format, ## args)
#else
#define tio_debug_printf(format, args...)
#define tio_debug_printf_raw(format, args...)
#endif
void print(const char *format, ...);
void print_hex(char c);
void print_normal(char c);
void print_init_ansi_formatting(void);
void tio_printf_array(const char *array);
void print_tainted_set(void);
void print_padded(char *string, size_t length, char pad_char);

View file

@ -1,276 +0,0 @@
/*
* 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.
*/
#include "print.h"
#include "misc.h"
#define RL_LINE_LENGTH_MAX PATH_MAX
#define RL_HISTORY_MAX 1000
static char rl_line[RL_LINE_LENGTH_MAX] = {};
static char *rl_history[RL_HISTORY_MAX];
static int rl_history_count = 0;
static int rl_history_index = 0;
static int rl_line_length = 0;
static int rl_cursor_pos = 0;
static int rl_escape = 0;
static void print_line(const char *string, int cursor_pos)
{
clear_line();
print("%s", string);
print("\r"); // Move the cursor back to the beginning
for (int i = 0; i < cursor_pos; ++i)
{
print("\x1b[C"); // Move the cursor right
}
}
void readline_init(void)
{
rl_history_count = 0;
rl_history_index = 0;
for (int i = 0; i < RL_HISTORY_MAX; ++i)
{
rl_history[i] = NULL;
}
rl_line[0] = 0;
rl_line_length = 0;
rl_cursor_pos = 0;
rl_escape = 0;
}
char * readline_get(void)
{
return rl_line;
}
static void readline_input_char(char input_char)
{
if (rl_line_length < RL_LINE_LENGTH_MAX - 1)
{
memmove(&rl_line[rl_cursor_pos + 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos);
rl_line[rl_cursor_pos] = input_char;
rl_line_length++;
rl_cursor_pos++;
rl_line[rl_line_length] = '\0';
print_line(rl_line, rl_cursor_pos);
}
rl_escape = 0;
}
static void readline_input_cr(void)
{
if (rl_line_length > 0)
{
// Save to history
if (rl_history_count < RL_HISTORY_MAX)
{
rl_history[rl_history_count] = strndup(rl_line, rl_line_length);
rl_history_count++;
}
else
{
free(rl_history[0]);
memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*));
rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length);
}
}
rl_line[rl_line_length] = '\0';
if (option.local_echo == false)
{
clear_line();
}
else
{
print("\r\n");
}
rl_line_length = 0;
rl_cursor_pos = 0;
rl_history_index = rl_history_count;
rl_escape = 0;
}
static void readline_input_bs(void)
{
if (rl_cursor_pos > 0)
{
memmove(&rl_line[rl_cursor_pos - 1], &rl_line[rl_cursor_pos], rl_line_length - rl_cursor_pos);
rl_line_length--;
rl_cursor_pos--;
rl_line[rl_line_length] = '\0';
print_line(rl_line, rl_cursor_pos);
}
rl_escape = 0;
}
static void readline_input_escape(void)
{
rl_escape = 1;
}
static void readline_input_left_bracket(void)
{
if (rl_escape == 1)
{
rl_escape = 2;
}
else
{
rl_escape = 0;
}
}
static void readline_input_A(void)
{
if (rl_escape == 2)
{
// Up arrow
if (rl_history_index > 0)
{
rl_history_index--;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line);
rl_cursor_pos = rl_line_length;
print_line(rl_line, rl_cursor_pos);
}
}
else
{
readline_input_char('A');
}
rl_escape = 0;
}
static void readline_input_B(void)
{
if (rl_escape == 2)
{
// Down arrow
if (rl_history_index < rl_history_count - 1)
{
rl_history_index++;
strncpy(rl_line, rl_history[rl_history_index], RL_LINE_LENGTH_MAX-1);
rl_line_length = strlen(rl_line);
rl_cursor_pos = rl_line_length;
print_line(rl_line, rl_cursor_pos);
}
else if (rl_history_index == rl_history_count - 1)
{
rl_history_index++;
rl_line_length = 0;
rl_cursor_pos = 0;
rl_line[rl_line_length] = '\0';
print_line(rl_line, rl_cursor_pos);
}
}
else
{
readline_input_char('B');
}
rl_escape = 0;
}
static void readline_input_C(void)
{
if (rl_escape == 2)
{
// Right arrow
if (rl_cursor_pos < rl_line_length)
{
rl_cursor_pos++;
print("\x1b[C");
}
}
else
{
readline_input_char('C');
}
rl_escape = 0;
}
static void readline_input_D(void)
{
if (rl_escape == 2)
{
// Left arrow
if (rl_cursor_pos > 0)
{
rl_cursor_pos--;
print("\b");
}
}
else
{
readline_input_char('D');
}
rl_escape = 0;
}
void readline_input(char input_char)
{
switch (input_char)
{
case '\r': // Carriage return
readline_input_cr();
break;
case 127: // Backspace
readline_input_bs();
break;
case 27: // Escape
readline_input_escape();
break;
case '[':
readline_input_left_bracket();
break;
case 'A':
readline_input_A();
break;
case 'B':
readline_input_B();
break;
case 'C':
readline_input_C();
break;
case 'D':
readline_input_D();
break;
default:
readline_input_char(input_char);
break;
}
}

View file

@ -1,26 +0,0 @@
/*
* 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
void readline_init(void);
void readline_input(char input_char);
char * readline_get(void);

View file

@ -1,210 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include "options.h"
#include "print.h"
#include "misc.h"
#ifdef HAVE_RS485
#include <linux/serial.h>
static struct serial_rs485 rs485_config_saved;
static struct serial_rs485 rs485_config;
static bool rs485_config_written = false;
void rs485_parse_config(const char *arg)
{
bool token_found = true;
char *token = NULL;
char *buffer = strdup(arg);
while (token_found == true)
{
if (token == NULL)
{
token = strtok(buffer,",");
}
else
{
token = strtok(NULL, ",");
}
if (token != NULL)
{
char keyname[31];
unsigned int value;
int match_count;
match_count = sscanf(token, "%30[^=]=%d", keyname, &value);
if (match_count == 2)
{
if (!strcmp(keyname, "RTS_ON_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 when sending */
option.rs485_config_flags |= SER_RS485_RTS_ON_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 when sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_ON_SEND);
}
}
else if (!strcmp(keyname, "RTS_AFTER_SEND"))
{
if (value)
{
/* Set logical level for RTS pin equal to 1 after sending */
option.rs485_config_flags |= SER_RS485_RTS_AFTER_SEND;
}
else
{
/* Set logical level for RTS pin equal to 0 after sending */
option.rs485_config_flags &= ~(SER_RS485_RTS_AFTER_SEND);
}
}
else if (!strcmp(keyname, "RTS_DELAY_BEFORE_SEND"))
{
/* Set RTS delay before send */
option.rs485_delay_rts_before_send = value;
}
else if (!strcmp(keyname, "RTS_DELAY_AFTER_SEND"))
{
/* Set RTS delay after send */
option.rs485_delay_rts_after_send = value;
}
}
else if (match_count == 1)
{
if (!strcmp(keyname, "RX_DURING_TX"))
{
/* Receive data even while sending data */
option.rs485_config_flags |= SER_RS485_RX_DURING_TX;
}
}
else
{
token_found = false;
}
}
else
{
token_found = false;
}
}
free(buffer);
}
void rs485_print_config(void)
{
tio_printf(" RS-485 Configuration:");
tio_printf(" RTS_ON_SEND: %s", (rs485_config.flags & SER_RS485_RTS_ON_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_AFTER_SEND = %d", rs485_config.delay_rts_after_send);
tio_printf(" RX_DURING_TX: %s", (rs485_config.flags & SER_RS485_RX_DURING_TX) ? "true" : "false");
}
int rs485_mode_enable(int fd)
{
/* Save existing RS-485 configuration */
ioctl (fd, TIOCGRS485, &rs485_config_saved);
/* Prepare new RS-485 configuration */
rs485_config.flags = SER_RS485_ENABLED;
rs485_config.flags |= option.rs485_config_flags;
if (option.rs485_delay_rts_before_send > 0)
{
rs485_config.delay_rts_before_send = option.rs485_delay_rts_before_send;
}
else
{
rs485_config.delay_rts_before_send = rs485_config_saved.delay_rts_before_send;
}
if (option.rs485_delay_rts_after_send > 0)
{
rs485_config.delay_rts_after_send = option.rs485_delay_rts_after_send;
}
else
{
rs485_config.delay_rts_after_send = rs485_config_saved.delay_rts_after_send;
}
/* Write new RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config) < 0)
{
tio_warning_printf("RS-485 mode is not supported by your device (%s)", strerror(errno));
return -1;
}
rs485_config_written = true;
return 0;
}
void rs485_mode_restore(int fd)
{
if (rs485_config_written)
{
/* Write saved RS-485 configuration */
if (ioctl(fd, TIOCSRS485, &rs485_config_saved) < 0)
{
tio_warning_printf("TIOCGRS485 ioctl failed (%s)", strerror(errno));
}
}
}
#else
void rs485_parse_config(const char *arg)
{
UNUSED(arg);
return;
}
void rs485_print_config(void)
{
return;
}
int rs485_mode_enable(int fd)
{
UNUSED(fd);
tio_error_printf("RS485 mode is not supported on your system");
exit(EXIT_FAILURE);
}
void rs485_mode_restore(int fd)
{
UNUSED(fd);
return;
}
#endif

View file

@ -1,27 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
void rs485_parse_config(const char *arg);
int rs485_mode_enable(int fd);
void rs485_mode_restore(int fd);
void rs485_print_config(void);

View file

@ -1,547 +0,0 @@
/*
* 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.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include "misc.h"
#include "print.h"
#include "options.h"
#include "tty.h"
#include "xymodem.h"
#include "log.h"
#include "script.h"
#include "fs.h"
#include "timestamp.h"
#include "termios.h"
#define MAX_BUFFER_SIZE 2000 // Maximum size of circular buffer
#define READ_LINE_SIZE 4096 // read_line buffer length
static int device_fd;
static char script_init[] =
"tio.set = function(arg)\n"
" local dtr = arg.DTR or -1\n"
" local rts = arg.RTS or -1\n"
" local cts = arg.CTS or -1\n"
" local dsr = arg.DSR or -1\n"
" local cd = arg.CD or -1\n"
" local ri = arg.RI or -1\n"
" tio.line_set(dtr, rts, cts, dsr, cd, ri)\n"
"end\n"
"tio.expect = function(pattern, timeout)\n"
" local str = ''\n"
" while true do\n"
" local c = tio.read(1, timeout)\n"
" if c then\n"
" str = str .. c\n"
" if string.match(str, pattern) then\n"
" return string.match(str, pattern)\n"
" end\n"
" else\n"
" return nil, str\n"
" end\n"
" end\n"
"end\n"
"tio.alwaysecho = true\n"
"setmetatable(tio, tio)\n";
static bool alwaysecho(lua_State *L)
{
bool b;
lua_getglobal(L, "tio");
lua_getfield(L, -1, "alwaysecho");
b = lua_toboolean(L, -1);
lua_pop(L, 2);
return b;
}
static int api_echo(lua_State *L)
{
size_t len = 0;
const char *str = luaL_checklstring(L, 1, &len);
if (option.timestamp)
{
char *pTimeStampNow = timestamp_current_time();
if (pTimeStampNow)
{
tio_printf("%s", str);
if (option.log)
{
log_printf("\n[%s] %s", pTimeStampNow, str);
}
}
} else {
for (size_t i=0; i<len; i++)
{
putchar(str[i]);
if (option.log)
log_putc(str[i]);
}
}
return 0;
}
static void maybe_echo(lua_State *L)
{
if (alwaysecho(L))
{
lua_pushcfunction(L, api_echo);
lua_pushvalue(L, -2);
lua_call(L, 1, 0);
}
}
// lua: tio.sleep(seconds)
static int api_sleep(lua_State *L)
{
long seconds = lua_tointeger(L, 1);
if (seconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld seconds", seconds);
sleep(seconds);
return 0;
}
// lua: tio.msleep(miliseconds)
static int api_msleep(lua_State *L)
{
long mseconds = lua_tointeger(L, 1);
long useconds = mseconds * 1000;
if (useconds < 0)
{
return 0;
}
tio_printf("Sleeping %ld ms", mseconds);
usleep(useconds);
return 0;
}
// lua: tio.line_set(dtr,rts,cts,dsr,cd,ri)
static int line_set(lua_State *L)
{
tty_line_config_t line_config[6] = { };
int dtr = lua_tointeger(L, 1);
int rts = lua_tointeger(L, 2);
int cts = lua_tointeger(L, 3);
int dsr = lua_tointeger(L, 4);
int cd = lua_tointeger(L, 5);
int ri = lua_tointeger(L, 6);
if (dtr != -1)
{
line_config[0].mask = TIOCM_DTR;
line_config[0].value = dtr;
line_config[0].reserved = true;
}
if (rts != -1)
{
line_config[1].mask = TIOCM_RTS;
line_config[1].value = rts;
line_config[1].reserved = true;
}
if (cts != -1)
{
line_config[2].mask = TIOCM_CTS;
line_config[2].value = cts;
line_config[2].reserved = true;
}
if (dsr != -1)
{
line_config[3].mask = TIOCM_DSR;
line_config[3].value = dsr;
line_config[3].reserved = true;
}
if (cd != -1)
{
line_config[4].mask = TIOCM_CD;
line_config[4].value = cd;
line_config[4].reserved = true;
}
if (ri != -1)
{
line_config[5].mask = TIOCM_RI;
line_config[5].value = ri;
line_config[5].reserved = true;
}
tty_line_set(device_fd, line_config);
return 0;
}
// lua: tio.send(file, protocol)
static int api_send(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
int protocol = luaL_checkinteger(L, 2);
int ret;
if (file == NULL)
{
return 0;
}
switch (protocol)
{
case XMODEM_1K:
tio_printf("Sending file '%s' using XMODEM-1K", file);
ret = xymodem_send(device_fd, file, XMODEM_1K);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
case XMODEM_CRC:
tio_printf("Sending file '%s' using XMODEM-CRC", file);
ret = xymodem_send(device_fd, file, XMODEM_CRC);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
case YMODEM:
tio_printf("Sending file '%s' using YMODEM", file);
ret = xymodem_send(device_fd, file, YMODEM);
tio_printf("%s", ret < 0 ? "Aborted" : "Done");
break;
}
return 0;
}
// lua: tio.write(string)
static int api_write(lua_State *L)
{
size_t len = 0;
const char *string = luaL_checklstring(L, 1, &len);
ssize_t ret;
int attempts = 100;
do {
ret = write(device_fd, string, len);
if (ret < 0)
return luaL_error(L, "%s", strerror(errno));
len -= ret;
string += ret;
} while (len > 0 && --attempts);
if (len > 0)
return luaL_error(L, "partial write");
fsync(device_fd); // flush these characters now
tcdrain(device_fd); //ensure we flushed characters to our device
lua_getglobal(L, "tio");
return 1;
}
// lua: tio.read(size, timeout)
static int api_read(lua_State *L)
{
int size = luaL_checkinteger(L, 1);
int timeout = lua_tointeger(L, 2);
if (timeout == 0)
{
timeout = -1; // Wait forever
}
luaL_Buffer buffer;
luaL_buffinit(L, &buffer);
#if LUA_VERSION_NUM >= 502
char *p = luaL_prepbuffsize(&buffer, size);
#else
if (size > LUAL_BUFFERSIZE)
return luaL_error(L, "buffer overflow, max size is: %d", LUAL_BUFFERSIZE);
char *p = luaL_prepbuffer(&buffer);
#endif
ssize_t ret = read_poll(device_fd, p, size, timeout);
if (ret < 0)
return luaL_error(L, "%s", strerror(errno));
luaL_addsize(&buffer, ret);
luaL_pushresult(&buffer);
if (ret == 0)
{
// On timeout return nil instead of an empty string
lua_pop(L, 1);
lua_pushnil(L);
}
else
{
maybe_echo(L);
}
return 1;
}
// lua: string = tio.readline(timeout)
static int api_readline(lua_State *L) {
int timeout = lua_tointeger(L, 1); //ms
luaL_Buffer b;
char ch;
if (timeout == 0)
{
timeout = -1; // Wait forever
}
luaL_buffinit(L, &b);
luaL_prepbuffer(&b);
while (true) {
int ret = read_poll(device_fd, &ch, 1, timeout);
if (ret < 0)
return luaL_error(L, "%s", strerror(errno));
if (ret == 0)
{
luaL_pushresult(&b);
maybe_echo(L);
lua_pushnil(L);
lua_insert(L, -2);
return 2;
}
if (ch == '\n')
{
luaL_pushresult(&b);
maybe_echo(L);
return 1;
}
luaL_addchar(&b, ch);
}
}
// lua: table = tio.ttysearch()
static int api_ttysearch(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;
error = luaL_loadbuffer(L, script_buffer, strlen(script_buffer), "tio") ||
lua_pcall(L, 0, 0, 0);
if (error)
{
tio_warning_printf("lua: %s\n", lua_tostring(L, -1));
lua_pop(L, 1); /* Pop error message from the stack */
}
}
static void script_file_run(lua_State *L, const char *filename)
{
if (strlen(filename) == 0)
{
tio_warning_printf("Missing script filename\n");
return;
}
if (luaL_dofile(L, filename))
{
tio_warning_printf("lua: %s", lua_tostring(L, -1));
lua_pop(L, 1); /* pop error message from the stack */
return;
}
}
static const struct luaL_Reg tio_lib[] =
{
{ "echo", api_echo},
{ "sleep", api_sleep},
{ "msleep", api_msleep},
{ "line_set", line_set},
{ "send", api_send},
{ "write", api_write},
{ "read", api_read},
{ "readline", api_readline},
{ "ttysearch", api_ttysearch},
{NULL, NULL}
};
static void script_load(lua_State *L)
{
int error;
error = luaL_loadbuffer(L, script_init, strlen(script_init), "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
}
}
static void script_set_global(lua_State *L, const char *name, long value)
{
lua_pushnumber(L, value);
lua_setglobal(L, name);
}
static void script_set_globals(lua_State *L)
{
script_set_global(L, "toggle", 2);
script_set_global(L, "high", 1);
script_set_global(L, "low", 0);
script_set_global(L, "XMODEM_CRC", XMODEM_CRC);
script_set_global(L, "XMODEM_1K", XMODEM_1K);
script_set_global(L, "YMODEM", YMODEM);
}
#if LUA_VERSION_NUM >= 502
static int luaopen_tio(lua_State *L)
{
luaL_newlib(L, tio_lib);
return 1;
}
#endif
void script_run(int fd, const char *script_filename)
{
lua_State *L;
device_fd = fd;
L = luaL_newstate();
luaL_openlibs(L);
#if LUA_VERSION_NUM >= 502
luaL_requiref(L, "tio", luaopen_tio, 1);
#else
luaL_register(L, "tio", tio_lib);
#endif
lua_pop(L, 1);
// Load lua init script
script_load(L);
// Initialize globals
script_set_globals(L);
if (script_filename != NULL)
{
tio_printf("Running script %s", script_filename);
script_file_run(L, script_filename);
}
else if (option.script_filename != NULL)
{
tio_printf("Running script %s", option.script_filename);
script_file_run(L, option.script_filename);
}
else if (option.script != NULL)
{
tio_printf("Running script");
script_buffer_run(L, option.script);
}
lua_close(L);
}
const char *script_run_state_to_string(script_run_t state)
{
switch (state)
{
case SCRIPT_RUN_ONCE:
return "once";
case SCRIPT_RUN_ALWAYS:
return "always";
case SCRIPT_RUN_NEVER:
return "never";
default:
return "Unknown";
}
}

View file

@ -1,33 +0,0 @@
/*
* 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
typedef enum
{
SCRIPT_RUN_ONCE,
SCRIPT_RUN_ALWAYS,
SCRIPT_RUN_NEVER,
SCRIPT_RUN_END,
} script_run_t;
void script_run(int fd, const char *script_filename);
const char *script_run_state_to_string(script_run_t state);

View file

@ -1,72 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2017-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include <errno.h>
#ifdef HAVE_TERMIOS2
#define termios asmtermios
#include <sys/ioctl.h>
#undef termios
#include <asm-generic/ioctls.h>
#include <asm-generic/termbits.h>
#elif HAVE_IOSSIOSPEED
#include <sys/ioctl.h>
#include <IOKit/serial/ioss.h>
#endif
#include "misc.h"
#ifdef HAVE_TERMIOS2
int setspeed(int fd, int baudrate)
{
struct termios2 tio;
int status;
status = ioctl(fd, TCGETS2, &tio);
// Set baudrate speed using termios2 interface
tio.c_cflag &= ~CBAUD;
tio.c_cflag |= BOTHER;
tio.c_ispeed = baudrate;
tio.c_ospeed = baudrate;
status = ioctl(fd, TCSETS2, &tio);
return status;
}
#elif HAVE_IOSSIOSPEED
int setspeed(int fd, int baudrate)
{
return ioctl(fd, IOSSIOSPEED, (char *)&baudrate);
}
#else
int setspeed(int fd, int baudrate)
{
UNUSED(fd);
UNUSED(baudrate);
errno = EINVAL;
return -1;
}
#endif

View file

@ -1,51 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include "error.h"
#include "print.h"
#include "misc.h"
#include "tty.h"
static void signal_handler(int signum)
{
switch (signum)
{
case SIGHUP:
tio_printf("Received SIGHUP signal!");
break;
case SIGINT:
tio_printf("Received SIGINT signal!");
break;
}
exit(EXIT_FAILURE);
}
void signal_handlers_install(void)
{
signal(SIGHUP, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGPIPE, SIG_IGN);
}

View file

@ -1,24 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
void signal_handlers_install();

View file

@ -1,384 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
* Copyright (c) 2022 Google LLC
*
* 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.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include "socket.h"
#include "options.h"
#include "print.h"
#include "tty.h"
#define MAX_SOCKET_CLIENTS 16
#define SOCKET_PORT_DEFAULT 3333
static int sockfd;
static int clientfds[MAX_SOCKET_CLIENTS];
static int socket_family = AF_UNSPEC;
static int port_number = SOCKET_PORT_DEFAULT;
static const char *socket_filename(void)
{
/* skip 'unix:' */
return option.socket + 5;
}
static int socket_inet_port(void)
{
/* skip 'inet:' */
int port = atoi(option.socket + 5);
if (port == 0)
{
port = SOCKET_PORT_DEFAULT;
}
return port;
}
static int socket_inet6_port(void)
{
/* skip 'inet6:' */
int port = atoi(option.socket + 6);
if (port == 0)
{
port = SOCKET_PORT_DEFAULT;
}
return port;
}
static void socket_exit(void)
{
if (socket_family == AF_UNIX)
{
unlink(socket_filename());
}
}
static bool socket_stale(const char *path)
{
struct sockaddr_un addr;
bool stale = false;
int sfd;
/* Test if socket file exists */
if (access(path, F_OK) == 0)
{
/* Create test socket */
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd < 0)
{
tio_warning_printf("Failure opening socket (%s)", strerror(errno));
return false;
}
/* Prepare address */
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
/* Perform connect to test if socket is active */
if (connect(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
{
if (errno == ECONNREFUSED)
{
// No one is listening on socket file
stale = true;
}
}
/* Cleanup */
close(sockfd);
}
return stale;
}
void socket_configure(void)
{
struct sockaddr_un sockaddr_unix = {};
struct sockaddr_in sockaddr_inet = {};
struct sockaddr_in6 sockaddr_inet6 = {};
struct sockaddr *sockaddr_p;
socklen_t socklen;
int optval;
/* Parse socket string */
if (strncmp(option.socket, "unix:", 5) == 0)
{
socket_family = AF_UNIX;
if (strlen(socket_filename()) == 0)
{
tio_error_printf("Missing socket filename");
exit(EXIT_FAILURE);
}
if (strlen(socket_filename()) > sizeof(sockaddr_unix.sun_path) - 1)
{
tio_error_printf("Socket file path %s too long", option.socket);
exit(EXIT_FAILURE);
}
}
if (strncmp(option.socket, "inet:", 5) == 0)
{
socket_family = AF_INET;
port_number = socket_inet_port();
if (port_number < 0)
{
tio_error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (strncmp(option.socket, "inet6:", 6) == 0)
{
socket_family = AF_INET6;
port_number = socket_inet6_port();
if (port_number < 0)
{
tio_error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (socket_family == AF_UNSPEC)
{
tio_error_printf("%s: Invalid socket scheme, must be prefixed with 'unix:', 'inet:', or 'inet6:'", option.socket);
exit(EXIT_FAILURE);
}
/* Configure socket */
switch (socket_family)
{
case AF_UNIX:
sockaddr_unix.sun_family = AF_UNIX;
strncpy(sockaddr_unix.sun_path, socket_filename(), sizeof(sockaddr_unix.sun_path) - 1);
sockaddr_p = (struct sockaddr *) &sockaddr_unix;
socklen = sizeof(sockaddr_unix);
/* Test for stale unix socket file */
if (socket_stale(socket_filename()))
{
tio_printf("Cleaning up old socket file");
unlink(socket_filename());
}
break;
case AF_INET:
sockaddr_inet.sin_family = AF_INET;
sockaddr_inet.sin_addr.s_addr = INADDR_ANY;
sockaddr_inet.sin_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet;
socklen = sizeof(sockaddr_inet);
break;
case AF_INET6:
sockaddr_inet6.sin6_family = AF_INET6;
sockaddr_inet6.sin6_addr = in6addr_any;
sockaddr_inet6.sin6_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet6;
socklen = sizeof(sockaddr_inet6);
break;
default:
tio_error_printf("Invalid socket family (%d)", socket_family);
exit(EXIT_FAILURE);
break;
}
/* Create socket */
sockfd = socket(socket_family, SOCK_STREAM, 0);
if (sockfd < 0)
{
tio_error_printf("Failed to create socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR | SO_NOSIGPIPE, &optval, sizeof(optval)))
#else
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)))
#endif
{
tio_error_printf("Failed to set socket options (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
/* Bind */
if (bind(sockfd, sockaddr_p, socklen) < 0)
{
tio_error_printf("Failed to bind to socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
/* Listen */
if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0)
{
tio_error_printf("Failed to listen on socket (%s)", strerror(errno));
exit(EXIT_FAILURE);
}
memset(clientfds, -1, sizeof(clientfds));
atexit(socket_exit);
if (socket_family == AF_UNIX)
{
tio_printf("Listening on socket %s", socket_filename());
}
else
{
tio_printf("Listening on socket port %d", port_number);
}
}
void socket_write(char input_char)
{
if (!option.socket)
{
return;
}
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1)
{
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
if (send(clientfds[i], &input_char, 1, 0) <= 0)
#else
if (send(clientfds[i], &input_char, 1, MSG_NOSIGNAL) <= 0)
#endif
{
tio_error_printf_silent("Failed to write to socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
}
}
}
}
int socket_add_fds(fd_set *rdfs, bool connected)
{
if (!option.socket)
{
return 0;
}
int numclients = 0, maxfd = 0;
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1)
{
/* let clients block if they try to send while we're disconnected */
if (connected)
{
FD_SET(clientfds[i], rdfs);
maxfd = MAX(maxfd, clientfds[i]);
}
numclients++;
}
}
/* don't bother to accept clients if we're already full */
if (numclients != MAX_SOCKET_CLIENTS)
{
FD_SET(sockfd, rdfs);
maxfd = MAX(maxfd, sockfd);
}
return maxfd;
}
bool socket_handle_input(fd_set *rdfs, char *output_char)
{
if (!option.socket)
{
return false;
}
if (FD_ISSET(sockfd, rdfs))
{
int clientfd = accept(sockfd, NULL, NULL);
/* this loop should always succeed because we don't select on sockfd when full */
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] == -1)
{
clientfds[i] = clientfd;
break;
}
}
}
for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
{
if (clientfds[i] != -1 && FD_ISSET(clientfds[i], rdfs))
{
int status = read(clientfds[i], output_char, 1);
if (status == 0)
{
close(clientfds[i]);
clientfds[i] = -1;
continue;
}
if (status < 0)
{
tio_error_printf_silent("Failed to read from socket (%s)", strerror(errno));
close(clientfds[i]);
clientfds[i] = -1;
continue;
}
/* If INLCR is set, a received NL character shall be translated into a CR character */
if (*output_char == '\n' && option.map_i_nl_cr)
{
*output_char = '\r';
}
else if (*output_char == '\r')
{
/* If IGNCR is set, a received CR character shall be ignored (not read). */
if (option.map_ign_cr)
{
return false;
}
/* If IGNCR is not set and ICRNL is set, a received CR character shall be translated into an NL character. */
if (option.map_i_cr_nl)
{
*output_char = '\n';
}
}
return true;
}
}
return false;
}

View file

@ -1,31 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
* Copyright (c) 2022 Google LLC
*
* 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 <sys/select.h>
void socket_configure(void);
void socket_write(char input_char);
int socket_add_fds(fd_set *fds, bool connected);
bool socket_handle_input(fd_set *fds, char *output_char);

View file

@ -1,7 +1,7 @@
/* /*
* tio - a serial device I/O tool * tio - the simple TTY terminal I/O application
* *
* Copyright (c) 2014-2022 Martin Lund * Copyright (c) 2014-2016 Martin Lund
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -19,21 +19,26 @@
* 02110-1301, USA. * 02110-1301, USA.
*/ */
#pragma once #include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "tio/error.h"
typedef enum char * current_time(void)
{ {
TIMESTAMP_NONE, static char time_string[20];
TIMESTAMP_24HOUR, time_t t;
TIMESTAMP_24HOUR_START, struct tm *tmp;
TIMESTAMP_24HOUR_DELTA,
TIMESTAMP_ISO8601,
TIMESTAMP_EPOCH,
TIMESTAMP_EPOCH_USEC,
TIMESTAMP_END,
} timestamp_t;
#define TIME_STRING_SIZE_MAX 24 t = time(NULL);
tmp = localtime(&t);
if (tmp == NULL)
{
error_printf("Retrieving local time failed");
exit(EXIT_FAILURE);
}
char *timestamp_current_time(void); strftime(time_string, sizeof(time_string), "%H:%M:%S", tmp);
return time_string;
}

View file

@ -1,104 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include "error.h"
#include "print.h"
#include "options.h"
#include "timestamp.h"
char *timestamp_current_time(void)
{
static char time_string[TIME_STRING_SIZE_MAX];
static struct timeval tv, tv_now, tv_start, tv_previous;
static bool first = true;
struct tm *tm;
size_t len;
// Get current time value
gettimeofday(&tv_now, NULL);
if (first)
{
tv_start = tv_now;
first = false;
}
// Add formatted timestamp
switch (option.timestamp)
{
case TIMESTAMP_NONE:
case TIMESTAMP_24HOUR:
// "hh:mm:ss.sss" (24 hour format)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_24HOUR_START:
// "hh:mm:ss.sss" (24 hour format relative to start time)
timersub(&tv_now, &tv_start, &tv);
tm = gmtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_24HOUR_DELTA:
// "hh:mm:ss.sss" (24 hour format relative to previous time stamp)
timersub(&tv_now, &tv_previous, &tv);
tm = gmtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%H:%M:%S", tm);
break;
case TIMESTAMP_ISO8601:
// "YYYY-MM-DDThh:mm:ss.sss" (ISO-8601)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%Y-%m-%dT%H:%M:%S", tm);
break;
case TIMESTAMP_EPOCH:
case TIMESTAMP_EPOCH_USEC:
// "N.sss" (seconds since Unix epoch, 1970-01-01 00:00:00Z)
tv = tv_now;
tm = localtime(&tv.tv_sec);
len = strftime(time_string, sizeof(time_string), "%s", tm);
break;
default:
return NULL;
}
// Append millis-/microseconds to all timestamps
if (len)
{
if ( option.timestamp == TIMESTAMP_EPOCH_USEC )
{
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%06ld", (long)tv.tv_usec);
}
else
{
len = snprintf(time_string + len, TIME_STRING_SIZE_MAX - len, ".%03ld", (long)tv.tv_usec / 1000);
}
}
// Save previous time value for next run
tv_previous = tv_now;
return (len < TIME_STRING_SIZE_MAX) ? time_string : NULL;
}

3042
src/tty.c

File diff suppressed because it is too large Load diff

View file

@ -1,86 +0,0 @@
/*
* tio - a serial device I/O tool
*
* Copyright (c) 2014-2022 Martin Lund
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#pragma once
#include <stdbool.h>
#include <glib.h>
#define LINE_HIGH true
#define LINE_LOW false
#define TOPOLOGY_ID_SIZE 4
typedef enum
{
FLOW_NONE,
FLOW_HARD,
FLOW_SOFT,
} flow_t;
typedef enum
{
PARITY_NONE,
PARITY_ODD,
PARITY_EVEN,
PARITY_MARK,
PARITY_SPACE,
} parity_t;
typedef enum
{
AUTO_CONNECT_DIRECT,
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;
typedef struct
{
int mask;
int value;
bool reserved;
} tty_line_config_t;
extern const char *device_name;
extern bool interactive_mode;
void stdout_configure(void);
void stdin_configure(void);
void tty_configure(void);
void tty_reconfigure(void);
int tty_connect(void);
void tty_wait_for_device(void);
void list_serial_devices(void);
void tty_input_thread_create(void);
void tty_input_thread_wait_ready(void);
void tty_line_set(int fd, tty_line_config_t line_config[]);
void tty_search(void);
GList *tty_search_for_serial_devices(void);

View file

@ -1,3 +0,0 @@
#pragma once
#define VERSION "@VERSION@"

View file

@ -1,739 +0,0 @@
/*
* Minimalistic implementation of the xmodem-1k and ymodem sender protocol.
* https://en.wikipedia.org/wiki/XMODEM
* https://en.wikipedia.org/wiki/YMODEM
*
* SPDX-License-Identifier: GPL-2.0-or-later OR MIT-0
*
*/
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <poll.h>
#include "xymodem.h"
#include "print.h"
#include "misc.h"
#define SOH 0x01
#define STX 0x02
#define ACK 0x06
#define NAK 0x15
#define CAN 0x18
#define EOT 0x04
#define SOH_STR "\001"
#define ACK_STR "\006"
#define NAK_STR "\025"
#define CAN_STR "\030"
#define EOT_STR "\004"
#define OK 0
#define ERR (-1)
#define ERR_FATAL (-2)
#define USER_CAN (-5)
#define RX_IGNORE 5
#define min(a, b) ((a) < (b) ? (a) : (b))
struct xpacket_1k {
uint8_t type;
uint8_t seq;
uint8_t nseq;
uint8_t data[1024];
uint8_t crc_hi;
uint8_t crc_lo;
} __attribute__((packed));
struct xpacket {
uint8_t type;
uint8_t seq;
uint8_t nseq;
uint8_t data[128];
uint8_t crc_hi;
uint8_t crc_lo;
} __attribute__((packed));
/* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */
static uint16_t crc16(const uint8_t *data, uint16_t size)
{
uint16_t crc, s;
for (crc = 0; size > 0; size--) {
s = *data++ ^ (crc >> 8);
s ^= (s >> 4);
crc = (crc << 8) ^ s ^ (s << 5) ^ (s << 12);
}
return crc;
}
static int xmodem_1k(int sio, const void *data, size_t len, int seq)
{
struct xpacket_1k packet;
const uint8_t *buf = data;
char resp = 0;
int rc, crc;
/* Drain pending characters from serial line. Insist on the
* last drained character being 'C'.
*/
while(1) {
if (key_hit)
return -1;
rc = read_poll(sio, &resp, 1, 50);
if (rc == 0) {
if (resp == 'C') break;
if (resp == CAN) return ERR;
continue;
}
else if (rc < 0) {
tio_error_print("Read sync from serial failed");
return ERR;
}
}
/* Always work with 1K packets */
packet.seq = seq;
packet.type = STX;
while (len) {
size_t sz, z = 0;
char *from, status;
/* Build next packet, pad with 0 to full seq */
z = min(len, sizeof(packet.data));
memcpy(packet.data, buf, z);
memset(packet.data + z, 0, sizeof(packet.data) - z);
crc = crc16(packet.data, sizeof(packet.data));
packet.crc_hi = crc >> 8;
packet.crc_lo = crc;
packet.nseq = 0xff - packet.seq;
/* Send packet */
from = (char *) &packet;
sz = sizeof(packet);
while (sz) {
if (key_hit)
return ERR;
if ((rc = write(sio, from, sz)) < 0 ) {
if (errno == EWOULDBLOCK) {
usleep(1000);
continue;
}
tio_error_print("Write packet to serial failed");
return ERR;
}
from += rc;
sz -= rc;
}
/* Clear response */
resp = 0;
/* 'lrzsz' does not ACK ymodem's fin packet */
if (seq == 0 && packet.data[0] == 0) resp = ACK;
/* Read receiver response, timeout 1 s */
for(int n=0; n < 20; n++) {
if (key_hit)
return ERR;
rc = read_poll(sio, &resp, 1, 50);
if (rc < 0) {
tio_error_print("Read ack/nak from serial failed");
return ERR;
} else if(rc > 0) {
break;
}
}
/* Update "progress bar" */
switch (resp) {
case NAK: status = 'N'; break;
case ACK: status = '.'; break;
case 'C': status = 'C'; break;
case CAN: status = '!'; return ERR;
default: status = '?';
}
write(STDOUT_FILENO, &status, 1);
/* Move to next block after ACK */
if (resp == ACK) {
packet.seq++;
len -= z;
buf += z;
}
}
/* Send EOT at 1 Hz until ACK or CAN received */
while (seq) {
if (key_hit)
return ERR;
if (write(sio, EOT_STR, 1) < 0) {
tio_error_print("Write EOT to serial failed");
return ERR;
}
write(STDOUT_FILENO, "|", 1);
/* 1s timeout */
rc = read_poll(sio, &resp, 1, 1000);
if (rc < 0) {
tio_error_print("Read from serial failed");
return ERR;
} else if(rc == 0) {
continue;
}
if (resp == ACK || resp == CAN) {
write(STDOUT_FILENO, "\r\n", 2);
return (resp == ACK) ? OK : ERR;
}
}
return 0; /* not reached */
}
static int xmodem(int sio, const void *data, size_t len)
{
struct xpacket packet;
const uint8_t *buf = data;
char resp = 0;
int rc, crc;
/* Drain pending characters from serial line. Insist on the
* last drained character being 'C'.
*/
while(1) {
if (key_hit)
return -1;
rc = read_poll(sio, &resp, 1, 50);
if (rc == 0) {
if (resp == 'C') break;
if (resp == CAN) return ERR;
continue;
}
else if (rc < 0) {
tio_error_print("Read sync from serial failed");
return ERR;
}
}
/* Always work with 128b packets */
packet.seq = 1;
packet.type = SOH;
while (len) {
size_t sz, z = 0;
char *from, status;
/* Build next packet, pad with 0 to full seq */
z = min(len, sizeof(packet.data));
memcpy(packet.data, buf, z);
memset(packet.data + z, 0, sizeof(packet.data) - z);
crc = crc16(packet.data, sizeof(packet.data));
packet.crc_hi = crc >> 8;
packet.crc_lo = crc;
packet.nseq = 0xff - packet.seq;
/* Send packet */
from = (char *) &packet;
sz = sizeof(packet);
while (sz) {
if (key_hit)
return ERR;
if ((rc = write(sio, from, sz)) < 0 ) {
if (errno == EWOULDBLOCK) {
usleep(1000);
continue;
}
tio_error_print("Write packet to serial failed");
return ERR;
}
from += rc;
sz -= rc;
}
/* Clear response */
resp = 0;
/* Read receiver response, timeout 1 s */
for(int n=0; n < 20; n++) {
if (key_hit)
return ERR;
rc = read_poll(sio, &resp, 1, 50);
if (rc < 0) {
tio_error_print("Read ack/nak from serial failed");
return ERR;
} else if(rc > 0) {
break;
}
}
/* Update "progress bar" */
switch (resp) {
case NAK: status = 'N'; break;
case ACK: status = '.'; break;
case 'C': status = 'C'; break;
case CAN: status = '!'; return ERR;
default: status = '?';
}
write(STDOUT_FILENO, &status, 1);
/* Move to next block after ACK */
if (resp == ACK) {
packet.seq++;
len -= z;
buf += z;
}
}
/* Send EOT at 1 Hz until ACK or CAN received */
while (1) {
if (key_hit)
return ERR;
if (write(sio, EOT_STR, 1) < 0) {
tio_error_print("Write EOT to serial failed");
return ERR;
}
write(STDOUT_FILENO, "|", 1);
/* 1s timeout */
rc = read_poll(sio, &resp, 1, 1000);
if (rc < 0) {
tio_error_print("Read from serial failed");
return ERR;
} else if(rc == 0) {
continue;
}
if (resp == ACK || resp == CAN) {
write(STDOUT_FILENO, "\r\n", 2);
return (resp == ACK) ? OK : ERR;
}
}
return 0; /* not reached */
}
int start_receive(int sio)
{
int rc;
struct pollfd fds;
fds.events = POLLIN;
fds.fd = sio;
for (int n = 0; n < 20; n++)
{
/* Send the 'C' byte until the sender of the file responds with
something. The start character will be sent once a second for a number of
seconds. If nothing is received in that time then return false to indicate
that the transfer did not start. */
rc = write(sio, "C", 1);
if (rc < 0) {
if (errno == EWOULDBLOCK) {
usleep(1000);
continue;
}
tio_error_print("Write packet to serial failed");
return ERR;
}
/* Wait until data is available */
rc = poll(&fds, 1, 3000);
if (rc < 0)
{
tio_error_print("%s", strerror(errno));
return rc;
}
else if (rc > 0)
{
if (fds.revents & POLLIN)
{
return rc;
}
}
if (key_hit)
return USER_CAN;
}
return rc;
}
uint16_t update_CRC(uint16_t crc, char data_char)
{
uint8_t data = data_char;
crc = crc ^ ((uint16_t)data << 8);
for (int ix = 0; (ix < 8); ix++)
{
if (crc & 0x8000)
{
crc = (crc << 1) ^ 0x1021;
}
else
{
crc <<= 1;
}
}
return crc;
}
int receive_packet(int sio, struct xpacket packet, int fd)
{
char rxSeq1, rxSeq2 = 0;
char resp = 0;
uint16_t calcCrc = 0;
uint16_t rxCrc = 0;
int rc;
struct pollfd fds;
fds.events = POLLIN;
fds.fd = sio;
/* Read seq bytes*/
rc = read_poll(sio, &rxSeq1, 1, 3000);
if (rc == 0) {
tio_error_print("Timeout waiting for first seq byte");
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading first seq byte")
return ERR_FATAL;
}
rc = read_poll(sio, &rxSeq2, 1, 3000);
if (rc == 0) {
tio_error_print("Timeout waiting for second seq byte");
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading second seq byte")
return ERR_FATAL;
}
if (key_hit)
return USER_CAN;
/* Read packet Data */
for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++)
{
rc = read_poll(sio, &resp, 1, 3000);
/* If the read times out or fails then fail this packet. */
if (rc == 0)
{
tio_error_print("Timeout waiting for next packet char");
rc = write(sio, CAN_STR, 1);
if (rc < 0) {
tio_error_print("Write cancel packet to serial failed");
return ERR_FATAL;
}
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading next packet char")
rc = write(sio, CAN_STR, 1);
if (rc < 0) {
tio_error_print("Write cancel packet to serial failed");
}
return ERR_FATAL;
}
packet.data[ix] = (uint8_t) resp;
calcCrc = update_CRC(calcCrc, resp);
if (key_hit)
return USER_CAN;
}
/* Read CRC */
rc = read_poll(sio, &resp, 1, 3000);
if (rc == 0) {
tio_error_print("Timeout waiting for first CRC byte");
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading first CRC byte")
return ERR_FATAL;
}
uint8_t uresp = resp;
uint16_t uresp16 = uresp;
rxCrc = uresp16 << 8;
rc = read_poll(sio, &resp, 1, 3000);
if (rc == 0) {
tio_error_print("Timeout waiting for second CRC byte");
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading second CRC byte")
return ERR_FATAL;
}
uresp = resp;
uresp16 = uresp;
rxCrc |= uresp16;
if (key_hit)
return USER_CAN;
/* At this point in the code, there should not be anything in the receive buffer
because the sender has just sent a complete packet and is waiting on a response. */
rc = poll(&fds, 1, 10);
if (rc < 0)
{
tio_error_print("%s", strerror(errno));
tio_error_print("Poll check error after packet finish");
rc = write(sio, CAN_STR, 1);
if (rc < 0) {
tio_error_print("Write cancel packet to serial failed");
}
return ERR_FATAL;
}
else if (rc > 0)
{
if (fds.revents & POLLIN)
{
tio_error_print("RX sync error");
char dummy = 0;
/* Drain buffer */
while (read_poll(sio, &dummy, 1, 100) > 0) {}
return ERR;
}
}
uint8_t tester = 0xff;
uint8_t seq1 = rxSeq1;
uint8_t seq2 = rxSeq2;
if ((calcCrc == rxCrc) && (seq1 == packet.seq - 1) && ((seq1 ^ seq2) == tester))
{
/* Resend of previously processed packet. */
rc = write(sio, ACK_STR, 1);
if (rc < 0) {
tio_error_print("Write acknowlegdement packet to serial failed");
return ERR_FATAL;
}
return RX_IGNORE;
}
else if ((calcCrc != rxCrc) || (seq1 != packet.seq) || ((seq1 ^ seq2) != tester))
{
/* Fail if the CRC or sequence number is not correct or if the two received
sequence numbers are not the complement of one another. */
tio_error_print("Bad CRC or sequence number");
tio_debug_printf("CRC read: %u", rxCrc);
tio_debug_printf("CRC calculated: %u", calcCrc);
tio_debug_printf("Seq read: %hhu", rxSeq1);
tio_debug_printf("Seq should be: %hhu", packet.seq);
tio_debug_printf("inv seq: %hhu", rxSeq2);
return ERR;
}
else
{
/* The data is good. Process the packet then ACK it to the sender. */
rc = write(fd, packet.data, sizeof(packet.data));
if (rc < 0)
{
tio_error_print("Problem writing to file");
rc = write(sio, CAN_STR, 1);
if (rc < 0) {
tio_error_print("Write cancel packet to serial failed");
}
return ERR_FATAL;
}
rc = write(sio, ACK_STR, 1);
if (rc < 0)
{
tio_error_print("Write acknowlegdement packet to serial failed");
return ERR_FATAL;
}
}
return OK;
}
int xmodem_receive(int sio, int fd)
{
struct xpacket packet;
char resp = 0;
int rc;
bool complete = false;
char status;
/* Drain pending characters from serial line.*/
while(1) {
if (key_hit)
return -1;
rc = read_poll(sio, &resp, 1, 50);
if (rc == 0) {
if (resp == CAN) return ERR;
break;
}
else if (rc < 0) {
if (rc != USER_CAN) {
tio_error_print("Read sync from serial failed");
}
return ERR;
}
}
/* Always work with 128b packets */
packet.seq = 1;
packet.type = SOH;
/* Start Receive*/
rc = start_receive(sio);
if (rc == 0)
{
tio_error_print("Timeout waiting for transfer to start");
return ERR;
} else if (rc < 0) {
tio_error_print("Error starting XMODEM receive");
return ERR;
}
while (!complete) {
/* Poll for 1 new byte for 3 seconds */
rc = read_poll(sio, &resp, 1, 3000);
if (rc == 0) {
tio_error_print("Timeout waiting for start of next packet");
return ERR;
} else if (rc < 0) {
tio_error_print("Error reading start of next packet")
return ERR;
}
if (key_hit)
return USER_CAN;
switch(resp)
{
case SOH:
/* Start of a packet */
rc = receive_packet(sio, packet, fd);
if (rc == OK) {
packet.seq++;
status = '.';
} else if (rc == ERR) {
rc = write(sio, NAK_STR, 1);
if (rc < 0) {
tio_error_print("Writing not acknowledge packet to serial failed");
return ERR;
}
status = 'N';
} else if (rc == ERR_FATAL) {
tio_error_print("Receive cancelled due to fatal error");
return ERR;
} else if (rc == USER_CAN) {
rc = write(sio, CAN_STR, 1);
if (rc < 0) {
tio_error_print("Writing cancel to serial failed");
return ERR;
}
return USER_CAN;
} else if (rc == RX_IGNORE) {
status = ':';
}
break;
case EOT:
/* End of Transfer */
rc = write(sio, ACK_STR, 1);
if (rc < 0)
{
tio_error_print("Write acknowlegdement packet to serial failed");
return ERR;
}
complete = true;
status = '\0';
write(STDOUT_FILENO, "|\r\n", 3);
break;
case CAN:
/* Cancel from sender */
tio_error_print("Transmission cancelled from sender");
return ERR;
break;
default:
tio_error_print("Unexpected character received waiting for next packet");
return ERR;
break;
}
/* Update "progress bar" */
write(STDOUT_FILENO, &status, 1);
}
return OK;
}
int xymodem_send(int sio, const char *filename, modem_mode_t mode)
{
size_t len;
int rc, fd;
struct stat stat;
const uint8_t *buf;
/* Open file, map into memory */
fd = open(filename, O_RDONLY);
if (fd < 0) {
tio_error_print("Could not open file");
return ERR;
}
fstat(fd, &stat);
len = stat.st_size;
buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (!buf) {
close(fd);
tio_error_print("Could not mmap file");
return ERR;
}
/* Do transfer */
key_hit = 0;
if (mode == XMODEM_1K) {
rc = xmodem_1k(sio, buf, len, 1);
}
else if (mode == XMODEM_CRC) {
rc = xmodem(sio, buf, len);
}
else {
/* Ymodem: hdr + file + fin */
while(1) {
char hdr[1024], *p;
rc = -1;
if (strlen(filename) > 977) break; /* hdr block overrun */
p = stpncpy(hdr, filename, 1024) + 1;
p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode);
if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */
if (xmodem_1k(sio, buf, len, 1) < 0) break; /* xmodem file */
if (xmodem_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */
rc = 0; break;
}
}
key_hit = 0xff;
/* Flush serial and release resources */
tcflush(sio, TCIOFLUSH);
munmap((void *)buf, len);
close(fd);
return rc;
}
int xymodem_receive(int sio, const char *filename, modem_mode_t mode)
{
int rc, fd;
/* Create new file */
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (fd < 0) {
tio_error_print("Could not open file");
return ERR;
}
/* Do transfer */
key_hit = 0;
if (mode == XMODEM_1K) {
tio_error_print("Not supported");
rc = -1;
}
else if (mode == XMODEM_CRC) {
rc = xmodem_receive(sio, fd);
}
else {
tio_error_print("Not supported");
rc = -1;
}
key_hit = 0xff;
/* Flush serial and release resources */
tcflush(sio, TCIOFLUSH);
close(fd);
return rc;
}

View file

@ -1,34 +0,0 @@
/*
* 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
typedef enum {
XMODEM_1K,
XMODEM_CRC,
YMODEM,
} modem_mode_t;
extern char key_hit;
int xymodem_send(int sio, const char *filename, modem_mode_t mode);
int xymodem_receive(int sio, const char *filename, modem_mode_t mode);