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 #include "_paths.h"
45 
46 #define ESCAPE_CHARACTER 0x1d
47 
48 static volatile sig_atomic_t received_signal = 0;
49 static char lockfile[sizeof (XEN_LOCK_DIR "/xenconsole.") + 8] = { 0 };
50 static int lockfd = -1;
51 
sighandler(int signum)52 static void sighandler(int signum)
53 {
54 	received_signal = 1;
55 }
56 
write_sync(int fd,const void * data,size_t size)57 static bool write_sync(int fd, const void *data, size_t size)
58 {
59 	size_t offset = 0;
60 	ssize_t len;
61 
62 	while (offset < size) {
63 		len = write(fd, data + offset, size - offset);
64 		if (len < 1) {
65 			return false;
66 		}
67 		offset += len;
68 	}
69 
70 	return true;
71 }
72 
usage(const char * program)73 static void usage(const char *program) {
74 	printf("Usage: %s [OPTION] DOMID\n"
75 	       "Attaches to a virtual domain console\n"
76 	       "\n"
77 	       "  -h, --help       display this help and exit\n"
78 	       "  -n, --num N      use console number N\n"
79 	       "  --type TYPE      console type. must be 'pv', 'serial' or 'vuart'\n"
80 	       "  --start-notify-fd N file descriptor used to notify parent\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 	int 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)177 static int console_loop(int fd, struct xs_handle *xs, char *pty_path,
178 		        bool interactive)
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 	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 		{ 0 },
340 
341 	};
342 	char *dom_path = NULL, *path = NULL, *test = NULL;
343 	int spty, xsfd;
344 	struct xs_handle *xs;
345 	char *end;
346 	console_type type = CONSOLE_INVAL;
347 	bool interactive = 0;
348 	char *console_names = "serial, pv, vuart";
349 
350 	while((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
351 		switch(ch) {
352 		case 'h':
353 			usage(argv[0]);
354 			exit(0);
355 			break;
356 		case 'n':
357 			num = atoi(optarg);
358 			break;
359 		case 't':
360 			if (!strcmp(optarg, "serial"))
361 				type = CONSOLE_SERIAL;
362 			else if (!strcmp(optarg, "pv"))
363 				type = CONSOLE_PV;
364 			else if (!strcmp(optarg, "vuart"))
365 				type = CONSOLE_VUART;
366 			else {
367 				fprintf(stderr, "Invalid type argument\n");
368 				fprintf(stderr, "Console types supported are: %s\n",
369 					console_names);
370 				exit(EINVAL);
371 			}
372 			break;
373 		case 's':
374 			start_notify_fd = atoi(optarg);
375 			break;
376 		case 'i':
377 			interactive = 1;
378 			break;
379 		default:
380 			fprintf(stderr, "Invalid argument\n");
381 			fprintf(stderr, "Try `%s --help' for more information.\n",
382 					argv[0]);
383 			exit(EINVAL);
384 		}
385 	}
386 
387 	if (optind >= argc) {
388 		fprintf(stderr, "DOMID should be specified\n");
389 		fprintf(stderr, "Try `%s --help' for more information.\n",
390 			argv[0]);
391 		exit(EINVAL);
392 	}
393 	domid = strtol(argv[optind], &end, 10);
394 	if (end && *end) {
395 		fprintf(stderr, "Invalid DOMID `%s'\n", argv[optind]);
396 		fprintf(stderr, "Try `%s --help' for more information.\n",
397 			argv[0]);
398 		exit(EINVAL);
399 	}
400 
401 	xs = xs_daemon_open();
402 	if (xs == NULL) {
403 		err(errno, "Could not contact XenStore");
404 	}
405 
406 	signal(SIGTERM, sighandler);
407 
408 	dom_path = xs_get_domain_path(xs, domid);
409 	if (dom_path == NULL)
410 		err(errno, "xs_get_domain_path()");
411 	if (type == CONSOLE_INVAL) {
412 		xc_dominfo_t xcinfo;
413 		xc_interface *xc_handle = xc_interface_open(0,0,0);
414 		if (xc_handle == NULL)
415 			err(errno, "Could not open xc interface");
416 		if ( (xc_domain_getinfo(xc_handle, domid, 1, &xcinfo) != 1) ||
417 		     (xcinfo.domid != domid) ) {
418 			xc_interface_close(xc_handle);
419 			err(errno, "Failed to get domain information");
420 		}
421 		/* default to pv console for pv guests and serial for hvm guests */
422 		if (xcinfo.hvm)
423 			type = CONSOLE_SERIAL;
424 		else
425 			type = CONSOLE_PV;
426 		xc_interface_close(xc_handle);
427 	}
428 	path = malloc(strlen(dom_path) + strlen("/device/console/0/tty") + 5);
429 	if (path == NULL)
430 		err(ENOMEM, "malloc");
431 	if (type == CONSOLE_SERIAL) {
432 		snprintf(path, strlen(dom_path) + strlen("/serial/0/tty") + 5, "%s/serial/%d/tty", dom_path, num);
433 		test = xs_read(xs, XBT_NULL, path, NULL);
434 		free(test);
435 		if (test == NULL)
436 			type = CONSOLE_PV;
437 	}
438 	if (type == CONSOLE_PV) {
439 
440 		if (num == 0)
441 			snprintf(path, strlen(dom_path) + strlen("/console/tty") + 1, "%s/console/tty", dom_path);
442 		else
443 			snprintf(path, strlen(dom_path) + strlen("/device/console/%d/tty") + 5, "%s/device/console/%d/tty", dom_path, num);
444 	}
445 	if (type == CONSOLE_VUART) {
446 		snprintf(path, strlen(dom_path) + strlen("/vuart/0/tty") + 1,
447 			 "%s/vuart/0/tty", dom_path);
448 	}
449 
450 	/* FIXME consoled currently does not assume domain-0 doesn't have a
451 	   console which is good when we break domain-0 up.  To keep us
452 	   user friendly, we'll bail out here since no data will ever show
453 	   up on domain-0. */
454 	if (domid == 0) {
455 		fprintf(stderr, "Can't specify Domain-0\n");
456 		exit(EINVAL);
457 	}
458 
459 	console_lock(domid);
460 	atexit(console_unlock);
461 
462 	/* Set a watch on this domain's console pty */
463 	if (!xs_watch(xs, path, ""))
464 		err(errno, "Can't set watch for console pty");
465 	xsfd = xs_fileno(xs);
466 
467 	/* Wait a little bit for tty to appear.  There is a race
468 	   condition that occurs after xend creates a domain.  This code
469 	   might be running before consoled has noticed the new domain
470 	   and setup a pty for it. */
471         spty = get_pty_fd(xs, path, 5);
472 	if (spty == -1) {
473 		err(errno, "Could not read tty from store");
474 	}
475 
476 	init_term(spty, &attr);
477 	if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
478 		interactive = 1;
479 		init_term(STDIN_FILENO, &stdin_old_attr);
480 		atexit(restore_term_stdin); /* if this fails, oh dear */
481 	}
482 
483 	if (start_notify_fd != -1) {
484 		/* Write 0x00 to notify parent about client's readiness */
485 		static const char msg[] = { 0x00 };
486 		int r;
487 
488 		do {
489 			r = write(start_notify_fd, msg, 1);
490 		} while ((r == -1 && errno == EINTR) || r == 0);
491 
492 		if (r == -1)
493 			err(errno, "Could not notify parent with fd %d",
494 			    start_notify_fd);
495 		close(start_notify_fd);
496 	}
497 
498 	console_loop(spty, xs, path, interactive);
499 
500 	free(path);
501 	free(dom_path);
502 	return 0;
503  }
504