OpenSolaris_b135/cmd/vntsd/vntsdvcc.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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"

/*
 * Configuration and setup interface to vcc driver.
 * At intialization time, vntsd opens vcc ctrl port and read initial
 * configuratioa. It manages console groups, creates the listen thread,
 * dynamically adds and removes virtual console within a group.
 */


#include <syslog.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <wait.h>
#include <time.h>
#include <synch.h>
#include <netinet/in.h>
#include <thread.h>
#include <signal.h>
#include "vntsd.h"

/* signal all clients that console has been deleted */
boolean_t
vntsd_notify_client_cons_del(vntsd_client_t *clientp)
{
	(void) mutex_lock(&clientp->lock);
	clientp->status |= VNTSD_CLIENT_CONS_DELETED;
	(void) thr_kill(clientp->cons_tid, SIGUSR1);
	(void) mutex_unlock(&clientp->lock);
	return (B_FALSE);
}

/* free console  structure */
static void
free_cons(vntsd_cons_t *consp)
{
	assert(consp);
	(void) mutex_destroy(&consp->lock);
	(void) cond_destroy(&consp->cvp);
	if (consp->vcc_fd != -1)
		(void) close(consp->vcc_fd);
	free(consp);
}

/* free group structure */
static void
free_group(vntsd_group_t *groupp)
{
	assert(groupp);
	(void) mutex_destroy(&groupp->lock);
	(void) cond_destroy(&groupp->cvp);
	if (groupp->sockfd != -1)
		(void) close(groupp->sockfd);
	free(groupp);
}

/*
 *  all clients connected to a console must disconnect before
 *  removing a console.
 */
static void
cleanup_cons(vntsd_cons_t *consp)
{
	vntsd_group_t	*groupp;
	timestruc_t	to;

	assert(consp);
	D1(stderr, "t@%d vntsd_disconn_clients@%d\n", thr_self(),
	    consp->cons_no);

	groupp = consp->group;
	assert(groupp);


	(void) mutex_lock(&consp->lock);

	/* wait for all clients disconnect from the console */
	while (consp->clientpq != NULL) {
		consp->status |= VNTSD_CONS_SIG_WAIT;

		/* signal client to disconnect the console */
		(void) vntsd_que_walk(consp->clientpq,
		    (el_func_t)vntsd_notify_client_cons_del);

		(void) thr_kill(consp->wr_tid, SIGUSR1);
		to.tv_sec = VNTSD_CV_WAIT_DELTIME;
		to.tv_nsec = 0;

		/* wait for clients to disconnect  */
		(void) cond_reltimedwait(&consp->cvp, &consp->lock, &to);
	}

	/* reduce console count in the group */
	(void) mutex_lock(&groupp->lock);
	assert(groupp->num_cons > 0);
	groupp->num_cons--;
	(void) mutex_unlock(&groupp->lock);

	(void) mutex_unlock(&consp->lock);

	free_cons(consp);
}

/* search for a group whose console is being deleted */
static boolean_t
find_clean_cons_group(vntsd_group_t *groupp)
{
	if (groupp->status & VNTSD_GROUP_CLEAN_CONS) {
		return (B_TRUE);
	} else {
		return (B_FALSE);
	}
}

/* search for a console that is being deleted */
static boolean_t
find_clean_cons(vntsd_cons_t *consp)
{
	if (consp->status & VNTSD_CONS_DELETED) {
		return (B_TRUE);
	} else {
		return (B_FALSE);
	}
}

/* delete a console */
void
vntsd_delete_cons(vntsd_t *vntsdp)
{
	vntsd_group_t *groupp;
	vntsd_cons_t *consp;

	for (; ; ) {
		/* get the group contains deleted console */
		(void) mutex_lock(&vntsdp->lock);
		groupp = vntsd_que_walk(vntsdp->grouppq,
		    (el_func_t)find_clean_cons_group);
		if (groupp == NULL) {
			/* no more group has console deleted */
			(void) mutex_unlock(&vntsdp->lock);
			return;
		}
		(void) mutex_lock(&groupp->lock);
		groupp->status &= ~VNTSD_GROUP_CLEAN_CONS;
		(void) mutex_unlock(&groupp->lock);
		(void) mutex_unlock(&vntsdp->lock);

		for (; ; ) {
			/* get the console to be deleted */
			(void) mutex_lock(&groupp->lock);

			/* clean up any deleted console in the group */
			if (groupp->conspq != NULL) {
				consp = vntsd_que_walk(groupp->conspq,
				    (el_func_t)find_clean_cons);
				if (consp == NULL) {
					/* no more cons to delete */
					(void) mutex_unlock(&groupp->lock);
					break;
				}

				/* remove console from the group */
				(void) vntsd_que_rm(&groupp->conspq, consp);
				(void) mutex_unlock(&groupp->lock);

				/* clean up the console */
				cleanup_cons(consp);
			}

			/* delete group? */
			if (groupp->conspq == NULL) {
				/* no more console in the group delete group */
				assert(groupp->vntsd);

				(void) mutex_lock(&groupp->vntsd->lock);
				(void) vntsd_que_rm(&groupp->vntsd->grouppq,
						    groupp);
				(void) mutex_unlock(&groupp->vntsd->lock);

				/* clean up the group */
				vntsd_clean_group(groupp);
				break;
			}
		}
	}
}

/* clean up a group */
void
vntsd_clean_group(vntsd_group_t *groupp)
{

	timestruc_t	to;

	D1(stderr, "t@%d clean_group() group=%s tcp=%lld\n", thr_self(),
	    groupp->group_name, groupp->tcp_port);

	(void) mutex_lock(&groupp->lock);

	/* prevent from reentry */
	if (groupp->status & VNTSD_GROUP_IN_CLEANUP) {
		(void) mutex_unlock(&groupp->lock);
		return;
	}
	groupp->status |= VNTSD_GROUP_IN_CLEANUP;

	/* mark group waiting for listen thread to exits */
	groupp->status |= VNTSD_GROUP_SIG_WAIT;
	(void) mutex_unlock(&groupp->lock);

	vntsd_free_que(&groupp->conspq, (clean_func_t)cleanup_cons);

	(void) mutex_lock(&groupp->lock);
	/* walk through no cons client queue */
	while (groupp->no_cons_clientpq != NULL) {
		(void) vntsd_que_walk(groupp->no_cons_clientpq,
		    (el_func_t)vntsd_notify_client_cons_del);
		to.tv_sec = VNTSD_CV_WAIT_DELTIME;
		to.tv_nsec = 0;
		(void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to);
	}

	/* waiting for listen thread to exit */
	while (groupp->status & VNTSD_GROUP_SIG_WAIT) {
		/* signal listen thread to exit  */
		(void) thr_kill(groupp->listen_tid, SIGUSR1);
		to.tv_sec = VNTSD_CV_WAIT_DELTIME;
		to.tv_nsec = 0;
		/* wait listen thread to exit  */
		(void) cond_reltimedwait(&groupp->cvp, &groupp->lock, &to);
	}

	(void) mutex_unlock(&groupp->lock);
	(void) thr_join(groupp->listen_tid, NULL, NULL);
	/* free group */
	free_group(groupp);
}

/* allocate and initialize console structure */
static vntsd_cons_t *
alloc_cons(vntsd_group_t *groupp, vcc_console_t *consolep)
{
	vntsd_cons_t *consp;
	int	rv;

	/* allocate console */
	consp = (vntsd_cons_t *)malloc(sizeof (vntsd_cons_t));
	if (consp == NULL) {
		vntsd_log(VNTSD_ERR_NO_MEM, "alloc_cons");
		return (NULL);
	}

	/* intialize console */
	bzero(consp, sizeof (vntsd_cons_t));

	(void) mutex_init(&consp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL);
	(void) cond_init(&consp->cvp, USYNC_THREAD, NULL);

	consp->cons_no = consolep->cons_no;
	(void) strlcpy(consp->domain_name, consolep->domain_name, MAXPATHLEN);
	(void) strlcpy(consp->dev_name, consolep->dev_name, MAXPATHLEN);
	consp->wr_tid = (thread_t)-1;
	consp->vcc_fd = -1;

	/* join the group */
	(void) mutex_lock(&groupp->lock);

	if ((rv = vntsd_que_append(&groupp->conspq, consp)) !=
	    VNTSD_SUCCESS) {
		(void) mutex_unlock(&groupp->lock);
		vntsd_log(rv, "alloc_cons");
		free_cons(consp);
		return (NULL);
	}
	groupp->num_cons++;
	consp->group = groupp;

	(void) mutex_unlock(&groupp->lock);

	D1(stderr, "t@%d alloc_cons@%d %s %s\n", thr_self(),
	    consp->cons_no, consp->domain_name, consp->dev_name);

	return (consp);
}

/* compare tcp with group->tcp */
static boolean_t
grp_by_tcp(vntsd_group_t *groupp, uint64_t *tcp_port)
{
	assert(groupp);
	assert(tcp_port);
	return (groupp->tcp_port == *tcp_port);
}

/* allocate and initialize group */
static vntsd_group_t *
alloc_group(vntsd_t *vntsdp, char *group_name, uint64_t tcp_port)
{
	vntsd_group_t *groupp;

	/* allocate group */
	groupp = (vntsd_group_t *)malloc(sizeof (vntsd_group_t));
	if (groupp == NULL) {
		vntsd_log(VNTSD_ERR_NO_MEM, "alloc_group");
		return (NULL);
	}

	/* initialize group */
	bzero(groupp, sizeof (vntsd_group_t));

	(void) mutex_init(&groupp->lock, USYNC_THREAD|LOCK_ERRORCHECK, NULL);
	(void) cond_init(&groupp->cvp, USYNC_THREAD, NULL);

	if (group_name != NULL) {
		(void) memcpy(groupp->group_name, group_name, MAXPATHLEN);
	}

	groupp->tcp_port = tcp_port;
	groupp->listen_tid = (thread_t)-1;
	groupp->sockfd = -1;
	groupp->vntsd = vntsdp;

	D1(stderr, "t@%d alloc_group@%lld:%s\n", thr_self(), groupp->tcp_port,
	    groupp->group_name);

	return (groupp);
}

/* mark a deleted console */
boolean_t
vntsd_mark_deleted_cons(vntsd_cons_t *consp)
{
	(void) mutex_lock(&consp->lock);
	consp->status |= VNTSD_CONS_DELETED;
	(void) mutex_unlock(&consp->lock);
	return (B_FALSE);
}

/*
 * Initialize a console, if console is associated with with a
 * new group, intialize the group.
 */
static int
alloc_cons_with_group(vntsd_t *vntsdp, vcc_console_t *consp,
    vntsd_group_t **new_groupp)
{
	vntsd_group_t	*groupp = NULL;
	int		rv;

	*new_groupp = NULL;

	/* match group by tcp port */


	(void) mutex_lock(&vntsdp->lock);
	groupp = vntsd_que_find(vntsdp->grouppq,
	    (compare_func_t)grp_by_tcp, (void *)&(consp->tcp_port));
	if (groupp != NULL)
		(void) mutex_lock(&groupp->lock);

	(void) mutex_unlock(&vntsdp->lock);

	if (groupp != NULL) {
		/*
		 *  group with same tcp port found.
		 *  if there is no console in the group, the
		 *  group should be removed and the tcp port can
		 *  be used for tne new group.
		 *  This is possible, when there is tight loop of
		 *  creating/deleting domains. When a vcc port is
		 *  removed, a read thread will have an I/O error because
		 *  vcc has closed the port. The read thread then marks
		 *  the console is removed and notify main thread to
		 *  remove the console.
		 *  Meanwhile, the same port and its group (with same
		 *  tcp port and group name) is created. Vcc notify
		 *  vntsd that new console is added.
		 *  Main thread now have two events. If main thread polls
		 *  out vcc notification first, it will find that there is
		 *  a group has no console.
		 */

		if (vntsd_chk_group_total_cons(groupp) == 0) {

			/* all consoles in the group have been removed */
			(void) vntsd_que_walk(groupp->conspq,
			    (el_func_t)vntsd_mark_deleted_cons);
			groupp->status |= VNTSD_GROUP_CLEAN_CONS;
			(void) mutex_unlock(&groupp->lock);
			groupp = NULL;

		} else if (strcmp(groupp->group_name, consp->group_name)) {
			/* conflict group name */
			vntsd_log(VNTSD_ERR_VCC_GRP_NAME,
			    "group name is different from existing group");
			(void) mutex_unlock(&groupp->lock);
			return (VNTSD_ERR_VCC_CTRL_DATA);

		} else {
			/* group already existed */
			(void) mutex_unlock(&groupp->lock);
		}

	}

	if (groupp == NULL) {
		/* new group */
		groupp = alloc_group(vntsdp, consp->group_name,
		    consp->tcp_port);
		if (groupp == NULL) {
			return (VNTSD_ERR_NO_MEM);
		}

		assert(groupp->conspq == NULL);
		/* queue group to vntsdp */
		(void) mutex_lock(&vntsdp->lock);
		rv = vntsd_que_append(&vntsdp->grouppq, groupp);
		(void) mutex_unlock(&vntsdp->lock);

		if (rv != VNTSD_SUCCESS) {
			return (rv);
		}

		*new_groupp = groupp;
	}

	/* intialize console */
	if (alloc_cons(groupp, consp) == NULL) {
		/* no memory */
		if (new_groupp != NULL) {
			/* clean up new group */
			free_group(groupp);
		}

		return (VNTSD_ERR_NO_MEM);
	}

	return (VNTSD_SUCCESS);

}


/* create listen thread */
static boolean_t
create_listen_thread(vntsd_group_t *groupp)
{

	char err_msg[VNTSD_LINE_LEN];
	int rv;

	assert(groupp);

	(void) mutex_lock(&groupp->lock);
	assert(groupp->num_cons);

	D1(stderr, "t@%d create_listen:%lld\n", thr_self(), groupp->tcp_port);

	if ((rv = thr_create(NULL, 0, (thr_func_t)vntsd_listen_thread,
			    (void *)groupp, THR_DETACHED, &groupp->listen_tid))
	    != 0) {
		(void) (void) snprintf(err_msg, sizeof (err_msg),
		    "Can not create listen thread for"
		    "group %s tcp %llx\n", groupp->group_name,
		    groupp->tcp_port);
		vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg);

		/* clean up group queue */
		vntsd_free_que(&groupp->conspq, (clean_func_t)free_cons);
		groupp->listen_tid = (thread_t)-1;
	}

	(void) mutex_unlock(&groupp->lock);

	return (rv != 0);
}

/* find deleted console by console no */
static boolean_t
deleted_cons_by_consno(vntsd_cons_t *consp, int *cons_no)
{
	vntsd_client_t *clientp;

	assert(consp);

	if (consp->cons_no != *cons_no)
		return (B_FALSE);

	/* has console marked as deleted? */
	if ((consp->status & VNTSD_CONS_DELETED) == 0)
		return (B_TRUE);

	if (consp->clientpq == NULL)
		/* there is no client for this console */
		return (B_TRUE);

	/* need to notify clients of console ? */
	clientp = (vntsd_client_t *)consp->clientpq->handle;

	if (clientp->status & VNTSD_CLIENT_CONS_DELETED)
		/* clients of console have notified */
		return (B_FALSE);

	return (B_TRUE);
}

/* find group structure from console no */
static boolean_t
find_cons_group_by_cons_no(vntsd_group_t *groupp, uint_t *cons_no)
{
	vntsd_cons_t *consp;

	consp = vntsd_que_find(groupp->conspq,
	    (compare_func_t)deleted_cons_by_consno, cons_no);
	return (consp != NULL);

}

/* delete a console if the console exists in the vntsd */
static void
delete_cons_before_add(vntsd_t *vntsdp, uint_t cons_no)
{
	vntsd_group_t	    *groupp;
	vntsd_cons_t	    *consp;

	/* group exists? */
	(void) mutex_lock(&vntsdp->lock);
	groupp = vntsd_que_find(vntsdp->grouppq,
	    (compare_func_t)find_cons_group_by_cons_no,
	    &cons_no);
	(void) mutex_unlock(&vntsdp->lock);

	if (groupp == NULL) {
		/* no such group */
		return;
	}

	/* group exists, if console exists? */
	(void) mutex_lock(&groupp->lock);
	consp = vntsd_que_find(groupp->conspq,
	    (compare_func_t)deleted_cons_by_consno, &cons_no);

	if (consp == NULL) {
		/* no such console */
		(void) mutex_unlock(&groupp->lock);
		return;
	}

	/* console exists - mark console for main thread to delete it */
	(void) mutex_lock(&consp->lock);

	if (consp->status & VNTSD_CONS_DELETED) {
		/* already marked */
		(void) mutex_unlock(&consp->lock);
		(void) mutex_unlock(&groupp->lock);
		return;
	}

	consp->status |= VNTSD_CONS_DELETED;
	groupp->status |= VNTSD_GROUP_CLEAN_CONS;

	(void) mutex_unlock(&consp->lock);
	(void) mutex_unlock(&groupp->lock);

}

/* add a console */
static void
do_add_cons(vntsd_t *vntsdp, int cons_no)
{
	vcc_console_t	console;
	vntsd_group_t	*groupp;
	int		rv;
	char		err_msg[VNTSD_LINE_LEN];


	(void) snprintf(err_msg, sizeof (err_msg),
	    "do_add_cons():Can not add console=%d", cons_no);

	/* get console configuration from vcc */

	if ((rv = vntsd_vcc_ioctl(VCC_CONS_INFO, cons_no, (void *)&console))
	    != VNTSD_SUCCESS) {
		vntsd_log(rv, err_msg);
		return;
	}

	/* clean up the console if console was deleted and added again */
	delete_cons_before_add(vntsdp, console.cons_no);

	/* initialize console */

	if ((rv = alloc_cons_with_group(vntsdp, &console, &groupp)) !=
	    VNTSD_SUCCESS) {
		/* no memory to add this new console */
		vntsd_log(rv, err_msg);
		return;
	}

	if (groupp != NULL) {
		/* new group */
		/* create listen thread for this console */
		if (create_listen_thread(groupp)) {
			vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, err_msg);
			free_group(groupp);
		}

	}
}

/* daemon wake up */
void
vntsd_daemon_wakeup(vntsd_t *vntsdp)
{

	vcc_response_t	inq_data;

	/* reason to wake up  */
	if (vntsd_vcc_ioctl(VCC_INQUIRY, 0, (void *)&inq_data) !=
	    VNTSD_SUCCESS) {
		vntsd_log(VNTSD_ERR_VCC_IOCTL, "vntsd_daemon_wakeup()");
		return;
	}

	D1(stderr, "t@%d vntsd_daemon_wakup:msg %d port %x\n", thr_self(),
	    inq_data.reason, inq_data.cons_no);

	switch (inq_data.reason) {

	case VCC_CONS_ADDED:
		do_add_cons(vntsdp, inq_data.cons_no);
		break;

	case VCC_CONS_MISS_ADDED:
		/* an added port was deleted before vntsd can process it */
		return;

	default:
		DERR(stderr, "t@%d daemon_wakeup:ioctl_unknown %d\n",
		    thr_self(), inq_data.reason);
		vntsd_log(VNTSD_ERR_UNKNOWN_CMD, "from vcc\n");
		break;
	}
}

/* initial console configuration */
void
vntsd_get_config(vntsd_t *vntsdp)
{

	int		i;
	int		num_cons;
	vcc_console_t	*consp;
	vntsd_group_t	*groupp;

	/* num of consoles */
	num_cons = 0;

	if (vntsd_vcc_ioctl(VCC_NUM_CONSOLE, 0, (void *)&num_cons) !=
	    VNTSD_SUCCESS) {
		vntsd_log(VNTSD_ERR_VCC_IOCTL, "VCC_NUM_CONSOLE failed\n");
		return;
	}

	D3(stderr, "get_config:num_cons=%d", num_cons);

	if (num_cons == 0) {
		return;
	}

	/* allocate memory for all consoles */
	consp = malloc(num_cons*sizeof (vcc_console_t));

	if (consp == NULL) {
		vntsd_log(VNTSD_ERR_NO_MEM, "for console table.");
		return;
	}

	/* get console table */
	if (vntsd_vcc_ioctl(VCC_CONS_TBL, 0, (void *)consp) != VNTSD_SUCCESS) {
		vntsd_log(VNTSD_ERR_VCC_IOCTL, " VCC_CONS_TBL "
		    "for console table\n");
		return;
	}

	/* intialize groups and consoles  */
	for (i = 0; i < num_cons; i++) {
		if (alloc_cons_with_group(vntsdp, &consp[i], &groupp)
		    != VNTSD_SUCCESS) {
			vntsd_log(VNTSD_ERR_ADD_CONS_FAILED, "get_config");
		}
	}

	/* create listen thread for each group */
	(void) mutex_lock(&vntsdp->lock);

	for (; ; ) {
		groupp = vntsd_que_walk(vntsdp->grouppq,
		    (el_func_t)create_listen_thread);
		if (groupp == NULL) {
			break;
		}
		vntsd_log(VNTSD_ERR_CREATE_LISTEN_THR, "get config()");
	}

	(void) mutex_unlock(&vntsdp->lock);
}