From e6d19b5adde38dac0ab7020fcb4de75abe06ba5e Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Mon, 3 Feb 2014 18:45:44 +0100 Subject: [PATCH] Add simple serial line terminal --- utils/Makefile.omk | 3 + utils/sterm.c | 247 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 utils/sterm.c diff --git a/utils/Makefile.omk b/utils/Makefile.omk index 438a170..10a3510 100644 --- a/utils/Makefile.omk +++ b/utils/Makefile.omk @@ -5,4 +5,7 @@ bin_PROGRAMS += dtrrts dtrrts_SOURCES = dtrrts.c +bin_PROGRAMS += sterm +sterm_SOURCES = sterm.c + SUBDIRS=ulut/ulut diff --git a/utils/sterm.c b/utils/sterm.c new file mode 100644 index 0000000..bbd80da --- /dev/null +++ b/utils/sterm.c @@ -0,0 +1,247 @@ +/* + * Simple terminal + * + * This is a minimalist terminal program like minicom or cu. The only + * thing it does is creating a bidirectional connection between + * stdin/stdout and a device (e.g. serial terminal). It can also set + * serial line baudrate and manipulate DTR/RTS modem lines. + * + * Copyright 2014 Michal Sojka + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#define _BSD_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STRINGIFY(val) #val +#define TOSTRING(val) STRINGIFY(val) +#define CHECK(cmd) ({ int ret = (cmd); if (ret == -1) { perror(#cmd " line " TOSTRING(__LINE__)); exit(1); }; ret; }) +#define CHECKPTR(cmd) ({ void *ptr = (cmd); if (ptr == (void*)-1) { perror(#cmd " line " TOSTRING(__LINE__)); exit(1); }; ptr; }) + +#define VERBOSE(format, ...) do { if (verbose) fprintf(stderr, format, ##__VA_ARGS__); } while (0) + +bool verbose = false; + +char template[] = "/var/lock/TMPXXXXXX"; +char lockfile[100]; +struct termios stdin_tio_backup; + +void rm_file(int status, void *arg) +{ + char *fn = arg; + if (fn[0]) + unlink(fn); + fn[0] = 0; +} + +void restore_stdin_term() +{ + tcsetattr(0, TCSANOW, &stdin_tio_backup); +} + +void sighandler(int arg) +{ + exit(0); +} + + +int main(int argc, char *argv[]) +{ + int fd; + char *dev = NULL; + int opt; + speed_t speed = 0; + int ret; + int dtr = 0, rts = 0; + struct termios tio; + bool stdin_tty; + bool raw = true; + + if ((stdin_tty = isatty(0))) { + CHECK(tcgetattr(0, &stdin_tio_backup)); + atexit(restore_stdin_term); + } + + while ((opt = getopt(argc, argv, "ndrs:v")) != -1) { + switch (opt) { + case 'd': + dtr = 1; + break; + case 'n': + raw = false; + case 'r': + rts = 1; + break; + case 's': { + int s = atoi(optarg); + switch (s) { +#define S(s) case s: speed = B##s; break; + S(0); + S(50); + S(75); + S(110); + S(134); + S(150); + S(200); + S(300); + S(600); + S(1200); + S(1800); + S(2400); + S(4800); + S(9600); + S(19200); + S(38400); + S(57600); + S(115200); + S(230400); +#undef S + default: + fprintf(stderr, "Unknown baud rate %d\n", s); + exit(1); + } + break; + } + case 'v': + verbose = true; + break; + default: /* '?' */ + fprintf(stderr, "Usage: %s [-s baudrate] [-v] \n", argv[0]); + exit(1); + } + } + + if (optind < argc) + dev = argv[optind]; + + if (!dev) { + fprintf(stderr, "No device specified\n"); + exit(1); + } + + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + signal(SIGHUP, sighandler); + + if (strncmp(dev, "/dev/", 5) == 0 && + strrchr(dev, '/') == dev + 4 && + dev[5] != 0) + { /* Create lock file (to be inter-operable with other programs) */ + /* This is racy, but what we can do - see also comments in uucp / cu */ + int tmp = CHECK(mkstemp(template)); + on_exit(rm_file, template); + char pid[20]; + snprintf(pid, sizeof(pid), "%u", getpid()); + CHECK(write(tmp, pid, strlen(pid))); + close(tmp); + snprintf(lockfile, sizeof(lockfile), "/var/lock/LCK..%s", dev + 5); + if (link(template, lockfile) == -1) { + perror(lockfile); + exit(1); + } + rm_file(0, template); + on_exit(rm_file, lockfile); + } + + if ((fd = open(dev, O_RDWR)) < 0) { + perror(dev); + exit(1); + } + + if (isatty(fd)) { + CHECK(ioctl(fd, TIOCEXCL, NULL)); + + CHECK(tcgetattr(fd, &tio)); + + cfmakeraw(&tio); + + if (speed) { + CHECK(cfsetospeed(&tio, speed)); + CHECK(cfsetispeed(&tio, speed)); + } + + if (dtr || rts) { + int status; + tio.c_cflag &= ~HUPCL; + + CHECK(ioctl(fd, TIOCMGET, &status)); + if (dtr == +1) status &= ~TIOCM_DTR; + if (dtr == -1) status |= TIOCM_DTR; + if (rts == +1) status &= ~TIOCM_RTS; + if (rts == -1) status |= TIOCM_RTS; + CHECK(ioctl(fd, TIOCMSET, &status)); + } + + CHECK(tcsetattr(fd, TCSANOW, &tio)); + } else if (speed || dtr || rts) { + fprintf(stderr, "Cannot set speed, DTR or RTS on non-terminal %s\n", dev); + exit(1); + } + + struct pollfd fds[2] = { + { .fd = 0, .events = POLLIN }, + { .fd = fd, .events = POLLIN }, + }; + char buf[4096]; + + if (stdin_tty) { + tio = stdin_tio_backup; + if (raw) + cfmakeraw(&tio); + CHECK(tcsetattr(0, TCSANOW, &tio)); + } + + VERBOSE("Connected.\n"); + while (1) { + int r1, r2; + ret = CHECK(poll(fds, 2, -1)); + if (fds[0].revents & POLLIN) { + r1 = CHECK(read(0, buf, sizeof(buf))); + if (r1 == 0) { + VERBOSE("EOF on stdin\n"); + break; + } + r2 = CHECK(write(fd, buf, r1)); + if (r1 != r2) { + fprintf(stderr, "Not all data written to %s (%d/%d)\n", dev, r1, r2); + exit(1); + } + } + if (fds[1].revents & POLLIN) { + r1 = CHECK(read(fd, buf, sizeof(buf))); + if (r1 == 0) { + VERBOSE("EOF on %s\n", dev); + break; + } + r2 = CHECK(write(1, buf, r1)); + if (r1 != r2) { + fprintf(stderr, "Not all data written to stdout (%d/%d)\n", r1, r2); + exit(1); + } + } + } + return 0; +} -- 2.39.2