Add simple serial line terminal
authorMichal Sojka <sojkam1@fel.cvut.cz>
Mon, 3 Feb 2014 17:45:44 +0000 (18:45 +0100)
committerMichal Sojka <sojkam1@fel.cvut.cz>
Mon, 3 Feb 2014 17:45:44 +0000 (18:45 +0100)
utils/Makefile.omk
utils/sterm.c [new file with mode: 0644]

index 438a170..10a3510 100644 (file)
@@ -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 (file)
index 0000000..bbd80da
--- /dev/null
@@ -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 <sojkam1@fel.cvut.cz>
+ *
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#define _BSD_SOURCE
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <string.h>
+#include <signal.h>
+
+#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] <device>\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;
+}