/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Listen thread creates a console thread whenever there is a tcp client * made a conection to its port. In the console thread, if there are * multiple consoles in the group, client will be asked for a console selection. * a write thread for a console is created when first client connects to a * selected console and console thread becomes read thread for the client. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vntsd.h" #include "chars.h" /* display domain names in the group */ static boolean_t display_domain_name(vntsd_cons_t *consp, int *fd) { char buf[VNTSD_LINE_LEN]; char *status; if (consp->clientpq != NULL) { status = gettext("connected"); } else if (consp->status & VNTSD_CONS_DELETED) { status = gettext("removing..."); } else { status = gettext("online"); } (void) snprintf(buf, sizeof (buf), "%-20d%-30s%-25s%s", consp->cons_no, consp->domain_name, status, vntsd_eol); return (vntsd_write_fd(*fd, buf, strlen(buf)) != VNTSD_SUCCESS); } /* output connected message to tcp client */ static int write_connect_msg(vntsd_client_t *clientp, char *group_name, char *domain_name) { int rv = VNTSD_SUCCESS; char buf[VNTSD_LINE_LEN]; if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN)) != VNTSD_SUCCESS) { return (rv); } (void) snprintf(buf, sizeof (buf), gettext("Connecting to console \"%s\" in group \"%s\" ...."), domain_name, group_name); if ((rv = vntsd_write_line(clientp, buf)) != VNTSD_SUCCESS) { return (rv); } if ((rv = vntsd_write_line(clientp, gettext("Press ~? for control options .."))) != VNTSD_SUCCESS) { return (rv); } return (VNTSD_SUCCESS); } static int create_write_thread(vntsd_cons_t *consp) { assert(consp); /* create write thread for the console */ (void) mutex_lock(&consp->lock); if (thr_create(NULL, 0, (thr_func_t)vntsd_write_thread, (void *)consp, NULL, &consp->wr_tid)) { DERR(stderr, "t@%d create_rd_wr_thread@%d: " "create write thread failed\n", thr_self(), consp->cons_no); (void) close(consp->vcc_fd); consp->vcc_fd = -1; (void) mutex_unlock(&consp->lock); return (VNTSD_ERR_CREATE_WR_THR); } (void) mutex_unlock(&consp->lock); return (VNTSD_SUCCESS); } /* Display all domain consoles in a group. */ static int list_all_domains(vntsd_group_t *groupp, vntsd_client_t *clientp) { char vntsd_line[VNTSD_LINE_LEN]; int rv = VNTSD_SUCCESS; if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN)) != VNTSD_SUCCESS) { return (rv); } /* * TRANSLATION_NOTE * The following three strings of the form "DOMAIN .." are table * headers and should be all uppercase. */ (void) snprintf(vntsd_line, sizeof (vntsd_line), "%-20s%-30s%-25s", gettext("DOMAIN ID"), gettext("DOMAIN NAME"), gettext("DOMAIN STATE")); if ((rv = vntsd_write_line(clientp, vntsd_line)) != VNTSD_SUCCESS) { return (rv); } (void) mutex_lock(&groupp->lock); if (vntsd_que_find(groupp->conspq, (compare_func_t)display_domain_name, &(clientp->sockfd)) != NULL) { rv = VNTSD_ERR_WRITE_CLIENT; } (void) mutex_unlock(&groupp->lock); return (rv); } /* display help */ static int display_help(vntsd_client_t *clientp) { int rv = VNTSD_SUCCESS; char *bufp; if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN)) != VNTSD_SUCCESS) { return (rv); } /* * TRANSLATION_NOTE * The following three strings of the form ". -- ..." are help * messages for single character commands. Do not translate the * character before the --. */ bufp = gettext("h -- this help"); if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) { return (rv); } bufp = gettext("l -- list of consoles"); if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) { return (rv); } bufp = gettext("q -- quit"); if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) { return (rv); } /* * TRANSLATION_NOTE * In the following string, "id" is a short mnemonic for * "identifier" and both occurrences should be translated. */ bufp = gettext("c{id}, n{name} -- connect to a console of domain {id}" " or domain {name}"); if ((rv = vntsd_write_line(clientp, bufp)) != VNTSD_SUCCESS) { return (rv); } return (VNTSD_SUCCESS); } /* cons_by_name() - find a console structure according to a ldom's name */ static boolean_t cons_by_name(vntsd_cons_t *consp, char *name) { if (consp->status & VNTSD_CONS_DELETED) { return (B_FALSE); } return (strcmp(consp->domain_name, name) == 0); } /* name_to_cons_no - convert a ldom's name to its consno */ static int name_to_cons_no(vntsd_group_t *groupp, char *name) { vntsd_cons_t *consp; consp = (vntsd_cons_t *)vntsd_que_find(groupp->conspq, (compare_func_t)cons_by_name, name); if (consp == NULL) { return (-1); } return (consp->cons_no); } /* select a console to connect */ static int select_cons(vntsd_group_t *groupp, vntsd_cons_t **consp, vntsd_client_t *clientp, char c) { int cons_no = -1; int n; int i; char buf[VNTSD_LINE_LEN]; int rv; (void) mutex_lock(&groupp->lock); if (groupp->num_cons == 0) { (void) mutex_unlock(&groupp->lock); /* no console in this group */ return (VNTSD_STATUS_NO_CONS); } (void) mutex_unlock(&groupp->lock); /* c{id} or n{name} */ n = VNTSD_LINE_LEN; if ((rv = vntsd_read_line(clientp, buf, &n)) != VNTSD_SUCCESS) { return (rv); } /* parse command */ for (i = 0; i < n; i++) { switch (c) { case 'c': /* c{id} or c {id} */ if (isspace(buf[i])) { continue; } if (!isdigit(buf[i])) { return (VNTSD_ERR_INVALID_INPUT); } cons_no = atoi(buf + i); break; case 'n': /* n{name) or n {name} */ if (isspace(buf[i])) { continue; } buf[n-1] = 0; cons_no = name_to_cons_no(groupp, buf+i); break; default: /* should never get here */ return (VNTSD_ERR_INVALID_INPUT); } /* got user selection */ break; } if (cons_no < 0) { return (VNTSD_ERR_INVALID_INPUT); } /* get selected console */ (void) mutex_lock(&groupp->lock); *consp = (vntsd_cons_t *)vntsd_que_find(groupp->conspq, (compare_func_t)vntsd_cons_by_consno, &cons_no); if (*consp == NULL) { /* during console selection, the console has been deleted */ (void) mutex_unlock(&groupp->lock); return (VNTSD_ERR_INVALID_INPUT); } if ((*consp)->status & VNTSD_CONS_DELETED) { return (VNTSD_ERR_INVALID_INPUT); } (void) mutex_unlock(&groupp->lock); return (VNTSD_SUCCESS); } /* compare if there is a match console in the gorup */ static boolean_t find_cons_in_group(vntsd_cons_t *consp_in_group, vntsd_cons_t *consp) { if (consp_in_group == consp) { return (B_TRUE); } else { return (B_FALSE); } } /* connect a client to a console */ static int connect_cons(vntsd_cons_t *consp, vntsd_client_t *clientp) { int rv, rv1; vntsd_group_t *groupp; assert(consp); groupp = consp->group; assert(groupp); assert(clientp); (void) mutex_lock(&groupp->lock); /* check if console is valid */ consp = vntsd_que_find(groupp->conspq, (compare_func_t)find_cons_in_group, consp); if (consp == NULL) { (void) mutex_unlock(&groupp->lock); return (VNTSD_STATUS_NO_CONS); } if (consp->status & VNTSD_CONS_DELETED) { (void) mutex_unlock(&groupp->lock); return (VNTSD_STATUS_NO_CONS); } (void) mutex_lock(&consp->lock); (void) mutex_lock(&clientp->lock); clientp->cons = consp; /* enable daemon cmd */ clientp->status &= ~VNTSD_CLIENT_DISABLE_DAEMON_CMD; if (consp->clientpq == NULL && consp->vcc_fd == -1) { /* * the first connection to a console - a writer * and the console has not opened. */ consp->vcc_fd = vntsd_open_vcc(consp->dev_name, consp->cons_no); if (consp->vcc_fd < 0) { (void) mutex_unlock(&clientp->lock); (void) mutex_unlock(&consp->lock); (void) mutex_unlock(&groupp->lock); assert(consp->group); return (vntsd_vcc_err(consp)); } } (void) mutex_unlock(&clientp->lock); /* * move the client from group's no console selected queue * to cons queue */ rv = vntsd_que_rm(&groupp->no_cons_clientpq, clientp); assert(rv == VNTSD_SUCCESS); rv = vntsd_que_append(&consp->clientpq, clientp); (void) mutex_unlock(&groupp->lock); if (rv != VNTSD_SUCCESS) { if (consp->clientpq->handle == clientp) { /* writer */ (void) close(consp->vcc_fd); consp->vcc_fd = -1; } (void) mutex_unlock(&consp->lock); return (rv); } (void) mutex_unlock(&consp->lock); if (consp->clientpq->handle == clientp) { /* create a write thread */ rv = create_write_thread(consp); if (rv != VNTSD_SUCCESS) { return (rv); } } /* write connecting message */ if ((rv = write_connect_msg(clientp, consp->group->group_name, consp->domain_name)) != VNTSD_SUCCESS) { return (rv); } /* process input from client */ rv = vntsd_read(clientp); /* client disconnected from the console */ (void) mutex_lock(&groupp->lock); /* remove client from console queue */ (void) mutex_lock(&consp->lock); rv1 = vntsd_que_rm(&consp->clientpq, clientp); assert(rv1 == VNTSD_SUCCESS); /* append client to group's no console selected queue */ rv1 = vntsd_que_append(&groupp->no_cons_clientpq, clientp); (void) mutex_unlock(&groupp->lock); if (consp->clientpq == NULL) { /* clean up console since there is no client connected to it */ assert(consp->vcc_fd != -1); /* force write thread to exit */ assert(consp->wr_tid != (thread_t)-1); (void) thr_kill(consp->wr_tid, SIGUSR1); (void) mutex_unlock(&consp->lock); (void) thr_join(consp->wr_tid, NULL, NULL); (void) mutex_lock(&consp->lock); } if (consp->status & VNTSD_CONS_SIG_WAIT) { /* console is waiting for client to disconnect */ (void) cond_signal(&consp->cvp); } (void) mutex_unlock(&consp->lock); return (rv1 == VNTSD_SUCCESS ? rv : rv1); } /* read command line input */ static int read_cmd(vntsd_client_t *clientp, char *prompt, char *cmd) { int rv; /* disable daemon special command */ (void) mutex_lock(&clientp->lock); clientp->status |= VNTSD_CLIENT_DISABLE_DAEMON_CMD; (void) mutex_unlock(&clientp->lock); if ((rv = vntsd_write_client(clientp, vntsd_eol, VNTSD_EOL_LEN)) != VNTSD_SUCCESS) { return (rv); } if ((rv = vntsd_write_client(clientp, prompt, strlen(prompt))) != VNTSD_SUCCESS) { return (rv); } if ((rv = vntsd_read_data(clientp, cmd)) != VNTSD_SUCCESS) { return (rv); } if (*cmd == BS) { return (VNTSD_SUCCESS); } rv = vntsd_write_client(clientp, cmd, 1); *cmd = tolower(*cmd); return (rv); } /* reset client for selecting a console in the group */ static void client_init(vntsd_client_t *clientp) { (void) mutex_lock(&clientp->lock); clientp->cons = NULL; clientp->status = 0; (void) mutex_unlock(&clientp->lock); } /* is there any connection to a given console? */ static boolean_t is_client_que_empty(vntsd_cons_t *consp) { boolean_t has_client = B_FALSE; (void) mutex_lock(&consp->lock); if (consp->clientpq != NULL) has_client = B_TRUE; (void) mutex_unlock(&consp->lock); return (has_client); } /* * close one opened console. * This function is passed to vntsd_que_walk to close one console. * The function returns B_FALSE so that vntsd_que_walk will * continue to apply the function to all consoles in the group. */ static boolean_t close_one_vcc_fd(vntsd_cons_t *consp) { (void) mutex_lock(&consp->lock); if (consp->vcc_fd != -1) { (void) close(consp->vcc_fd); consp->vcc_fd = -1; } (void) mutex_unlock(&consp->lock); return (B_FALSE); } /* clean up client and exit the thread */ static void client_fini(vntsd_group_t *groupp, vntsd_client_t *clientp) { assert(groupp); assert(clientp); /* disconnct client from tcp port */ assert(clientp->sockfd != -1); (void) close(clientp->sockfd); (void) mutex_lock(&groupp->lock); /* * close all consoles in the group if the client is the * last one connected to the group */ if (vntsd_que_walk(groupp->conspq, (el_func_t)is_client_que_empty) == VNTSD_SUCCESS) { (void) vntsd_que_walk(groupp->conspq, (el_func_t)close_one_vcc_fd); } (void) vntsd_que_rm(&groupp->no_cons_clientpq, clientp); if ((groupp->no_cons_clientpq == NULL) && (groupp->status & VNTSD_GROUP_SIG_WAIT)) { /* * group is waiting to be deleted. - signal the group's * listen thread - the VNTSD_GROUP_SIG_WAIT state will * be cleared when the listen thread exits. */ (void) cond_signal(&groupp->cvp); } (void) mutex_unlock(&groupp->lock); (void) mutex_destroy(&clientp->lock); free(clientp); thr_exit(0); } /* check client's status. exit if client quits or fatal errors */ static void console_chk_status(vntsd_group_t *groupp, vntsd_client_t *clientp, int status) { char err_msg[VNTSD_LINE_LEN]; D1(stderr, "t@%d console_chk_status() status=%d " "client status=%x num consoles=%d \n", thr_self(), status, clientp->status, groupp->num_cons); (void) snprintf(err_msg, VNTSD_LINE_LEN, "console_chk_status client%d" " num_cos=%d", clientp->sockfd, groupp->num_cons); /* * obtain group lock to protect groupp->num_cons. * When groupp->num_cons == 0, close client and exit the tread. */ (void) mutex_lock(&groupp->lock); if (groupp->num_cons == 0) { /* no more console in the group */ (void) mutex_unlock(&groupp->lock); client_fini(groupp, clientp); return; } if (status == VNTSD_STATUS_INTR) { /* reason for signal? */ status = vntsd_cons_chk_intr(clientp); } switch (status) { case VNTSD_STATUS_CLIENT_QUIT: (void) mutex_unlock(&groupp->lock); client_fini(groupp, clientp); return; case VNTSD_STATUS_RESELECT_CONS: if (clientp->cons == NULL) { /* * domain was deleted before client connects to it * connect to other console in the same group */ (void) mutex_unlock(&groupp->lock); client_init(clientp); return; } if ((groupp->num_cons == 1) && ((clientp->status & VNTSD_CLIENT_CONS_DELETED) || (groupp->conspq->handle == clientp->cons))) { /* no other selection available */ (void) mutex_unlock(&groupp->lock); client_fini(groupp, clientp); } else { (void) mutex_unlock(&groupp->lock); client_init(clientp); } return; case VNTSD_STATUS_VCC_IO_ERR: if ((clientp->status & VNTSD_CLIENT_CONS_DELETED) == 0) { /* check if console was deleted */ (void) mutex_unlock(&groupp->lock); status = vntsd_vcc_err(clientp->cons); (void) mutex_lock(&groupp->lock); } if (status != VNTSD_STATUS_CONTINUE) { /* console was deleted */ if (groupp->num_cons <= 1) { (void) mutex_unlock(&groupp->lock); client_fini(groupp, clientp); return; } } (void) mutex_unlock(&groupp->lock); /* console is ok */ client_init(clientp); return; case VNTSD_STATUS_MOV_CONS_FORWARD: case VNTSD_STATUS_MOV_CONS_BACKWARD: if (groupp->num_cons == 1) { /* same console */ (void) mutex_unlock(&groupp->lock); return; } /* get selected console */ clientp->cons = vntsd_que_pos(groupp->conspq, clientp->cons, (status == VNTSD_STATUS_MOV_CONS_FORWARD)?(1):(-1)); (void) mutex_unlock(&groupp->lock); return; case VNTSD_SUCCESS: case VNTSD_STATUS_CONTINUE: (void) mutex_unlock(&groupp->lock); client_init(clientp); return; case VNTSD_STATUS_NO_CONS: /* * there are two cases when the status is VNTSD_SATATUS_NO_CONS. * case 1. the console was removed but there is at least one * another console in the group that client can connect to. * case 2. there is no console in the group. Client needs to * be disconnected from vntsd. */ if (groupp->num_cons == 0) { (void) mutex_unlock(&groupp->lock); client_fini(groupp, clientp); } else { (void) mutex_unlock(&groupp->lock); client_init(clientp); } return; case VNTSD_ERR_INVALID_INPUT: (void) mutex_unlock(&groupp->lock); return; default: /* fatal error */ (void) mutex_unlock(&groupp->lock); vntsd_log(status, err_msg); client_fini(groupp, clientp); return; } } /* console thread */ void * vntsd_console_thread(vntsd_thr_arg_t *argp) { vntsd_group_t *groupp; vntsd_cons_t *consp; vntsd_client_t *clientp; char buf[MAXHOSTNAMELEN]; char prompt[72]; char cmd; int rv = VNTSD_SUCCESS; int num_cons; groupp = (vntsd_group_t *)argp->handle; clientp = (vntsd_client_t *)argp->arg; assert(groupp); assert(clientp); /* free argp, which was allocated in listen thread */ free(argp); /* check if group is removed */ D1(stderr, "t@%d get_client_sel@%lld:client@%d\n", thr_self(), groupp->tcp_port, clientp->sockfd); bzero(buf, MAXHOSTNAMELEN); /* host name */ if (gethostname(buf, MAXHOSTNAMELEN)) { vntsd_log(VNTSD_STATUS_NO_HOST_NAME, "vntsd_console_thread()"); (void) snprintf(buf, sizeof (buf), "unkown host"); } if (snprintf(prompt, sizeof (prompt), "%s-vnts-%s: h, l, c{id}, n{name}, q:", buf, groupp->group_name) >= sizeof (prompt)) { /* long prompt doesn't fit, use short one */ (void) snprintf(prompt, sizeof (prompt), "vnts: h, l, c{id}, n{name}, q:"); } for (;;) { cmd = ' '; D1(stderr, "t@%d console_thread()@%lld:client@%d\n", thr_self(), groupp->tcp_port, clientp->sockfd); num_cons = vntsd_chk_group_total_cons(groupp); if ((num_cons > 1) && (clientp->cons == NULL)) { /* console to connect to */ rv = read_cmd(clientp, prompt, &cmd); /* check error and may exit */ console_chk_status(groupp, clientp, rv); /* any console is removed from group? */ num_cons = vntsd_chk_group_total_cons(groupp); if (num_cons <= 1) { cmd = ' '; } } switch (cmd) { case 'l': /* list domain names */ rv = list_all_domains(groupp, clientp); break; case 'q': rv = VNTSD_STATUS_CLIENT_QUIT; break; case ' ': if (num_cons == 0) { /* no console in the group */ rv = VNTSD_STATUS_NO_CONS; break; } if (clientp->cons == NULL) { if (num_cons == 1) { /* by pass selecting console */ consp = (vntsd_cons_t *) (groupp->conspq->handle); } else { continue; } } else { consp = clientp->cons; } /* connect to console */ rv = connect_cons(consp, clientp); break; case 'c': case 'n': /* select console */ if (clientp->cons == NULL) { rv = select_cons(groupp, &consp, clientp, cmd); if (rv == VNTSD_ERR_INVALID_INPUT) { rv = display_help(clientp); break; } /* * all consoles in the group * may be gone before this client * could select one. */ if (rv != VNTSD_SUCCESS) break; } else { consp = clientp->cons; } assert(consp); /* connect to console */ rv = connect_cons(consp, clientp); D1(stderr, "t@%d console_thread()" "connect_cons returns %d\n", thr_self(), rv); break; case 'h': default: rv = display_help(clientp); break; } /* check error and may exit */ console_chk_status(groupp, clientp, rv); } /*NOTREACHED*/ return (NULL); }