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