xref: /openbmc/obmc-console/console-server.c (revision 172178452b4e0062fff497b296059b10cd3c85ef)
1d831f960SJeremy Kerr /**
2d831f960SJeremy Kerr  * Console server process for OpenBMC
3d831f960SJeremy Kerr  *
4d831f960SJeremy Kerr  * Copyright © 2016 IBM Corporation <jk@ozlabs.org>
5d831f960SJeremy Kerr  */
6d831f960SJeremy Kerr 
7*17217845SJeremy Kerr #define _GNU_SOURCE
8*17217845SJeremy Kerr 
9d831f960SJeremy Kerr #include <stdint.h>
10d831f960SJeremy Kerr #include <stdbool.h>
11d831f960SJeremy Kerr #include <stdlib.h>
12d831f960SJeremy Kerr #include <stdio.h>
13d831f960SJeremy Kerr #include <fcntl.h>
14d831f960SJeremy Kerr #include <unistd.h>
15d831f960SJeremy Kerr #include <err.h>
16d831f960SJeremy Kerr #include <termios.h>
17d831f960SJeremy Kerr #include <string.h>
18d831f960SJeremy Kerr #include <getopt.h>
19*17217845SJeremy Kerr #include <limits.h>
20d831f960SJeremy Kerr 
21d831f960SJeremy Kerr #include <sys/types.h>
22d831f960SJeremy Kerr #include <sys/poll.h>
23d831f960SJeremy Kerr 
24d831f960SJeremy Kerr #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
25d831f960SJeremy Kerr 
26d831f960SJeremy Kerr static const char esc_str[] = { '\r', '~', '.' };
27d831f960SJeremy Kerr 
28d831f960SJeremy Kerr struct console_ctx {
29*17217845SJeremy Kerr 	const char	*tty_kname;
30*17217845SJeremy Kerr 	char		*tty_sysfs_devnode;
31*17217845SJeremy Kerr 	char		*tty_dev;
32d831f960SJeremy Kerr 	int		tty_fd;
33d831f960SJeremy Kerr 	int		console_fd_in;
34d831f960SJeremy Kerr 	int		console_fd_out;
35d831f960SJeremy Kerr 	bool		console_is_tty;
36d831f960SJeremy Kerr 	struct termios	orig_termios;
37d831f960SJeremy Kerr 	int		esc_str_pos;
38d831f960SJeremy Kerr };
39d831f960SJeremy Kerr 
40d831f960SJeremy Kerr static void usage(const char *progname)
41d831f960SJeremy Kerr {
42d831f960SJeremy Kerr 	fprintf(stderr,
43d831f960SJeremy Kerr "usage: %s [options]\n"
44d831f960SJeremy Kerr "\n"
45d831f960SJeremy Kerr "Options:\n"
46*17217845SJeremy Kerr "  --device <TTY>  Use serial device TTY (eg, ttyS0)\n"
47d831f960SJeremy Kerr "",
48d831f960SJeremy Kerr 		progname);
49d831f960SJeremy Kerr }
50d831f960SJeremy Kerr 
51*17217845SJeremy Kerr /* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
52*17217845SJeremy Kerr static int tty_find_device(struct console_ctx *ctx)
53*17217845SJeremy Kerr {
54*17217845SJeremy Kerr 	char *tty_class_device_link;
55*17217845SJeremy Kerr 	char *tty_device_tty_dir;
56*17217845SJeremy Kerr 	char *tty_device_reldir;
57*17217845SJeremy Kerr 	int rc;
58*17217845SJeremy Kerr 
59*17217845SJeremy Kerr 	rc = -1;
60*17217845SJeremy Kerr 	tty_class_device_link = NULL;
61*17217845SJeremy Kerr 	tty_device_tty_dir = NULL;
62*17217845SJeremy Kerr 	tty_device_reldir = NULL;
63*17217845SJeremy Kerr 
64*17217845SJeremy Kerr 	rc = asprintf(&tty_class_device_link,
65*17217845SJeremy Kerr 			"/sys/class/tty/%s", ctx->tty_kname);
66*17217845SJeremy Kerr 	if (rc < 0)
67*17217845SJeremy Kerr 		return -1;
68*17217845SJeremy Kerr 
69*17217845SJeremy Kerr 	tty_device_tty_dir = realpath(tty_class_device_link, NULL);
70*17217845SJeremy Kerr 	if (rc < 0) {
71*17217845SJeremy Kerr 		warn("Can't query sysfs for device %s", ctx->tty_kname);
72*17217845SJeremy Kerr 		goto out_free;
73*17217845SJeremy Kerr 	}
74*17217845SJeremy Kerr 
75*17217845SJeremy Kerr 	rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
76*17217845SJeremy Kerr 	if (rc < 0)
77*17217845SJeremy Kerr 		goto out_free;
78*17217845SJeremy Kerr 
79*17217845SJeremy Kerr 	ctx->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
80*17217845SJeremy Kerr 	if (!ctx->tty_sysfs_devnode)
81*17217845SJeremy Kerr 		warn("Can't find parent device for %s", ctx->tty_kname);
82*17217845SJeremy Kerr 
83*17217845SJeremy Kerr 
84*17217845SJeremy Kerr 	/* todo: lookup from major/minor info in sysfs, in case udev has
85*17217845SJeremy Kerr 	 * renamed us */
86*17217845SJeremy Kerr 	rc = asprintf(&ctx->tty_dev, "/dev/%s", ctx->tty_kname);
87*17217845SJeremy Kerr 	if (rc < 0)
88*17217845SJeremy Kerr 		goto out_free;
89*17217845SJeremy Kerr 
90*17217845SJeremy Kerr 	rc = 0;
91*17217845SJeremy Kerr 
92*17217845SJeremy Kerr out_free:
93*17217845SJeremy Kerr 	free(tty_class_device_link);
94*17217845SJeremy Kerr 	free(tty_device_tty_dir);
95*17217845SJeremy Kerr 	free(tty_device_reldir);
96*17217845SJeremy Kerr 	return rc;
97*17217845SJeremy Kerr }
98*17217845SJeremy Kerr 
99d831f960SJeremy Kerr /**
100d831f960SJeremy Kerr  * Open and initialise the serial device
101d831f960SJeremy Kerr  */
102d831f960SJeremy Kerr static int tty_init_io(struct console_ctx *ctx)
103d831f960SJeremy Kerr {
104*17217845SJeremy Kerr 
105d831f960SJeremy Kerr 	ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
106d831f960SJeremy Kerr 	if (ctx->tty_fd <= 0) {
107d831f960SJeremy Kerr 		warn("Can't open tty %s", ctx->tty_dev);
108d831f960SJeremy Kerr 		return -1;
109d831f960SJeremy Kerr 	}
110d831f960SJeremy Kerr 
111d831f960SJeremy Kerr 	/* Disable character delay. We may want to later enable this when
112d831f960SJeremy Kerr 	 * we detect larger amounts of data
113d831f960SJeremy Kerr 	 */
114d831f960SJeremy Kerr 	fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
115d831f960SJeremy Kerr 
116d831f960SJeremy Kerr 	return 0;
117d831f960SJeremy Kerr }
118d831f960SJeremy Kerr 
119d831f960SJeremy Kerr /*
120d831f960SJeremy Kerr  * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
121d831f960SJeremy Kerr  * put it in canonical mode
122d831f960SJeremy Kerr  */
123d831f960SJeremy Kerr static int console_init_io(struct console_ctx *ctx)
124d831f960SJeremy Kerr {
125d831f960SJeremy Kerr 	struct termios termios;
126d831f960SJeremy Kerr 	int rc;
127d831f960SJeremy Kerr 
128d831f960SJeremy Kerr 	ctx->console_fd_in = STDIN_FILENO;
129d831f960SJeremy Kerr 	ctx->console_fd_out = STDOUT_FILENO;
130d831f960SJeremy Kerr 	ctx->console_is_tty = isatty(ctx->console_fd_in);
131d831f960SJeremy Kerr 
132d831f960SJeremy Kerr 	if (!ctx->console_is_tty)
133d831f960SJeremy Kerr 		return 0;
134d831f960SJeremy Kerr 
135d831f960SJeremy Kerr 	rc = tcgetattr(ctx->console_fd_in, &termios);
136d831f960SJeremy Kerr 	if (rc) {
137d831f960SJeremy Kerr 		warn("Can't get terminal attributes for console");
138d831f960SJeremy Kerr 		return -1;
139d831f960SJeremy Kerr 	}
140d831f960SJeremy Kerr 	memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
141d831f960SJeremy Kerr 	cfmakeraw(&termios);
142d831f960SJeremy Kerr 
143d831f960SJeremy Kerr 	rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
144d831f960SJeremy Kerr 	if (rc) {
145d831f960SJeremy Kerr 		warn("Can't set terminal attributes for console");
146d831f960SJeremy Kerr 		return -1;
147d831f960SJeremy Kerr 	}
148d831f960SJeremy Kerr 
149d831f960SJeremy Kerr 	return 0;
150d831f960SJeremy Kerr }
151d831f960SJeremy Kerr 
152d831f960SJeremy Kerr static int console_process_input(struct console_ctx *ctx,
153d831f960SJeremy Kerr 		uint8_t *buf, size_t len)
154d831f960SJeremy Kerr {
155d831f960SJeremy Kerr 	unsigned long i;
156d831f960SJeremy Kerr 	uint8_t e;
157d831f960SJeremy Kerr 
158d831f960SJeremy Kerr 	e = esc_str[ctx->esc_str_pos];
159d831f960SJeremy Kerr 
160d831f960SJeremy Kerr 	for (i = 0; i < len; i++) {
161d831f960SJeremy Kerr 		if (buf[i] == e) {
162d831f960SJeremy Kerr 			ctx->esc_str_pos++;
163d831f960SJeremy Kerr 			if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
164d831f960SJeremy Kerr 				return 1;
165d831f960SJeremy Kerr 			e = esc_str[ctx->esc_str_pos];
166d831f960SJeremy Kerr 		} else {
167d831f960SJeremy Kerr 
168d831f960SJeremy Kerr 			ctx->esc_str_pos = 0;
169d831f960SJeremy Kerr 		}
170d831f960SJeremy Kerr 	}
171d831f960SJeremy Kerr 	return 0;
172d831f960SJeremy Kerr }
173d831f960SJeremy Kerr 
174d831f960SJeremy Kerr static void console_restore_termios(struct console_ctx *ctx)
175d831f960SJeremy Kerr {
176d831f960SJeremy Kerr 	if (ctx->console_is_tty)
177d831f960SJeremy Kerr 		tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
178d831f960SJeremy Kerr }
179d831f960SJeremy Kerr 
180d831f960SJeremy Kerr static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
181d831f960SJeremy Kerr {
182d831f960SJeremy Kerr 	size_t pos;
183d831f960SJeremy Kerr 	ssize_t rc;
184d831f960SJeremy Kerr 
185d831f960SJeremy Kerr 	for (pos = 0; pos < len; pos += rc) {
186d831f960SJeremy Kerr 		rc = write(fd, buf + pos, len - pos);
187d831f960SJeremy Kerr 		if (rc <= 0) {
188d831f960SJeremy Kerr 			warn("Write error");
189d831f960SJeremy Kerr 			return -1;
190d831f960SJeremy Kerr 		}
191d831f960SJeremy Kerr 	}
192d831f960SJeremy Kerr 
193d831f960SJeremy Kerr 	return 0;
194d831f960SJeremy Kerr }
195d831f960SJeremy Kerr 
196d831f960SJeremy Kerr int run_console(struct console_ctx *ctx)
197d831f960SJeremy Kerr {
198d831f960SJeremy Kerr 	struct pollfd pollfds[2];
199d831f960SJeremy Kerr 	int rc, len;
200d831f960SJeremy Kerr 
201d831f960SJeremy Kerr 	pollfds[0].fd = ctx->tty_fd;
202d831f960SJeremy Kerr 	pollfds[0].events = POLLIN;
203d831f960SJeremy Kerr 	pollfds[1].fd = ctx->console_fd_in;
204d831f960SJeremy Kerr 	pollfds[1].events = POLLIN;
205d831f960SJeremy Kerr 
206d831f960SJeremy Kerr 	for (;;) {
207d831f960SJeremy Kerr 		uint8_t buf[4096];
208d831f960SJeremy Kerr 
209d831f960SJeremy Kerr 		rc = poll(pollfds, 2, -1);
210d831f960SJeremy Kerr 		if (rc < 0) {
211d831f960SJeremy Kerr 			warn("poll error");
212d831f960SJeremy Kerr 			return -1;
213d831f960SJeremy Kerr 		}
214d831f960SJeremy Kerr 
215d831f960SJeremy Kerr 		if (pollfds[0].revents) {
216d831f960SJeremy Kerr 			rc = read(ctx->tty_fd, buf, sizeof(buf));
217d831f960SJeremy Kerr 			if (rc <= 0) {
218d831f960SJeremy Kerr 				warn("Error reading from tty device");
219d831f960SJeremy Kerr 				return -1;
220d831f960SJeremy Kerr 			}
221d831f960SJeremy Kerr 			rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
222d831f960SJeremy Kerr 			if (rc < 0)
223d831f960SJeremy Kerr 				return -1;
224d831f960SJeremy Kerr 		}
225d831f960SJeremy Kerr 		if (pollfds[1].revents) {
226d831f960SJeremy Kerr 			rc = read(ctx->console_fd_in, buf, sizeof(buf));
227d831f960SJeremy Kerr 			if (rc == 0)
228d831f960SJeremy Kerr 				return 0;
229d831f960SJeremy Kerr 
230d831f960SJeremy Kerr 			if (rc <= 0) {
231d831f960SJeremy Kerr 				warn("Error reading from console");
232d831f960SJeremy Kerr 				return -1;
233d831f960SJeremy Kerr 			}
234d831f960SJeremy Kerr 			len = rc;
235d831f960SJeremy Kerr 			rc = console_process_input(ctx, buf, len);
236d831f960SJeremy Kerr 			if (rc) {
237d831f960SJeremy Kerr 				rc = 0;
238d831f960SJeremy Kerr 				return 0;
239d831f960SJeremy Kerr 			}
240d831f960SJeremy Kerr 			rc = write_buf_to_fd(ctx->tty_fd, buf, len);
241d831f960SJeremy Kerr 			if (rc < 0)
242d831f960SJeremy Kerr 				return -1;
243d831f960SJeremy Kerr 		}
244d831f960SJeremy Kerr 	}
245d831f960SJeremy Kerr }
246d831f960SJeremy Kerr 
247d831f960SJeremy Kerr static const struct option options[] = {
248d831f960SJeremy Kerr 	{ "device",	required_argument,	0, 'd'},
249d831f960SJeremy Kerr 	{ },
250d831f960SJeremy Kerr };
251d831f960SJeremy Kerr 
252d831f960SJeremy Kerr int main(int argc, char **argv)
253d831f960SJeremy Kerr {
254d831f960SJeremy Kerr 	struct console_ctx *ctx;
255d831f960SJeremy Kerr 	int rc;
256d831f960SJeremy Kerr 
257d831f960SJeremy Kerr 	ctx = malloc(sizeof(struct console_ctx));
258d831f960SJeremy Kerr 	memset(ctx, 0, sizeof(*ctx));
259d831f960SJeremy Kerr 
260d831f960SJeremy Kerr 	for (;;) {
261d831f960SJeremy Kerr 		int c, idx;
262d831f960SJeremy Kerr 
263d831f960SJeremy Kerr 		c = getopt_long(argc, argv, "d", options, &idx);
264d831f960SJeremy Kerr 		if (c == -1)
265d831f960SJeremy Kerr 			break;
266d831f960SJeremy Kerr 
267d831f960SJeremy Kerr 		switch (c) {
268d831f960SJeremy Kerr 		case 'd':
269*17217845SJeremy Kerr 			ctx->tty_kname = optarg;
270d831f960SJeremy Kerr 			break;
271d831f960SJeremy Kerr 
272d831f960SJeremy Kerr 		case 'h':
273d831f960SJeremy Kerr 		case '?':
274d831f960SJeremy Kerr 			usage(argv[0]);
275d831f960SJeremy Kerr 			break;
276d831f960SJeremy Kerr 		}
277d831f960SJeremy Kerr 	}
278d831f960SJeremy Kerr 
279*17217845SJeremy Kerr 	if (!ctx->tty_kname) {
280d831f960SJeremy Kerr 		fprintf(stderr,
281d831f960SJeremy Kerr 			"Error: No TTY device specified (use --device)\n");
282d831f960SJeremy Kerr 		return EXIT_FAILURE;
283d831f960SJeremy Kerr 	}
284d831f960SJeremy Kerr 
285*17217845SJeremy Kerr 	rc = tty_find_device(ctx);
286*17217845SJeremy Kerr 	if (rc)
287*17217845SJeremy Kerr 		return EXIT_FAILURE;
288*17217845SJeremy Kerr 
289*17217845SJeremy Kerr 	return EXIT_SUCCESS;
290*17217845SJeremy Kerr 
291d831f960SJeremy Kerr 	rc = tty_init_io(ctx);
292d831f960SJeremy Kerr 	if (rc)
293d831f960SJeremy Kerr 		return EXIT_FAILURE;
294d831f960SJeremy Kerr 
295d831f960SJeremy Kerr 	rc = console_init_io(ctx);
296d831f960SJeremy Kerr 	if (rc)
297d831f960SJeremy Kerr 		return EXIT_FAILURE;
298d831f960SJeremy Kerr 
299d831f960SJeremy Kerr 	rc = run_console(ctx);
300d831f960SJeremy Kerr 
301d831f960SJeremy Kerr 	console_restore_termios(ctx);
302d831f960SJeremy Kerr 
303*17217845SJeremy Kerr 	free(ctx->tty_sysfs_devnode);
304*17217845SJeremy Kerr 	free(ctx->tty_dev);
305d831f960SJeremy Kerr 	free(ctx);
306d831f960SJeremy Kerr 
307d831f960SJeremy Kerr 	return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
308d831f960SJeremy Kerr }
309