]> rtime.felk.cvut.cz Git - sojka/sterm.git/blob - sterm.c
127dcea895deb8629b63265f915a640e6777f3c8
[sojka/sterm.git] / sterm.c
1 /*
2  * Simple serial terminal
3  *
4  * Copyright 2014, 2015 Michal Sojka <sojkam1@fel.cvut.cz>
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see
18  * <http://www.gnu.org/licenses/>.
19  */
20
21 /*
22  * This is a minimalist terminal program like minicom or cu. The only
23  * thing it does is creating a bidirectional connection between
24  * stdin/stdout and a device (e.g. serial terminal). It can also set
25  * serial line baudrate and manipulate DTR/RTS modem lines.
26  *
27  * The -d and -r option create short pulse on DTR/RTS. The lines are
28  * always raised when the device is opened and those options lower the
29  * lines immediately after opening.
30  */
31
32 #define _BSD_SOURCE
33 #include <sys/ioctl.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <termios.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <getopt.h>
41 #include <poll.h>
42 #include <stdbool.h>
43 #include <string.h>
44 #include <signal.h>
45 #include <lockdev.h>
46
47 #define STRINGIFY(val) #val
48 #define TOSTRING(val) STRINGIFY(val)
49 #define CHECK(cmd) ({ int ret = (cmd); if (ret == -1) { perror(#cmd " line " TOSTRING(__LINE__)); exit(1); }; ret; })
50 #define CHECKPTR(cmd) ({ void *ptr = (cmd); if (ptr == (void*)-1) { perror(#cmd " line " TOSTRING(__LINE__)); exit(1); }; ptr; })
51
52 #define VERBOSE(format, ...) do { if (verbose) fprintf(stderr, "sterm: " format, ##__VA_ARGS__); } while (0)
53
54 bool verbose = false;
55 bool exit_on_escape = true;
56
57 struct termios stdin_tio_backup;
58 char *dev = NULL;
59
60 void rm_file(int status, void *arg)
61 {
62         char *fn = arg;
63         if (fn[0])
64                 unlink(fn);
65         fn[0] = 0;
66 }
67
68 void restore_stdin_term()
69 {
70         tcsetattr(0, TCSANOW, &stdin_tio_backup);
71 }
72
73 void unlock()
74 {
75         dev_unlock(dev, getpid());
76 }
77
78 void sighandler(int arg)
79 {
80         exit(0); /* Invoke exit handlers */
81 }
82
83 int dtr_rts_arg(const char option)
84 {
85         int val = -1;
86
87         if (optarg) {
88                 switch (optarg[0]) {
89                 case '+': val = +1; break;
90                 case '-': val = -1; break;
91                 default:
92                         fprintf(stderr, "Unknown -%c argument: %s", option, optarg);
93                         exit(1);
94                 }
95         }
96         return val;
97 }
98
99 void exit_on_escapeseq(const char *buf, int len)
100 {
101         static const char escseq[] = "\r~.";
102         static const char *state = escseq+1;
103         int i;
104         for (i = 0; i < len; i++) {
105                 if (buf[i] == *state) {
106                         state++;
107                         if (*state == 0)
108                                 exit(0);
109                 } else {
110                         state = escseq;
111                         if (buf[i] == *state)
112                                 state++;
113                 }
114         }
115 }
116
117 void usage(const char* argv0)
118 {
119         fprintf(stderr, "Usage: %s [options] <device>\n", argv0);
120         fprintf(stderr, "Options:\n");
121         fprintf(stderr, "  -d [+|-] create short positive/negative pulse on DTR\n");
122         fprintf(stderr, "  -e       ignore '~.' escape sequence\n");
123         fprintf(stderr, "  -n       do not switch the device to raw mode\n");
124         fprintf(stderr, "  -r [+|-] create short positive/negative pulse on RTS\n");
125         fprintf(stderr, "  -s <baudrate>\n");
126         fprintf(stderr, "  -v       verbose\n");
127 }
128
129 int main(int argc, char *argv[])
130 {
131         int fd;
132         int opt;
133         speed_t speed = 0;
134         int dtr = 0, rts = 0;
135         struct termios tio;
136         bool stdin_tty;
137         bool raw = true;
138
139         if ((stdin_tty = isatty(0))) {
140                 CHECK(tcgetattr(0, &stdin_tio_backup));
141                 atexit(restore_stdin_term);
142         }
143
144         while ((opt = getopt(argc, argv, "nd::er::s:v")) != -1) {
145                 switch (opt) {
146                 case 'd': dtr = dtr_rts_arg(opt); break;
147                 case 'e': exit_on_escape = false; break;
148                 case 'n': raw = false; break;
149                 case 'r': rts = dtr_rts_arg(opt); break;
150                 case 's': {
151                         int s = atoi(optarg);
152                         switch (s) {
153 #define S(s) case s: speed = B##s; break;
154                                 S(0);
155                                 S(50);
156                                 S(75);
157                                 S(110);
158                                 S(134);
159                                 S(150);
160                                 S(200);
161                                 S(300);
162                                 S(600);
163                                 S(1200);
164                                 S(1800);
165                                 S(2400);
166                                 S(4800);
167                                 S(9600);
168                                 S(19200);
169                                 S(38400);
170                                 S(57600);
171                                 S(115200);
172                                 S(230400);
173 #undef S
174                         default:
175                                 fprintf(stderr, "Unknown baud rate %d\n", s);
176                                 exit(1);
177                         }
178                         break;
179                 }
180                 case 'v':
181                         verbose = true;
182                         break;
183                 default: /* '?' */
184                         usage(argv[0]);
185                         exit(1);
186                 }
187         }
188
189         if (optind < argc)
190                 dev = argv[optind];
191
192         if (!dev) {
193                 fprintf(stderr, "No device specified\n");
194                 usage(argv[0]);
195                 exit(1);
196         }
197
198         signal(SIGINT, sighandler);
199         signal(SIGTERM, sighandler);
200         signal(SIGHUP, sighandler);
201
202         pid_t pid = dev_lock(dev);
203         if (pid > 0) {
204                 fprintf(stderr, "%s is used by PID %d\n", dev, pid);
205                 exit(1);
206         } else if (pid < 0) {
207                 perror("dev_lock()");
208                 exit(1);
209         }
210         atexit(unlock);
211
212         /* O_NONBLOCK is needed to not wait for the CDC signal. See tty_ioctl(4). */
213         if ((fd = open(dev, O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0) {
214                 perror(dev);
215                 exit(1);
216         }
217         /* Cancel the efect of O_NONBLOCK flag. */
218         int n = fcntl(fd, F_GETFL, 0);
219         fcntl(fd, F_SETFL, n & ~O_NDELAY);
220
221         if (isatty(fd)) {
222                 CHECK(ioctl(fd, TIOCEXCL, NULL));
223
224                 CHECK(tcgetattr(fd, &tio));
225
226                 cfmakeraw(&tio);
227
228                 if (speed) {
229                         CHECK(cfsetospeed(&tio, speed));
230                         CHECK(cfsetispeed(&tio, speed));
231                 }
232
233                 if (dtr || rts) {
234                         int status;
235                         /* tio.c_cflag &= ~HUPCL; */ /* Don't lower DTR/RTS on close */
236
237                         CHECK(ioctl(fd, TIOCMGET, &status));
238                         if (dtr == -1) status &= ~TIOCM_DTR;
239                         if (dtr == +1) status |=  TIOCM_DTR;
240                         if (rts == -1) status &= ~TIOCM_RTS;
241                         if (rts == +1) status |=  TIOCM_RTS;
242                         CHECK(ioctl(fd, TIOCMSET, &status));
243                 }
244
245                  /* Disable flow control */
246                 tio.c_cflag &= ~(CRTSCTS);
247                 tio.c_iflag &= ~(IXON|IXOFF);
248
249                 CHECK(tcsetattr(fd, TCSANOW, &tio));
250         } else if (speed || dtr || rts) {
251                 fprintf(stderr, "Cannot set speed, DTR or RTS on non-terminal %s\n", dev);
252                 exit(1);
253         }
254
255         struct pollfd fds[2] = {
256                 { .fd = 0,  .events = POLLIN },
257                 { .fd = fd, .events = POLLIN },
258         };
259         char buf[4096];
260
261         if (stdin_tty) {
262                 tio = stdin_tio_backup;
263                 if (raw)
264                         cfmakeraw(&tio);
265                 CHECK(tcsetattr(0, TCSANOW, &tio));
266         }
267
268         VERBOSE("Connected.\r\n");
269         if (exit_on_escape)
270                 VERBOSE("Use '<Enter>~.' sequence to exit.\r\n");
271
272         while (1) {
273                 int r1, r2;
274                 CHECK(poll(fds, 2, -1));
275                 if (fds[0].revents & POLLIN) {
276                         r1 = CHECK(read(0, buf, sizeof(buf)));
277                         if (r1 == 0) {
278                                 VERBOSE("EOF on stdin\r\n");
279                                 break;
280                         }
281                         if (exit_on_escape)
282                                 exit_on_escapeseq(buf, r1);
283                         r2 = CHECK(write(fd, buf, r1));
284                         if (r1 != r2) {
285                                 fprintf(stderr, "Not all data written to %s (%d/%d)\n", dev, r1, r2);
286                                 exit(1);
287                         }
288                 }
289                 if (fds[1].revents & POLLIN) {
290                         r1 = CHECK(read(fd, buf, sizeof(buf)));
291                         if (r1 == 0) {
292                                 VERBOSE("EOF on %s\r\n", dev);
293                                 break;
294                         }
295                         r2 = CHECK(write(1, buf, r1));
296                         if (r1 != r2) {
297                                 fprintf(stderr, "Not all data written to stdout (%d/%d)\n", r1, r2);
298                                 exit(1);
299                         }
300                 }
301         }
302         return 0;
303 }