1 /*\
2  *  Copyright (C) International Business Machines  Corp., 2005
3  *  Author(s): Anthony Liguori <aliguori@us.ibm.com>
4  *
5  *  Xen Console Daemon
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; under version 2 of the License.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU 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 <http://www.gnu.org/licenses/>.
18 \*/
19 
20 #include <sys/file.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <sys/socket.h>
24 #include <sys/un.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <time.h>
30 #include <fcntl.h>
31 #include <sys/wait.h>
32 #include <termios.h>
33 #include <signal.h>
34 #include <getopt.h>
35 #include <sys/select.h>
36 #include <err.h>
37 #include <string.h>
38 #ifdef __sun__
39 #include <sys/stropts.h>
40 #endif
41 
42 #include <xenstore.h>
43 #include "xenctrl.h"
44 
45 #define DEFAULT_ESCAPE_CHARACTER 0x1d
46 
47 static volatile sig_atomic_t received_signal = 0;
48 static char lockfile[sizeof (XEN_LOCK_DIR "/xenconsole.") + 8] = { 0 };
49 static int lockfd = -1;
50 
sighandler(int signum)51 static void sighandler(int signum)
52 {
53 	received_signal = 1;
54 }
55 
write_sync(int fd,const void * data,size_t size)56 static bool write_sync(int fd, const void *data, size_t size)
57 {
58 	size_t offset = 0;
59 	ssize_t len;
60 
61 	while (offset < size) {
62 		len = write(fd, data + offset, size - offset);
63 		if (len < 1) {
64 			return false;
65 		}
66 		offset += len;
67 	}
68 
69 	return true;
70 }
71 
usage(const char * program)72 static void usage(const char *program) {
73 	printf("Usage: %s [OPTION] DOMID\n"
74 	       "Attaches to a virtual domain console\n"
75 	       "\n"
76 	       "  -h, --help       display this help and exit\n"
77 	       "  -n, --num N      use console number N\n"
78 	       "  --type TYPE      console type. must be 'pv', 'serial' or 'vuart'\n"
79 	       "  --start-notify-fd N file descriptor used to notify parent\n"
80 	       "  --escape E       escape sequence to exit console\n"
81 	       , program);
82 }
83 
84 #ifdef	__sun__
cfmakeraw(struct termios * termios_p)85 void cfmakeraw(struct termios *termios_p)
86 {
87 	termios_p->c_iflag &=
88 	    ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
89 	termios_p->c_oflag &= ~OPOST;
90 	termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
91 	termios_p->c_cflag &= ~(CSIZE|PARENB);
92 	termios_p->c_cflag |= CS8;
93 
94 	termios_p->c_cc[VMIN] = 0;
95 	termios_p->c_cc[VTIME] = 0;
96 }
97 #endif
98 
get_pty_fd(struct xs_handle * xs,char * path,int seconds)99 static int get_pty_fd(struct xs_handle *xs, char *path, int seconds)
100 /* Check for a pty in xenstore, open it and return its fd.
101  * Assumes there is already a watch set in the store for this path. */
102 {
103 	struct timeval tv;
104 	fd_set watch_fdset;
105 	int xs_fd = xs_fileno(xs), pty_fd = -1;
106 	time_t start, now;
107 	unsigned int len = 0;
108 	char *pty_path, **watch_paths;
109 
110 	start = now = time(NULL);
111 	do {
112 		tv.tv_usec = 0;
113 		tv.tv_sec = (start + seconds) - now;
114 		FD_ZERO(&watch_fdset);
115 		FD_SET(xs_fd, &watch_fdset);
116 		if (select(xs_fd + 1, &watch_fdset, NULL, NULL, &tv)) {
117 			/* Read the watch to drain the buffer */
118 			watch_paths = xs_read_watch(xs, &len);
119 			free(watch_paths);
120 			/* We only watch for one thing, so no need to
121 			 * disambiguate: just read the pty path */
122 			pty_path = xs_read(xs, XBT_NULL, path, &len);
123 			if (pty_path != NULL && pty_path[0] != '\0') {
124 				pty_fd = open(pty_path, O_RDWR | O_NOCTTY);
125 				if (pty_fd == -1)
126 					warn("Could not open tty `%s'", pty_path);
127 			}
128 			free(pty_path);
129 		}
130 	} while (pty_fd == -1 && (now = time(NULL)) < start + seconds);
131 
132 #ifdef __sun__
133 	if (pty_fd != -1) {
134 		struct termios term;
135 
136 		/*
137 		 * The pty may come from either xend (with pygrub) or
138 		 * xenconsoled.  It may have tty semantics set up, or not.
139 		 * While it isn't strictly necessary to have those
140 		 * semantics here, it is good to have a consistent
141 		 * state that is the same as under Linux.
142 		 *
143 		 * If tcgetattr fails, they have not been set up,
144 		 * so go ahead and set them up now, by pushing the
145 		 * ptem and ldterm streams modules.
146 		 */
147 		if (tcgetattr(pty_fd, &term) < 0) {
148 			ioctl(pty_fd, I_PUSH, "ptem");
149 			ioctl(pty_fd, I_PUSH, "ldterm");
150 		}
151 	}
152 #endif
153 
154 	return pty_fd;
155 }
156 
157 
158 /* don't worry too much if setting terminal attributes fail */
init_term(int fd,struct termios * old)159 static void init_term(int fd, struct termios *old)
160 {
161 	struct termios new_term;
162 
163 	if (tcgetattr(fd, old) == -1)
164 		return;
165 
166 	new_term = *old;
167 	cfmakeraw(&new_term);
168 
169 	tcsetattr(fd, TCSANOW, &new_term);
170 }
171 
restore_term(int fd,struct termios * old)172 static void restore_term(int fd, struct termios *old)
173 {
174 	tcsetattr(fd, TCSANOW, old);
175 }
176 
console_loop(int fd,struct xs_handle * xs,char * pty_path,bool interactive,char escape_character)177 static int console_loop(int fd, struct xs_handle *xs, char *pty_path,
178 			bool interactive, char escape_character)
179 {
180 	int ret, xs_fd = xs_fileno(xs), max_fd = -1;
181 
182 	do {
183 		fd_set fds;
184 
185 		FD_ZERO(&fds);
186 		if (interactive) {
187 			FD_SET(STDIN_FILENO, &fds);
188 			max_fd = STDIN_FILENO;
189 		}
190 		FD_SET(xs_fd, &fds);
191 		if (xs_fd > max_fd) max_fd = xs_fd;
192 		if (fd != -1) FD_SET(fd, &fds);
193 		if (fd > max_fd) max_fd = fd;
194 
195 		ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
196 		if (ret == -1) {
197 			if (errno == EINTR || errno == EAGAIN) {
198 				continue;
199 			}
200 			return -1;
201 		}
202 
203 		if (FD_ISSET(xs_fileno(xs), &fds)) {
204 			int newfd = get_pty_fd(xs, pty_path, 0);
205 			if (fd != -1)
206 				close(fd);
207                         if (newfd == -1)
208 				/* Console PTY has become invalid */
209 				return 0;
210 			fd = newfd;
211 			continue;
212 		}
213 
214 		if (FD_ISSET(STDIN_FILENO, &fds)) {
215 			ssize_t len;
216 			char msg[60];
217 
218 			len = read(STDIN_FILENO, msg, sizeof(msg));
219 			if (len == 1 && msg[0] == escape_character) {
220 				return 0;
221 			}
222 
223 			if (len == 0 || len == -1) {
224 				if (len == -1 &&
225 				    (errno == EINTR || errno == EAGAIN)) {
226 					continue;
227 				}
228 				return -1;
229 			}
230 
231 			if (!write_sync(fd, msg, len)) {
232 				close(fd);
233 				fd = -1;
234 				continue;
235 			}
236 		}
237 
238 		if (fd != -1 && FD_ISSET(fd, &fds)) {
239 			ssize_t len;
240 			char msg[512];
241 
242 			len = read(fd, msg, sizeof(msg));
243 			if (len == 0 || len == -1) {
244 				if (len == -1 &&
245 				    (errno == EINTR || errno == EAGAIN)) {
246 					continue;
247 				}
248 				close(fd);
249 				fd = -1;
250 				continue;
251 			}
252 
253 			if (!write_sync(STDOUT_FILENO, msg, len)) {
254 				perror("write() failed");
255 				return -1;
256 			}
257 		}
258 	} while (received_signal == 0);
259 
260 	return 0;
261 }
262 
263 typedef enum {
264        CONSOLE_INVAL,
265        CONSOLE_PV,
266        CONSOLE_SERIAL,
267        CONSOLE_VUART,
268 } console_type;
269 
270 static struct termios stdin_old_attr;
271 
restore_term_stdin(void)272 static void restore_term_stdin(void)
273 {
274 	restore_term(STDIN_FILENO, &stdin_old_attr);
275 }
276 
277 /* The following locking strategy is based on that from
278  * libxl__domain_userdata_lock(), with the difference that we want to fail if we
279  * cannot acquire the lock rather than wait indefinitely.
280  */
console_lock(int domid)281 static void console_lock(int domid)
282 {
283 	struct stat stab, fstab;
284 	int fd;
285 
286 	snprintf(lockfile, sizeof lockfile, "%s%d", XEN_LOCK_DIR "/xenconsole.", domid);
287 
288 	while (true) {
289 		fd = open(lockfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
290 		if (fd < 0)
291 			err(errno, "Could not open %s", lockfile);
292 
293 		while (flock(fd, LOCK_EX | LOCK_NB)) {
294 			if (errno == EINTR)
295 				continue;
296 			else
297 				err(errno, "Could not lock %s", lockfile);
298 		}
299 		if (fstat(fd, &fstab))
300 			err(errno, "Could not fstat %s", lockfile);
301 		if (stat(lockfile, &stab)) {
302 			if (errno != ENOENT)
303 				err(errno, "Could not stat %s", lockfile);
304 		} else {
305 			if (stab.st_dev == fstab.st_dev && stab.st_ino == fstab.st_ino)
306 				break;
307 		}
308 
309 		close(fd);
310 	}
311 
312 	lockfd = fd;
313 	return;
314 }
315 
console_unlock(void)316 static void console_unlock(void)
317 {
318 	if (lockfile[0] && lockfd != -1) {
319 		unlink(lockfile);
320 		close(lockfd);
321 	}
322 }
323 
main(int argc,char ** argv)324 int main(int argc, char **argv)
325 {
326 	struct termios attr;
327 	int domid;
328 	const char *sopt = "hn:";
329 	int ch;
330 	unsigned int num = 0;
331 	int opt_ind=0;
332 	int start_notify_fd = -1;
333 	struct option lopt[] = {
334 		{ "type",     1, 0, 't' },
335 		{ "num",     1, 0, 'n' },
336 		{ "help",    0, 0, 'h' },
337 		{ "start-notify-fd", 1, 0, 's' },
338 		{ "interactive", 0, 0, 'i' },
339 		{ "escape",  1, 0, 'e' },
340 		{ 0 },
341 
342 	};
343 	char *dom_path = NULL, *path = NULL, *test = NULL;
344 	int spty, xsfd;
345 	struct xs_handle *xs;
346 	char *end;
347 	console_type type = CONSOLE_INVAL;
348 	bool interactive = 0;
349 	const char *console_names = "serial, pv, vuart";
350 	char escape_character = DEFAULT_ESCAPE_CHARACTER;
351 
352 	while((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
353 		switch(ch) {
354 		case 'h':
355 			usage(argv[0]);
356 			exit(0);
357 			break;
358 		case 'n':
359 			num = atoi(optarg);
360 			break;
361 		case 't':
362 			if (!strcmp(optarg, "serial"))
363 				type = CONSOLE_SERIAL;
364 			else if (!strcmp(optarg, "pv"))
365 				type = CONSOLE_PV;
366 			else if (!strcmp(optarg, "vuart"))
367 				type = CONSOLE_VUART;
368 			else {
369 				fprintf(stderr, "Invalid type argument\n");
370 				fprintf(stderr, "Console types supported are: %s\n",
371 					console_names);
372 				exit(EINVAL);
373 			}
374 			break;
375 		case 's':
376 			start_notify_fd = atoi(optarg);
377 			break;
378 		case 'i':
379 			interactive = 1;
380 			break;
381 		case 'e':
382 			if (optarg[0] == '^' && optarg[1] && optarg[2] == '\0')
383 				escape_character = optarg[1] & 0x1f;
384 			else if (optarg[0] && optarg[1] == '\0')
385 				escape_character = optarg[0];
386 			else {
387 				fprintf(stderr, "Invalid escape argument\n");
388 				exit(EINVAL);
389 			}
390 			break;
391 		default:
392 			fprintf(stderr, "Invalid argument\n");
393 			fprintf(stderr, "Try `%s --help' for more information.\n",
394 					argv[0]);
395 			exit(EINVAL);
396 		}
397 	}
398 
399 	if (optind >= argc) {
400 		fprintf(stderr, "DOMID should be specified\n");
401 		fprintf(stderr, "Try `%s --help' for more information.\n",
402 			argv[0]);
403 		exit(EINVAL);
404 	}
405 	domid = strtol(argv[optind], &end, 10);
406 	if (end && *end) {
407 		fprintf(stderr, "Invalid DOMID `%s'\n", argv[optind]);
408 		fprintf(stderr, "Try `%s --help' for more information.\n",
409 			argv[0]);
410 		exit(EINVAL);
411 	}
412 
413 	xs = xs_open(0);
414 	if (xs == NULL) {
415 		err(errno, "Could not contact XenStore");
416 	}
417 
418 	signal(SIGTERM, sighandler);
419 
420 	dom_path = xs_get_domain_path(xs, domid);
421 	if (dom_path == NULL)
422 		err(errno, "xs_get_domain_path()");
423 	if (type == CONSOLE_INVAL) {
424 		xc_domaininfo_t xcinfo;
425 		xc_interface *xc_handle = xc_interface_open(0,0,0);
426 		if (xc_handle == NULL)
427 			err(errno, "Could not open xc interface");
428 		if (xc_domain_getinfo_single(xc_handle, domid, &xcinfo) < 0) {
429 			xc_interface_close(xc_handle);
430 			err(errno, "Failed to get domain information");
431 		}
432 		/* default to pv console for pv guests and serial for hvm guests */
433 		if (xcinfo.flags & XEN_DOMINF_hvm_guest)
434 			type = CONSOLE_SERIAL;
435 		else
436 			type = CONSOLE_PV;
437 		xc_interface_close(xc_handle);
438 	}
439 	path = malloc(strlen(dom_path) + strlen("/device/console/0/tty") + 5);
440 	if (path == NULL)
441 		err(ENOMEM, "malloc");
442 	if (type == CONSOLE_SERIAL) {
443 		snprintf(path, strlen(dom_path) + strlen("/serial/0/tty") + 5, "%s/serial/%d/tty", dom_path, num);
444 		test = xs_read(xs, XBT_NULL, path, NULL);
445 		free(test);
446 		if (test == NULL)
447 			type = CONSOLE_PV;
448 	}
449 	if (type == CONSOLE_PV) {
450 
451 		if (num == 0)
452 			snprintf(path, strlen(dom_path) + strlen("/console/tty") + 1, "%s/console/tty", dom_path);
453 		else
454 			snprintf(path, strlen(dom_path) + strlen("/device/console/%d/tty") + 5, "%s/device/console/%d/tty", dom_path, num);
455 	}
456 	if (type == CONSOLE_VUART) {
457 		snprintf(path, strlen(dom_path) + strlen("/vuart/0/tty") + 1,
458 			 "%s/vuart/0/tty", dom_path);
459 	}
460 
461 	/* FIXME consoled currently does not assume domain-0 doesn't have a
462 	   console which is good when we break domain-0 up.  To keep us
463 	   user friendly, we'll bail out here since no data will ever show
464 	   up on domain-0. */
465 	if (domid == 0) {
466 		fprintf(stderr, "Can't specify Domain-0\n");
467 		exit(EINVAL);
468 	}
469 
470 	console_lock(domid);
471 	atexit(console_unlock);
472 
473 	/* Set a watch on this domain's console pty */
474 	if (!xs_watch(xs, path, ""))
475 		err(errno, "Can't set watch for console pty");
476 	xsfd = xs_fileno(xs);
477 
478 	/* Wait a little bit for tty to appear.  There is a race
479 	   condition that occurs after xend creates a domain.  This code
480 	   might be running before consoled has noticed the new domain
481 	   and setup a pty for it. */
482         spty = get_pty_fd(xs, path, 5);
483 	if (spty == -1) {
484 		err(errno, "Could not read tty from store");
485 	}
486 
487 	init_term(spty, &attr);
488 	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
489 		interactive = 1;
490 		init_term(STDIN_FILENO, &stdin_old_attr);
491 		atexit(restore_term_stdin); /* if this fails, oh dear */
492 	}
493 
494 	if (start_notify_fd != -1) {
495 		/* Write 0x00 to notify parent about client's readiness */
496 		static const char msg[] = { 0x00 };
497 		int r;
498 
499 		do {
500 			r = write(start_notify_fd, msg, 1);
501 		} while ((r == -1 && errno == EINTR) || r == 0);
502 
503 		if (r == -1)
504 			err(errno, "Could not notify parent with fd %d",
505 			    start_notify_fd);
506 		close(start_notify_fd);
507 	}
508 
509 	console_loop(spty, xs, path, interactive, escape_character);
510 
511 	free(path);
512 	free(dom_path);
513 	return 0;
514  }
515