xref: /illumos-gate/usr/src/cmd/vntsd/console.c (revision 7636cb21)
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 && consp->vcc_fd == -1) {
386 
387 		/*
388 		 *  the first connection to a console - a writer
389 		 *  and the console has not opened.
390 		 */
391 		consp->vcc_fd = vntsd_open_vcc(consp->dev_name, consp->cons_no);
392 		if (consp->vcc_fd < 0) {
393 			(void) mutex_unlock(&clientp->lock);
394 			(void) mutex_unlock(&consp->lock);
395 			(void) mutex_unlock(&groupp->lock);
396 			assert(consp->group);
397 			return (vntsd_vcc_err(consp));
398 		}
399 	}
400 
401 	(void) mutex_unlock(&clientp->lock);
402 
403 	/*
404 	 * move the client from group's no console selected queue
405 	 * to cons queue
406 	 */
407 
408 	rv = vntsd_que_rm(&groupp->no_cons_clientpq, clientp);
409 	assert(rv == VNTSD_SUCCESS);
410 
411 	rv = vntsd_que_append(&consp->clientpq, clientp);
412 	(void) mutex_unlock(&groupp->lock);
413 
414 	if (rv != VNTSD_SUCCESS) {
415 		if (consp->clientpq->handle == clientp) {
416 			/* writer */
417 			(void) close(consp->vcc_fd);
418 			consp->vcc_fd = -1;
419 		}
420 
421 		(void) mutex_unlock(&consp->lock);
422 		return (rv);
423 	}
424 
425 	(void) mutex_unlock(&consp->lock);
426 
427 	if (consp->clientpq->handle == clientp) {
428 		/* create a write thread */
429 		rv = create_write_thread(consp);
430 		if (rv != VNTSD_SUCCESS) {
431 			return (rv);
432 		}
433 	}
434 
435 	/* write connecting message */
436 	if ((rv = write_connect_msg(clientp, consp->group->group_name,
437 	    consp->domain_name)) != VNTSD_SUCCESS) {
438 			return (rv);
439 	}
440 
441 	/* process input from client */
442 	rv = vntsd_read(clientp);
443 
444 	/* client disconnected from the console */
445 	(void) mutex_lock(&groupp->lock);
446 
447 	/* remove client from console queue */
448 	(void) mutex_lock(&consp->lock);
449 	rv1 = vntsd_que_rm(&consp->clientpq, clientp);
450 	assert(rv1 == VNTSD_SUCCESS);
451 
452 	/* append client to group's no console selected  queue */
453 	rv1 = vntsd_que_append(&groupp->no_cons_clientpq, clientp);
454 	(void) mutex_unlock(&groupp->lock);
455 
456 	if (consp->clientpq == NULL) {
457 		/* clean up console since there is no client connected to it */
458 		assert(consp->vcc_fd != -1);
459 
460 		/* force write thread to exit */
461 		assert(consp->wr_tid != (thread_t)-1);
462 		(void) thr_kill(consp->wr_tid, SIGUSR1);
463 		(void) mutex_unlock(&consp->lock);
464 		(void) thr_join(consp->wr_tid, NULL, NULL);
465 		(void) mutex_lock(&consp->lock);
466 	}
467 
468 	if (consp->status & VNTSD_CONS_SIG_WAIT) {
469 		/* console is waiting for client to disconnect */
470 		(void) cond_signal(&consp->cvp);
471 	}
472 
473 	(void) mutex_unlock(&consp->lock);
474 
475 	return (rv1 == VNTSD_SUCCESS ? rv : rv1);
476 
477 }
478 
479 /* read command line input */
480 static int
481 read_cmd(vntsd_client_t *clientp, char *prompt, char *cmd)
482 {
483 	int		rv;
484 
485 	/* disable daemon special command */
486 	(void) mutex_lock(&clientp->lock);
487 	clientp->status |= VNTSD_CLIENT_DISABLE_DAEMON_CMD;
488 	(void) mutex_unlock(&clientp->lock);
489 
490 	if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN))
491 	    != VNTSD_SUCCESS) {
492 		return (rv);
493 	}
494 
495 	if ((rv = vntsd_write_client(clientp, prompt, strlen(prompt)))
496 		!= VNTSD_SUCCESS) {
497 		return (rv);
498 	}
499 
500 	if ((rv = vntsd_read_data(clientp, cmd)) != VNTSD_SUCCESS) {
501 		return (rv);
502 	}
503 	if (*cmd == BS) {
504 		return (VNTSD_SUCCESS);
505 	}
506 
507 	rv = vntsd_write_client(clientp, cmd, 1);
508 
509 	*cmd = tolower(*cmd);
510 
511 	return (rv);
512 }
513 
514 /* reset client for selecting a console in the group */
515 static void
516 client_init(vntsd_client_t *clientp)
517 {
518 	(void) mutex_lock(&clientp->lock);
519 	clientp->cons = NULL;
520 	clientp->status = 0;
521 	(void) mutex_unlock(&clientp->lock);
522 }
523 /* is there any connection to a given console? */
524 static boolean_t
525 is_client_que_empty(vntsd_cons_t *consp)
526 {
527 	boolean_t  has_client = B_FALSE;
528 
529 	(void) mutex_lock(&consp->lock);
530 
531 	if (consp->clientpq != NULL)
532 		has_client = B_TRUE;
533 
534 	(void) mutex_unlock(&consp->lock);
535 
536 	return (has_client);
537 }
538 
539 /*
540  * close one opened console.
541  * This function is passed to vntsd_que_walk to close one console.
542  * The function returns B_FALSE so that vntsd_que_walk will
543  * continue to apply the function to all consoles in the group.
544  */
545 static boolean_t
546 close_one_vcc_fd(vntsd_cons_t *consp)
547 {
548 	(void) mutex_lock(&consp->lock);
549 
550 	if (consp->vcc_fd != -1) {
551 		(void) close(consp->vcc_fd);
552 		consp->vcc_fd = -1;
553 	}
554 
555 	(void) mutex_unlock(&consp->lock);
556 
557 	return (B_FALSE);
558 }
559 
560 
561 /* clean up client and exit the thread */
562 static void
563 client_fini(vntsd_group_t *groupp, vntsd_client_t *clientp)
564 {
565 
566 	assert(groupp);
567 	assert(clientp);
568 
569 	/* disconnct client from tcp port */
570 	assert(clientp->sockfd != -1);
571 	(void) close(clientp->sockfd);
572 
573 	(void) mutex_lock(&groupp->lock);
574 
575 	/*
576 	 * close all consoles in the group if the client is the
577 	 * last one connected to the group
578 	 */
579 	if (vntsd_que_walk(groupp->conspq, (el_func_t)is_client_que_empty) ==
580 	    VNTSD_SUCCESS) {
581 		(void) vntsd_que_walk(groupp->conspq,
582 		    (el_func_t)close_one_vcc_fd);
583 	}
584 
585 
586 	(void) vntsd_que_rm(&groupp->no_cons_clientpq, clientp);
587 
588 	if ((groupp->no_cons_clientpq == NULL) &&
589 	    (groupp->status & VNTSD_GROUP_SIG_WAIT)) {
590 		/* group is waiting to be deleted */
591 		groupp->status &= ~VNTSD_GROUP_SIG_WAIT;
592 		(void) cond_signal(&groupp->cvp);
593 	}
594 	(void) mutex_unlock(&groupp->lock);
595 
596 	(void) mutex_destroy(&clientp->lock);
597 	free(clientp);
598 
599 	thr_exit(0);
600 }
601 
602 /*  check client's status. exit if client quits or fatal errors */
603 static void
604 console_chk_status(vntsd_group_t *groupp, vntsd_client_t *clientp, int status)
605 {
606 	char    err_msg[VNTSD_LINE_LEN];
607 
608 	D1(stderr, "t@%d console_chk_status() status=%d "
609 	    "client status=%x num consoles=%d \n",
610 	    thr_self(), status, clientp->status, groupp->num_cons);
611 
612 	(void) snprintf(err_msg, VNTSD_LINE_LEN, "console_chk_status client%d"
613 	    " num_cos=%d", clientp->sockfd, groupp->num_cons);
614 
615 	if (groupp->num_cons == 0) {
616 		/* no more console in the group */
617 		client_fini(groupp, clientp);
618 	}
619 
620 	if (status == VNTSD_STATUS_INTR) {
621 		/* reason for signal? */
622 		status = vntsd_cons_chk_intr(clientp);
623 	}
624 
625 	switch (status) {
626 
627 	case VNTSD_STATUS_CLIENT_QUIT:
628 		client_fini(groupp, clientp);
629 		return;
630 
631 	case VNTSD_STATUS_RESELECT_CONS:
632 		assert(clientp->cons);
633 		if ((groupp->num_cons == 1) &&
634 		    (groupp->conspq->handle == clientp->cons)) {
635 			/* no other selection available */
636 			client_fini(groupp, clientp);
637 		} else {
638 			client_init(clientp);
639 		}
640 		return;
641 
642 	case VNTSD_STATUS_VCC_IO_ERR:
643 		if ((clientp->status & VNTSD_CLIENT_CONS_DELETED) == 0) {
644 			/* check if console was deleted  */
645 			status = vntsd_vcc_err(clientp->cons);
646 		}
647 
648 		if (status != VNTSD_STATUS_CONTINUE) {
649 			/* console was deleted */
650 			if (groupp->num_cons == 1) {
651 				client_fini(groupp, clientp);
652 			}
653 		}
654 
655 		/* console is ok */
656 		client_init(clientp);
657 		return;
658 
659 	case VNTSD_STATUS_MOV_CONS_FORWARD:
660 	case VNTSD_STATUS_MOV_CONS_BACKWARD:
661 		if (groupp->num_cons == 1) {
662 			/* same console */
663 			return;
664 		}
665 
666 		/* get selected console */
667 		(void) mutex_lock(&(clientp->cons->group->lock));
668 		clientp->cons = vntsd_que_pos(clientp->cons->group->conspq,
669 		    clientp->cons,
670 		    (status == VNTSD_STATUS_MOV_CONS_FORWARD)?(1):(-1));
671 		(void) mutex_unlock(&(clientp->cons->group->lock));
672 		return;
673 
674 	case VNTSD_SUCCESS:
675 	case VNTSD_STATUS_CONTINUE:
676 	case VNTSD_STATUS_NO_CONS:
677 		client_init(clientp);
678 		return;
679 
680 	case VNTSD_ERR_INVALID_INPUT:
681 		return;
682 
683 	default:
684 		/* fatal error */
685 		vntsd_log(status, err_msg);
686 		client_fini(groupp, clientp);
687 		return;
688 	}
689 }
690 
691 /* console thread */
692 void *
693 vntsd_console_thread(vntsd_thr_arg_t *argp)
694 {
695 	vntsd_group_t	    *groupp;
696 	vntsd_cons_t	    *consp;
697 	vntsd_client_t	    *clientp;
698 
699 	char		    buf[MAXHOSTNAMELEN];
700 	char		    prompt[72];
701 	char		    cmd;
702 	int		    rv = VNTSD_SUCCESS;
703 	int		    num_cons;
704 
705 
706 	groupp = (vntsd_group_t *)argp->handle;
707 	clientp = (vntsd_client_t *)argp->arg;
708 
709 	assert(groupp);
710 	assert(clientp);
711 
712 	/* check if group is removed */
713 
714 	D1(stderr, "t@%d get_client_sel@%lld:client@%d\n", thr_self(),
715 	    groupp->tcp_port, clientp->sockfd);
716 
717 	bzero(buf, MAXHOSTNAMELEN);
718 
719 	/* host name */
720 	if (gethostname(buf, MAXHOSTNAMELEN)) {
721 		vntsd_log(VNTSD_STATUS_NO_HOST_NAME, "vntsd_console_thread()");
722 		(void) snprintf(buf, sizeof (buf), "unkown host");
723 	}
724 
725 	if (snprintf(prompt, sizeof (prompt),
726 		    "%s-vnts-%s: h, l, c{id}, n{name}, q:",
727 	    buf, groupp->group_name) >= sizeof (prompt)) {
728 		/* long prompt doesn't fit, use short one */
729 		(void) snprintf(prompt, sizeof (prompt),
730 				"vnts: h, l, c{id}, n{name}, q:");
731 	}
732 
733 
734 	for (;;) {
735 		cmd = ' ';
736 		D1(stderr, "t@%d console_thread()@%lld:client@%d\n", thr_self(),
737 		    groupp->tcp_port, clientp->sockfd);
738 
739 		num_cons = vntsd_chk_group_total_cons(groupp);
740 
741 		if ((num_cons > 1) && (clientp->cons == NULL)) {
742 			/*  console to connect to */
743 			rv = read_cmd(clientp, prompt, &cmd);
744 			/* check error and may exit */
745 			console_chk_status(groupp, clientp, rv);
746 		}
747 
748 		switch (cmd) {
749 
750 		case 'l':
751 
752 			/* list domain names */
753 			rv = list_all_domains(groupp, clientp);
754 			break;
755 
756 
757 		case 'q':
758 
759 			rv = VNTSD_STATUS_CLIENT_QUIT;
760 			break;
761 
762 		case ' ':
763 
764 			if (clientp->cons == NULL) {
765 				if (num_cons == 1) {
766 					/* by pass selecting console */
767 					consp = (vntsd_cons_t *)
768 					    (groupp->conspq->handle);
769 				} else {
770 					continue;
771 				}
772 
773 			} else {
774 				consp = clientp->cons;
775 			}
776 
777 			/* connect to console */
778 			rv = connect_cons(consp, clientp);
779 
780 			break;
781 
782 		case 'c':
783 		case 'n':
784 			/* select console */
785 			if (clientp->cons == NULL) {
786 				rv = select_cons(groupp, &consp, clientp, cmd);
787 				if (rv == VNTSD_ERR_INVALID_INPUT) {
788 					rv = display_help(clientp);
789 					break;
790 				}
791 			} else {
792 				consp = clientp->cons;
793 			}
794 			assert(consp);
795 
796 			/* connect to console */
797 			rv = connect_cons(consp, clientp);
798 			D1(stderr, "t@%d console_thread()"
799 			    "connect_cons returns %d\n",
800 			    thr_self(), rv);
801 			break;
802 
803 		case 'h':
804 		default:
805 			rv = display_help(clientp);
806 			break;
807 
808 		}
809 
810 		/* check error and may  exit */
811 		console_chk_status(groupp, clientp, rv);
812 	}
813 
814 	/*NOTREACHED*/
815 	return (NULL);
816 }
817