pax_global_header00006660000000000000000000000064146035156550014524gustar00rootroot0000000000000052 comment=02d6a1a0539f696010770a46d1fd52df53400e69 bootterm-0.5/000077500000000000000000000000001460351565500132235ustar00rootroot00000000000000bootterm-0.5/.gear/000077500000000000000000000000001460351565500142175ustar00rootroot00000000000000bootterm-0.5/.gear/bootterm.spec000066400000000000000000000012361460351565500167300ustar00rootroot00000000000000%define _unpackaged_files_terminate_build 1 Name: bootterm Version: 0.4.0 License: %mit Release: alt1 Summary: Simple terminal designed to ease connection to ephemeral serial ports Group: Other URL: https://github.com/wtarreau/bootterm Source0: %name-%version.tar BuildRequires(pre): rpm-build-licenses %description BootTerm is a simple, reliable and powerful terminal designed to ease connection to ephemeral serial ports as found on various SBCs, and typically USB-based ones. %prep %setup -q %build %make %install make PREFIX=%buildroot/usr install %files %_bindir/bt %changelog * Fri Oct 01 2021 Igor Chudov 0.4.0-alt1 - Initial release bootterm-0.5/.gear/rules000066400000000000000000000000421460351565500152700ustar00rootroot00000000000000spec: .gear/bootterm.spec tar: . bootterm-0.5/.gitignore000066400000000000000000000000041460351565500152050ustar00rootroot00000000000000bin bootterm-0.5/LICENSE000066400000000000000000000020541460351565500142310ustar00rootroot00000000000000Copyright (C) 2020 Willy Tarreau Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bootterm-0.5/Makefile000066400000000000000000000031501460351565500146620ustar00rootroot00000000000000# verbosity: pass V=1 for verbose shell invocation V = 0 TOPDIR := $(PWD) DESTDIR := PREFIX := /usr/local LIBDIR := $(PREFIX)/lib CROSS_COMPILE := CC := $(CROSS_COMPILE)cc OPT_CFLAGS := -O2 CPU_CFLAGS := -fomit-frame-pointer DEB_CFLAGS := -Wall -g DEF_CFLAGS := USR_CFLAGS := INC_CFLAGS := THR_CFLAGS := CFLAGS := $(OPT_CFLAGS) $(CPU_CFLAGS) $(DEB_CFLAGS) $(DEF_CFLAGS) $(USR_CFLAGS) $(INC_CFLAGS) $(THR_CFLAGS) LD := $(CC) DEB_LFLAGS := -g USR_LFLAGS := LIB_LFLAGS := THR_LFLAGS := LDFLAGS := $(DEB_LFLAGS) $(USR_LFLAGS) $(LIB_LFLAGS) $(THR_LFLAGS) AR := $(CROSS_COMPILE)ar STRIP := $(CROSS_COMPILE)strip INSTALL := install BINS := bin/bt OBJS := OBJS += $(patsubst %.c,%.o,$(wildcard src/*.c)) OBJS += $(patsubst %.S,%.o,$(wildcard src/*.S)) ifeq ($V,1) Q= cmd_AR = $(AR) cmd_CC = $(CC) cmd_LD = $(LD) cmd_STRIP = $(STRIP) cmd_INSTALL = $(INSTALL) else Q = @ cmd_AR = $(Q)echo " AR $@";$(AR) cmd_CC = $(Q)echo " CC $@";$(CC) cmd_LD = $(Q)echo " LD $@";$(LD) cmd_STRIP = $(Q)echo " STRIP $@";$(STRIP) cmd_INSTALL = $(Q)echo " INSTALL $@";$(INSTALL) endif all: static shared bins static: $(STATIC) shared: $(SHARED) bins: $(BINS) bin/%: src/%.o $(Q)mkdir -p bin $(cmd_LD) $(LDFLAGS) -o $@ $^ %.o: %.c $(cmd_CC) $(CFLAGS) -o $@ -c $< install: install-bins install-bins: bins $(cmd_STRIP) $(BINS) $(Q)[ -d "$(DESTDIR)$(PREFIX)/bin/." ] || mkdir -p -m 0755 $(DESTDIR)$(PREFIX)/bin $(cmd_INSTALL) -m 0755 $(BINS) $(DESTDIR)$(PREFIX)/bin clean: $(Q)-rm -f $(BINS) $(OBJS) *.o *~ bootterm-0.5/README.md000066400000000000000000000514751460351565500145160ustar00rootroot00000000000000# BootTerm BootTerm is a simple, reliable and powerful terminal designed to ease connection to ephemeral serial ports as found on various SBCs, and typically USB-based ones. ## Main Features - automatic port detection (uses the most recently registered one by default) - enumeration of available ports with detected drivers and descriptions - wait for a specific, a new, or any port to appear (convenient with USB ports) - support for non-standard baud rates (e.g. 74880 bauds for ESP8266) - can send a Break sequence and toggling RTS/DTR for various reset sequences, even on startup - fixed/timed captures to files (may be enabled at run time) - optionally time-stamped captures (relative/absolute dates) - reliable with proper error handling - single binary without annoying dependencies, builds out of the box - supports stdin/stdout to inject/download data - configurable escape character and visible character ranges ## Setup Setting it up and installing it are fairly trivial. By default it will install in `/usr/local/bin` though this can be changed with the `PREFIX` variable, e.g. to install in ~/.local/bin instead: ``` $ git clone https://github.com/wtarreau/bootterm $ cd bootterm $ make install PREFIX=~/.local $ (or "sudo make install" for a system-wide installation) ``` The program comes with no other dependency than a basic libc and produces a single binary (`bt`). It can easily be cross-compiled by setting `CROSS_COMPILE` or `CC`, though the makefile only adds unneeded abstraction and could simply be bypassed (please check it, it's self-explanatory). It was tested on several Linux distros and platforms (i386, x86\_64, arm, aarch64), on macOS, on FreeBSD 12 (arm64) and on AIX 5.1 (ppc). ## Using it ### Most common usage By default, `bt` connects to the last registered serial port, which usually is the most recently connected USB adapter. A list of currently available ports is obtained by `bt -l`: ``` $ bt -l port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 0 | 524847 | ttyAMA0 | uart-pl011 | 1 | 524847 | ttyUSB0 | ftdi_sio | TTL232R-3V3 2 | 1320 | ttyUSB1 | ch341-uart | * 3 | 206 | ttyUSB2 | cp210x | CP2102 USB to UART Bridge Controller ``` In the example above, `ttyUSB2` will be used when no option is specified (which is indicated by the star in front of the port number). Otherwise, running `bt ttyUSB2`, `bt /dev/ttyS0`, or anything else will also do what is expected. The help is shown with `bt -h`. Once connected, the default escape character is the same as telnet: Ctrl-] (Control and right-square-bracket). After the escape character, a single character is expected to issue an internal command. Quitting is achieved by pressing `q` (either upper or lower case) or `.` (dot) after the escape character. Sending the escape character itself is possible by issuing it again. If the command that must follow the escape character doesn't arrive within two seconds, the escape character is ignored, and the terminal will emit a visible flash when supported, to indicate the user that the escape sequence was aborted. A help page is available with `h` or `?`: ``` $ bt No port specified, using ttyUSB0 (last registered). Use -l to list ports. Trying port ttyUSB0... Connected to ttyUSB0 at 115200 bps. Escape character is 'Ctrl-]'. Use escape followed by '?' for help. BootTerm supports several single-character commands after the escape character: H h ? display this help Q q . quit P p show port status D d flip DTR pin R r flip RTS pin F f flip both DTR and RTS pins B b send break C c enable / disable capture T t enable / disable timestamps on terminal Enter the escape character again after this menu to use these commands. ``` ### Waiting for a new serial port Most often you don't want to know what name your serial port will have, you just want to connect to the one you're about to plug, as soon as it's available, in order to grasp most of the boot sequence. That's what `bt -n` is made for. It will check the list of current ports and will wait for a new one to be inserted. It even works if one port is unplugged and replugged. This is very convenient to avoid having to follow cables on a desk, or when connecting to a device that gets both the power and the console from the same USB connector. It's worth noting that this wait feature is ignored when `-l` is used to get an instant list of detected ports. The automatic port detection is more precise on Linux and FreeBSD where a detailed list of available ports with their respective drivers and descriptions are available. On other operating systems, the automatic detection falls back to a scan of node in `/dev`, avoiding known unrelated ones, and focusing on specific ones for certain operating systems (e.g. macOS uses `cu.*`). Example below booting a Breadbee board with an integrated CH340E adapter after `bt -n`: ``` $ bt -n 3 ports found, waiting for a new one... port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 3 | 0 | ttyUSB1 | ch341-uart | Trying port ttyUSB1... Connected to ttyUSB1 at 115200 bps. Escape character is 'Ctrl-]'. Use escape followed by '?' for help. ! U-Boot 2019.07-00068-g18b9e73630-dirty (May 30 2020 - 13:37:48 +0900) DRAM: 64 MiB ``` One particularly appreciable case is when connecting to an emulated port, such as below on a NanoPI NEO2 running Armbian: ``` $ bt -n 2 ports found, waiting for a new one... port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 2 | 0 | ttyACM0 | cdc_acm | CDC Abstract Control Model (ACM) ``` This port selection mode can be automatically enabled by setting the `BT_SCAN_WAIT_NEW` environment variable to any value: ``` $ export BT_SCAN_WAIT_NEW=1 $ bt 3 ports found, waiting for a new one... ``` ### Waiting for any port A different approach consists in waiting for either a specific port or any port. By default, issuing `bt ttyUSB0` will fail if this terminal doesn't exist yet. But with `bt -a ttyUSB0`, BootTerm will wait for the device to appear. Just like with `-n`, this wait feature is ignored when `-l` is used to get an instant list of detected ports. With no device name specified, it will wait for any usable device and use the most recent one. If some ports are already on board and must never be used, they can be excluded using the environment variable `BT_SCAN_EXCLUDE_PORTS`, which is a comma-delimited list of device names, possibly ending with `*`: ``` $ export BT_SCAN_EXCLUDE_PORTS=ttyS0,ttyS1 $ bt -a Waiting for one port to appear... Port ttyUSB0 available, using it. port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 0 | 0 | ttyUSB0 | ch341-uart | Trying port ttyUSB0... Connected to ttyUSB0 at 115200 bps. Escape character is 'Ctrl-]'. Use escape followed by '?' for help. ``` It is also possible to enable this port selection mode by default using `BT_SCAN_WAIT_ANY`: ``` $ export BT_SCAN_EXCLUDE_PORTS=ttyS* $ export BT_SCAN_WAIT_ANY=1 $ bt Waiting for one port to appear... ``` It is probably what most laptop users will want to do so as never to have to pass any argument and automatically connect to a USB serial port. One may also exclude some drivers from the scan using `BT_SCAN_EXCLUDE_DRIVERS`, which can sometimes be more convenient to ignore some known uninteresting internal devices: ``` $ bt -l port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 0 | 12562 | ttyACM0 | cdc_acm | Fibocom L830-EB 1 | 99 | ttyS0 | serial | * 2 | 21 | ttyUSB0 | cp210x | CP2102 USB to UART Bridge Controller $ BT_SCAN_EXCLUDE_DRIVERS=serial,cdc_acm bt -l port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- * 0 | 24 | ttyUSB0 | cp210x | CP2102 USB to UART Bridge Controller ``` Alternately, it is possible to restrict the port enumeration to only a specific set by listing them in `BT_SCAN_RESTRICT_PORTS`. This can be more convenient when you know that your port is always called `ttyACM0` or any such thing for example. Similarly these ports can end with `*` to validate any suffix, such as `ttyACM*`. ### Changing the baud rate On opening, the port's baud rate is automatically changed to the value specified with `-b`, or in the `BT_PORT_BAUD_RATE` variable, or by default to 115200 if neither is set. However if the baud rate is explicitly set to 0 (either using `-b` or using the variable) then the baud rate is unchanged. Example below with a NodeMCU module: ``` $ bt -b 74880 Port ttyUSB2 available, using it. port | age (sec) | device | driver | description ------+------------+------------+------------------+---------------------- 2 | 524342 | ttyUSB2 | cp210x | CP2102 USB to UART Bridge Controller Trying port ttyUSB2... Connected to ttyUSB2 at 76800 bps. Escape character is 'Ctrl-]'. Use escape followed by '?' for help. ``` It is interesting to note above that the hardware does not support 74880 bauds and selected its closest support speed (76800). This is a 2.5% error, it will not cause any trouble. ### Using BootTerm to detect ports BootTerm supports a "print" mode. In this mode it will simply print the device name without the leading `/dev/`. It can be convenient as an assistant to other flashing tools to wait for a port and print its name. By default the newly detected port is reported however, and it is wise to switch to quiet mode to avoid intermediary information: ``` $ bt -np 4 ports found, waiting for a new one... ttyUSB2 ``` ``` $ bt -npq ttyUSB2 ``` Thus a script that needs to connect to the port as early as possible to reprogram a board could be doing something like this to wait for the device to show up: ``` PORT=$(bt -npq) if [ -n "$PORT" ]; then flash -p "$PORT" -i "$IMAGE" fi ``` ### Issuing special sequences The current port status (name, speed, pins) is reported when pressing `p` after the escape sequence. Pin names in lower cases are in the low state, those in upper case are in the high state. When pin toggling is required, it is wise to check the real pin's polarity, as most circuits invert it multiple times along the chain. The reported status here is the one seen by the serial port driver. A break sequence can be used to trigger the SysRq feature in Linux, or to reboot some boards. The break happens by pressing `b` after the escape key. E.g. `Ctrl-] b`. It is also possible to send a break sequence just before starting the terminal by passing `-B` on the command line. Combined with `-N`, it may be used to simply program a reset from a script (e.g. `bt -aqNB`). The DTR pin can be toggled by pressing `D` after the escape character, and the RTS pin can be toggled by pressing `R`. Both pins can be toggled together (for example to reset an ESP8266 in flashing mode) by pressing `F`. ### Capturing responses There are two ways of capturing responses. If no terminal is needed, simply redirecting `bt`'s output to a file will work: ``` $ bt /dev/ttyS0 > server-panic.log ``` If a terminal is needed and a capture of the session is desired, there is a special capture function. It supports three modes, which are enabled either by `-c` on the command line, otherwise by environment variable `BT_CAPTURE_MODE`: - `none` : capture is disabled, this is the default - `fixed`: a file name is fixed on start of the capture and will not change - `timed`: the file name is re-evaluated each second and if it changes, the previous file is closed and a new one is opened. In all cases, the files are only appended to and never truncated, so that BootTerm will never destroy previous traces. If a fresh file is needed, just remove the file before starting BootTerm. The `fixed` mode usually is the one that users will want to capture a session when dumping a boot loader's configuration or the kernel's boot messages. The `timed` mode can be useful when connecting to a port waiting for rare or periodic events in order to get a dated file. The file name is defined by the format passed to `-f`, which defaults to the current date and time. It uses `strftime()` so please check this man page to get all format options supported on your system. Thus when exploring a new device, one would likely use: ``` $ bt -c fixed ``` And when collecting event logs from a server with one file per day: ``` $ bt -c timed -f "server-%Y%m%d.log" ``` ### Timestamps in captures Timestamps may be enabled in captures, according to the `-t` command line argument, or the `BT_TIMESTAMP_MODE`, which may contain one of the following values: - `none`: no timestamp is added, lines are dumped exactly as received, this is the default - `abs` : the date and time of the first character at the beginning of a line are dumped using year,month,day,hour,minute,second,micro enclosed in square brackets at the beginning of the line, followed by a space, such as `[20201219-192208.888633]`. - `init`: the time of the first character of each line, relative to the start of the program is printed at the beginning of each line using seconds and microseconds inside square brackets followed by a space, such as `[ 3.557564]`. The seconds are padded on 6 characters, which are enough for 11 days of capture. Above this the field's width will increase. - `line`: the time of the first character of each line, relative to the date of the previous line is printed at the beginning of each line using seconds and microseconds inside square brackets followed by a space, such as `[ 1.273017]`. The seconds are padded on 6 characters, which are enough for 11 days of pause between two lines. Above this the field's width will increase. Example of capture with line-relative timestamps, showing a kernel decompression time of 1.27 second: ![capture](doc/kernel-boot.png) In addition, timestamps may also be temporarily enabled on the terminal by pressing 'T' after the escape character. The timestamp mode configured above will be used, except if not set, in which case the absolute mode will be used. It is important to understand that timestamps on a terminal will quickly cause trouble on the output since the terminal and the application are seeing different contents and positions. It can be convenient to observe boot times for example, but should be disabled when starting any interactive application (e.g. an editor). Please note that there is a subtle difference between terminal timestamp and file-based captures. In file-based captures, the time is taken when the first character is printed after a line feed. This provides the most accurate timing between two consecutive lines because it will take the processing time into account. However on the terminal it cannot be done exactly this way without rendering the terminal very painful to use, because on a typical prompt, the leading timestamp would be missing until the user presses a character that would suddenly cause the timestamp to be printed before it is echoed. Thus on the terminal, the timestamp is calculated when the cursor goes to the beginning of a new line, and is (re-)printed every time the cursor goes to the beginning of the line. This means that on the terminal, the processing time, if any, will usually be accounted as part of the time needed to print the next line. ### Changing the escape character The escape character may be specified either as a single character after -e or as an integer or hexadecimal value representing the ASCII code of this character. Users coming from the venerable `screen` utility will probably want to set the escape character to Ctrl-A (unless of course they want to call screen from within these sessions, or are irritated by the confiscation of this common sequence): ``` $ bt -e1 ``` Those coming from tmux would rather use Ctrl-B: ``` $ bt -e2 ``` Here are a few other convenient and less common escape characters: |Sequence | Code | Argument | |---------|------|----------| | Ctrl-@ | 0 | `-e0` | | Ctrl-A | 1 | `-e1` | | Ctrl-Z | 26 | `-e26` | | Esc | 27 | `-e27` | | Ctrl-\ | 28 | `-e28` | | Ctrl-] | 29 | `-e29` | | Ctrl-^ | 30 | `-e30` | | Ctrl-_ | 31 | `-e31` | ### Masking problematic characters Running at wrong speeds or connecting during a boot sequence often results in some garbage to be received on a terminal. And different terminal emulators handle these differently. For example, Xterm supports a very large variety of codes in the C1 range (0x80-0x9F) among which CSI (0x9B) which is an escape with the high bit set. The problem is that the prefix range is large and that many of them will result in reconfiguring it or hanging it until a sequence ends. This is often translated into unreadble fonts, the terminal definitely freezing, or arrow keys making the cursor move on the screen instead of being sent to the remote terminal. Other terminals have their own problems with 0x00, 0xFF or can be at risk with improper handling of ANSI codes prefixed with 0x1B. In order to address this, BootTerm can block dangerous characters and print them encoded instead. It will always block raw and UTF-8 encoded C1 codes (0x80-0x9F with or without the 0xC2 prefix), as these are always terminal configuration codes which should never be sent to the terminal emulator. In addition it's possible to restrict the range of printable characters using `-m` and `-M`. ### Remapping CR to CRLF or LF to CRLF In raw mode, terminals emit CR and LF separately and expect them both to be received. This is true as well for the user terminal. If the device being connected to works in raw mode but does not emit CR or does not emit LF, then the user terminal will quickly be confusing and painful to use. An environment variable, `BT_PORT_CRLF` may be used to force a remapping of input characters to the expected CRLF pair: | BT\_PORT\_CRLF | Behavior | Use case | |--------------|----------|----------| | 0 | no remapping | all the time | | 1 | LF is replaced by CRLF | when lines look like stairs | | 2 | CR is replaced by CRLF | when the same line always gets overwritten | Note that in order to make this compatible with captures and terminal timestamps, the remapping is performed inside the port buffer before the captures, and as such the fixed CRLF sequence will appear in the captures. Most users will never need to change this setting. ## Motivation The first motivation behind writing BootTerm was that when working with single board computers (SBC), the serial port is the only interface available during most of the development or exploration of the board, and that sadly, the available tools are either pretty poorly designed, or incomplete as they were not initially made for this purpose. Some tools like `screen` use particularly inconvenient key mappings making it a real pain to use the line editing on some devices, others like `minicom` reconfigure the terminal disabling scrolling and making copy-paste a nightmare. Some like `cu` were not initially designed for this and nobody ever knows the right command-line options nor how to quit it. Then comes the myriad of Python 3-liners which do no error checking, resulting in spinning loops when a port is disconnected and the port being renumbered once reconnected, or conversely crashes and backtraces when facing binary character sequences that do not match UTF-8. Not to mention that many times they can't even install as they depend on various modules that are incompatible with those installed on the local machine. The author has had the best experience with `kwboot`, a tool initially made for flashing some Marvell-based devices, which integrates a terminal that is started at the end of the transfer, and which is now part of U-Boot. A few fixes and changes were brought there (such as allowing the terminal to start without flashing), and it served the purpose reasonably well for a few years. But it doesn't support non-standard speeds that some chips require (ESP8266 at 74880 bps, Rockchip SoCs at 1.5 Mbps) and is not easy to build out of U-boot. After losing too much time fighting with such tools and cycling between them, the author considered that it was about time to address the root cause of the problem, which is that none of these tools was initially written for being used as they are used nowadays, and that if they fit the purpose so badly, it's because they're simply abused. ## Miscellaneous This program was written by Willy Tarreau and is licensed under MIT license. Fixes and contributions are welcome. bootterm-0.5/doc/000077500000000000000000000000001460351565500137705ustar00rootroot00000000000000bootterm-0.5/doc/kernel-boot.png000066400000000000000000001215221460351565500167220ustar00rootroot00000000000000PNG  IHDRB˽gAMA a=tEXtSoftwareXV version 3.10a-jumboFix+Enh of 20081216 (interim!)| IDATxwxUE'="RAf@K.ZXX E ` :(" HI0pCx9d>Ͻyygi;nBV0a+kH$I5gΜie D"iX̜9fH$Dp2&H$#1D"I$To2՛[Dd&񧇯ݜHk/82L1L -DToROֹ(gs3Ϝ9SVUv3/Nn)3g\vd2]vmR@ &G”̙Ct-ƍ}[O,N+g2 K/nܸ̙3۷ߵsug>7S!66M6 2a۫_իW>}׭f2gΜyoܲQIo&6,zgf.]H\JY~wy6FIYо=q3zSBmڴ1՛|F}T6m Og t51Pw;ME zB]xTo*@IQ(((zgs$n߹=TZ"ieL"1õknz7ؿa#|n/H|C"իW#~M`VGNMMu]wTLҼP?ӾFp/a㺍e͙3kK?rĢ2uԍ6f|!}WyXwc鱤 _~")z?S_>z;)41CjLD"82gcD"10rH$˘D"H ';jkD"4[""H$f>H$Db nnnxٞH$Ďo|C"H$F.cD"10rH$˘D"H UXϻܰ_]\Db ,cp@+iMH$odD/_:y=(yw_ yxSՉӞ~ڣ2cCh q@kβQ!H~|9"8ӿ}IԔIϿm8`2:4H$b_qQ xhwn޼i B.yp@xX-[)C{lب6瞛T 5b˗K2$&|tG}V2mgm!#]?u_X?9SL5rTD{oҔ{RJ_ (pҏ$kFyp@x^~RkWéys?=4$f}{t܄I}Oi+7fGfϻ^I$`7c?,;xg[ӿ[W_6!akW}'iWbC_9өsǴ}{ѽ:خ}8B?F?~b<[w~'_322n9uqc#g=bھoccG1С#;bO;2t`%CT3?}Pzfƾy5߻v~'k6Ɛy Obg H$FAh#;JXΝ;n}ǂܛWVCwo;Z/瘘(_nk>vB_ur B(!o?pAppڵk??xzE]t&_ϝN[⿾׹S'??fHIaa}}}ߘZ!b85 yg]7I(***..رC-H(W(X^ϣ|uw"RXXԥKg;ZP-Agg4h@f6^*خ׿},[dS&O:sf?vA38(|E*_M&f0wߟ_|~(/@yxxXNJWvڥ|޸~od6mZ΂CA>L+Db1taM;uImjBBssBC{ _fpss^x~S̖Ali3jѢE.S}}}n݆}ڵK-,e`MY6獨W_Z;cVUUU x5+]˾}lX 8`|{䠭^>e&1'ÿ}sԽ<[CaMT__?&2LG+燆Xr DJ"7oLl?wHKf7kfkXH_83sA l8+l2QH$,cMOY];v50J$qF-f\%#1D"I$x"23qD"H$D̰ʑD"H$2͝o?O$D"Gc1Buu~IBEEE[96&MJHH !k1lmk[!T0%+`V^Jy~Skǚ5kJKK֬YiDj'ܹSu|ɔ5aOTz&W/DbKx||0sΝ7o^ӕ0&"ڽ{7xԩ~~~ HIIUɑ#ɬV/ۺy B֯%x@w-44c+= /cUUUe%xc!/$''wMy˖-eee$'Xj$_ccc&Mj۶mZZZEEE||< _yqTTBkyyyEEE6lP6WcIYS"4+ةG8ԉUTT`Zl1.//ו'Obo͛۷o=`ku͛7l^OcsrrBׯ'kҜ9sD1c466.[$YfM:ck)H2v޽{!z^\kkhh`tG"e>x Ƹ1>>> @8ƸD-$$D:+~:8((($$Dd+//?pnc|Uu~q'B-cON݂''Npe]x1ݻYGyJبΦ} B|>b======EJe}y&VA6k(HeLLY\BS~@ŋuGM8vBCe前}#""sP@@@vD4w1//'ozzf~"$lԛ˖~@qe?GM8'Pu0=d׮] 8p Bرcʟ B={$_bƌ"5SIMME=SO=H͚5_~O?!pB>}l߾]uVqr&RűȓH#N[eee[,HX.L81;;(!!k׮ʟf͚Kȑ?Syy?vU'888==rΝXuRR䮻'럗{wetYe- Zg:t Eƞ\$怵/c_}UL4I֭[c D"]~}&!%"DGG\rԩÇ;D"a"|ތvΝlw$F-$DhⰇ<(i<H$T½D"HK<{WgϞoXʕ+k֬iD\ɫ.D"qq~!bvvwqcǎ 4H<=90&N D"4y>s޽{# ^ziDD"XZl%۔jJWN{$J$buu5BLJlXUU+=%Db\bk D"7.qS?8pĉ)Jާs#Q"H$Émz D"->__]-{PTTm&M@>%X½<.ExH$xH$DbrH$ : kxogm҄c %qM=8}Lɔ}: v횔TRRRRRعsgx)qȑ4L6mj۶-lL팜 ,dd<)&ݘ … ?q};w1bۇ:fО={ ְoF[ ,8}cbbx F8lb#x6O#V6lXjjjMMͮ]B @=}}7|P]B(..!4jԨ7o~W!CX+г>{骪˗/VX?Xˋo^Z3&&;vTڲeKYYYོ/_WTTaÆ-Z _~d2}d9?ZԶm۴x N )))>cbAOЩS-[(:v^lR^^In69.Jbmm۶O2Ų;㨨(NqD_wH/+**0~~~-[[O>IF-B(&&c1޴iǧcܯ_?m 65sx1999cܱcлヒ1~W^y;C ߸qCITr~j7onٲ /9k֬Sb-[1wߗ_~Jh2gcO!0}w>}{b>3ٳg+9G9v8T ZՕ5S_-b:sssu3fLccU\;tuH$6Gϟ-khhS醆 rrΝ;Bqqq$qǎjɓ'cO<.ީS9777 oK}&|||ݕcߩ$(((((c?RP(JμcL.)oߎ1;v!gٳ-Z8qSשwdR߼yc1AeL]V]9S!4|ӧOHV"vEj8UCM2eWJN \p>}oߎJNN0`@yyI &CNzꩧzJWACOXXr:B3櫯ڹsgddC=I⃓HqД)S8;ĭC@ B={Foqqqf"NN¨*EqbXp5 "##O8Q[[[XXpw"BCCIPRٳg1O>$eWrF&ДҔݻ#M?OgϞzYBޫV*(((((XjPPc7֨7.yyy{/_6LgϞ%TTT9rf%A[^YYsNqcc#+J:E M2%??mAߏ14hyOOҥKVVVyy?%?ԟ 5`+++I=[n"Yfͺ"/Ca!t]wד5U\ە:… J$6G*T'"Pnݺ}0̘1#66vܸq<%@7n#0DC,сvÇ펑z] ],qKvΝlwĠA%H$ aME幮Tg nЇ6q۲J#$cp { σoX `z1eeeݻaKh>}h:|ꢢ"{eeeiiizB7@)B"1//o(GىoK郢r-b|lH?;I벮 CmwB[!8ـXΙOY?rd02f1~߾}ODPJtv" G&M6%%%d6m`uYB>S dw6j ܧt L1ʑy$pw <rIIID+gϞLVqFm| wﮩݳg߁oѣ{%)/_cAAA_0?3+3300P{e6jڵkǟX:ɩ%8No!gΜ㏿; Y͎:0177co߾J ]GJ郢"שs=|o[oNblw=bjG;;c %0! #xk8z۔W`NAz2Ν[UUor MNN&ɡ,@TV*ǣۧ^z//{&ٳ6G\Jɓ'={d2z+dVAm:lMbŋۭ|Dv!@£LdIkEcyŕ+Wj(&R{ ̟?oر޽{=J2" 2wZ?YMXU.^(1;XTh]b% %111+{@-뛑qʕŋ_rc=o[i\mV^pa֬Y-԰iSWכ} *v,HFVV0&Gz_]UUkWH>}0uGttt`` =. %^p… !T"1Db/vߢI iTp(K\;MiӦ]r~Sܐsu)7+l;$½5*]~;''Ryl=BhᙙEEEgV7667a______ߪU+u=7nHMM%71s qrR-jh'ωGu޽{!oP nɭAdd*2 6GH >BbcjXpOI9s&8&&uD Yp^WWw޽{knڵJAwwwC~ܪͲ.xGI#Gqss?~$m۶m۶"°V/`f0'Q7(ϱĉH *;lʈ+0>GZG!3 1"ԣGu+wލ17nhYUUl-Zߧ!" JҥK-[d6l؀1^|94"%]- m`"ː%Rצhsb *8>F!ڶ ʟjV99pʈ+܃#5t)33|˖- YHT" 'Nݻ7Z_~ke?Wc=o߾#G ;cLTgHbuu5Q`#5q~X)jmlqqPoACKFS8TxNYYy 9L\W3ZȁSF\SQ K^p$+Ϋh, }"+̞=}\ƬT'Oi*ԩܴ!26V*ܓe)d߿ըkj1QQQy7VTThL$_srr(T&L<_ "%]- m`"ht(u G_J ޠ@>UV">"!!o Lr#½ROH2OfDQ$g32ܫ1lJ{ Xub{+ݻ@kC^Nar_7WAim04$(>"l;8A|DV3}DE3B(CAګ1GNkX#>`||G M@@~C.cV*ħ4x)ZHYpO;֬399qS/W_Uz^z544޽ZkPHHH]]ݭ[8YЦ+CZJ q\qk`e ?F Gȃ#ea#6S)#p5MG 䃻Ol ^ XSPxIdhZp]__ݫW:YѣѣG=!?pmmWmm-{ŝ;w3VUFM>3%%ƍ*8 Jk[l;kxkK!Z\S\)Rncp⺶8ef<V*܃'#^^^֭+...-- DV+#FqС(VДҔݻ2tSNϧO~:UO> D<*^#Ξ=1~'YjAmPZLdޜRcS6EvD s Tt!uɟ#OUwːMԀ n`ZC`q]GhҥK,Z½ N9>A#*Ϙ1#66vܸqɎwUc]"HGvvv\\D]Ƥ4D"8A9ۅK!H$eH{H +~ƕ+f!F~6{iE\PS'xN 6Uw9uK6 mÙN`Mxcw(:q[p&ֵ "r=X'XxNddd :פ[ny{{{{{%&&:%b#j=:}nX1xXp&ֵ*"r=X'XxN>K1Ђ 0wߏXCiRO JJJu2UӵUAڶmVQQϏK,\i;g o۶8??ʔ)!/!uuI] |f NOC.cVj*9B3(fϱa `Dr_!y3s k jLA s*"r=hGJ(^.\o߾%0t#G<~8Fi*,.$.dHyP,zA3 =BwIIQQݦ Yp/VhYY*"r½DrJ#K1BlllAAAaam8&R&i(L&9R]bGb*K5smP,㒸i5Xs5F}lb cGx.qR݉V$g0j&6G{0ThY֭Qyw½#YhѢEDr;v,$$$88رcvvF\=***f̘Nx"B#p).zꩧJb![Ϟ=,)}$<3 =BD s <WO8qO?:t M`qqwХʝ;wb>P,"ЬYrssc`~N-ZsVqmN,)}qw 0C:A԰6eʔRg0u~J{;Ҝ322:ۑfA֭ ۷ol_~iTT\k ~O6l\@ttt`` ٖvǡDEEr ӜG p/׮];~x@@@rrܹsC"4¢]UH$ĕM*ܳpiӦ]rNsq<4랝#p}Lɔ}:(tRUUWwww_jy>"wrGu޽{f=#s`!֡?Xf~[cc7RSS틸/Q JHH(---..^v-y\|"[jT)"00p֭&)++k„ 8q:W>##c-Z oJ$m="ϙ37K(xc2V^^޶m۶mۖfݳ+}f~.\PEDD`?wJ*7+>aB>(sȑ$)h L:o)))ĉXY X"a2vQqΝv1,k@ s0^CK.]l53V^Pc7ߔ,--%?( YMýHە<Ƙ tRQQBƍ?+?c{_WZZ:VuuH7n*D*>aB-)//I SDeeeUU瞍rs½“O>I,!??+W*#1^^^d2~AS9YJ:E )#hJ~kDATND9)@'b+=%!VC''$q̘1?U0=zׯ8::Sƍ{r`D҉Xwss;v,ƘhpE% }9BU&=y$8))C.cU'ܹ>,YܹsD_ƔߨQ:9pLQ8u0ROT2 xD|(//?p 1bxڴid8p +/7nضm۹ssoC̏.*1==&ãb7n b:Nw}1ƍNC.cU'GaM&Ӱa@xm9uA峕f*GPː5)+v򡪪R  *++[zuBBB^^Y8RjyӧOϝ;?ONN΋/Xۻwo]]}oطoߓ'OԐ[Hτ߿?V]Fq5F8pc0ƻwfwĐR&S(oWNB\͛g1 L~4($$DۃbP6mڶmk'|0""vBw^h@@yoP#.RM6$|2DQڵ! ]x1z/\C[lAե 2d׮]d|9\~{֭[PP͛7_{5ww%Kr<@-?";g)u֑7W ,رcǏG$iڸ2\޽{}3g 4=lbnҤIfuf*@%i Y)j.G\PKܚ4]?>..n֬Ydq={k."~uO?TTTt%+\xqΝcƌ1+N`"yP*>a~Z/////Z;EڵkРAVwQ.Zpyĉ„;S6,"rww㏉K+WT /|*'tNЪS wY)j5r9~( ilHەwuW}}=ٕ%@PZZ<:HjhhHJJB}cڥ>}444PvEsVUU$E|^~ԩSӧZ"&N][[[TTеkW~H T#Y^j&&&3L"1zŨ~} fWH슻y g"8p/[nmܸO"ieL"1D u F^W{8864v1%W*܏9&)77wӦMC"/C`1lXxmMn2;H~8Xv p iii~~~=Ƙ^1GEDw:N tVoߎ16mH~ vma/8 Cx<Dؐ9QFeeeݼy󫯾B 2D]bW;z뭷~; ):iG2[l!LjtBĔ>|cǎCÜV^%UUUDq }}3+' `N%/(ƍdkjj*srj:o<qq1pE+DkZg&FM v5I:m).H,F*TwAstI"4a4qAstI"4a2flįvji#uYXjxU3{ R*ܳ^zsrr*++~mf JHH(---..^v-yY -8G\aÆ8pvtRUUG}433d2o߾Cdpqv횔TRRRRRعsg!7)NE X\NC0 x>N+oeK֨xǹCuݤ$ufdd=ED1==]}Y2220Ƒc.қYWj,.~yLC'"`H1lqnBCC1ٺr`s:1&T7n`$ZQتIf9:D&jE'ș)آN:effoٲEL/.Nǁx"Hm۶iii|_~d2} ԥͻe=""T___qee%+ c޴h7QYDF 6l/_ 8\졩HCIԓ^]]}ĉ޽{+5S'E? $(' 'բIuNάLD)뱱}g$ٳDMtH!H~㨨9s'Bwߗ_~IS\t1߮vV;&&.)P hߛfǒ8%N@G0NoR;ojce~ D=ɩܣ LjWc+(' 'բIuNάS*H'񤬓}I呜DUT&7+T< D$gHH|rpy衇$ԥMdɒsyxxhr6pعs" nE;=<|ӧIJ~~~ddbz*f.LaÆj7ppss#kRR[}+M;v:Xk՛YcI[%m@mr5A%BE!1rg;ѣx6Fo׮ԩSXlҳgOqffٜjH@˗1mڴ3S8T$ۂ(X)Ʌ"rM>ŋm:,Hœ7xVZ)5+'1Vo"`g}A~]LYԙճC\_rY'%%UWW?y Qȳ1uo[X%u@G0Voݺu+((HɬMCgA\Cnb{P>>>!4~xs޽,X@^l[JN^\ឣ NI'Rc*j9^O2E- &ׅ "z)'''0ć\N!-Zh"]t1h6$Ύ޽{<33gΰ N\pJ{yy[4>>>00O[Ν[UUo Cɳ YTSL/--%OI]jl֭YYY+`t钕EIŝtHz镕;wķ4GDD߿ȑ#[1뻘N|gGdd'jkk NQ}d2aCCCuup,M ӓs`v 0Q0tpoGI0m{d:3ijueee۷/rfr06ӰdAgPZ1e۷Ozek;~4[dt  /~P%PAi8qCT}ŋtMGyc|11L߉jig9ˮ2K,FjT }U+1mԪϠ>xqeVAwʉlN//+V+V ?*"-..˛4i22$K|x`HLJ߲>ҥKEEE!777n,A}}r_t2 USQ C'J3dݻªeEtuh=_nk+:dc6RO?1Vdiu/S<f??QJ\jƸGGGG Q>PrIX 3u0'v6q:lA89՚UP5/o\pO> "*:D NGOd'62Q]o%xN] .4g5JWQ#FO6rW*T+sZf#wKZǒMj9JlZ-AxqH tWf?y <4 ? fgϞڵS[u8V:Hg;fdE@~*kc {(kO<==A}hK Jʬ,2 @qk!4t .Sf͚K\N){ժUV"7(YZACEv :fRB}ĉJKKt,@}}CEQGI΃rIXb5JTq:pgĞ] %7A~*xPpoG%aƌƍKNNv//,,LLLXXB&c؆. )CA7 F *FyS"**_7>o"G[L & =Dftp:8 LEqbC^|Ϝ9cӺuk!,, cϯe-^LpǬÆkeXLݻwcN7`g[GYێ4uo V½ ?tƘ jٰaxJ@CMII!g|=xT9IXNų>{骪˗/@l p< oN:effoٲłe $xAUr8LqV"(_YYYUUE]܈OCD~_` .8h" "Q2K^ܐ4d8O9uwz0r"iNuuuaa[lJ$C$==ĉ-.}||JJJ0SEEOx+_^-UNRx1;v A\rJLD7<+2{l2P麢$(Q,u?E%R'O'%%_)> ŭ3NA}Whjǧ%p("!]2bS 5 Ճ˘?s۝VDR|ԩD.S-3ydɓ'>ill+*')VS?cND'xT7"%laVp>FI0PzDYUY:Z@~DPJ>x di(nNCjSeW;>)H>H#h‚MCfR8!u6 tQ˘]#q$j`"kѢƸBɶgٳeEE<___dN^_;&M &ޠq( bAԟAUrκzE%rx1|{?<{\)PP}Mo'8zMXАD!R|CV|C.cPWh׮;TSjSfZHHH]]ݭ[eE!x̛7u#C<x &ޠ400PjLP9^W JZ(UrκX+ D~S7i:A=#,ڠ]KP$D$9mө!`q,C9;8nܸcǎ|BhǎxI{[ӧOLIIQg͛sssϟ҃J9O>!r:z)XZ8@ DG:e)8AO'QUYŵwӠ;ߵkנA=cǎ!=PpƁGOmP EzqC`{S! 8L{KKݑC~gϞ5L?K^֭[W\\\ZZHl={cORފJ祗^/Z9Rg?ٳW^5k2'@NM5At钕UT%063JY6<݁ĉkkkvP;DpiszzzZ`@H>H#@ܖA!`q,Cx?d_ TRF@rE+F}yمfWH\]R F@L ISF>q42&4ed Tׁ);+[u3֖W6'<4֠Vx ꗳj#6'1m۶)SZ Uꂳ iCRV~*\=hWbm!]ZΟGP[Io(.8ZA-ίSDDDii)&j'x`=!1+5 R"mbV*#2/'9JҠ!VA@vIV9)?Z";XꂳK[5eo= H Zppp-UN>BKAAX]su@Iu=%us~]r)`"88+\ƬT'PcME* [p":GTfԊנ~9HUN$9y0 "]pNq$"j,ڲʹG9AC`DՃNjC;w$b%:ܺunQ%I:K,9w:L'bP˘={ɚr^ZZG+{[pn_~6c5~9JV9A?uP%XgJ_b0'hH\R)F_'j9:JZAwVڜj&iذaT:N΁Cѐ˘ ={ķN)PM%9/kMsՀXd5j,.'m p̱ Ŕ޺亵H1{)f/z"$յ[=Ga1{P_ȃ9YGEmwpA.8 IDATg\JAӰ9*܃D`{ԨQ?8Bٳ!͒ PJn)f/u%Tdg Cٳ6,FuTv>rlrbV*܃$1999441DY`61NA o]r R^s':9'n*3!YbX,f#GEmw:K#hmP!**6&MJHH %"111F1cFllq㚌aaabbX-{ RA :pA{ĠH1{Xb RX"Ht Epd2&H$͗&pI*5wtiӦ]rN]0,zhW Ou*8q^C^+uQ`V.cu"A sk, Oud[7_}ٳg+Vrʚ5ksr{UJCD2$! &zA8\Ā,!|#zu!E$ Q$8+3"!؝γ~gU#X]Us>| ^f/0a”)SswyO>2dѣ !QQQ)))w@KV𣄊0Q:3S > !dÆ |UT/{!|P^ ׯ!d۶m;,I)ݾ}fbIn7Dq4xяNv;V{ש7ӧZG-l$1Z2j*ñwބI+h%6$, (}uKK(dZ_0; 58 [%Q&:)ݎ|]`*-Yb[b=֫gb)_F%_0䋫wMh VO4NQh%TP2}Atjƈ]=2|IDg<]^nGc ݒndd*0+++w%Oz$ ϧK>0 8FzwЄHPJ)_(}uIHJ]*(1_\Bf# Ue3rhDR* 0*ŋfnDG%f?/zwЄIF),$uK(@PAFHuOyqMd<~x~qBw}G7n!d֭[l1z(8ŋ{&ڵkԈr6ͼpHX8 1R<>Ү]ꫯǧ<ф:u0`@|MI,"FߢEH Bވ&DdG)r'uS\ i{N-BV0{KnE{DЩ ^ tW9aU`f?'x¼f.Wy1:BT *Qq+фHa뒃/Rwɏפ ܣtjKp*2|˙QI&vX\S"mPOuI*0{"eGcc#\a!IK%9!fzzzeeeAA:Qx8j%OUOQm]S҄{J\e簷^ P*&PpR w|jT@PRʿ?d"y^^^rrrXXԩS)@vF[G= %jto<ܵ~aJ)idԨs'%T>m˻3#a}=fQ8pRz 7x㍔XDXyy95>l~H)}w啠 ;3 "8Teh8qR^^@KJMMN RW=eQ#Zd%%%2WƥqJiEEupDڵk߾=fjɈBT]Gb8?4i W:֐b!}y`dТ b#pO9vtL*B "5tLLLSSn7șOHhh_QQa322bbbDFNtyJܹsK1tQNf~0"M'߳gOڳgO''8yIիWWUU %u^m۶?pBOs9s'4W !?0$uPt/֭[QQD[3y4?OiapWh½ 6_ݻw;+99yժU 2PPpC f͞=;,,lݺu;JEEEzkyi48T__b KZZZ@(=PKKK t5 |TVk}lMi5Pk ܣG^ujxYzuII,**3f Qxl4p8muuVVV.^pXt{+e-Zdv ZjU}$>t|'PQԳ%Z8Knp)RJJJAAAMMMYYj 477;cxCI.x{(}rrr.\t:/I>͂O!j" 4,(h^4EOVwowrBW1(hu2:|0tƍ}U9BNJ͛-׹bŊN_~傂5kրʜؗ/_N)>}s=Q&pr8ͤsKq8*l^QOWfsYO׉f Łܮ]n (!ȧY.\l\iȼ/6 jn>RPNcAh*FmNF7|ݻ)TwY񙚴g3lMuW^)//_fͷ~_|_RsZ c?}4466K.L AXPj&[b%='TeZ%b2uYBhq\rРA/(otJiee;n" 1K( y7`%jn|rZ'˗S.k3>̽͟y#G̚5944 ~)Γ9̓vyŬ,z*Ѫ2f-^… fOKqHf4jԨCՕfff^&F'%+/Oy1;vVUUJ`DT!dС{쩭-++>}:gNnMx^%@'̪鏊TS<ĺ(SN~155wMp,6 `PK&Ә&[P N<w^5tоpo , 8] N:_GEE̚5w ]%@&ѣ\(в$RӘGZrqV&VK-(+NI 7:xuQZZ@w:; Yz2\^^ecb\{E}iFjS@Θ1^lEox1~'C ׁRRRC XڌKZ06mڌ9򫯾ܹsN8qw͝;׍:0UM)?yHGhwP#Jn ]TGg,Kv ^Gk֬())4i($u(rҹ)۷ow8v; >Vr8{MHHՏnN'(huu--o)(aT2Q#tt\sȈy"%Sԋ[bK=2¬n &$$${凜%[jQQQyya4hOt-4^ÆKZ\A9yHK'0Fpo Lt:'9[Wg33{1Hu4$ OԺ<ʄlذT$sE}aѠXB~tsuJ-ZD)ѣG޽)iiiԳR\-c iws٣HCn#R";~?{,5MNTf̘1cǎZ& 2PrJN@%%%ҕ+W4ȸz7%VR ^"Ǝf 4`mChUeϡCRJLDIIIԳR\-c iGWcr89'=\{=[&_\ȎQG IDATꨉ.fxx\`… /]wh^(9^#GgIIɨQ̫N}é/`͕XR,{*4ԙ344p,Y$33ٳ. QϒJq}5^=z Oh6nXSS?jR]]M{c$$Dne ܻ "={ljjڼy3|o{5{vJ)W98)ر9N {䋋"\JO ͒Jqu p?ԱcQ=t{/=[&_\ȎI&crssw[D{NJgӿJB0 N}Y%$rh@*t@&YԐޑi&y$YR/Aӻrj۶- G=p-;%z7Dvޭ["8={OϞ=q=-# %T;vVUU?$(.8: *K u~/dʫ1@&Y:#&&n3wY͒JqɡRRNM4^KK+QZZakWڵ3nE >TRʇ(Fgb@lE̓bbbV^]RRt:ƌ3ںK/9sŋ/ ]ASPzkrbk/hwP#$ |714z0_ӧO>s/tԩϓ'O ߿R%1ZSNfգH#[myѿjPS04z0_╕P1uaJƍF'DvYSSsС^zT&Bğ![JB ½{$Ÿ+Q#׷_ B M嶂r>&&?_AA5Mc F+S@Pћoyݔ挌 `zBC'O~衇(۷oWM*g!ŁYI{%!Oh\RďQ j=\p@jnb-ӘO3(A8q e%W8z5JJJ:x xVŴ?SJܮlb.ĭ2UZJB8 #NG(_|.8 5x7 J40,W\T7DF`nݺu˖-W;wf.]9sf{뭷ܨ)}ҥ z~BȐ!C!3NC.n/\@6v۷o\\ܫIs $$22aFCZTT3{Ef՚C&LO?iӦk;xQVUFF!$o]/sN% o& K~B$~Ԉ-l@|akx0~ĉǎs:?kbFE($T[ -g.ax7!镕̓fOni(뫯l@@mb-_K}@&܏=z'O?v/''%UKK6*ep7G:p~l2/8ńW*B-Z-/' tBJOcTp[@MH 7c "4jM$MgħE/qL?744u/<&ܻ&V ޽{,..^rKJJJAAAMMMYYٌ3Du>t|'Dp8VϧSyFǶmۀTft,G^^^Ϟ=1,X{ݻwVD^?3hlllllر#Xbcc333v{EEŲe˘䴮j4` RJKJJ䕨46uTJƍ ! ǏիWΝ%1"<<u ڜz>=(~KIFYfy`dV!@[AW!O6vX*edg(7áλ,3Q/ %*` ?R+QiPDDp8աC*%Cm<^D:7^~ D$4WUU!Owĉ2BHHHHyy9@RPe ȍf03YqѾBzmPJ ͝;_Kyy951>,) i,` ۷({6*?^_pR+ڵkEͩCm<'J7Bu -_R:}{NF7 JP=zݻ74--MԺQ;珞`dV!@{B6 .ƘQnСhN4$fr- ߷nx-Hm۶E[r Xĉ)w4xܹb[8ejKVVA^S'J%x9`_ PzqqmPg.]vΠJ)2e ʼU蕂*@w>~[/;/t(Z F>")K6m>+VTUUsNjQZIA9,>;;RVI'<<СCۋ]{dE%|TrsmgtEviARg~g u8K,<{ ,;`dV!Y>zQyCjhH6" <,wuS\C߾}Z=![o=|pmm-,kiU$ i \=z OyΝ;+`7֥KXsYD>6mƍ555fܐ{'.ɓR@k3>SJܱcGbbbEN'8ц\^qMKؿiiBQ!zgϞMMM*S||<˘}rrY:։l#h.;;[ҳ>KM6m7te״KAPK`z衶m2h秦;05663>>~ҥ6lիWy#lT|2B$snQκ`x%gvvvRRR\\ܦMTO>,_@ BB:U=z볲F5h QmBCCCCC?QQQJ?pO9vtLWcfF8:tϞ=eeeӧO9jԨCՕfff^$dRыtV^]UUUTT .IP\\Ν;/^E\Q%xj(>&&n7,2:rŢ"̭}GW!@tUĭWVTCN y >}oG1f͚U]]}̙9sK+ >T [:up8JKKږl7""tÆ *sZZZ^h`}\v)u'JKK[pɓ !{mɦm6ٳ֭[גjii-q 8:u믿ə5kVK6T__b 3--ƴN+W4ޒWZZA'MJw JgOՋ3$~EzZVKGq(^Jw JGy{i5__N~ɣ0{:A1` tSWxRDFAL.ސ'4_=&S'Ӯ`E0{^(zBDާO .8};[fĂtZ^QPNc>e*q(j*-n*,*ފu{/-JѣӧCf;:q۵kwmah-_W(>c 7kyKA9pQ: (=ZUYh %ސn)T~=zHZ ͔wJʕ+ $Ob  4Kg[䚯-4棫1'[j,hqWg=uيEt^""z7xӧcMQzJJʑ#GdԨQЄxڐh--&U!$P1_Q< Jwc -_m6ۍ?=t:}O<5ODc\ 3=ϫ?3@uɏ_F{%4{(رcGL2E/Dw;ɾ'3O*zV. ½4bQ r뭷R)&&?7 ԈbQ#H%Q".A7x}`)(=ªx?~R|H!ũh~ƈ3/ȍS} UPNc>]o [C(5T% 4½Q$nyʻ!ĩYSS84xޟQpՙg^"EOcG7ZA|A7Թsg"#G D>H7 ݛ*3X+ qV\pZ<h~3/V6PY1@+H +~mBg}&2{p߱c_|b`o߾]䣎ix2{p%Kt;&ǫQz=W6P_1@몓ޒ/'N''߁hi w.^{^KK0`C PY`hK?i)JJ!ءDK50۵5)-IƃN=x = nۇ ҡC]fΜ9sLxU3$yCgyyĉ:w<}t Cm:$$$< ZT r4K3gtl7o^n"""̈&:O;vltttttqxhV~~~JJ(NC?sssu֭[[Jz<kIh%^YÇjo̘1Rxh…',YĒ'dBHvvvvv6!䮻b*2ddh߾}xjek󉉉{ڵ{AZZ>;{O!!a߾}z<}X3f#]FtFP<== _|EۓO>Y]]b ?o111.wԬ,BO?M) VWWB~߹)z‘8,Y*6R<-Wngy{-^X ;|πuGUtiѣzj=iEi,}9W*}J4VVVVVVFILLn]5Bqnvy b _{BFA.ܭz=bpͧM6Ǐ:y۽[n`<<N_N槂a~=SEEE@iW_/a0Y6l oll !> ,@̥(##,mM t̗/\'Xz5\iL/b>_9lҪJpkhhhhh 19:t0H]5BqCe PjjjשSGEt/z~~{XX) ^8\4󌧤N^|v0ac׉_ٹs{gggKZj߾]*GqáŁ}ӦMG}g!tRjikRn0c?~ᇜ]vu˖-zi,X`Μ9/9L8ùvBB@NQ#KF(nnTO>###332l0BEFAv"4a~ŋ?ã>y̙qF3$$lذaƍWuODJKK!')qܸ%?5޳gOB 'ׯ_e:ՅԯSN0\\!&M"|0qIDATKu!yyycsΝ;eH>qȑCdSJm6^R+ڶJmn6<""bРAgΜ=]tҥ?FPzzzbb""c}}oq7o߾O>_|zVK\=jhhhll1cFN:u4c 3`=OQqԓRpkk׮Yxvvv~ڵk7a„SN `A{iiiJJJǎǏO Sd&0t|WC HNN/]&~}}iӢ###'NX^^nNɓ'G lȑ)& RڧOBHVVGq͛7'$$$&&1-꓊UVVFFFvuQK7ހWVF- /BS3 ki?/_|A TZpBoSSS-ZpZ,! YZZZZZ+tVP }hiiiiiYڔik}QV0w~QKKKK+1---- ƴXz biLKKKK+G8etIME #'\SPIENDB`bootterm-0.5/src/000077500000000000000000000000001460351565500140125ustar00rootroot00000000000000bootterm-0.5/src/bt.c000066400000000000000000001777401460351565500146030ustar00rootroot00000000000000/* * Copyright (C) 2020 Willy Tarreau * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) && !defined(NO_TCGETS2) #include #else #include #endif #include #include #if defined(__FreeBSD__) #include #endif #define MAXPORTS 100 #define BUFSIZE 2048 #define MARGIN 1024 #ifndef VERSION #define VERSION "0.5.0" #endif #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef CRTSCTS #define CRTSCTS 0 #endif #ifndef O_NOFOLLOW #define O_NOFOLLOW 0 #endif const char version_str[] = "BootTerm " VERSION "\n" "\n" #if defined(TCGETS2) && !defined(NO_TCGETS2) "Built with support for custom baud rates (TCGETS2).\n" #endif "Copyright (C) 2020-2021 Willy Tarreau \n" "This is free software: you are encouraged to improve and redistribute it.\n" "There is no warranty of any kind, see the source for license details.\n"; const char usage_msg[] = "Usage: %s [options] [/dev/ttyXXX | port#]\n" "Options:\n" " -h display this help\n" " -q quiet mode: do not report events\n" " -p only print selected port name and quit\n" " -l list detected serial ports and quit\n" " -a wait for a port to be available (BT_SCAN_WAIT_ANY)\n" " -n wait for a new port to be registered (BT_SCAN_WAIT_NEW)\n" " -m specify lowest printable character (default: 0)\n" " -M specify highest printable character (default: 255)\n" " -b specify baud rate (BT_PORT_BAUD_RATE; default:115200; 0=current)\n" " -c {none|fixed|timed} enable capture to file (BT_CAPTURE_MODE)\n" " -t {none|abs|init|line} enable timestamps in captures (BT_TIMESTAMP_MODE)\n" " -T enable timestamps in terminal (see -t for formats)\n" " -e escape character or ASCII code (default: 29 = Ctrl-])\n" " -f capture file name (default: 'bootterm-%%Y%%m%%d-%%H%%M%%S.log')\n" " -V show version and license\n" " -B send a break sequence before starting the terminal\n" " -N do not launch the terminal, just quit\n" " -L new line mode: 0=CRLF, 1=LF only, 2=CR only (BT_PORT_CRLF)\n" "\n" "Ports are sorted in reverse registration order so that port 0 is the most\n" "recently added one. A number may be set instead of the port. With no name nor\n" "number, last port is used. Use '?' or 'help' in baud rate to list them all.\n" "Comma-delimited lists of ports to exclude/include/restrict may be passed in\n" "BT_SCAN_EXCLUDE_PORTS, BT_SCAN_INCLUDE_PORTS, and BT_SCAN_RESTRICT_PORTS.\n" "BT_SCAN_EXCLUDE_DRIVERS ignores ports matching these drivers.\n" ""; /* Note that we need CRLF in raw mode */ const char menu_str[] = "\r\n" "BootTerm supports several single-character commands after the escape character:\r\n" " H h ? display this help\r\n" " Q q . quit\r\n" " P p show port status\r\n" " D d flip DTR pin\r\n" " R r flip RTS pin\r\n" " F f flip both DTR and RTS pins\r\n" " B b send break\r\n" " C c enable / disable capture\r\n" " T t enable / disable timestamps on terminal\r\n" "Enter the escape character again after this menu to use these commands.\r\n" ""; enum crlf_mode { CRLF_NORMAL = 0, CRLF_LFONLY = 1, CRLF_CRONLY = 2, }; enum cap_mode { CAP_NONE = 0, CAP_FIXED, CAP_TIMED, }; enum ts_mode { TS_NONE = 0, TS_ABS, // absolute time TS_INIT, // time relative to init time TS_LINE, // time relative to previous line }; /* In order to better deal with isolated LF, isolated CR, CRLF and LFCR when it * comes to inserting timestamps, we'll see a line in 4 different states: * Beginning-Of-New-Line (BONL), Beginning-Of-Same-Line (BOSL), Same-Line (SL), * New-Line (NL). The transitions will occur like this: * * | CR The timestamp is emitted after entering * /\ v ----------> the BONL state on LF, and before leaving * LF \_> BONL <---------- BOSL BOSL for SL. This deals with line * ^ LF *| ^ overwriting using CR and LFCR cleanly. * CR | V | CR BONL ensures a TS was already printed, * NL <------------ SL so none is needed on CR there. In BOSL, * a timestamp is needed only before data. * SL is assumed first. Time is retrieved * when entering BONL. * * We stay in NL until a CR is there so that even with LFxxxCR we emit the TS. */ enum line_state { LS_BONL, // beginning of new line LS_BOSL, // beginning of same line LS_SL, // same line (at least one char) LS_NL, // new line (changed but not homed) }; struct serial { char *name; // device name without /dev char *driver; // driver name (usually a short word) char *desc; // descrption when reported time_t ctime; // device attachment date }; struct buffer { int data; // where data area starts, <0 if output closed. int room; // where room area starts, <0 if input closed. int len; // number of bytes in. unsigned char b[BUFSIZE]; }; const struct { int b; // baud rate tcflag_t f; // corresponding flag } baud_rates[] = { #ifdef B50 { .b = 50, .f = B50 }, #endif #ifdef B75 { .b = 75, .f = B75 }, #endif #ifdef B110 { .b = 110, .f = B110 }, #endif #ifdef B134 { .b = 134, .f = B134 }, #endif #ifdef B150 { .b = 150, .f = B150 }, #endif #ifdef B200 { .b = 200, .f = B200 }, #endif #ifdef B300 { .b = 300, .f = B300 }, #endif #ifdef B600 { .b = 600, .f = B600 }, #endif #ifdef B1200 { .b = 1200, .f = B1200 }, #endif #ifdef B1800 { .b = 1800, .f = B1800 }, #endif #ifdef B2400 { .b = 2400, .f = B2400 }, #endif #ifdef B4800 { .b = 4800, .f = B4800 }, #endif #ifdef B9600 { .b = 9600, .f = B9600 }, #endif #ifdef B19200 { .b = 19200, .f = B19200 }, #endif #ifdef B38400 { .b = 38400, .f = B38400 }, #endif #ifdef B57600 { .b = 57600, .f = B57600 }, #endif #ifdef B76800 { .b = 76800, .f = B76800 }, #endif #ifdef B115200 { .b = 115200, .f = B115200 }, #endif #ifdef B153600 { .b = 153600, .f = B153600 }, #endif #ifdef B230400 { .b = 230400, .f = B230400 }, #endif #ifdef B307200 { .b = 307200, .f = B307200 }, #endif #ifdef B460800 { .b = 460800, .f = B460800 }, #endif #ifdef B500000 { .b = 500000, .f = B500000 }, #endif #ifdef B576000 { .b = 576000, .f = B576000 }, #endif #ifdef B614400 { .b = 614400, .f = B614400 }, #endif #ifdef B921600 { .b = 921600, .f = B921600 }, #endif #ifdef B1000000 { .b = 1000000, .f = B1000000 }, #endif #ifdef B1152000 { .b = 1152000, .f = B1152000 }, #endif #ifdef B1500000 { .b = 1500000, .f = B1500000 }, #endif #ifdef B2000000 { .b = 2000000, .f = B2000000 }, #endif #ifdef B2500000 { .b = 2500000, .f = B2500000 }, #endif #ifdef B3000000 { .b = 3000000, .f = B3000000 }, #endif #ifdef B3500000 { .b = 3500000, .f = B3500000 }, #endif #ifdef B4000000 { .b = 4000000, .f = B4000000 }, #endif { .b = 0, .f = ~0 } // end }; /* operating modes */ struct serial serial_ports[MAXPORTS]; const char *baud_rate_str = NULL; const char *cap_mode_str = NULL; enum cap_mode cap_mode = CAP_NONE; enum crlf_mode crlf_mode = CRLF_NORMAL; const char *capture_fmt = "bootterm-%Y%m%d-%H%M%S.log"; unsigned char escape = 0x1d; // Ctrl-] unsigned char chr_min = 0; unsigned char chr_max = 255; const char *port = NULL; int nbports = 0; int currport = -1; // last one int quiet = 0; int baud = 115200; char cap_name[PATH_MAX]; int cap_fd = -1; // -1 = no capture in progress time_t last_cap_sec = 0; struct timeval start_ts = { }; // timestamp of current line enum ts_mode ts_mode = TS_NONE; enum line_state line_state = LS_SL; // anywhere on the line const char *ts_mode_str = NULL; int ts_term = 0; /* list made of port names (without slashes) delimited by commas */ char *exclude_drivers = NULL; char *exclude_list = NULL; char *include_list = NULL; char *restrict_list = NULL; const char *always_ignore = "pty*,null,zero,rtc*,nvram*,mem,kmem,kbd*,mouse*,psaux*,audio*,dsp*,fb*,random,urandom,mt*,rmt*,console,ptmx,ptc,tty,stderr,stdin,stdout"; /* display the message and exit with the code */ void die(int code, const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(code); } /* displays the usage message and exits with code */ void usage(int code, const char *progname) { die(code, usage_msg, progname); } /* parse a long in (which may be NULL) and copy the result in . * Returns 0 on failure to parse an valid number. */ int parse_long(const char *in, long *out) { char *endptr; if (!in || !*in) return 0; *out = strtol(in, &endptr, 0); return *endptr == 0; } /* parse an int in (which may be NULL) and copy the result in . * Returns 0 on failure to parse an valid number. */ int parse_int(const char *in, int *out) { char *endptr; long ret; if (!in || !*in) return 0; ret = strtol(in, &endptr, 0); *out = ret; return *endptr == 0 && ret >= INT_MIN && ret <= INT_MAX; } /* parse a byte in (which may be NULL) and copy the result in . * Returns 0 on failure to parse an valid number. */ int parse_byte(const char *in, unsigned char *out) { char *endptr; long ret; if (!in || !*in) return 0; ret = strtol(in, &endptr, 0); *out = ret; return *endptr == 0 && ret >= 0 && ret <= 255; } /* Look for item in comma-delimited list . A null list is empty. * may not contain commas. Returns true if found, false otherwise. */ int in_list(const char *list, const char *item) { size_t ilen = strlen(item); const char *word; while (list && *list) { word = list; while (*list && *list != ',' && *list != '*') list++; if (*list == '*' && ilen >= list - word) { if (strncmp(word, item, list - word) == 0) return 1; } if (strncmp(word, item, ilen) == 0 && (word[ilen] == 0 || word[ilen] == ',')) return 1; while (*list && *(list++) != ',') ; } return 0; } #if defined(TCGETS2) && !defined(NO_TCGETS2) // since linux 2.6.20 (commit edc6afc54) /* try to set the baud rate and mode on the port corresponding to the fd. The * baud rate remains unchanged if is zero. We have two APIs, one using * TCSETS and one using TCSETS2, which allows non-standard baud rates. On * failure -1 is returned with errno set, otherwise zero is returned. If a * baud rate couldn't be found, -1 is returned and errno set. */ int set_port(int fd, int baud) { struct termios2 tio; ioctl(fd, TCGETS2, &tio); /* c_cflag serves to set both directions at once this way (CBAUD is a * mask for all the Bxxxx speed bits) (see termbits.h): * (Bxxxx & CBAUD) << IBSHIFT for the input speed * (Bxxxx & CBAUD) for the output speed. * A special baud rate value, BOTHER, indicates that the speed is to be * taken from the c_ispeed and c_ospeed fields instead. */ tio.c_cflag = (tio.c_cflag & ~CSIZE) | CS8; // 8-bit tio.c_cflag = (tio.c_cflag & ~CSTOPB); // 1-stop tio.c_cflag &= ~(PARENB | PARODD); // no parity tio.c_cflag &= ~HUPCL; // no DTR/RTS down tio.c_cflag &= ~CRTSCTS; // no flow control tio.c_cflag |= CREAD; // enable rx tio.c_cflag |= CLOCAL; // local echo if (baud) { tio.c_cflag &= ~(CBAUD | (CBAUD << IBSHIFT)); tio.c_cflag |= BOTHER | (BOTHER << IBSHIFT); tio.c_ospeed = tio.c_ispeed = baud; } tio.c_iflag = 0; tio.c_oflag = 0; tio.c_lflag = 0; return ioctl(fd, TCSETS2, &tio); } /* returns the currently configured baud rate for the port, or 0 if unknown */ int get_baud_rate(int fd) { struct termios2 tio; int idx; ioctl(fd, TCGETS2, &tio); if ((tio.c_cflag & (CBAUD|CBAUDEX)) == BOTHER) return tio.c_ospeed; for (idx = 0; baud_rates[idx].b; idx++) if ((tio.c_cflag & (CBAUD|CBAUDEX)) == baud_rates[idx].f) return baud_rates[idx].b; return 0; } void list_baud_rates() { printf("termios2 is supported, so any baud rate is supported\n"); } /* we have to redefined these ones because GNU libc tries very hard to * prevent us from using those above, and notably purposely creates * redefinition errors to prevent us from loading termios.h */ static int tcgetattr(int fd, struct termios *tio) { return ioctl(fd, TCGETS, tio); } static int tcsetattr(int fd, int ignored, const struct termios *tio) { return ioctl(fd, TCSETS, tio); } static int tcsendbreak(int fd, int duration) { return ioctl(fd, TCSBRK, duration); } #else /* TCSETS2 not defined */ /* returns a combination of Bxxxx flags to set on termios c_cflag depending on * the baud rate values supported on the platform. Only exact values match. If * no value is found, (tcflag_t)~0 is returned, assuming it will never match an * existing value on any platform. */ tcflag_t baud_encode(int baud) { int idx; for (idx = 0; baud_rates[idx].b; idx++) { if (baud_rates[idx].b == baud) return baud_rates[idx].f; } return baud_rates[idx].f; } /* try to set the baud rate and mode on the port corresponding to the fd. The * baud rate remains unchanged if is zero. We have two APIs, one using * TCSETS and one using TCSETS2, which allows non-standard baud rates. On * failure -1 is returned with errno set, otherwise zero is returned. If a * baud rate couldn't be found, -1 is returned and errno set. */ int set_port(int fd, int baud) { struct termios tio = { }; tcflag_t baud_flag; // see man tty_ioctl tcgetattr(fd, &tio); /* c_cflag serves to set both directions at once this way (CBAUD is a * mask for all the Bxxxx speed bits) (see termbits.h): * (Bxxxx & CBAUD) << IBSHIFT for the input speed * (Bxxxx & CBAUD) for the output speed. * A special baud rate value, BOTHER, indicates that the speed is to be * taken from the c_ispeed and c_ospeed fields instead. */ tio.c_cflag = (tio.c_cflag & ~CSIZE) | CS8; // 8-bit tio.c_cflag = (tio.c_cflag & ~CSTOPB); // 1-stop tio.c_cflag &= ~(PARENB | PARODD); // no parity tio.c_cflag &= ~HUPCL; // no DTR/RTS down tio.c_cflag &= ~CRTSCTS; // no flow control tio.c_cflag |= CREAD; // enable rx tio.c_cflag |= CLOCAL; // local echo if (baud) { //tio.c_cflag &= ~(CBAUD | (CBAUD << IBSHIFT)); //tio.c_cflag |= BOTHER | (BOTHER << IBSHIFT); baud_flag = baud_encode(baud); if (baud_flag == ~0) { /* baud rate not found */ errno = EINVAL; return -1; } if (cfsetospeed(&tio, baud_flag) == -1) return -1; if (cfsetispeed(&tio, baud_flag) == -1) return -1; } tio.c_iflag = 0; tio.c_oflag = 0; tio.c_lflag = 0; return tcsetattr(fd, TCSANOW, &tio); } /* returns the currently configured baud rate for the port, or 0 if unknown */ int get_baud_rate(int fd) { struct termios tio; speed_t spd; int idx; tcgetattr(fd, &tio); spd = cfgetospeed(&tio); for (idx = 0; baud_rates[idx].b; idx++) if (spd == baud_rates[idx].f) return baud_rates[idx].b; return 0; } void list_baud_rates() { int idx; printf("The following baud rates are known (not necessarily supported though):"); for (idx = 0; baud_rates[idx].b; idx++) printf("%s %8d", (idx % 5 == 0) ? "\n " : "", baud_rates[idx].b); putchar('\n'); } #endif // TCSETS2 not defined /* returns the value of configuration variable or NULL if not found. * The variable is first looked up in the environment by prepending "BT_", by * turning all letters to upper case, and changing "." and "-" to "_". Example: * "scan.exclude-ports" becomes "BT_SCAN_EXCLUDE_PORTS". */ const char *get_conf(const char *var_name) { const char *value = NULL; if (strlen(var_name) + 3 < 256) { char envname[256]; char *d, c; const char *s; strcpy(envname, "BT_"); d = envname + 3; s = var_name; do { c = *(s++); if (c == '.' || c == '-') c = '_'; else if (islower(c)) c = toupper(c); *(d++) = c; } while (c); value = getenv(envname); if (!value || !*value) value = NULL; } /* TODO: implement an option to perform a look-up in a config file if * value is still NULL. */ return value; } /* removes all blanks and all occurrences of "/dev/" from var and make it a * port list used to replace . NULL is just an empty list and is valid. */ void set_port_list(char **list, const char *var) { char *value, *s, *d; free(*list); *list = NULL; value = var ? strdup(var) : NULL; if (!value) return; /* strip off "/dev/" in front of each name, and trim all blanks */ d = s = value; while (*s) { while (isblank((int)(unsigned char)*s)) s++; if (strncmp(s, "/dev/", 5) == 0) { s += 5; continue; } *d++ = *s++; } *d++ = 0; if (!*value) { free(value); value = NULL; } *list = value; } /* read one line from file name assembled from , return the contents * copied into allocated memory or NULL in case of error. */ char *read_line_from(const char *format, ...) { char ftmp[PATH_MAX]; char ltmp[4096]; int needed = 0; va_list args; char *line = NULL; FILE *f; va_start(args, format); needed = vsnprintf(ftmp, sizeof(ftmp), format, args); va_end(args); if (needed < sizeof(ftmp)) { f = fopen(ftmp, "r"); if (f) { line = fgets(ltmp, sizeof(ltmp), f); fclose(f); if (line) { /* delete the trailing LF */ if (*line && line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = 0; line = strdup(line); } } } return line; } /* read the symlink from the name assembled from , return the contents * copied into allocated memory or NULL in case of error. */ char *read_link_from(const char *format, ...) { char ftmp[PATH_MAX]; char ltmp[4096]; int needed = 0; va_list args; char *line = NULL; ssize_t ret; va_start(args, format); needed = vsnprintf(ftmp, sizeof(ftmp), format, args); va_end(args); if (needed < sizeof(ftmp)) { ret = readlink(ftmp, ltmp, sizeof(ltmp) - 1); if (ret > 0) { ltmp[ret] = 0; line = strdup(ltmp); } } return line; } /* checks if a file exists. returns non-zero if OK, otherwise zero. */ int file_exists(const char *format, ...) { char ftmp[PATH_MAX]; int needed = 0; va_list args; struct stat st; va_start(args, format); needed = vsnprintf(ftmp, sizeof(ftmp), format, args); va_end(args); if (needed < sizeof(ftmp)) return stat(ftmp, &st) == 0; return 0; } /* updates the capture file name if needed, and if it changed, closes the * current capture and opens a new one. */ void set_capture_name() { char new_name[PATH_MAX]; time_t now; if (cap_mode == CAP_NONE && cap_fd == -1) return; if (cap_mode == CAP_FIXED && cap_fd >= 0) return; now = time(NULL); if (now == last_cap_sec) return; if (cap_mode == CAP_NONE || !strftime(new_name, sizeof(new_name), capture_fmt, localtime(&now))) { if (cap_fd >= 0) close(cap_fd); cap_fd = -1; return; } if (cap_fd == -1 || strcmp(new_name, cap_name) != 0) { if (cap_fd >= 0) close(cap_fd); memcpy(cap_name, new_name, sizeof(cap_name)); cap_fd = open(cap_name, O_APPEND | O_CREAT | O_WRONLY | O_NOFOLLOW, 0666); /* note that cap_fd==-1 is handled as a temporarily disabled capture */ } last_cap_sec = now; } /* sorting function for serial ports: ensures the most recently registered port * appears first. If some are equal, the device name is used instead for normal * ordering with an attempt at respecting numeric ordering. */ static int serial_port_cmp(const void *a, const void *b) { const struct serial *pa = (const struct serial *)a; const struct serial *pb = (const struct serial *)b; long long la, lb; int pos; if (pa->ctime != pb->ctime) return pa->ctime - pb->ctime; pos = 0; while (pa->name[pos] == pb->name[pos]) { if (!pa->name[pos]) return 0; // identical strings pos++; } /* if one of the string stops here, it comes first. Eg "tty" comes * before "tty1". */ if (!pa->name[pos]) return -1; if (!pb->name[pos]) return 1; /* if the first different char is not a digit in any of them, that's * OK. Eg "ttyS1" comes before "ttyUSB0". */ if (!isdigit((int)(unsigned char)pa->name[pos]) && !isdigit((int)(unsigned char)pb->name[pos])) return (int)pa->name[pos] - (int)pb->name[pos]; /* if only one is a digit and previous char is not a digit, the digit * goes first. Eg. "tty63" comes before "ttyp0". */ if (isdigit((int)(unsigned char)pa->name[pos]) && (!pos || !isdigit((int)(unsigned char)pa->name[pos-1])) && !isdigit((int)(unsigned char)pb->name[pos])) return -1; if (isdigit((int)(unsigned char)pb->name[pos]) && (!pos || !isdigit((int)(unsigned char)pb->name[pos-1])) && !isdigit((int)(unsigned char)pa->name[pos])) return 1; /* so both are digits preceeded by digits. We need to roll back to the * full number because we could be on a series of zeroes in a larger * number, e.g. "port100011" comes after "port10012". */ /* look back for the beginning of a possible number */ while (pos > 0 && isdigit((int)(unsigned char)pa->name[pos-1])) pos--; la = strtoll(pa->name + pos, 0, 0); lb = strtoll(pb->name + pos, 0, 0); if (la < lb) return -1; else return 1; } /* Tries to open device node after verifying that it looks like a * char device, and checks if it supports termios. Returns non-zero on success, * zero on failure. * * WT: maybe we should only do that on devices shared with groups or users so * as to limit ourselves to non-dangerous candidates only ? */ int file_isatty(const char *devname) { #ifdef __linux__ struct termios tio; #endif struct winsize ws; struct stat st; int ret = 0; int fd = -1; if (stat(devname, &st) != 0) goto fail; if ((st.st_mode & S_IFMT) != S_IFCHR) goto fail; fd = open(devname, O_RDONLY | O_NONBLOCK | O_NOCTTY); if (fd == -1) goto fail; ret = isatty(fd); /* terminals with columns and rows set are usually local consoles */ if (ret && (ioctl(fd, TIOCGWINSZ, &ws) != 0 || (ws.ws_row && ws.ws_col))) ret = 0; #ifdef __linux__ /* On Linux, unless there is a real port_number indicating it's a * hardware device, only keep terminals having CLOCAL set, those * without are local consoles. */ if (ret && (ioctl(fd, TCGETS, &tio) != 0 || (!(tio.c_cflag & CLOCAL) && !file_exists("/sys/class/tty/%s/device/port_number", devname+5)))) ret = 0; #endif close(fd); fail: return ret; } /* scans available ports, fills serial_ports[] and returns the number of * entries filled in (also stored in nbports). */ int scan_ports_generic() { /* all entries must start with "/dev" */ const char *dirs[] = { "/dev", "/dev/usb/tts", "/dev/usb/acm", NULL }; struct dirent *ent; char ftmp[PATH_MAX]; const char *devname; struct stat st; int diridx = 0; DIR *dir = NULL; nbports = 0; for (diridx = 0; dirs[diridx]; diridx++) { dir = opendir(dirs[diridx]); if (!dir) continue; while (nbports < MAXPORTS) { ent = readdir(dir); if (!ent) break; snprintf(ftmp, sizeof(ftmp), "%s/%s", dirs[diridx], ent->d_name); devname = ftmp + 5; // skip "/dev/" if (in_list(always_ignore, devname)) continue; if (in_list(exclude_list, devname)) continue; if (restrict_list && !in_list(restrict_list, devname)) continue; #ifdef __APPLE__ /* On macOS all serial ports appear as /dev/cu. */ if (strncmp(devname, "cu.", 3) != 0) continue; #elif __FreeBSD__ /* On FreeBSD, USB serial ports appear as /dev/cua* and we need * to drop *.lock and *.init. */ if (strncmp(devname, "cua", 3) == 0) { size_t len = strlen(devname); const char *end = devname + len; if (len > 5 && (strcmp(end - 5, ".init") == 0 || strcmp(end - 5, ".lock") == 0)) continue; } else { /* nothing other than cua* for FreeBSD */ continue; } #elif __linux__ /* On Linux, tty[0-9]* are virtual consoles, tty[a-z]* are * pseudo-ttys, and vcs* are virtual consoles. */ if (strncmp(devname, "tty", 3) == 0 && (islower(devname[3]) || isdigit(devname[3]))) continue; if (strncmp(devname, "vcs", 3) == 0) continue; #endif if (!file_isatty(ftmp)) continue; if (stat(ftmp, &st) == 0) { serial_ports[nbports].name = strdup(devname); serial_ports[nbports].driver = NULL; serial_ports[nbports].desc = NULL; serial_ports[nbports].ctime = st.st_ctime; nbports++; continue; } } closedir(dir); } if (nbports) qsort(serial_ports, nbports, sizeof(serial_ports[0]), serial_port_cmp); return nbports; } #ifdef __linux__ /* This version relies on /sys/class/tty. If not found, it falls back to the * generic version which uses /dev. */ int scan_ports() { struct dirent *ent; char ftmp[PATH_MAX]; struct stat st; DIR *dir = NULL; char *link, *driver, *desc, *name; int candidates = 0; nbports = 0; dir = opendir("/sys/class/tty"); if (!dir) return scan_ports_generic(); while (nbports < MAXPORTS) { ent = readdir(dir); if (!ent) break; if (in_list(always_ignore, ent->d_name)) continue; if (in_list(exclude_list, ent->d_name)) continue; if (restrict_list && !in_list(restrict_list, ent->d_name)) continue; link = driver = desc = name = NULL; snprintf(ftmp, sizeof(ftmp), "/sys/class/tty/%s/device/.", ent->d_name); if (stat(ftmp, &st) == 0) { /* really populated ports have either a "resources" * entry (8250/16550), a "resource" entry (platform), * an "of_node" entry (anything from a DT), an * "interface" entry (e.g. for cdc_acm) or a "port_number" * entry (e.g. for USB), or "bInterfaceClass" for any USB * device with limited implementation. This seems to cover * most cases where auto-detection matters. Some on-board * devices on some non-DT platforms will have nothing but * a "type" entry showing a non-zero value for present * devices. Since we test for the device's presence anyway * we don't even need to check that entry's contents. */ if (!file_exists("/sys/class/tty/%s/device/resources", ent->d_name) && !file_exists("/sys/class/tty/%s/device/resource", ent->d_name) && !file_exists("/sys/class/tty/%s/device/bus", ent->d_name) && !file_exists("/sys/class/tty/%s/device/of_node", ent->d_name) && !file_exists("/sys/class/tty/%s/device/interface", ent->d_name) && !file_exists("/sys/class/tty/%s/device/bInterfaceClass", ent->d_name) && !file_exists("/sys/class/tty/%s/device/port_number", ent->d_name) && !file_exists("/sys/class/tty/%s/type", ent->d_name) && !in_list(include_list, ent->d_name)) goto fail; candidates++; link = read_link_from("/sys/class/tty/%s/device/driver", ent->d_name); if (link) { driver = strrchr(link, '/'); if (driver) driver++; else driver = link; driver = strdup(driver); free(link); link = NULL; } else if (!in_list(include_list, ent->d_name)) { driver = strdup(""); } else goto fail; if (*driver && in_list(exclude_drivers, driver)) goto fail; snprintf(ftmp, sizeof(ftmp), "/dev/%s", ent->d_name); if (!file_isatty(ftmp)) goto fail; name = strdup(ent->d_name); if (!name) goto fail; /* the descrption usually appears in ../interface for ttyUSB or * ./interface for ttyACM. Some devices like CH341 do not provide * anything but they come with a "product" node which identifies * the USB device. Finally on embedded devices using a device tree, * the platform devices sometimes come with a self-explanatory * "compatible" string that is worth showing as a last resort. */ desc = read_line_from("/sys/class/tty/%s/device/../interface", ent->d_name); if (!desc) desc = read_line_from("/sys/class/tty/%s/device/interface", ent->d_name); if (!desc) desc = read_line_from("/sys/class/tty/%s/device/../../product", ent->d_name); if (!desc) desc = read_line_from("/sys/class/tty/%s/device/of_node/compatible", ent->d_name); /* note: the model is not always set, so we accept NULL */ serial_ports[nbports].name = name; serial_ports[nbports].driver = driver; serial_ports[nbports].desc = desc; serial_ports[nbports].ctime = st.st_ctime; nbports++; continue; } fail: free(driver); free(link); free(desc); free(name); } closedir(dir); if (nbports) qsort(serial_ports, nbports, sizeof(serial_ports[0]), serial_port_cmp); else if (!candidates) return scan_ports_generic(); return nbports; } #elif __FreeBSD__ /* Use sysctls to enumerate all entries starting with "dev..". * Those that have a "ttyname" entry are real ttys. "uart" is one as well and * doesn't have a ttyname entry but is mapped as cuau. The scanning * method was figured using "truss sysctl -a" but appears to work fine. */ int scan_ports() { /* For lookups, the first two elements contain the request type and the * the rest contains the oids making the key name. When retrieving * values however this starts directly with the oid. */ int oid[CTL_MAXNAME + 2]; size_t oidlen, nextlen; char ftmp[PATH_MAX]; struct stat st; char name[1024]; // the sysctl entry name size_t namelen; // #bytes char *lastword; // last word of the sysctl name char value[1024]; size_t valuelen; /* buffers to keep values that possibly arrive in any order */ char currtty[1024]; char currdrv[1024]; char currdsc[1024]; char currname[1024]; size_t currnamelen; int done; /* This loop is quite odd because we need to collect data and commit * them when detecting a name change, or when failing a next operation. * The first call retrieves an OID. This OID must be turned into a name * which we use to detect that we changed to a new node, and to * retrieve variables. But the variables are then for the new node so * they must not yet be checked, which is why we commit them before * retrieving new values. */ currnamelen = done = 0; *currtty = *currdrv = *currdsc = 0; while (nbports < MAXPORTS) { if (!currnamelen) { /* first call, doesn't use sizeof but #entries! */ oidlen = CTL_MAXNAME; if (sysctlnametomib("dev", &oid[2], &oidlen) != 0) return scan_ports_generic(); } else { /* Warning, contrary to the first call, this one takes * and returns a sizeof! */ oid[0] = CTL_SYSCTL; oid[1] = CTL_SYSCTL_NEXT; nextlen = CTL_MAXNAME * sizeof(*oid); if (sysctl(oid, oidlen + 2, &oid[2], &nextlen, 0, 0) != 0) { /* WT: we should only stop on ENOENT, but don't we * face a risk of endless loop if a next fails and * we don't break ? */ done = 1; goto commit; } oidlen = nextlen / sizeof(*oid); } /* Now our OID is always set */ /* this will return the current sysctl name */ oid[0] = CTL_SYSCTL; oid[1] = CTL_SYSCTL_NAME; namelen = sizeof(name); if (sysctl(oid, oidlen + 2, name, &namelen, 0, 0) != 0) { if (errno != ENOENT) done = 1; goto commit; } lastword = strrchr(name, '.'); if (!lastword) lastword = name + strlen(name); /* "dev" is an entry on its own and appears first */ if (strcmp(name, "dev") == 0) goto commit; /* nothing but dev. should appear otherwise it's the end */ if (strncmp(name, "dev.", 4) != 0) { done = 1; goto commit; } /* name is now for example "dev.uart.0.%driver" */ commit: /* If we've reached the end or changed to a new device, we must * first commit what we have before retrieving new values. */ if (done || !currnamelen || lastword - name != currnamelen || memcmp(name, currname, currnamelen) != 0) { if (*currtty && snprintf(ftmp, sizeof(ftmp), "/dev/%s", currtty) < sizeof(ftmp) && !in_list(always_ignore, currtty) && !in_list(exclude_list, currtty) && (!restrict_list || in_list(restrict_list, currtty)) && stat(ftmp, &st) == 0) { serial_ports[nbports].name = strdup(currtty); serial_ports[nbports].driver = *currdrv ? strdup(currdrv) : NULL; serial_ports[nbports].desc = *currdsc ? strdup(currdsc) : NULL; serial_ports[nbports].ctime = st.st_ctime; nbports++; } /* prepare new name */ currnamelen = lastword - name; memcpy(currname, name, currnamelen); currname[currnamelen] = 0; *currtty = *currdrv = *currdsc = 0; if (done) break; } /* Let's take only "driver=uart" (then dev.uart.X => /dev/cuauX) * and those having ttyname=X (then dev.foo.X.ttyname => /dev/cuaX) * store last driver, ttyname, desc for each node and compare * when switching to another node. */ valuelen = sizeof(value); if (sysctl(oid + 2, oidlen, value, &valuelen, 0, 0) == 0) { if (strcmp(lastword, ".ttyname") == 0) snprintf(currtty, sizeof(currtty), "cua%s", value); else if (strcmp(lastword, ".%driver") == 0) strlcpy(currdrv, value, sizeof(currdrv)); else if (strcmp(lastword, ".%desc") == 0) strlcpy(currdsc, value, sizeof(currdsc)); } *lastword = 0; if (!*currtty && strncmp(name, "dev.uart.", 9) == 0) { /* preset ttyname from dev.uart.NN to cuauNN */ snprintf(currtty, sizeof(currtty), "cuau%s", name + 9); } } if (nbports) qsort(serial_ports, nbports, sizeof(serial_ports[0]), serial_port_cmp); return nbports; } #else /* OS-agnostic version, scans /dev */ int scan_ports() { return scan_ports_generic(); } #endif /* list all ports if portspec < 0 or just this port. The current port is never * reported if a single one is requested. */ void list_ports(int portspec) { time_t now; int p; printf(" port | age (sec) | device | driver | description \n" "------+------------+------------+------------------+----------------------\n" ); now = time(NULL); for (p = (portspec < 0 ? 0 : portspec); p < (portspec < 0 ? nbports : portspec + 1); p++) { printf(" %c%3d | %10u | %-10s | %-16s | %-16s \n", portspec < 0 && p == currport ? '*' : ' ', p, (unsigned int)(now - serial_ports[p].ctime), serial_ports[p].name, serial_ports[p].driver ? serial_ports[p].driver : "", serial_ports[p].desc ? serial_ports[p].desc : ""); } putchar('\n'); } /* Try to open this port (prepending /dev if the path doesn't start with '/'). * The FD is returned on success, otherwise -1 with errno set appropriately. * It also deals with the occasional case where the port just appears but the * permissions are not yet there. */ int open_port(const char *port) { char ftmp[PATH_MAX]; int retry; int fd; if (snprintf(ftmp, sizeof(ftmp), (*port == '/') ? "%s" : "/dev/%s", port) > sizeof(ftmp)) { errno = ENAMETOOLONG; return -1; } for (retry = 3; retry > 0; retry--) { fd = open(ftmp, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd == -1 && errno == EPERM) { /* maybe the port was just connected and permissions * not yet adjusted. */ usleep(100000); continue; } break; } return fd; } /* returns the first pending char in the buffer, or <0 if none */ int b_peek(const struct buffer *buf) { if (!buf->len) return -1; return buf->b[buf->data]; } /* inserts one character into the buffer if possible, always leaving at least * chars unused. Returns the number of chars added (0 or 1). */ int b_putchar(struct buffer *buf, int c, int margin) { if (buf->len + margin >= BUFSIZE) return 0; buf->b[buf->room] = c; buf->len++; buf->room++; if (buf->room >= BUFSIZE) buf->room = 0; return 1; } /* atomically inserts one string into the buffer if possible, always leaving at * least chars unused. Returns the number of chars added (0 or len). */ int b_puts(struct buffer *buf, const char *str, int len, int margin) { int i; if (buf->len + margin + len >= BUFSIZE) return 0; for (i = 0; i < len; i++) { buf->b[buf->room] = str[i]; buf->len++; buf->room++; if (buf->room >= BUFSIZE) buf->room = 0; } return len; } /* skips characters from the buffer. must not be larger than len */ void b_skip(struct buffer *buf, int num) { buf->len -= num; buf->data += num; if (buf->data >= BUFSIZE) buf->data = 0; if (!buf->len) buf->data = buf->room = 0; } /* make a timeval from , */ static inline struct timeval tv_set(time_t sec, suseconds_t usec) { struct timeval ret = { .tv_sec = sec, .tv_usec = usec }; return ret; } /* used to unset a timeout */ static inline struct timeval tv_unset() { return tv_set(0, ~0); } /* used to zero a timeval */ static inline struct timeval tv_zero() { return tv_set(0, 0); } /* returns true if the timeval is set */ static inline int tv_isset(struct timeval tv) { return tv.tv_usec != ~0; } /* returns true if is before , taking account unsets */ static inline int tv_isbefore(const struct timeval a, const struct timeval b) { return !tv_isset(b) ? 1 : !tv_isset(a) ? 0 : ( a.tv_sec < b.tv_sec || (a.tv_sec == b.tv_sec && a.tv_usec < b.tv_usec)); } /* returns the lowest of the two timers, for use in delay computation */ static inline struct timeval tv_min(const struct timeval a, const struct timeval b) { if (tv_isbefore(a, b)) return a; else return b; } /* returns the normalized sum of the plus */ static inline struct timeval tv_add(const struct timeval from, const struct timeval off) { struct timeval ret; ret.tv_sec = from.tv_sec + off.tv_sec; ret.tv_usec = from.tv_usec + off.tv_usec; if (ret.tv_usec >= 1000000) { ret.tv_usec -= 1000000; ret.tv_sec += 1; } return ret; } /* returns the delay between and or zero if is after */ static inline struct timeval tv_diff(const struct timeval past, const struct timeval now) { struct timeval ret = { .tv_sec = 0, .tv_usec = 0 }; if (tv_isbefore(past, now)) { ret.tv_sec = now.tv_sec - past.tv_sec; ret.tv_usec = now.tv_usec - past.tv_usec; if ((signed)ret.tv_usec < 0) { // overflow ret.tv_usec += 1000000; ret.tv_sec -= 1; } } return ret; } /* attempts to write a timestamp into output buffer which is bytes * long, based on , start_ts, and . The return value is the * snprintf() return value, that is, larger than if it failed. A 30-char * long buffer will never fail. */ int write_ts(char *out, int size, enum ts_mode mode, const struct timeval *prev, const struct timeval *line) { struct timeval t; int ret; if (mode == TS_INIT) { t = tv_diff(start_ts, *line); ret = snprintf(out, size, "[%6u.%06u] ", (unsigned)t.tv_sec, (unsigned)t.tv_usec); } else if (mode == TS_LINE) { if (!tv_isset(*prev)) { // first line t = tv_zero(); } else t = tv_diff(*prev, *line); ret = snprintf(out, size, "[%6u.%06u] ", (unsigned)t.tv_sec, (unsigned)t.tv_usec); } else { // TS_ABS ret = strftime(out, size, "[%Y%m%d-%H%M%S.", localtime(&line->tv_sec)); ret += snprintf(out+ret, size-ret, "%06u] ", (unsigned int)line->tv_usec); } return ret; } void write_to_capture(int fd, const struct buffer *buf, int len) { static struct timeval prev_ts; // timestamp of previous line static struct timeval line_ts; // timestamp of current line static int cap_bol = 1; // beginning of line const unsigned char *lf; char hdr[30]; int pos = 0; int max; int ret; while (len > 0) { if (ts_mode > TS_NONE && cap_bol) { /* at beginning of line, must write a timestamp */ gettimeofday(&line_ts, NULL); ret = write_ts(hdr, sizeof(hdr), ts_mode, &prev_ts, &line_ts); ret = write(fd, hdr, ret); cap_bol = 0; } lf = memchr(buf->b + buf->room + pos, '\n', len); if (lf) { cap_bol = 1; max = lf - (buf->b + buf->room + pos) + 1; } else max = len; ret = write(fd, buf->b + buf->room + pos, max); if (ret < 0) break; pos += ret; len -= ret; prev_ts = line_ts; } } /* send as much as possible of a buffer to the fd */ void buf_to_fd(int fd, struct buffer *buf) { ssize_t ret; if (buf->data < 0) return; ret = write(fd, buf->b + buf->data, MIN(buf->len, BUFSIZE - buf->data)); if (ret < 0) { /* error */ if (errno != EAGAIN) buf->data = -1; return; } b_skip(buf, ret); } /* receive as much as possible from the fd to the buffer */ void fd_to_buf(int fd, struct buffer *buf, int capfd) { ssize_t ret; if (buf->room < 0) return; ret = read(fd, buf->b + buf->room, MIN(BUFSIZE - buf->len, BUFSIZE - buf->room)); if (ret <= 0) { /* error or close */ if (ret == 0 || errno != EAGAIN) buf->room = -1; return; } if (capfd >= 0) write_to_capture(capfd, buf, ret); buf->len += ret; buf->room += ret; if (buf->room >= BUFSIZE) buf->room = 0; } /* receive as much as possible from the fd to the buffer, and applies CRLF * transformations. In order to make sure we'll always have enough room to * insert the missing CRs or LFs, we read no more than 1/2 of the buffer * room at a time. */ void fd_to_buf_slow(int fd, struct buffer *buf, int capfd) { unsigned char tmpbuf[(BUFSIZE+1)/2]; int room; ssize_t ret; int i; int wpos; int wtot; if (buf->room < 0) return; room = MIN(BUFSIZE - buf->len, BUFSIZE - buf->room); if (room < 2) return; room /= 2; ret = read(fd, tmpbuf, room); if (ret <= 0) { /* error or close */ if (ret == 0 || errno != EAGAIN) buf->room = -1; return; } wtot = 0; wpos = buf->room; for (i = 0; i < ret; i++) { if ((tmpbuf[i] == '\n' && crlf_mode == CRLF_LFONLY) || (tmpbuf[i] == '\r' && crlf_mode == CRLF_CRONLY)) { buf->b[wpos++] = '\r'; if (wpos == BUFSIZE) wpos = 0; buf->b[wpos++] = '\n'; if (wpos == BUFSIZE) wpos = 0; wtot += 2; } else { buf->b[wpos++] = tmpbuf[i]; if (wpos == BUFSIZE) wpos = 0; wtot++; } } if (capfd >= 0) write_to_capture(capfd, buf, wtot); buf->len += wtot; buf->room = wpos; } /* transfer as many bytes as possible from port buffer to user buffer * , encoding bytes that are not between and . Input * buffer is always realigned once empty. The forwarding stops after each new * line. If in_utf8 is not null, it will be set to either 0 or 1 depending on * the sequence. The function always returns zero for regular characters, >0 * if it returns after copying a line feed. */ int xfer_port_to_user(struct buffer *in, struct buffer *out, int chr_min, int chr_max, int *in_utf8) { enum line_state new_state; static struct timeval prev_ts; // timestamp of previous line static struct timeval line_ts; // timestamp of current line char hdr[30]; int len; int c; while (in->len && out->len < BUFSIZE) { new_state = line_state; c = b_peek(in); /* retrieve current time when entering BONL */ if ((c == '\n' && (line_state == LS_BONL || line_state == LS_BOSL)) || (c == '\r' && line_state == LS_NL)) { new_state = LS_BONL; } else if (line_state == LS_BONL && c == '\r') { new_state = LS_BOSL; /* TS going to be overwritten */ } else if (line_state != LS_NL && c != '\n' && c != '\r') { new_state = LS_SL; } else if (line_state == LS_SL) { if (c == '\n') new_state = LS_NL; else if (c == '\r') new_state = LS_BOSL; } /* If we're leaving BOSL for SL, we're going to overwrite the * TS header so we must print it again before attempting to * emit the character. */ if (line_state == LS_BOSL && new_state == LS_SL) { /* rewrite current line's TS */ if (ts_term) { len = write_ts(hdr, sizeof(hdr), (ts_mode > TS_NONE) ? ts_mode : TS_ABS, &prev_ts, &line_ts); b_puts(out, hdr, len, 0); /* commit the change since printed already */ line_state = new_state; } } /* only transfer the unprotected chars, unless they belong to * the unescaped C1 set of control characters which tends to * make some terminals choke and which easily happen when using * incorrect baud rates. */ if (c >= chr_min && c <= chr_max && (!in_utf8 || *in_utf8 || c <= 0x80 || c >= 0x9f)) { /* transfer as-is */ if (!b_putchar(out, c, MARGIN)) break; } else { /* transcode to "<0xHH>" (6 chars) */ char tmp[7]; snprintf(tmp, sizeof(tmp), "<0x%02X>", c); if (!b_puts(out, tmp, 6, MARGIN)) break; } /* need to print the TS at the beginning of this new line */ if (new_state == LS_BONL) { prev_ts = line_ts; gettimeofday(&line_ts, NULL); /* print */ if (ts_term) { len = write_ts(hdr, sizeof(hdr), (ts_mode > TS_NONE) ? ts_mode : TS_ABS, &prev_ts, &line_ts); b_puts(out, hdr, len, 0); } } /* commit the new state */ line_state = new_state; if (in_utf8) { if (*in_utf8 && (c < 0x80 || c >= 0xc0)) *in_utf8 = 0; if (!*in_utf8 && c >= 0xc0 && c <= 0xf7) *in_utf8 = 1; } b_skip(in, 1); if (c == '\n') return 1; } return 0; } /* transfer as many bytes as possible from user buffer to port buffer * . Input buffer is always realigned once empty. The forwarding stops * before character . Set esc to -1 to ignore it. The function always * returns zero for regular characters, <0 if it stops before an escape * character. */ int xfer_user_to_port(struct buffer *in, struct buffer *out, int esc) { int c; while (in->len && out->len < BUFSIZE) { c = b_peek(in); if (c == esc) return -1; if (!b_putchar(out, c, MARGIN)) break; b_skip(in, 1); } return 0; } /* show the port status into for size in a way suitable for * sending to a raw terminal. */ void show_port(int fd, char *resp, size_t size) { int pins; ioctl(fd, TIOCMGET, &pins); snprintf(resp, size, "\r\nPort name: %s Speed: %d bps Pins: %s %s %s %s %s %s\r\n", port, get_baud_rate(fd), (pins & TIOCM_DTR) ? "DTR" : "dtr", (pins & TIOCM_RTS) ? "RTS" : "rts", (pins & TIOCM_CTS) ? "CTS" : "cts", (pins & TIOCM_DSR) ? "DSR" : "dsr", (pins & TIOCM_CD) ? "CD" : "cd", (pins & TIOCM_RI) ? "RI" : "ri"); } /* Bidirectional forwarding between stdin/stdout and fd. We take a great care * of making sure stdin and stdout are both a terminal before switching to raw * mode, in which case they are assumed to be the same. Otherwise they are used * as independent, regular files, allowing to inject/receive from/to stdio. */ void forward(int fd) { int stdio_is_term = 0; fd_set rfds, wfds; struct termios tio_bck; struct buffer port_ibuf = { }; struct buffer port_obuf = { }; struct buffer user_ibuf = { }; struct buffer user_obuf = { }; struct timeval escape_timeout = tv_unset(); struct timeval flash_timeout = tv_unset(); struct timeval now; int term_flashing = 0; int in_utf8 = 0; int in_esc = 0; int err = 0; if (isatty(0) && isatty(1)) { struct termios tio; if (tcgetattr(0, &tio) != 0) die(4, "Failed to retrieve stdio settings.\n"); memcpy(&tio_bck, &tio, sizeof(tio)); /* turn the terminal to raw (character mode, no interrupt etc) */ tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); tio.c_oflag &= ~OPOST; tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); tio.c_cflag &= ~(CSIZE | PARENB); tio.c_cflag |= CS8; if (tcsetattr(0, TCSANOW, &tio) != 0) die(4, "Failed to set stdio to raw mode.\n"); stdio_is_term = 1; } /* Note: we're going to use select(). We should theoretically switch * both FDs to non-blocking mode. The problem is that it usually is a * bad idea (files are not pollable, and terminals may misbehave once * once this is done, and will be hard to fix). Since select() is level * triggered, it will still report events on blocking devices and on * files. We must just take care not to try to read more than once on * each wakeup. */ FD_ZERO(&rfds); FD_ZERO(&wfds); /* forward until both sides are closed and buffers empty. The terminal * is detected as closed on either a write error or a read end. */ while (((user_obuf.len + port_ibuf.len || port_ibuf.room >= 0) && (user_obuf.data >= 0)) || ((port_obuf.len + user_ibuf.len || user_ibuf.room >= 0) && (port_obuf.data >= 0 && port_ibuf.room >= 0))) { struct timeval interval; int cnt; if (user_ibuf.room >= 0 && user_ibuf.len < BUFSIZE) FD_SET(0, &rfds); else FD_CLR(0, &rfds); if (user_obuf.data >= 0 && user_obuf.len > 0) FD_SET(1, &wfds); else FD_CLR(1, &wfds); if (port_ibuf.room >= 0 && port_ibuf.len < BUFSIZE) FD_SET(fd, &rfds); else FD_CLR(fd, &rfds); if (port_obuf.data >= 0 && port_obuf.len > 0) FD_SET(fd, &wfds); else FD_CLR(fd, &wfds); gettimeofday(&now, NULL); interval = tv_unset(); if (in_esc) interval = tv_min(interval, tv_diff(now, escape_timeout)); if (term_flashing) interval = tv_min(interval, tv_diff(now, flash_timeout)); cnt = select(FD_SETSIZE, &rfds, &wfds, NULL, tv_isset(interval) ? &interval : NULL); gettimeofday(&now, NULL); if (in_esc && !tv_isbefore(now, escape_timeout)) { /* abort escape mode */ in_esc = 0; /* flash the screen by reversing it */ if (stdio_is_term) { b_puts(&user_obuf, "\e[?5h", 5, 0); term_flashing = 1; flash_timeout = tv_add(now, tv_set(0, 100000)); // 100ms flash } } if (term_flashing && !tv_isbefore(now, flash_timeout)) { /* restore the terminal */ b_puts(&user_obuf, "\e[?5l", 5, 0); term_flashing = 0; } if (cnt <= 0) // signal or timeout continue; set_capture_name(); /* flush pending data */ if (FD_ISSET(1, &wfds)) buf_to_fd(1, &user_obuf); if (FD_ISSET(fd, &wfds)) buf_to_fd(fd, &port_obuf); /* receive new data */ if (FD_ISSET(0, &rfds)) fd_to_buf(0, &user_ibuf, -1); if (FD_ISSET(fd, &rfds)) fd_to_buf_slow(fd, &port_ibuf, cap_fd); if (in_esc) { char resp[BUFSIZE]; int c = b_peek(&user_ibuf); if (c == 'q' || c == 'Q' || c == '.') { /* quit */ break; } else if (c == 'h' || c == 'H' || c == '?') { if (b_puts(&user_obuf, menu_str, strlen(menu_str), 0)) { in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == 'd' || c == 'D' || c == 'f' || c == 'F' || c == 'r' || c == 'R') { /* flip DTR/RTS/both pin */ int pins; /* note that technically if we can't write a * response we could flip multiple times but * practically speaking this will not happen * and almost never be an issue. */ ioctl(fd, TIOCMGET, &pins); if (c == 'd' || c == 'D' || c == 'f' || c == 'F') pins ^= TIOCM_DTR; if (c == 'r' || c == 'R' || c == 'f' || c == 'F') pins ^= TIOCM_RTS; ioctl(fd, TIOCMSET, &pins); show_port(fd, resp, sizeof(resp)); if (b_puts(&user_obuf, resp, strlen(resp), 0)) { in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == 'b' || c == 'B') { tcsendbreak(fd, 0); if (b_puts(&user_obuf, "Sent break\r\n", 12, 0)) { in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == 'c' || c == 'C') { if (cap_mode == CAP_NONE && b_puts(&user_obuf, "Enabling capture\r\n", 18, 0)) { cap_mode = CAP_FIXED; in_esc = 0; b_skip(&user_ibuf, 1); } else if (cap_mode > CAP_NONE && b_puts(&user_obuf, "Disabling capture\r\n", 19, 0)) { cap_mode = CAP_NONE; in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == 't' || c == 'T') { if (!ts_term && b_puts(&user_obuf, "Enabling terminal timestamps\r\n", 30, 0)) { ts_term = 1; in_esc = 0; b_skip(&user_ibuf, 1); } else if (ts_term && b_puts(&user_obuf, "Disabling terminal timestamps\r\n", 31, 0)) { ts_term = 0; in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == 'p' || c == 'P') { /* show port status */ show_port(fd, resp, sizeof(resp)); if (b_puts(&user_obuf, resp, strlen(resp), 0)) { in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c == escape) { /* two escape chars cause one to be sent */ if (b_putchar(&port_obuf, c, 0)) { in_esc = 0; b_skip(&user_ibuf, 1); } } else if (c >= 0 && b_putchar(&port_obuf, escape, 0)) { /* other chars will be sent as-is, preceeded by escape */ in_esc = 0; } } /* transfer between IN and opposite OUT buffers */ if (!in_esc && xfer_user_to_port(&user_ibuf, &port_obuf, escape) < 0) { /* we stopped in front of the escape character, let's * wait up to two seconds for the sequence to be entered. */ b_skip(&user_ibuf, 1); escape_timeout = tv_add(now, tv_set(2, 0)); in_esc = 1; } while (xfer_port_to_user(&port_ibuf, &user_obuf, chr_min, chr_max, &in_utf8) > 0) { /* xfer everything */ } } if (stdio_is_term && term_flashing) { /* restore the terminal now */ if (write(1, "\e[?5l", 5) < 0) err = 1; } if (stdio_is_term) { /* restore settings and skip current line */ if (tcsetattr(0, TCSANOW, &tio_bck) != 0) err = 1; putchar('\n'); } if (err) die(4, "Failed to restore stdio settings. Try 'stty sane'\n"); } int main(int argc, char **argv) { const char *progname; const char *curr = NULL; const char *arg = NULL; int idx, opt; int do_list = 0; int do_wait_new = 0; int do_wait_any = 0; int do_print = 0; int do_send_break = 0; int no_term = 0; int forced = 0; int usepath = 0; int retries = 0; int fd, ret; progname = strrchr(argv[0], '/'); if (!progname) progname = argv[0]; else progname++; /* read environment variables */ set_port_list(&exclude_drivers, get_conf("scan.exclude-drivers")); set_port_list(&exclude_list, get_conf("scan.exclude-ports")); set_port_list(&include_list, get_conf("scan.include-ports")); set_port_list(&restrict_list, get_conf("scan.restrict-ports")); do_wait_any = !!get_conf("scan.wait-any"); do_wait_new = !do_wait_any && !!get_conf("scan.wait-new"); baud_rate_str = get_conf("port.baud-rate"); arg = get_conf("port.crlf"); if (arg) crlf_mode = atoi(arg); cap_mode_str = get_conf("capture.mode"); ts_mode_str = get_conf("timestamp.mode"); /* simple parsing loop, stops before isolated '-', before words * starting with other chars, but after '--', in which case * points to whatever follows in the same argument. is reset to * NULL when consumed. points to first unparsed word at end of * loop. */ for (idx = 0;;) { if (!idx || !*curr || !arg) { if (idx && !arg && !*curr) idx++; // skip previously consumed separate arg idx++; curr = (idx < argc) ? argv[idx] : NULL; if (!curr) break; if (curr[0] != '-' || !curr[1]) break; curr++; } opt = *(curr++); arg = *curr ? curr : (idx+1 < argc) ? argv[idx+1] : NULL; /* now we have the current option in and its optional * argument in which may be NULL if the end of string * was reached. */ if (opt == '-') { if (*curr) usage(1, progname); idx++; curr = arg; break; } switch (opt) { case 'h': usage(0, progname); break; case 'V': die(0, version_str); break; case 'B': do_send_break = 1; break; case 'N': no_term = 1; break; case 'l': do_list = 1; break; case 'p': do_print = 1; break; case 'q': quiet = 1; break; case 'T': ts_term = 1; break; case 'a': do_wait_any = 1; do_wait_new = 0; break; case 'n': do_wait_new = 1; do_wait_any = 0; break; case 'b': if (!arg) die(1, "missing argument for -%c\n", opt); baud_rate_str = arg; arg = NULL; break; case 'c': if (!arg) die(1, "missing argument for -%c\n", opt); cap_mode_str = arg; arg = NULL; break; case 't': if (!arg) die(1, "missing argument for -%c\n", opt); ts_mode_str = arg; arg = NULL; break; case 'e': if (!arg) die(1, "missing argument for -%c\n", opt); if (!parse_byte(arg, &escape)) { if (*arg && arg[1] == 0) escape = *arg; else die(1, "failed to parse escape code for -%c\n", opt); } arg = NULL; break; case 'f': if (!arg) die(1, "missing argument for -%c\n", opt); capture_fmt = arg; arg = NULL; break; case 'm': if (!parse_byte(arg, &chr_min)) die(1, "failed to parse argument for -%c\n", opt); arg = NULL; break; case 'M': if (!parse_byte(arg, &chr_max)) die(1, "failed to parse argument for -%c\n", opt); arg = NULL; break; case 'L': if (!arg) die(1, "missing argument for -%c\n", opt); crlf_mode = atoi(arg); arg = NULL; break; default: die(1, "Unknown option '%c'. Use -h to get help\n", opt); break; } } if (argc > idx + 1) die(1, "Too many arguments: '%s'. Use -h to get help\n", argv[idx + 1]); /* these are the remaining arguments not starting with "-" */ port = curr && *curr ? curr : NULL; if (cap_mode_str) { if (strcmp(cap_mode_str, "none") == 0) cap_mode = CAP_NONE; else if (strcmp(cap_mode_str, "fixed") == 0) cap_mode = CAP_FIXED; else if (strcmp(cap_mode_str, "timed") == 0) cap_mode = CAP_TIMED; else die(1, "Unknown capture mode <%s>. Use -h for help.\n", cap_mode_str); } if (ts_mode_str) { if (strcmp(ts_mode_str, "none") == 0) ts_mode = TS_NONE; else if (strcmp(ts_mode_str, "abs") == 0) ts_mode = TS_ABS; else if (strcmp(ts_mode_str, "init") == 0) ts_mode = TS_INIT; else if (strcmp(ts_mode_str, "line") == 0) ts_mode = TS_LINE; else die(1, "Unknown timestamp mode <%s>. Use -h for help.\n", ts_mode_str); } if (baud_rate_str) { if (strcmp(baud_rate_str, "?") == 0 || strcmp(baud_rate_str, "help") == 0) { list_baud_rates(); exit(0); } if (!parse_int(baud_rate_str, &baud)) die(1, "Failed to parse baud rate <%s>\n", baud_rate_str); } /* Possibilities: * - no port (NULL) : automatically use last one (=-1) * - numeric port (negative) : use that relative port after discovery * - numeric port (positive) : use that absolute port after discovery * - device path : use that path without discovery */ if (port && !parse_int(port, &currport)) { /* device path */ currport = -1; usepath = 1; forced = 1; } if (currport >= 0) forced = 1; /* we may need to scan the ports on the system for listing and * automatic discovery. */ if (!usepath || do_wait_any || do_wait_new || do_list) { /* find the first eligible port */ scan_ports(); } /* when waiting for a port to be available, we check that the port * specified in the path is listed, or that the requested number is * available, otherwise that any port is available. */ if (do_wait_any && !do_list) { if (currport >= 0 && currport < nbports) { port = serial_ports[currport].name; if (!quiet) printf("Port %d (%s) available, using it.\n", currport, port); goto done_scan; } if (usepath) { fd = open_port(port); if (fd < 0) { if (!quiet) printf("Waiting for port %s to appear...\n", port); do { usleep(100000); fd = open_port(port); } while (fd == -1); } if (!quiet) printf("Trying port %s...", port); goto go_with_fd; } if (!nbports) { if (!quiet) printf("Waiting for one port to appear...\n"); do { usleep(100000); scan_ports(); } while (!nbports); } currport = nbports - 1; port = serial_ports[currport].name; if (!quiet) printf("Port %s available, using it.\n", port); if (!quiet && !do_list && !do_print) list_ports(nbports - 1); } /* when waiting for new ports, we wait for at least one port to be * added since the previous 100ms. We do explicitly support unplugging * and replugging. */ if (!usepath && do_wait_new && !do_list) { int prev_nbports = nbports; if (!quiet) printf("%d ports found, waiting for a new one...\n", nbports); do { usleep(100000); scan_ports(); /* detect if a port was disconnected */ if (nbports < prev_nbports) prev_nbports = nbports; } while (nbports <= prev_nbports || nbports <= currport); if (!quiet && !do_list && !do_print) list_ports(nbports - 1); } /* port was designated using a number */ if (currport >= 0 && currport < nbports) port = serial_ports[currport].name; /* it's time to figure what port we're going to use. Check if the port * is designated by a number. Note that currport remains -1 if the * port was forced via a path. */ if (!usepath) { if (currport >= nbports) die(1, "No such port number %d. Use -l to list ports.\n", currport); else if (currport < 0) { currport = nbports + currport; if (currport < 0) currport = -1; } } done_scan: if (do_list) { /* list available ports and quit */ list_ports(-1); return 0; } /* get a real name for the port (if numeric or absent) and warn unless * latest port was explicitly requested. */ if (currport >= 0) { if (!port && !do_print && !quiet && !do_wait_new) printf("No port specified, using %s (last registered). Use -l to list ports.\n", serial_ports[currport].name); port = serial_ports[currport].name; } if (do_print) { /* print the selected port and quit */ if (port) { printf("%s\n", port); return 0; } return 1; } if (!port) die(2, "No port found nor specified. Use -a to wait or -h for help.\n"); /* try to open the port */ do { if (!quiet && !(forced && do_wait_new)) printf("Trying port %s...", port); fd = open_port(port); if (fd == -1) { /* failures have different fallbacks: * - ENOENT when the port is forced and new is set * will be retried. * - EBUSY, ENODEV, ENOENT, ENOMEM will automatically * be retried if the port was forced. * - other errors always cause an abort */ if (errno == ENOENT && forced && do_wait_new) { if (!retries) printf("Waiting for port %s to appear.\n", port); usleep(200000); retries++; continue; } if (!forced && (currport > 0) && (errno == EBUSY || errno == ENODEV || errno == ENOENT || errno == ENOMEM)) { if (!quiet) { if (forced && do_wait_new) printf("Failed to open port %s: %s\n", port, strerror(errno)); else printf(" Failed! (%s)\n", strerror(errno)); } currport--; port = serial_ports[currport].name; continue; } if (!quiet) { if (forced && do_wait_new) printf("Failed to open port %s: %s\n", port, strerror(errno)); else printf(" Failed! (%s)\n", strerror(errno)); } die(2, "Failed to open port: %s\n", strerror(errno)); } } while (fd == -1); if (!quiet && (forced && do_wait_new)) printf("Trying port %s...", port); go_with_fd: ret = set_port(fd, baud); if (ret == -1) { int err = errno; if (!quiet) printf(" Failed.\n"); fprintf(stderr, "Failed to configure port: %s\n", strerror(err)); if (!quiet && err == EINVAL) { printf("Hint: maybe the baud rate is not supported by the port.\n"); list_baud_rates(); } else if (!quiet && err == EIO) { printf("Hint: the port might be listed but not physically present.\n"); } exit(3); } if (!quiet) { char esc[8]; printf(" Connected to %s at %d bps.\n", port, get_baud_rate(fd)); if (escape < 32) { switch (escape) { case 9: snprintf(esc, sizeof(esc), "Tab"); break; case 27: snprintf(esc, sizeof(esc), "Esc"); break; default: snprintf(esc, sizeof(esc), "Ctrl-%c", escape + '@'); break; } } else if (isprint(escape)) snprintf(esc, sizeof(esc), "%c", escape); else snprintf(esc, sizeof(esc), "0x%02X", escape); printf("Escape character is '%s'. Use escape followed by '?' for help.\n", esc); } if (do_send_break) tcsendbreak(fd, 0); if (no_term) return 0; /* set start time of capture just before forwarding */ gettimeofday(&start_ts, NULL); /* perform the actual forwarding */ forward(fd); return 0; }