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