OpenSolaris_b135/lib/lvm/libmeta/common/meta_notify.c

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 1995-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Just in case we're not in a build environment, make sure that
 * TEXT_DOMAIN gets set to something.
 */
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif

/*
 * libmeta wrappers for event notification
 */

#include <meta.h>
#include <sys/lvm/md_notify.h>

#if defined(DEBUG)
#include <assert.h>
#endif /* DEBUG */

struct tag2obj_type {
	md_tags_t	tag;
	ev_obj_t	obj;
} tag2obj_typetab[] =
{
	{ TAG_EMPTY,		EVO_EMPTY	},
	{ TAG_METADEVICE,	EVO_METADEV	},
	{ TAG_REPLICA,		EVO_REPLICA	},
	{ TAG_HSP,		EVO_HSP		},
	{ TAG_HS,		EVO_HS		},
	{ TAG_SET,		EVO_SET		},
	{ TAG_DRIVE,		EVO_DRIVE	},
	{ TAG_HOST,		EVO_HOST	},
	{ TAG_MEDIATOR,		EVO_MEDIATOR	},
	{ TAG_UNK,		EVO_UNSPECIFIED	},

	{ TAG_LAST,		EVO_LAST	}
};

struct evdrv2evlib_type {
	md_event_type_t	drv;
	evid_t		lib;
} evdrv2evlib_typetab[] =
{
	{ EQ_EMPTY,		EV_EMPTY		},
	{ EQ_CREATE,		EV_CREATE		},
	{ EQ_DELETE,		EV_DELETE		},
	{ EQ_ADD,		EV_ADD			},
	{ EQ_REMOVE,		EV_REMOVE		},
	{ EQ_REPLACE,		EV_REPLACE		},
	{ EQ_MEDIATOR_ADD,	EV_MEDIATOR_ADD		},
	{ EQ_MEDIATOR_DELETE,	EV_MEDIATOR_DELETE	},
	{ EQ_HOST_ADD,		EV_HOST_ADD		},
	{ EQ_HOST_DELETE,	EV_HOST_DELETE		},
	{ EQ_DRIVE_ADD,		EV_DRIVE_ADD		},
	{ EQ_DRIVE_DELETE,	EV_DRIVE_DELETE		},
	{ EQ_RENAME_SRC,	EV_RENAME_SRC		},
	{ EQ_RENAME_DST,	EV_RENAME_DST		},
	{ EQ_INIT_START,	EV_INIT_START		},
	{ EQ_INIT_FAILED,	EV_INIT_FAILED		},
	{ EQ_INIT_FATAL,	EV_INIT_FATAL		},
	{ EQ_INIT_SUCCESS,	EV_INIT_SUCCESS		},
	{ EQ_IOERR,		EV_IOERR		},
	{ EQ_ERRED,		EV_ERRED		},
	{ EQ_LASTERRED,		EV_LASTERRED		},
	{ EQ_OK,		EV_OK			},
	{ EQ_ENABLE,		EV_ENABLE		},
	{ EQ_RESYNC_START,	EV_RESYNC_START		},
	{ EQ_RESYNC_FAILED,	EV_RESYNC_FAILED	},
	{ EQ_RESYNC_SUCCESS,	EV_RESYNC_SUCCESS	},
	{ EQ_RESYNC_DONE,	EV_RESYNC_DONE		},
	{ EQ_HOTSPARED,		EV_HOTSPARED		},
	{ EQ_HS_FREED,		EV_HS_FREED		},
	{ EQ_TAKEOVER,		EV_TAKEOVER		},
	{ EQ_RELEASE,		EV_RELEASE		},
	{ EQ_OPEN_FAIL,		EV_OPEN_FAIL		},
	{ EQ_OFFLINE,		EV_OFFLINE		},
	{ EQ_ONLINE,		EV_ONLINE		},
	{ EQ_GROW,		EV_GROW			},
	{ EQ_DETACH,		EV_DETACH		},
	{ EQ_DETACHING,		EV_DETACHING		},
	{ EQ_ATTACH,		EV_ATTACH		},
	{ EQ_ATTACHING,		EV_ATTACHING		},
	{ EQ_CHANGE,		EV_CHANGE		},
	{ EQ_EXCHANGE,		EV_EXCHANGE		},
	{ EQ_REGEN_START,	EV_REGEN_START		},
	{ EQ_REGEN_DONE,	EV_REGEN_DONE		},
	{ EQ_REGEN_FAILED,	EV_REGEN_FAILED		},
	{ EQ_USER,		EV_USER			},
	{ EQ_NOTIFY_LOST,	EV_NOTIFY_LOST		},

	{ EQ_LAST,		EV_LAST }
};

static ev_obj_t
dev2tag(md_dev64_t dev, set_t setno, md_error_t *ep)
{
	mdname_t	*np	= NULL;
	mdsetname_t	*sp	= NULL;
	ev_obj_t	 obj	= EVO_METADEV;
	char		*miscname;

	if ((sp = metasetnosetname(setno, ep)) == NULL) {
		goto out;
	}
	if (!(np = metamnumname(&sp, meta_getminor(dev), 0, ep))) {
		goto out;
	}

	/* need to invalidate name in case rename or delete/create done */
	meta_invalidate_name(np);

	if (!(miscname = metagetmiscname(np, ep))) {
		goto out;
	}
	if (strcmp(miscname, MD_STRIPE) == 0) {
		obj = EVO_STRIPE;
	} else if (strcmp(miscname, MD_MIRROR) == 0) {
		obj = EVO_MIRROR;
	} else if (strcmp(miscname, MD_RAID) == 0) {
		obj = EVO_RAID5;
	} else if (strcmp(miscname, MD_TRANS) == 0) {
		obj = EVO_TRANS;
	}
out:
	return (obj);
}

static ev_obj_t
tagdrv_2_objlib(md_tags_t tag)
{
	int i;

	for (i = 0; tag2obj_typetab[i].tag != TAG_LAST; i++) {
		if (tag2obj_typetab[i].tag == tag)
			return (tag2obj_typetab[i].obj);
	}
	return (EVO_UNSPECIFIED);
}

static md_tags_t
objlib_2_tagdrv(ev_obj_t obj)
{
	int i;

	for (i = 0; tag2obj_typetab[i].tag != TAG_LAST; i++) {
		if (tag2obj_typetab[i].obj == obj)
			return (tag2obj_typetab[i].tag);
	}
	return (TAG_UNK);
}


static evid_t
evdrv_2_evlib(md_event_type_t drv_ev)
{
	int	i;

	for (i = 0; evdrv2evlib_typetab[i].drv != EQ_LAST; i++) {
		if (evdrv2evlib_typetab[i].drv == drv_ev)
			return (evdrv2evlib_typetab[i].lib);
	}
	return (EV_UNK);
}

static md_event_type_t
evlib_2_evdrv(evid_t lib_ev)
{
	int	i;

	for (i = 0; evdrv2evlib_typetab[i].drv != EQ_LAST; i++) {
		if (evdrv2evlib_typetab[i].lib == lib_ev)
			return (evdrv2evlib_typetab[i].drv);
	}
	return (EQ_EMPTY);
}


/*
 * meta_event
 *  returns 0 on succcess or < 0 to indicate error.
 *  abs(return code) = errno
 */
static int
meta_event(md_event_ioctl_t *evctl, md_error_t *ep)
{
	int	l;

	if (!evctl || !ep)
		return (-EINVAL);

	l = strlen(evctl->mdn_name);
	if ((l == 0 && evctl->mdn_cmd != EQ_PUT) || l >= MD_NOTIFY_NAME_SIZE) {
		return (-EINVAL);
	}

	MD_SETDRIVERNAME(evctl, MD_NOTIFY, 0);
	mdclrerror(ep);
	errno = 0;

	if (metaioctl(MD_IOCNOTIFY, evctl, ep, evctl->mdn_name) != 0) {
		if (errno == 0) {
			errno = EINVAL;
		}
		if (mdisok(ep)) {
			(void) mdsyserror(ep, errno, evctl->mdn_name);
		}
		return (-errno);
	}

	return (0);
}

static void
init_evctl(char *qname,
	md_tags_t tag,
	md_event_type_t ev,
	uint_t flags,
	set_t set,
	md_dev64_t dev,
	md_event_cmds_t cmd,
	u_longlong_t udata,
	md_event_ioctl_t *evctlp)
{

	assert(evctlp);

	(void) memset(evctlp, 0, sizeof (md_event_ioctl_t));

	evctlp->mdn_magic	= MD_EVENT_ID;
	evctlp->mdn_rev		= MD_NOTIFY_REVISION;

	if (qname)
		(void) strncpy(evctlp->mdn_name, qname, MD_NOTIFY_NAME_SIZE-1);
	else
		(void) memset(evctlp->mdn_name, 0, MD_NOTIFY_NAME_SIZE);

	evctlp->mdn_tag		= tag;
	evctlp->mdn_event	= ev;
	evctlp->mdn_flags	= flags;
	evctlp->mdn_set		= set;
	evctlp->mdn_dev		= dev;
	evctlp->mdn_cmd		= cmd;
	evctlp->mdn_user	= udata;
}

/*
 * meta_notify_createq
 * - creates an eventq
 * - returns 0 on success or errno and sets ep
 */
int
meta_notify_createq(char *qname, ulong_t flags, md_error_t *ep)
{
	md_event_ioctl_t	evctl;
	int			err	= 0;

	mdclrerror(ep);
	if (!qname || strlen(qname) == 0) {
		(void) mdsyserror(ep, EINVAL,
		    dgettext(TEXT_DOMAIN,
			"null or zero-length queue name"));
		return (EINVAL);
	}

	init_evctl(qname,
			TAG_EMPTY,
			EQ_EMPTY,
			(flags & EVFLG_PERMANENT) != 0? EQ_Q_PERM: 0,
			/* set */ 0,
			/* dev */ 0,
			EQ_ON,
			/* user-defined event data */ 0,
			&evctl);

	err = meta_event(&evctl, ep);

	if (err == -EEXIST && !(flags & EVFLG_EXISTERR)) {
		err = 0;
		mdclrerror(ep);
	}
	if (!mdisok(ep) && mdanysyserror(ep)) {
		err = (ep)->info.md_error_info_t_u.ds_error.errnum;
	}
	return (-err);
}

/*
 * meta_notify_deleteq
 * - deletes an eventq
 * - free's any underlying resources
 * - returns 0 on success or errno and sets ep
 */
int
meta_notify_deleteq(char *qname, md_error_t *ep)
{
	md_event_ioctl_t	evctl;
	int			err;

	init_evctl(qname,
			TAG_EMPTY,
			EQ_EMPTY,
			/* flags */ 0,
			/* set */ 0,
			/* dev */ 0,
			EQ_OFF,
			/* user-defined event data */ 0,
			&evctl);

	err = meta_event(&evctl, ep);
	return (-err);
}

/*
 * meta_notify_validq
 * - verifies that the queue exists
 * - returns true or false, ep may be changed as a side-effect
 */
bool_t
meta_notify_validq(char *qname, md_error_t *ep)
{
	md_event_ioctl_t	evctl;

	init_evctl(qname,
			TAG_EMPTY,
			EQ_EMPTY,
			/* flags */ 0,
			/* set */ 0,
			/* dev */ 0,
			EQ_ON,
			/* user-defined event data */ 0,
			&evctl);

	return (meta_event(&evctl, ep) == -EEXIST);
}

/*
 * meta_notify_listq
 * - returns number of (currently) active queus or -errno
 * - allocates qnames array and sets user's pointer to it,
 *   fills in array with vector of qnames
 */
int
meta_notify_listq(char ***qnames, md_error_t *ep)
{

#ifdef lint
	qnames = qnames;
#endif /* lint */

	mdclrerror(ep);
	(void) mdsyserror(ep, EOPNOTSUPP, "EOPNOTSUPP");
	return (-EOPNOTSUPP);
}

/*
 * meta_notify_flushq
 * - calls the underlying notify driver to flush all events
 *   from the named queue
 * - returns 0 on success or errno and sets ep as necessary
 */
int
meta_notify_flushq(char *qname, md_error_t *ep)
{

#ifdef lint
	qname = qname;
#endif /* lint */

	mdclrerror(ep);
	(void) mdsyserror(ep, EOPNOTSUPP, "EOPNOTSUPP");
	return (EOPNOTSUPP);
}

static void
cook_ev(md_event_ioctl_t *evctlp, md_ev_t *evp, md_error_t *ep)
{
	assert(evctlp);
	assert(evp);

	evp->obj_type = tagdrv_2_objlib(evctlp->mdn_tag);

	if (evp->obj_type == EVO_METADEV) {
		evp->obj_type = dev2tag(evctlp->mdn_dev, evctlp->mdn_set, ep);
	}

	evp->setno	= evctlp->mdn_set;
	evp->ev		= evdrv_2_evlib(evctlp->mdn_event);
	evp->obj	= evctlp->mdn_dev;
	evp->uev	= evctlp->mdn_user;
}

/*
 * meta_notify_getev
 * - collects up to 1 event and stores it into md_ev_t
 * - returns number of events found (0 or 1) on success or -errno
 * - flags governs whether an empty queue is waited upon (EVFLG_WAIT)
 */
int
meta_notify_getev(char *qname, ulong_t flags, md_ev_t *evp, md_error_t *ep)
{
	md_event_ioctl_t	evctl;
	int			n_ev;
	int			err	= -EINVAL;

	if (!evp) {
		goto out;
	}

	init_evctl(qname,
			TAG_EMPTY,
			EQ_EMPTY,
			/* flags (unused in get) */ 0,
			(evp->setno == EV_ALLSETS)? MD_ALLSETS: evp->setno,
			(evp->obj == EV_ALLOBJS)? MD_ALLDEVS: evp->obj,
			(flags & EVFLG_WAIT) != 0? EQ_GET_WAIT: EQ_GET_NOWAIT,
			/* user-defined event data */ 0,
			&evctl);

	err = meta_event(&evctl, ep);

	/*
	 * trap EAGAIN so that EV_EMPTY events get returned, but
	 * be sure n_ev = 0 so that users who just watch the count
	 * will also work
	 */
	switch (err) {
	case -EAGAIN:
		err = n_ev = 0;
		cook_ev(&evctl, evp, ep);
		break;
	case 0:
		n_ev = 1;
		cook_ev(&evctl, evp, ep);
		break;
	}
out:
	return (err == 0? n_ev: err);
}


/*
 * meta_notify_getevlist
 * - collects all pending events in the named queue and allocates
 *   an md_evlist_t * to return them
 * - returns the number of events found (may be 0 if !WAIT) on success
 *   or -errno and sets ep as necessary
 */
int
meta_notify_getevlist(char *qname,
			ulong_t  flags,
			md_evlist_t **evpp_arg,
			md_error_t *ep)
{
	md_ev_t		*evp		= NULL;
	md_evlist_t	*evlp		= NULL;
	md_evlist_t	*evlp_head	= NULL;
	md_evlist_t	*new		= NULL;
	int		 n_ev		= 0;
	int		 err		= -EINVAL;

	mdclrerror(ep);
	if (!evpp_arg) {
		(void) mdsyserror(ep, EINVAL, dgettext(TEXT_DOMAIN,
		    "No event list pointer"));
		goto out;
	}

	if (!qname || strlen(qname) == 0) {
		(void) mdsyserror(ep, EINVAL, dgettext(TEXT_DOMAIN,
		    "Null or zero-length queue name"));
		goto out;
	}

	do {
		if (!(evp = (md_ev_t *)Malloc(sizeof (md_ev_t)))) {
			(void) mdsyserror(ep, ENOMEM, qname);
			continue;
		}
		evp->obj_type	= EVO_EMPTY;
		evp->setno	= EV_ALLSETS;
		evp->ev		= EV_EMPTY;
		evp->obj	= EV_ALLOBJS;
		evp->uev	= 0ULL;

		err = meta_notify_getev(qname, flags, evp, ep);

		if (evp->ev != EV_EMPTY) {
			new = (md_evlist_t *)Zalloc(sizeof (md_evlist_t));
			if (evlp_head == NULL) {
				evlp = evlp_head = new;
			} else {
				evlp->next = new;
				evlp = new;
			}
			evlp->evp = evp;
			n_ev++;
		}

	} while (err >= 0 && evp && evp->ev != EV_EMPTY);
out:
	if (err == -EAGAIN) {
		err = 0;
	}

	if (err < 0) {
		meta_notify_freeevlist(evlp_head);
		evlp_head = NULL;
		return (err);
	} else if ((err == 0) && (evp->ev == EV_EMPTY)) {
	    Free(evp);
	    evp = NULL;
	}

	if (evpp_arg) {
		*evpp_arg = evlp_head;
	}

	return (n_ev);
}


/*
 * the guts of meta_notify_putev() and meta_notify_sendev()
 * are within this function.
 *
 * meta_notify_putev() is intended for general use by user-level code,
 * such as the GUI, to send user-defined events.
 *
 * meta_notify_sendev() is for "user-level driver" code, such as
 * set manipulation and the multi-host daemon to generate events.
 *
 * Note- only convention enforces this usage.
 */
int
meta_notify_doputev(md_ev_t *evp, md_error_t *ep)
{
	md_event_ioctl_t	evctl;

	if (!evp || !ep) {
		return (EINVAL);
	}

	/*
	 * users may only put events of type EQ_USER
	 */
	init_evctl(/* qname (unused in put) */ NULL,
			TAG_EMPTY,
			EQ_EMPTY,
			/* flags (unused in put) */ 0,
			(evp->setno == EV_ALLSETS)? MD_ALLSETS: evp->setno,
			(evp->obj == EV_ALLOBJS)? MD_ALLDEVS: evp->obj,
			EQ_PUT,
			evp->uev,
			&evctl);

	evctl.mdn_tag	= objlib_2_tagdrv(evp->obj_type);
	evctl.mdn_event	= evlib_2_evdrv(evp->ev);

	return (-meta_event(&evctl, ep));
}

/*
 * meta_notify_putev
 * - sends an event down to the notify driver (hence, all queues)
 * - returns 0 on success or errno
 */
int
meta_notify_putev(md_ev_t *evp, md_error_t *ep)
{
	if (!evp || !ep) {
		return (EINVAL);
	}

	evp->ev = EV_USER;	/* by definition */

	return (meta_notify_doputev(evp, ep));
}

/*
 * alternate put event entry point which allows
 * more control of event innards (for use by md "user-level drivers")
 *
 * Since this routine isn't for use by clients, the user event data
 * is always forced to be 0. That is only meaningful for events
 * of type EQ_USER (and those go through meta_notify_putev()), so
 * this is consistent.
 */
int
meta_notify_sendev(
	ev_obj_t	tag,
	set_t		set,
	md_dev64_t	dev,
	evid_t		ev)
{
	md_error_t		 status	= mdnullerror;
	md_error_t		*ep	= &status;
	md_ev_t			 ev_packet;
	int			 rc;

	ev_packet.obj_type	= tag;
	ev_packet.setno		= set;
	ev_packet.obj		= dev;
	ev_packet.ev		= ev;
	ev_packet.uev		= 0ULL;

	rc = meta_notify_doputev(&ev_packet, ep);

	if (0 == rc && !mdisok(ep)) {
		rc = EINVAL;
		mdclrerror(ep);
	}
	return (rc);
}

/*
 * meta_notify_putevlist
 * - sends all of the events in the event list
 * - returns number of events sent (>= 0) on success or -errno
 */
int
meta_notify_putevlist(md_evlist_t *evlp, md_error_t *ep)
{
	md_evlist_t	*evlpi;
	int		 n_ev	= 0;
	int		 err;

	if (!evlp) {
		err = 0;
		goto out;	/* that was easy */
	}

	for (n_ev = 0, evlpi = evlp; evlpi; evlpi = evlpi->next) {
		if ((err = meta_notify_putev(evlpi->evp, ep)) < 0) {
			goto out;
		}
		n_ev++;
	}
out:
	return (err != 0? err: n_ev);
}

/*
 * meta_notify_freevlist
 * - frees any memory allocated within the event list
 * - returns 0 on success or errno and sets ep as necessary
 */
void
meta_notify_freeevlist(md_evlist_t *evlp)
{
	md_evlist_t	*i;
	md_evlist_t	*next;

	for (i = evlp; i; i = i->next) {
		if (i && i->evp) {
			Free(i->evp);
			i->evp = NULL;
		}
	}
	for (i = evlp; i; /* NULL */) {
		next = i->next;
		Free(i);
		i = next;
	}
}