xref: /illumos-gate/usr/src/cmd/vntsd/console.c (revision 8e6a2a04)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 #pragma ident	"%Z%%M%	%I%	%E% SMI"
26 
27 /*
28  * Listen thread creates a console thread whenever there is a tcp client
29  * made a conection to its port. In the console thread, if there are
30  * multiple consoles in the group, client will be asked for a console selection.
31  * a write thread for a console is created when first client connects to a
32  * selected console and console thread becomes read thread for the client.
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <sys/types.h>
40 #include <sys/socket.h>
41 #include <netinet/in.h>
42 #include <thread.h>
43 #include <synch.h>
44 #include <signal.h>
45 #include <assert.h>
46 #include <ctype.h>
47 #include <syslog.h>
48 #include <libintl.h>
49 #include <netdb.h>
50 #include "vntsd.h"
51 #include "chars.h"
52 
53 /*  display domain names in the group */
54 static boolean_t
55 display_domain_name(vntsd_cons_t *consp,  int  *fd)
56 {
57 	char	buf[VNTSD_LINE_LEN];
58 	char	*status;
59 
60 
61 	if (consp->clientpq != NULL) {
62 		status = gettext("connected");
63 	} else if (consp->status & VNTSD_CONS_DELETED) {
64 		status = gettext("removing...");
65 	} else {
66 		status = gettext("online");
67 	}
68 
69 	(void) snprintf(buf, sizeof (buf), "%-20d%-30s%-25s%s",
70 	    consp->cons_no, consp->domain_name, status, vntsd_eol);
71 
72 	return (vntsd_write_fd(*fd, buf, strlen(buf)) != VNTSD_SUCCESS);
73 }
74 
75 /* output connected message to tcp client */
76 static int
77 write_connect_msg(vntsd_client_t *clientp, char *group_name,
78     char *domain_name)
79 {
80 
81 	int	rv = VNTSD_SUCCESS;
82 	char	buf[VNTSD_LINE_LEN];
83 
84 	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN)) !=
85 	    VNTSD_SUCCESS) {
86 		return (rv);
87 	}
88 
89 	(void) snprintf(buf, sizeof (buf),
90 	    gettext("Connecting to console \"%s\" in group \"%s\" ...."),
91 	    domain_name, group_name);
92 
93 	if ((rv = vntsd_write_line(clientp, buf)) != VNTSD_SUCCESS) {
94 		return (rv);
95 	}
96 
97 	if ((rv = vntsd_write_line(clientp,
98 			    gettext("Press ~? for control options .."))) !=
99 	    VNTSD_SUCCESS) {
100 		return (rv);
101 	}
102 
103 	return (VNTSD_SUCCESS);
104 }
105 
106 static int
107 create_write_thread(vntsd_cons_t *consp)
108 {
109 
110 	assert(consp);
111 
112 	/* create write thread for the console */
113 	(void) mutex_lock(&consp->lock);
114 	if (thr_create(NULL, 0, (thr_func_t)vntsd_write_thread,
115 		    (void *)consp, NULL, &consp->wr_tid)) {
116 
117 		DERR(stderr, "t@%d create_rd_wr_thread@%d: "
118 		    "create write thread failed\n",
119 		    thr_self(), consp->cons_no);
120 		(void) close(consp->vcc_fd);
121 		consp->vcc_fd = -1;
122 		(void) mutex_unlock(&consp->lock);
123 
124 		return (VNTSD_ERR_CREATE_WR_THR);
125 	}
126 	(void) mutex_unlock(&consp->lock);
127 	return (VNTSD_SUCCESS);
128 }
129 
130 /* Display all domain consoles in a group. */
131 static int
132 list_all_domains(vntsd_group_t *groupp, vntsd_client_t *clientp)
133 {
134 	char	    vntsd_line[VNTSD_LINE_LEN];
135 	int	    rv = VNTSD_SUCCESS;
136 
137 	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
138 	    != VNTSD_SUCCESS) {
139 		return (rv);
140 	}
141 
142 	/*
143 	 * TRANSLATION_NOTE
144 	 * The following three strings of the form "DOMAIN .." are table
145 	 * headers and should be all uppercase.
146 	 */
147 	(void) snprintf(vntsd_line, sizeof (vntsd_line),
148 	    "%-20s%-30s%-25s",
149 	    gettext("DOMAIN ID"), gettext("DOMAIN NAME"),
150 	    gettext("DOMAIN STATE"));
151 
152 	if ((rv = vntsd_write_line(clientp, vntsd_line)) != VNTSD_SUCCESS) {
153 		return (rv);
154 	}
155 
156 	(void) mutex_lock(&groupp->lock);
157 
158 	if (vntsd_que_find(groupp->conspq, (compare_func_t)display_domain_name,
159 		    &(clientp->sockfd)) != NULL) {
160 		rv = VNTSD_ERR_WRITE_CLIENT;
161 	}
162 
163 	(void) mutex_unlock(&groupp->lock);
164 
165 	return (rv);
166 }
167 
168 /* display help */
169 static int
170 display_help(vntsd_client_t *clientp)
171 {
172 	int	rv = VNTSD_SUCCESS;
173 	char	*bufp;
174 
175 	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
176 		!= VNTSD_SUCCESS) {
177 		return (rv);
178 	}
179 
180 	/*
181 	 * TRANSLATION_NOTE
182 	 * The following three strings of the form ". -- ..." are help
183 	 * messages for single character commands. Do not translate the
184 	 * character before the --.
185 	 */
186 	bufp = gettext("h -- this help");
187 
188 	if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) {
189 		return (rv);
190 	}
191 
192 	bufp = gettext("l -- list of consoles");
193 
194 	if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) {
195 		return (rv);
196 	}
197 
198 	bufp = gettext("q -- quit");
199 
200 	if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) {
201 		return (rv);
202 	}
203 
204 	/*
205 	 * TRANSLATION_NOTE
206 	 * In the following string, "id" is a short mnemonic for
207 	 * "identifier" and both occurrences should be translated.
208 	 */
209 
210 	bufp = gettext("c{id}, n{name} -- connect to a console of domain {id}"
211 		" or domain {name}");
212 
213 	if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) {
214 		return (rv);
215 	}
216 
217 	return (VNTSD_SUCCESS);
218 }
219 
220 /* cons_by_name() - find a console structure according to  a ldom's name */
221 static boolean_t
222 cons_by_name(vntsd_cons_t *consp, char *name)
223 {
224 	if (consp->status & VNTSD_CONS_DELETED) {
225 		return (B_FALSE);
226 	}
227 	return (strcmp(consp->domain_name, name) == 0);
228 }
229 
230 /* name_to_cons_no - convert a ldom's name to its consno */
231 static int
232 name_to_cons_no(vntsd_group_t *groupp, char *name)
233 {
234 	vntsd_cons_t *consp;
235 
236 	consp = (vntsd_cons_t *)vntsd_que_find(groupp->conspq,
237 		    (compare_func_t)cons_by_name, name);
238 
239 	if (consp == NULL) {
240 		return (-1);
241 	}
242 
243 	return (consp->cons_no);
244 }
245 
246 /* select a console to connect */
247 static int
248 select_cons(vntsd_group_t *groupp, vntsd_cons_t **consp,
249     vntsd_client_t *clientp, char c)
250 {
251 	int	    cons_no = -1;
252 	int	    n;
253 	int	    i;
254 	char	    buf[VNTSD_LINE_LEN];
255 	int	    rv;
256 
257 
258 
259 	(void) mutex_lock(&groupp->lock);
260 	if (groupp->num_cons == 0) {
261 		(void) mutex_unlock(&groupp->lock);
262 		/* no console in this group */
263 		return (VNTSD_STATUS_NO_CONS);
264 	}
265 	(void) mutex_unlock(&groupp->lock);
266 
267 
268 	/* c{id} or n{name} */
269 
270 	n = VNTSD_LINE_LEN;
271 
272 	if ((rv = vntsd_read_line(clientp, buf, &n)) != VNTSD_SUCCESS) {
273 		return (rv);
274 	}
275 
276 	/* parse command */
277 	for (i = 0; i < n; i++) {
278 		switch (c) {
279 
280 		case 'c':
281 			/* c{id} or c {id} */
282 			if (isspace(buf[i])) {
283 			    continue;
284 			}
285 
286 			if (!isdigit(buf[i])) {
287 				return (VNTSD_ERR_INVALID_INPUT);
288 			}
289 
290 			cons_no = atoi(buf + i);
291 			break;
292 
293 		case 'n':
294 			/* n{name) or n {name} */
295 			if (isspace(buf[i])) {
296 			    continue;
297 			}
298 
299 			buf[n-1] = 0;
300 			cons_no = name_to_cons_no(groupp, buf+i);
301 			break;
302 
303 		default:
304 			/* should never get here */
305 			return (VNTSD_ERR_INVALID_INPUT);
306 
307 		}
308 
309 		/* got user selection */
310 		break;
311 	}
312 
313 	if (cons_no < 0) {
314 		return (VNTSD_ERR_INVALID_INPUT);
315 	}
316 
317 	/* get selected console */
318 	(void) mutex_lock(&groupp->lock);
319 
320 	*consp = (vntsd_cons_t *)vntsd_que_find(groupp->conspq,
321 		    (compare_func_t)vntsd_cons_by_consno, &cons_no);
322 
323 	if (*consp == NULL) {
324 		/* during console selection, the console has been  deleted */
325 		(void) mutex_unlock(&groupp->lock);
326 
327 		return (VNTSD_ERR_INVALID_INPUT);
328 	}
329 	if ((*consp)->status & VNTSD_CONS_DELETED) {
330 		return (VNTSD_ERR_INVALID_INPUT);
331 	}
332 
333 	(void) mutex_unlock(&groupp->lock);
334 
335 	return (VNTSD_SUCCESS);
336 }
337 
338 /* compare if there is a match console in the gorup */
339 static boolean_t
340 find_cons_in_group(vntsd_cons_t *consp_in_group, vntsd_cons_t *consp)
341 {
342 	if (consp_in_group == consp) {
343 		return (B_TRUE);
344 	} else {
345 		return (B_FALSE);
346 	}
347 }
348 
349 /* connect a client to a console */
350 static int
351 connect_cons(vntsd_cons_t *consp, vntsd_client_t *clientp)
352 {
353 	int	rv, rv1;
354 	vntsd_group_t *groupp;
355 
356 	assert(consp);
357 	groupp = consp->group;
358 	assert(groupp);
359 	assert(clientp);
360 
361 	(void) mutex_lock(&groupp->lock);
362 
363 	/* check if console is valid */
364 	consp = vntsd_que_find(groupp->conspq,
365 	    (compare_func_t)find_cons_in_group, consp);
366 
367 	if (consp == NULL) {
368 		(void) mutex_unlock(&groupp->lock);
369 		return (VNTSD_STATUS_NO_CONS);
370 	}
371 	if (consp->status & VNTSD_CONS_DELETED) {
372 		(void) mutex_unlock(&groupp->lock);
373 		return (VNTSD_STATUS_NO_CONS);
374 	}
375 
376 	(void) mutex_lock(&consp->lock);
377 	(void) mutex_lock(&clientp->lock);
378 
379 
380 	clientp->cons = consp;
381 
382 	/* enable daemon cmd */
383 	clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD;
384 
385 	if (consp->clientpq == NULL) {
386 		/* first connect to console - a writer */
387 		assert(consp->vcc_fd == -1);
388 		/* open vcc */
389 		consp->vcc_fd = vntsd_open_vcc(consp->dev_name, consp->cons_no);
390 		if (consp->vcc_fd < 0) {
391 			(void) mutex_unlock(&clientp->lock);
392 			(void) mutex_unlock(&consp->lock);
393 			(void) mutex_unlock(&groupp->lock);
394 			assert(consp->group);
395 			return (vntsd_vcc_err(consp));
396 		}
397 	}
398 
399 	(void) mutex_unlock(&clientp->lock);
400 
401 	/*
402 	 * move the client from group's no console selected queue
403 	 * to cons queue
404 	 */
405 
406 	rv = vntsd_que_rm(&groupp->no_cons_clientpq, clientp);
407 	assert(rv == VNTSD_SUCCESS);
408 
409 	rv = vntsd_que_append(&consp->clientpq, clientp);
410 	(void) mutex_unlock(&groupp->lock);
411 
412 	if (rv != VNTSD_SUCCESS) {
413 		if (consp->clientpq->handle == clientp) {
414 			/* writer */
415 			(void) close(consp->vcc_fd);
416 			consp->vcc_fd = -1;
417 		}
418 
419 		(void) mutex_unlock(&consp->lock);
420 		return (rv);
421 	}
422 
423 	(void) mutex_unlock(&consp->lock);
424 
425 	if (consp->clientpq->handle == clientp) {
426 		/* create a write thread */
427 		rv = create_write_thread(consp);
428 		if (rv != VNTSD_SUCCESS) {
429 			return (rv);
430 		}
431 	}
432 
433 	/* write connecting message */
434 	if ((rv = write_connect_msg(clientp, consp->group->group_name,
435 	    consp->domain_name)) != VNTSD_SUCCESS) {
436 			return (rv);
437 	}
438 
439 	/* process input from client */
440 	rv = vntsd_read(clientp);
441 
442 	/* client disconnected from the console */
443 	(void) mutex_lock(&groupp->lock);
444 
445 	/* remove client from console queue */
446 	(void) mutex_lock(&consp->lock);
447 	rv1 = vntsd_que_rm(&consp->clientpq, clientp);
448 	assert(rv1 == VNTSD_SUCCESS);
449 
450 	/* append client to group's no console selected  queue */
451 	rv1 = vntsd_que_append(&groupp->no_cons_clientpq, clientp);
452 	(void) mutex_unlock(&groupp->lock);
453 
454 	if (consp->clientpq == NULL) {
455 		/* clean up console since there is no client connected to it */
456 		assert(consp->vcc_fd != -1);
457 
458 		/* close vcc port */
459 		(void) close(consp->vcc_fd);
460 		consp->vcc_fd = -1;
461 
462 		/* force write thread to exit */
463 		assert(consp->wr_tid != (thread_t)-1);
464 		(void) thr_kill(consp->wr_tid, SIGUSR1);
465 		(void) mutex_unlock(&consp->lock);
466 		(void) thr_join(consp->wr_tid, NULL, NULL);
467 		(void) mutex_lock(&consp->lock);
468 	}
469 
470 	if (consp->status & VNTSD_CONS_SIG_WAIT) {
471 		/* console is waiting for client to disconnect */
472 		(void) cond_signal(&consp->cvp);
473 	}
474 
475 	(void) mutex_unlock(&consp->lock);
476 
477 	return (rv1 == VNTSD_SUCCESS ? rv : rv1);
478 
479 }
480 
481 /* read command line input */
482 static int
483 read_cmd(vntsd_client_t *clientp, char *prompt, char *cmd)
484 {
485 	int		rv;
486 
487 	/* disable daemon special command */
488 	(void) mutex_lock(&clientp->lock);
489 	clientp->status |= VNTSD_CLIENT_DISABLE_DAEMON_CMD;
490 	(void) mutex_unlock(&clientp->lock);
491 
492 	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
493 	    != VNTSD_SUCCESS) {
494 		return (rv);
495 	}
496 
497 	if ((rv = vntsd_write_client(clientp, prompt, strlen(prompt)))
498 		!= VNTSD_SUCCESS) {
499 		return (rv);
500 	}
501 
502 	if ((rv = vntsd_read_data(clientp, cmd)) != VNTSD_SUCCESS) {
503 		return (rv);
504 	}
505 	if (*cmd == BS) {
506 		return (VNTSD_SUCCESS);
507 	}
508 
509 	rv = vntsd_write_client(clientp, cmd, 1);
510 
511 	*cmd = tolower(*cmd);
512 
513 	return (rv);
514 }
515 
516 /* reset client for selecting a console in the group */
517 static void
518 client_init(vntsd_client_t *clientp)
519 {
520 	(void) mutex_lock(&clientp->lock);
521 	clientp->cons = NULL;
522 	clientp->status = 0;
523 	(void) mutex_unlock(&clientp->lock);
524 }
525 
526 /* clean up client and exit the thread */
527 static void
528 client_fini(vntsd_group_t *groupp, vntsd_client_t *clientp)
529 {
530 
531 	assert(groupp);
532 	assert(clientp);
533 
534 	/* disconnct client from tcp port */
535 	assert(clientp->sockfd != -1);
536 	(void) close(clientp->sockfd);
537 
538 	(void) mutex_lock(&groupp->lock);
539 	(void) vntsd_que_rm(&groupp->no_cons_clientpq, clientp);
540 
541 	if ((groupp->no_cons_clientpq == NULL) &&
542 	    (groupp->status & VNTSD_GROUP_SIG_WAIT)) {
543 		/* group is waiting to be deleted */
544 		groupp->status &= ~VNTSD_GROUP_SIG_WAIT;
545 		(void) cond_signal(&groupp->cvp);
546 	}
547 	(void) mutex_unlock(&groupp->lock);
548 
549 	(void) mutex_destroy(&clientp->lock);
550 	free(clientp);
551 
552 	thr_exit(0);
553 }
554 
555 /*  check client's status. exit if client quits or fatal errors */
556 static void
557 console_chk_status(vntsd_group_t *groupp, vntsd_client_t *clientp, int status)
558 {
559 	char    err_msg[VNTSD_LINE_LEN];
560 
561 	D1(stderr, "t@%d console_chk_status() status=%d "
562 	    "client status=%x num consoles=%d \n",
563 	    thr_self(), status, clientp->status, groupp->num_cons);
564 
565 	(void) snprintf(err_msg, VNTSD_LINE_LEN, "console_chk_status client%d"
566 	    " num_cos=%d", clientp->sockfd, groupp->num_cons);
567 
568 	if (groupp->num_cons == 0) {
569 		/* no more console in the group */
570 		client_fini(groupp, clientp);
571 	}
572 
573 	if (status == VNTSD_STATUS_INTR) {
574 		/* reason for signal? */
575 		status = vntsd_cons_chk_intr(clientp);
576 	}
577 
578 	switch (status) {
579 
580 	case VNTSD_STATUS_CLIENT_QUIT:
581 		client_fini(groupp, clientp);
582 		return;
583 
584 	case VNTSD_STATUS_RESELECT_CONS:
585 		assert(clientp->cons);
586 		if ((groupp->num_cons == 1) &&
587 		    (groupp->conspq->handle == clientp->cons)) {
588 			/* no other selection available */
589 			client_fini(groupp, clientp);
590 		} else {
591 			client_init(clientp);
592 		}
593 		return;
594 
595 	case VNTSD_STATUS_VCC_IO_ERR:
596 		if ((clientp->status & VNTSD_CLIENT_CONS_DELETED) == 0) {
597 			/* check if console was deleted  */
598 			status = vntsd_vcc_err(clientp->cons);
599 		}
600 
601 		if (status != VNTSD_STATUS_CONTINUE) {
602 			/* console was deleted */
603 			if (groupp->num_cons == 1) {
604 				client_fini(groupp, clientp);
605 			}
606 		}
607 
608 		/* console is ok */
609 		client_init(clientp);
610 		return;
611 
612 	case VNTSD_STATUS_MOV_CONS_FORWARD:
613 	case VNTSD_STATUS_MOV_CONS_BACKWARD:
614 		if (groupp->num_cons == 1) {
615 			/* same console */
616 			return;
617 		}
618 
619 		/* get selected console */
620 		(void) mutex_lock(&(clientp->cons->group->lock));
621 		clientp->cons = vntsd_que_pos(clientp->cons->group->conspq,
622 		    clientp->cons,
623 		    (status == VNTSD_STATUS_MOV_CONS_FORWARD)?(1):(-1));
624 		(void) mutex_unlock(&(clientp->cons->group->lock));
625 		return;
626 
627 	case VNTSD_SUCCESS:
628 	case VNTSD_STATUS_CONTINUE:
629 	case VNTSD_STATUS_NO_CONS:
630 		client_init(clientp);
631 		return;
632 
633 	case VNTSD_ERR_INVALID_INPUT:
634 		return;
635 
636 	default:
637 		/* fatal error */
638 		vntsd_log(status, err_msg);
639 		client_fini(groupp, clientp);
640 		return;
641 	}
642 }
643 
644 /* console thread */
645 void *
646 vntsd_console_thread(vntsd_thr_arg_t *argp)
647 {
648 	vntsd_group_t	    *groupp;
649 	vntsd_cons_t	    *consp;
650 	vntsd_client_t	    *clientp;
651 
652 	char		    buf[MAXHOSTNAMELEN];
653 	char		    prompt[72];
654 	char		    cmd;
655 	int		    rv = VNTSD_SUCCESS;
656 	int		    num_cons;
657 
658 
659 	groupp = (vntsd_group_t *)argp->handle;
660 	clientp = (vntsd_client_t *)argp->arg;
661 
662 	assert(groupp);
663 	assert(clientp);
664 
665 	/* check if group is removed */
666 
667 	D1(stderr, "t@%d get_client_sel@%lld:client@%d\n", thr_self(),
668 	    groupp->tcp_port, clientp->sockfd);
669 
670 	bzero(buf, MAXHOSTNAMELEN);
671 
672 	/* host name */
673 	if (gethostname(buf, MAXHOSTNAMELEN)) {
674 		vntsd_log(VNTSD_STATUS_NO_HOST_NAME, "vntsd_console_thread()");
675 		(void) snprintf(buf, sizeof (buf), "unkown host");
676 	}
677 
678 	if (snprintf(prompt, sizeof (prompt),
679 		    "%s-vnts-%s: h, l, c{id}, n{name}, q:",
680 	    buf, groupp->group_name) >= sizeof (prompt)) {
681 		/* long prompt doesn't fit, use short one */
682 		(void) snprintf(prompt, sizeof (prompt),
683 				"vnts: h, l, c{id}, n{name}, q:");
684 	}
685 
686 
687 	for (;;) {
688 		cmd = ' ';
689 		D1(stderr, "t@%d console_thread()@%lld:client@%d\n", thr_self(),
690 		    groupp->tcp_port, clientp->sockfd);
691 
692 		num_cons = vntsd_chk_group_total_cons(groupp);
693 
694 		if ((num_cons > 1) && (clientp->cons == NULL)) {
695 			/*  console to connect to */
696 			rv = read_cmd(clientp, prompt, &cmd);
697 			/* check error and may exit */
698 			console_chk_status(groupp, clientp, rv);
699 		}
700 
701 		switch (cmd) {
702 
703 		case 'l':
704 
705 			/* list domain names */
706 			rv = list_all_domains(groupp, clientp);
707 			break;
708 
709 
710 		case 'q':
711 
712 			rv = VNTSD_STATUS_CLIENT_QUIT;
713 			break;
714 
715 		case ' ':
716 
717 			if (clientp->cons == NULL) {
718 				if (num_cons == 1) {
719 					/* by pass selecting console */
720 					consp = (vntsd_cons_t *)
721 					    (groupp->conspq->handle);
722 				} else {
723 					continue;
724 				}
725 
726 			} else {
727 				consp = clientp->cons;
728 			}
729 
730 			/* connect to console */
731 			rv = connect_cons(consp, clientp);
732 
733 			break;
734 
735 		case 'c':
736 		case 'n':
737 			/* select console */
738 			if (clientp->cons == NULL) {
739 				rv = select_cons(groupp, &consp, clientp, cmd);
740 				if (rv == VNTSD_ERR_INVALID_INPUT) {
741 					rv = display_help(clientp);
742 					break;
743 				}
744 			} else {
745 				consp = clientp->cons;
746 			}
747 			assert(consp);
748 
749 			/* connect to console */
750 			rv = connect_cons(consp, clientp);
751 			D1(stderr, "t@%d console_thread()"
752 			    "connect_cons returns %d\n",
753 			    thr_self(), rv);
754 			break;
755 
756 		case 'h':
757 		default:
758 			rv = display_help(clientp);
759 			break;
760 
761 		}
762 
763 		/* check error and may  exit */
764 		console_chk_status(groupp, clientp, rv);
765 	}
766 
767 	/*NOTREACHED*/
768 	return (NULL);
769 }
770