Skip to content

Commit 7d5adf5

Browse files
committedAug 9, 2017
Initial add ttypxy sources
* Copied/reworked interceptty v0.6 code. * Remved multi disto openpty compatibility. * Remove all non required options. * Remove dead code, simplify code structure. * Add multi frontend devices support.
1 parent 4a14d24 commit 7d5adf5

File tree

3 files changed

+498
-0
lines changed

3 files changed

+498
-0
lines changed
 

‎.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
ttypxy
2+
3+
# Ctags cache
4+
cscope.*
5+
tags
6+
7+
# Vim cache
8+
*.swp
9+
*.swo
10+
111
# Prerequisites
212
*.d
313

‎Makefile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
CFLAGS = -DHAVE_CONFIG_H -I. -g -O2 -Wall
3+
LDFLAGS = -g -O2 -Wall
4+
LDLIBS = -lutil
5+
6+
TARGET = ttypxy
7+
8+
all: $(TARGET)
9+
10+
clean:
11+
rm *.o $(TARGET)
12+
13+
$(TARGET): ttypxy.o
14+
$(CC) $(LDFLAGS) -o $@ $^ ${LDLIBS}
15+
16+
ttypxy.o: ttypxy.c

‎ttypxy.c

+472
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,472 @@
1+
/**
2+
* Utility intended for proxying/duplicating/multiplexing single TTY
3+
* device into multiple PTY devices. Provided backend TTY device and
4+
* one or more frontend PTY devices are linked together thus whole
5+
* traffic is mirrored.
6+
* Original idea and concepts are borrowed from interceptty utility
7+
* maintained by Chris Wilson and adapted to my needs. Credits for
8+
* TTY/PTY management code forwarding to him.
9+
*
10+
* Edvinas Stunžėnas <edvinas@8devices.com>
11+
*/
12+
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
#include <stdarg.h>
16+
#include <string.h>
17+
#include <unistd.h>
18+
#include <signal.h>
19+
#include <errno.h>
20+
#include <fcntl.h>
21+
#include <termios.h>
22+
#include <pty.h>
23+
#include <sys/types.h>
24+
#include <sys/stat.h>
25+
26+
#define TTYNAMSZ 16
27+
#define TTY_BUFF_SIZE 4096
28+
#define TTY_DEVS_MAX 8
29+
30+
#define max(x,y) ((x) > (y) ? (x) : (y))
31+
32+
enum {
33+
FAIL_NONE,
34+
FAIL_USAGE,
35+
FAIL_BACKEND,
36+
FAIL_FRONTEND,
37+
FAIL_OTHER
38+
};
39+
40+
struct ttydevice {
41+
char td_path[TTYNAMSZ];
42+
int td_fd;
43+
struct termios td_origattr;
44+
int td_pts_fd;
45+
char td_pts_link[TTYNAMSZ];
46+
};
47+
48+
#if 0
49+
/* Code lended from stty */
50+
/* TODO(edzius): configure TTY baud rate for multi TTY support */
51+
struct speed_map
52+
{
53+
const char *string; /* ASCII representation. */
54+
speed_t speed; /* Internal form. */
55+
unsigned long int value; /* Numeric value. */
56+
};
57+
58+
static struct speed_map const speeds[] =
59+
{
60+
{"0", B0, 0},
61+
{"50", B50, 50},
62+
{"75", B75, 75},
63+
{"110", B110, 110},
64+
{"134", B134, 134},
65+
{"134.5", B134, 134},
66+
{"150", B150, 150},
67+
{"200", B200, 200},
68+
{"300", B300, 300},
69+
{"600", B600, 600},
70+
{"1200", B1200, 1200},
71+
{"1800", B1800, 1800},
72+
{"2400", B2400, 2400},
73+
{"4800", B4800, 4800},
74+
{"9600", B9600, 9600},
75+
{"19200", B19200, 19200},
76+
{"38400", B38400, 38400},
77+
{"exta", B19200, 19200},
78+
{"extb", B38400, 38400},
79+
{"57600", B57600, 57600},
80+
{"115200", B115200, 115200},
81+
{"230400", B230400, 230400},
82+
{"460800", B460800, 460800},
83+
{"500000", B500000, 500000},
84+
{"576000", B576000, 576000},
85+
{"921600", B921600, 921600},
86+
{"1000000", B1000000, 1000000},
87+
{"1152000", B1152000, 1152000},
88+
{"1500000", B1500000, 1500000},
89+
{"2000000", B2000000, 2000000},
90+
{"2500000", B2500000, 2500000},
91+
{"3000000", B3000000, 3000000},
92+
{"3500000", B3500000, 3500000},
93+
{"4000000", B4000000, 4000000},
94+
{NULL, 0, 0}
95+
};
96+
97+
static speed_t
98+
string_to_baud (const char *arg)
99+
{
100+
int i;
101+
102+
for (i = 0; speeds[i].string != NULL; ++i)
103+
if (STREQ (arg, speeds[i].string))
104+
return speeds[i].speed;
105+
return (speed_t) -1;
106+
}
107+
#endif
108+
109+
static int verbose = 0;
110+
static int quit = 0;
111+
112+
static void sigdeath(int sig)
113+
{
114+
quit = 1;
115+
}
116+
117+
static void warn(const char *format, ...)
118+
{
119+
va_list ap;
120+
121+
va_start(ap, format);
122+
vfprintf(stderr, format, ap);
123+
va_end(ap);
124+
fflush(stderr);
125+
}
126+
127+
static void die(int code, const char *format, ...)
128+
{
129+
va_list ap;
130+
131+
va_start(ap, format);
132+
vfprintf(stderr, format, ap);
133+
va_end(ap);
134+
fflush(stderr);
135+
exit(code);
136+
}
137+
138+
static ssize_t xread(int fd, void *buf, size_t count)
139+
{
140+
int nread;
141+
142+
do {
143+
nread = read(fd, buf, count);
144+
} while (nread < 0 && errno == EINTR);
145+
146+
if (nread < 0 && errno != EINTR && errno != EAGAIN) {
147+
fprintf(stderr, "read(): %s\n", strerror(errno));
148+
}
149+
150+
return nread;
151+
}
152+
153+
static ssize_t xwrite(int fd, const void *buff, size_t size)
154+
{
155+
ssize_t nbytes = 0;
156+
157+
while (size > 0) {
158+
ssize_t n;
159+
do {
160+
n = write(fd, buff, size);
161+
} while (n < 0 && errno == EINTR);
162+
163+
if (n < 0) {
164+
fprintf(stderr, "write(): %s\n", strerror(errno));
165+
return -1;
166+
} else if (n == 0) {
167+
fprintf(stderr, "huh? out-of-space ?\n");
168+
break;
169+
} else {
170+
buff += n;
171+
nbytes += n;
172+
size -= n;
173+
}
174+
}
175+
176+
return nbytes;
177+
}
178+
179+
static int tty_set_raw(int fd)
180+
{
181+
struct termios tty_state;
182+
183+
if (tcgetattr(fd, &tty_state) < 0)
184+
return -1;
185+
186+
tty_state.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO);
187+
tty_state.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT);
188+
tty_state.c_oflag &= ~OPOST;
189+
tty_state.c_cflag |= CS8;
190+
191+
tty_state.c_cc[VMIN] = 1;
192+
tty_state.c_cc[VTIME] = 0;
193+
194+
if (tcsetattr(fd, TCSAFLUSH, &tty_state) < 0)
195+
return -1;
196+
197+
return 0;
198+
}
199+
200+
static int tty_set_speed(int fd)
201+
{
202+
struct termios tp;
203+
204+
if (tcgetattr(fd, &tp) < 0)
205+
return -1;
206+
207+
cfsetispeed(&tp, B115200);
208+
cfsetospeed(&tp, B115200);
209+
210+
if (tcsetattr(fd, TCSAFLUSH, &tp) < 0)
211+
return -1;
212+
213+
return 0;
214+
}
215+
216+
static void hexdump(char *ttyname, void *addr, int len) {
217+
int i;
218+
unsigned char buff[17];
219+
unsigned char *pc = (unsigned char*)addr;
220+
221+
if (len == 0) {
222+
printf(" ZERO LENGTH\n");
223+
return;
224+
}
225+
if (len < 0) {
226+
printf(" NEGATIVE LENGTH: %i\n",len);
227+
return;
228+
}
229+
230+
// Process every byte in the data.
231+
for (i = 0; i < len; i++) {
232+
// Multiple of 16 means new line (with line offset).
233+
234+
if ((i % 16) == 0) {
235+
// Just don't print ASCII for the zeroth line.
236+
if (i != 0)
237+
printf (" %s\n", buff);
238+
239+
// Output the offset.
240+
printf ("%-16s", ttyname);
241+
}
242+
243+
// Now the hex code for the specific character.
244+
printf (" %02x", pc[i]);
245+
246+
// And store a printable ASCII character for later.
247+
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
248+
buff[i % 16] = '.';
249+
else
250+
buff[i % 16] = pc[i];
251+
buff[(i % 16) + 1] = '\0';
252+
}
253+
254+
// Pad out last line if not exactly 16 characters.
255+
while ((i % 16) != 0) {
256+
printf (" ");
257+
i++;
258+
}
259+
260+
// And print the final ASCII bit.
261+
printf (" %s\n", buff);
262+
}
263+
264+
static void setup_back_tty(struct ttydevice *td, const char *ttyfile)
265+
{
266+
/* Open the serial port */
267+
td->td_fd = open(ttyfile, O_RDWR | O_NOCTTY | O_SYNC);
268+
if (td->td_fd < 0)
269+
die(FAIL_BACKEND, "open(%s) failed: %s\n", ttyfile, strerror(errno));
270+
271+
/* Capture current settings to restore them on exti */
272+
if (tcgetattr(td->td_fd, &td->td_origattr))
273+
die(FAIL_BACKEND, "tcgetattr(%s) failed: %s\n", ttyfile, strerror(errno));
274+
275+
/* Update TTY mode settings */
276+
if (tty_set_raw(td->td_fd))
277+
die(FAIL_BACKEND, "Failed setup TTY '%s' RAW mode\n", td->td_path);
278+
if (tty_set_speed(td->td_fd))
279+
die(FAIL_BACKEND, "Failed setup TTY '%s' baud rate\n", td->td_path);
280+
281+
/* Extra copy tty device file name, for debugging */
282+
strncpy(td->td_path, ttyfile, TTYNAMSZ);
283+
}
284+
285+
static void setup_front_tty(struct ttydevice *td, const char *ttyfile, const struct stat *ttyst)
286+
{
287+
struct stat ptsst;
288+
int ptsfd;
289+
290+
/*
291+
* PTS tty FD is not required, we we don't ever plan to read
292+
* or write any data, so we needn't remember it.
293+
*/
294+
295+
if (openpty(&td->td_fd, &ptsfd, NULL, NULL, NULL) < 0)
296+
die(FAIL_FRONTEND, "openpty() failed: %s\n", strerror(errno));
297+
298+
if (ttyname_r(ptsfd, td->td_path, TTYNAMSZ))
299+
die(FAIL_FRONTEND, "ttyname() failed: %s\n", strerror(errno));
300+
301+
if (tty_set_raw(ptsfd))
302+
die(FAIL_FRONTEND, "Failed setup TTY '%s' RAW mode\n", td->td_path);
303+
304+
/* Now set permissions, owners, etc. */
305+
if (stat(td->td_path, &ptsst) < 0)
306+
die(FAIL_FRONTEND, "stat(%s) failed: %s\n",
307+
td->td_path, strerror(errno));
308+
309+
if (chown(td->td_path, ttyst->st_uid, ttyst->st_gid) < 0)
310+
warn("chown() '%s' failed: %s\n", td->td_path, strerror(errno));
311+
if (chmod(td->td_path, ttyst->st_mode & 07777) < 0)
312+
warn("chmod() '%s' failed: %s\n", td->td_path, strerror(errno));
313+
314+
/* Make user requested symlink */
315+
unlink(ttyfile);
316+
if (!symlink(td->td_path, ttyfile)) {
317+
strncpy(td->td_pts_link, ttyfile, TTYNAMSZ);
318+
} else {
319+
warn("symlink() '%s' -> '%s' failed: %s\n", td->td_pts_link, ttyfile, strerror(errno));
320+
}
321+
}
322+
323+
static void usage(char *name)
324+
{
325+
fprintf(stderr, "Usage: %s back-device front-device [front-device2 ...]\n", name);
326+
exit(FAIL_USAGE);
327+
}
328+
329+
struct ttypxy {
330+
struct ttydevice backdev;
331+
struct ttydevice *frontdevs;
332+
int frontdev_cnt;
333+
};
334+
335+
static void ttypxy_init(struct ttypxy *ctx, int argc, char *argv[])
336+
{
337+
int i, c, showhelp = 0;
338+
struct stat backdev_st;
339+
struct sigaction sigact;
340+
sigset_t sigmask;
341+
342+
/* Process options */
343+
while ((c = getopt(argc, argv, "hv")) != EOF)
344+
switch (c) {
345+
case 'v':
346+
verbose = 1;
347+
break;
348+
case 'h':
349+
case '?':
350+
default:
351+
showhelp = 1;
352+
break;
353+
}
354+
355+
ctx->frontdev_cnt = argc - optind - 1;
356+
if (showhelp || (ctx->frontdev_cnt < 2))
357+
usage(argv[0]);
358+
359+
/* Prepare back TTY */
360+
setup_back_tty(&ctx->backdev, argv[optind]);
361+
optind++;
362+
if (stat(ctx->backdev.td_path, &backdev_st))
363+
die(FAIL_OTHER, "stat(%s) failed (wuh!?): %s\n", ctx->backdev.td_path, strerror(errno));
364+
365+
/* Prepare front TTYs */
366+
ctx->frontdevs = calloc(ctx->frontdev_cnt, sizeof(*ctx->frontdevs));
367+
if (!ctx->frontdevs)
368+
die(FAIL_OTHER, "calloc() failed: %s\n", strerror(errno));
369+
370+
for (i = 0; i < ctx->frontdev_cnt; i++)
371+
setup_front_tty(&ctx->frontdevs[i], argv[optind+i], &backdev_st);
372+
373+
if (chroot("/"))
374+
warn("chroot() failed: %s\n", strerror(errno));
375+
376+
sigemptyset(&sigmask);
377+
memset(&sigact, 0, sizeof(sigact));
378+
sigact.sa_handler = sigdeath;
379+
sigact.sa_mask = sigmask;
380+
381+
sigaction(SIGHUP,&sigact,NULL);
382+
sigaction(SIGINT,&sigact,NULL);
383+
sigaction(SIGQUIT,&sigact,NULL);
384+
sigaction(SIGPIPE,&sigact,NULL);
385+
sigaction(SIGTERM,&sigact,NULL);
386+
}
387+
388+
static void ttypxy_shutdown(struct ttypxy *ctx)
389+
{
390+
int i;
391+
392+
for (i = 0; i < ctx->frontdev_cnt; i++)
393+
unlink(ctx->frontdevs[i].td_pts_link);
394+
free(ctx->frontdevs);
395+
tcsetattr(ctx->backdev.td_fd, TCSAFLUSH, &ctx->backdev.td_origattr);
396+
}
397+
398+
static void ttypxy_run(struct ttypxy *ctx)
399+
{
400+
int i;
401+
char buffer[TTY_BUFF_SIZE];
402+
403+
while (!quit) {
404+
int rv, n = 0;
405+
int maxfd = 0;
406+
fd_set rfds;
407+
408+
FD_ZERO (&rfds);
409+
410+
FD_SET (ctx->backdev.td_fd, &rfds);
411+
maxfd = max(maxfd, ctx->backdev.td_fd);
412+
413+
for (i = 0; i < ctx->frontdev_cnt; i++) {
414+
FD_SET(ctx->frontdevs[i].td_fd, &rfds);
415+
maxfd = max(maxfd, ctx->frontdevs[i].td_fd);
416+
}
417+
418+
rv = select(maxfd + 1, &rfds, NULL, NULL, NULL);
419+
if (rv < 0) {
420+
if (errno == EINTR)
421+
continue;
422+
warn("select() failed: %s\n", strerror(errno));
423+
break;
424+
}
425+
426+
if (rv == 0)
427+
continue;
428+
429+
if (FD_ISSET(ctx->backdev.td_fd, &rfds)) {
430+
n = xread(ctx->backdev.td_fd, buffer, sizeof(buffer));
431+
if (n <= 0)
432+
break;
433+
434+
if (verbose)
435+
hexdump(ctx->backdev.td_path, buffer, n);
436+
}
437+
438+
for (i = 0; i < ctx->frontdev_cnt; i++) {
439+
if (n > 0)
440+
if (xwrite(ctx->frontdevs[i].td_fd, buffer, n) != n)
441+
warn("write(%s) front TTY failed: %s\n",
442+
ctx->frontdevs[i].td_pts_link,
443+
strerror(errno));
444+
445+
if (FD_ISSET(ctx->frontdevs[i].td_fd, &rfds)) {
446+
n = xread(ctx->frontdevs[i].td_fd, buffer, sizeof(buffer));
447+
if (n <= 0)
448+
continue;
449+
450+
if (xwrite(ctx->backdev.td_fd, buffer, n) != n)
451+
warn("write(%s) back TTY failed: %s\n",
452+
ctx->backdev.td_path,
453+
strerror(errno));
454+
455+
if (verbose)
456+
hexdump(ctx->frontdevs[i].td_pts_link, buffer, n);
457+
}
458+
}
459+
}
460+
}
461+
462+
int main (int argc, char *argv[])
463+
{
464+
struct ttypxy ctx;
465+
466+
memset(&ctx, 0, sizeof(ctx));
467+
ttypxy_init(&ctx, argc, argv);
468+
ttypxy_run(&ctx);
469+
ttypxy_shutdown(&ctx);
470+
471+
return 0;
472+
}

0 commit comments

Comments
 (0)
Please sign in to comment.