/*
 * Copyright (c) 2000 Silicon Graphics, Inc.  All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Further, this software is distributed without any warranty that it is
 * free of the rightful claim of any third person regarding infringement
 * or the like.	 Any license provided herein, whether implied or
 * otherwise, applies only to this software file.  Patent licenses, if
 * any, provided herein do not apply to combinations of this program with
 * other software, or any other product whatsoever.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc., 59
 * Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
 * Mountain View, CA  94043, or:
 *
 * http://www.sgi.com
 *
 * For further information regarding this notice, see:
 *
 * http://oss.sgi.com/projects/GenInfo/SGIGPLNoticeExplan/
 */
#include "xfs.h"
#include "dmapi.h"
#include "dmapi_kern.h"
#include "dmapi_private.h"

/* The "rights" portion of the DMAPI spec is not currently implemented.	 A
   framework for rights is provided in the code, but turns out to be a noop
   in practice.	 The following comments are a brain dump to serve as input to
   the poor soul that eventually has to get DMAPI rights working in IRIX.

   A DMAPI right is similar but not identical to the mrlock_t mechanism
   already used within the kernel.  The similarities are that it is a
   sleeping lock, and that a multiple-reader, single-writer protocol is used.
   How locks are obtained and dropped are different however.  With a mrlock_t,
   a thread grabs the lock, does some stuff, then drops the lock, and all other
   threads block in the meantime (assuming a write lock).  There is a one-to-
   one relationship between the lock and the thread which obtained the lock.
   Not so with DMAPI right locks.  A DMAPI lock is associated with a particular
   session/token/hanp/hlen quad; since there is a dm_tokdata_t structure for
   each such quad, you can think of it as a one-to-one relationship between the
   lock and a dm_tokdata_t.  Any application thread which presents the correct
   quad is entitled to grab or release the lock, or to use the rights
   associated with that lock.  The thread that grabs the lock does not have to
   be the one to use the lock, nor does it have to be the thread which drops
   the lock.  The lock can be held for very long periods of time, even across
   multiple systems calls by multiple application threads.  The idea is that a
   coordinated group of DMAPI application threads can grab the lock, issue a
   series of inode accesses and/or updates, then drop the lock, and be assured
   that no other thread in the system could be modifying the inode at the same
   time.  The kernel is expected to blindly trust that the application will
   not forget to unlock inodes it has locked, and will not deadlock itself
   against the kernel.

   There are two types of DMAPI rights, file object (inode) and filesystem
   object (superblock?).  An inode right is the equivalent of the combination
   of both the XFS ilock and iolock; if held exclusively, no data or metadata
   within the file can be changed by non-lock-holding threads.	The filesystem
   object lock is a little fuzzier; I think that if it is held, things like
   unmounts can be blocked, plus there is an event mask associated with the
   filesystem which can't be updated without the lock.	(By the way, that
   event mask is supposed to be persistent in the superblock; add that to
   your worklist :-)

   All events generated by XFS currently arrive with no rights, i.e.
   DM_RIGHT_NULL, and return to the filesystem with no rights.	It would be
   smart to leave it this way if possible, because it otherwise becomes more
   likely that an application thread will deadlock against the kernel if the
   one responsible for calling dm_get_events() happens to touch a file which
   was locked at the time the event was queued.	 Since the thread is blocked,
   it can't read the event in order to find and drop the lock.	Catch-22.  If
   you do have events that arrive with non-null rights, then dm_enqueue() needs
   to have code added for synchronous events which atomically switches the
   right from being a thread-based right to a dm_tokdata_t-based right without
   allowing the lock to drop in between.  You will probably have to add a new
   dm_fsys_vector entry point to do this.  The lock can't be lost during the
   switch, or other threads might change the inode or superblock in between.
   Likewise, if you need to return to the filesystem holding a right, then
   you need a DMAPI-to-thread atomic switch to occur, most likely in
   dm_change_right().  Again, the lock must not be lost during the switch; the
   DMAPI spec spends a couple of pages stressing this.	Another dm_fsys_vector
   entry point is probably the answer.

   There are several assumptions implied in the current layout of the code.
   First of all, if an event returns to the filesystem with a return value of
   zero, then the filesystem can assume that any locks (rights) held at the
   start of the event are still in effect at the end of the event.  (Note that
   the application could have temporarily dropped and reaquired the right
   while the event was outstanding, however).  If the event returns to the
   filesystem with an errno, then the filesystem must assume that it has lost
   any and all rights associated with any of the objects in the event.	This
   was done for a couple of reasons.  First of all, since an errno is being
   returned, most likely the filesystem is going to immediately drop all the
   locks anyway.  If the DMAPI code was required to unconditionally reobtain
   all locks before returning to the filesystem, then dm_pending() wouldn't
   work for NFS server threads because the process would block indefinitely
   trying to get its thread-based rights back, because the DMAPI-rights
   associated with the dm_tokdata_t in the outstanding event would prevent
   the rights from being obtained.  That would be a bad thing.	We wouldn't
   be able to let users Cntl-C out of read/write/truncate events either.

   If a case should ever surface where the thread has lost its rights even
   though it has a zero return status, or where the thread has rights even
   though it is returning with an errno, then this logic will have to be
   reworked.  This could be done by changing the 'right' parameters on all
   the event calls to (dm_right_t *), so that they could serve both as IN
   and OUT parameters.

   Some events such as DM_EVENT_DESTROY arrive without holding a vnode
   reference; if you don't have a vnode reference, you can't have a right
   on the file.

   One more quirk.  The DM_EVENT_UNMOUNT event is defined to be synchronous
   when it's behavior is asynchronous.	If an unmount event arrives with
   rights, the event should return with the same rights and should NOT leave
   any rights in the dm_tokdata_t where the application could use them.
*/


#define GETNEXTOFF(vdat)	((vdat).vd_offset + (vdat).vd_length)
#define HANDLE_SIZE(tdp)	\
	((tdp)->td_type & DM_TDT_VFS ? FSHSIZE : XFS_HSIZE((tdp)->td_handle))


/* Given a vnode pointer in a filesystem known to support DMAPI,
   build a tdp structure for the corresponding vnode.
*/

static dm_tokdata_t *
dm_vp_data(
	vnode_t		*vp,
	dm_right_t	right,
	int		referenced)	/* != 0, caller holds vnode reference */
{
	int		error;
	dm_tokdata_t	*tdp;

	tdp = kmem_cache_alloc(dm_tokdata_cachep, SLAB_KERNEL);
	if (tdp == NULL) {
		printk("%s/%d: kmem_cache_alloc(dm_tokdata_cachep) returned NULL\n", __FUNCTION__, __LINE__);
		return NULL;
	}

	tdp->td_next = NULL;
	tdp->td_tevp = NULL;
	tdp->td_app_ref = 0;
	tdp->td_orig_right = right;
	tdp->td_right = right;
	tdp->td_flags = DM_TDF_ORIG;
	if (referenced) {
		tdp->td_flags |= DM_TDF_EVTREF;
	}

	if (vp->v_type == VREG) {
		tdp->td_type = DM_TDT_REG;
	} else if (vp->v_type == VDIR) {
		tdp->td_type = DM_TDT_DIR;
	} else if (vp->v_type == VLNK) {
		tdp->td_type = DM_TDT_LNK;
	} else {
		tdp->td_type = DM_TDT_OTH;
	}

	if (referenced) {
		tdp->td_vp = vp;
	} else {
		tdp->td_vp = NULL;
	}
	tdp->td_vcount = 0;

	if ((error = dm_vp_to_handle(vp, &tdp->td_handle)) != 0) {
		panic("dm_vp_data: dm_vp_to_handle failed for vp %p in "
			"a DMAPI filesystem, errno %d\n", vp, error);
	}

	return(tdp);
}


/* Given a vfs pointer to a filesystem known to support DMAPI, build a tdp
   structure for that vfsp.
*/
static dm_tokdata_t *
dm_vfs_data(
	vfs_t		*vfsp,
	vnode_t		*vp,		/* will be NULL for DM_EVENT_UNMOUNT */
	dm_right_t	right)
{
	dm_tokdata_t	*tdp;

	tdp = kmem_cache_alloc(dm_tokdata_cachep, SLAB_KERNEL);
	if (tdp == NULL) {
		printk("%s/%d: kmem_cache_alloc(dm_tokdata_cachep) returned NULL\n", __FUNCTION__, __LINE__);
		return NULL;
	}

	tdp->td_next = NULL;
	tdp->td_tevp = NULL;
	tdp->td_app_ref = 0;
	tdp->td_orig_right = right;
	tdp->td_right = right;
	tdp->td_flags = DM_TDF_ORIG;
	if (vp) {
		tdp->td_flags |= DM_TDF_EVTREF;
	}
	tdp->td_type = DM_TDT_VFS;
	if (vp) {
		tdp->td_vp = vp;
	} else {
		tdp->td_vp = NULL;
	}
	tdp->td_vcount = 0;

	memcpy(&tdp->td_handle.ha_fsid, vfsp->vfs_altfsid, sizeof(xfs_fsid_t));
	memset((char *)&tdp->td_handle.ha_fsid + sizeof(xfs_fsid_t), 0,
		sizeof(tdp->td_handle) - sizeof(xfs_fsid_t));

	return(tdp);
}


/* Link a tdp structure into the tevp. */

static void
dm_add_handle_to_event(
	dm_tokevent_t	*tevp,
	dm_tokdata_t	*tdp)
{
	tdp->td_next = tevp->te_tdp;
	tevp->te_tdp = tdp;
	tdp->td_tevp = tevp;
}


/* Generate the given data event for the vnode, and wait for a reply.  The
   caller must guarantee that the vnode's reference count is greater than zero
   so that the filesystem can't disappear while the request is outstanding.
*/

int
dm_send_data_event(
	dm_eventtype_t	event,
	bhv_desc_t	*bdp,
	dm_right_t	vp_right,	/* current right for vp */
	xfs_off_t	offset,
	size_t		length,
	int		flags)		/* 0 or DM_FLAGS_NDELAY */
{
	dm_data_event_t *datap;
	dm_tokevent_t	*tevp;
	dm_tokdata_t	*tdp;
	vnode_t		*vp;
	int		error;

	vp = BHV_TO_VNODE(bdp);
	tdp = dm_vp_data(vp, vp_right, /* reference held */ 1);
	if (tdp == NULL)
		return ENOMEM;

	/* Calculate the size of the event in bytes, create an event structure
	   for it, and insert the file's handle into the event.
	*/

	tevp = dm_evt_create_tevp(event, HANDLE_SIZE(tdp), (void **)&datap);
	if (tevp == NULL) {
		kmem_cache_free(dm_tokdata_cachep, tdp);
		return(ENOMEM);
	}
	dm_add_handle_to_event(tevp, tdp);

	/* Now fill in all the dm_data_event_t fields. */

	datap->de_handle.vd_offset = sizeof(*datap);
	datap->de_handle.vd_length = HANDLE_SIZE(tdp);
	memcpy((char *)datap + datap->de_handle.vd_offset, &tdp->td_handle,
		datap->de_handle.vd_length);
	datap->de_offset = offset;
	datap->de_length = length;

	/* Queue the message and wait for the reply. */

	error = dm_enqueue_normal_event(vp->v_vfsp, tevp, flags);

	/* If no errors occurred, we must leave with the same rights we had
	   upon entry.	If errors occurred, we must leave with no rights.
	*/

	dm_evt_rele_tevp(tevp, error);

	return(error);
}


/* Generate the destroy event for the vnode and wait until the request has been
   queued.  The caller does not hold a vnode reference or a right on the vnode,
   but it must otherwise lock down the vnode such that the filesystem can't
   disappear while the request is waiting to be queued.	 While waiting to be
   queued, the vnode must not be referenceable either by path or by a call
   to dm_handle_to_vp().
*/

int
dm_send_destroy_event(
	bhv_desc_t	*bdp,
	dm_right_t	vp_right)	/* always DM_RIGHT_NULL */
{
	dm_fsys_vector_t *fsys_vector;
	dm_tokevent_t	*tevp;
	dm_tokdata_t	*tdp;
	dm_destroy_event_t *destp;
	dm_attrname_t	attrname;
	vnode_t		*vp;
	char		*value;
	int		value_len;
	int		error;

	vp = BHV_TO_VNODE(bdp);
	tdp = dm_vp_data(vp, vp_right, /* no reference held */ 0);
	if (tdp == NULL)
		return ENOMEM;

	if ((error = dm_waitfor_destroy_attrname(vp->v_vfsp, &attrname)) != 0)
		return(error);

	/* If a return-on-destroy attribute name exists for this filesystem,
	   see if the object being deleted has this attribute.	If the object
	   doesn't have the attribute or if we encounter an error, then send
	   the event without the attribute.
	 */

	value_len = -1;		/* because zero is a valid attribute length */
	if (attrname.an_chars[0] != '\0') {
		fsys_vector = dm_fsys_vector(vp);
		error = fsys_vector->get_destroy_dmattr(vp, vp_right, &attrname,
			&value, &value_len);
		if (error)
			return error;
	}

	/* Now that we know the size of the attribute value, if any, calculate
	   the size of the event in bytes, create an event structure for it,
	   and insert the handle into the event.
	*/

	tevp = dm_evt_create_tevp(DM_EVENT_DESTROY,
		HANDLE_SIZE(tdp) + (value_len >= 0 ? value_len : 0),
		(void **)&destp);
	if (tevp == NULL) {
		kmem_cache_free(dm_tokdata_cachep, tdp);
		if (value_len > 0)
			kfree(value);
		return(ENOMEM);
	}
	dm_add_handle_to_event(tevp, tdp);

	/* Now fill in all the dm_destroy_event_t fields. */

	destp->ds_handle.vd_offset = sizeof(*destp);
	destp->ds_handle.vd_length = HANDLE_SIZE(tdp);
	memcpy((char *)destp + destp->ds_handle.vd_offset, &tdp->td_handle,
		destp->ds_handle.vd_length);
	if (value_len >= 0) {
		destp->ds_attrname = attrname;
		destp->ds_attrcopy.vd_length = value_len;
		if (value_len == 0) {
			destp->ds_attrcopy.vd_offset = 0;
		} else {
			destp->ds_attrcopy.vd_offset = GETNEXTOFF(destp->ds_handle);
			memcpy((char *)destp + destp->ds_attrcopy.vd_offset, value,
				value_len);
			kfree(value);
		}
	}

	/* Queue the message asynchronously. */

	error = dm_enqueue_normal_event(vp->v_vfsp, tevp, 0);

	/* Since we had no rights upon entry, we have none to reobtain before
	   leaving.
	*/

	dm_evt_rele_tevp(tevp, 1);

	return(error);
}


/* The dm_mount_event_t event is sent in turn to all sessions that have asked
   for it until one either rejects it or accepts it.  The filesystem is not
   going anywhere because the mount is blocked until the event is answered.
*/

int
dm_send_mount_event(
	vfs_t		*vfsp,		/* filesystem being mounted */
	dm_right_t	vfsp_right,
	bhv_desc_t	*bdp,		/* mounted on directory */
	dm_right_t	vp_right,
	bhv_desc_t	*rootbdp,
	dm_right_t	rootvp_right,
	char		*name1,		/* mount path */
	char		*name2)		/* filesystem device name */
{
	int		error;
	dm_tokevent_t	*tevp = NULL;
	dm_tokdata_t	*tdp1 = NULL;	/* filesystem handle for event */
	dm_tokdata_t	*tdp2 = NULL;	/* file handle for mounted-on dir. */
	dm_tokdata_t	*tdp3 = NULL;	/* file handle for root vnode */
	dm_mount_event_t *mp;
	size_t		nextoff;
	vnode_t		*vp = NULL; /* mounted on directory */
	vnode_t		*rootvp = BHV_TO_VNODE(rootbdp);

	/* Convert the vfsp to a filesystem handle, and vp and rootvp into
	   file handles.  vp (the mounted-on directory) may not have a handle
	   if it is a different filesystem type such as EFS which does not
	   support DMAPI.
	*/

	if(bdp)
		vp = BHV_TO_VNODE(bdp);

	tdp1 = dm_vfs_data(vfsp, rootvp, vfsp_right);
	if (tdp1 == NULL)
		goto out_nomem;

	if ((vp == NULL) || dm_check_dmapi_vp(vp)) {
		vp = NULL;	/* assume we are mounting on non XFS */
	} else {
		tdp2 = dm_vp_data(vp, vp_right, /* reference held */ 1);
		if (tdp2 == NULL)
			goto out_nomem;
	}

	tdp3 = dm_vp_data(rootvp, rootvp_right, /* reference held */ 1);
	if (tdp3 == NULL)
		goto out_nomem;

	/* Calculate the size of the event in bytes, create an event structure
	   for it, and insert the handles into the event.
	*/

	tevp = dm_evt_create_tevp(DM_EVENT_MOUNT,
			HANDLE_SIZE(tdp1) + (vp ? HANDLE_SIZE(tdp2) : 0) +
			HANDLE_SIZE(tdp3) + strlen(name1) + 1 +
			strlen(name2) + 1, (void **)&mp);
	if (tevp == NULL)
		goto out_nomem;

	dm_add_handle_to_event(tevp, tdp1);
	if (vp)
		dm_add_handle_to_event(tevp, tdp2);
	dm_add_handle_to_event(tevp, tdp3);

	/* Now fill in all the dm_mount_event_t fields. */

	mp->me_handle1.vd_offset = sizeof(*mp);
	mp->me_handle1.vd_length = HANDLE_SIZE(tdp1);
	memcpy((char *) mp + mp->me_handle1.vd_offset, &tdp1->td_handle,
			mp->me_handle1.vd_length);
	nextoff = GETNEXTOFF(mp->me_handle1);

	if (vp) {
		mp->me_handle2.vd_offset = nextoff;
		mp->me_handle2.vd_length = HANDLE_SIZE(tdp2);
		memcpy((char *)mp + mp->me_handle2.vd_offset, &tdp2->td_handle,
			mp->me_handle2.vd_length);
		nextoff = GETNEXTOFF(mp->me_handle2);
	}

	mp->me_name1.vd_offset = nextoff;
	mp->me_name1.vd_length = strlen(name1) + 1;
	memcpy((char *)mp + mp->me_name1.vd_offset, name1, mp->me_name1.vd_length);
	nextoff = GETNEXTOFF(mp->me_name1);

	mp->me_name2.vd_offset = nextoff;
	mp->me_name2.vd_length = strlen(name2) + 1;
	memcpy((char *)mp + mp->me_name2.vd_offset, name2, mp->me_name2.vd_length);
	nextoff = GETNEXTOFF(mp->me_name2);

	mp->me_roothandle.vd_offset = nextoff;
	mp->me_roothandle.vd_length = HANDLE_SIZE(tdp3);
	memcpy((char *)mp + mp->me_roothandle.vd_offset, &tdp3->td_handle,
			mp->me_roothandle.vd_length);

	mp->me_mode = (vfsp->vfs_flag & VFS_RDONLY ? DM_MOUNT_RDONLY : 0);

	/* Queue the message and wait for the reply. */

	error = dm_enqueue_mount_event(vfsp, tevp);

	/* If no errors occurred, we must leave with the same rights we had
	   upon entry.	If errors occurred, we must leave with no rights.
	*/

	dm_evt_rele_tevp(tevp, error);

	return(error);

out_nomem:
	if (tevp)
		kfree(tevp);
	if (tdp1)
		kmem_cache_free(dm_tokdata_cachep, tdp1);
	if (tdp2)
		kmem_cache_free(dm_tokdata_cachep, tdp2);
	if (tdp3)
		kmem_cache_free(dm_tokdata_cachep, tdp3);
	return ENOMEM;
}


/* Generate an DM_EVENT_UNMOUNT event and wait for a reply.  The 'retcode'
   field indicates whether this is a successful or unsuccessful unmount.
   If successful, the filesystem is already unmounted, and any pending handle
   reference to the filesystem will be failed.	If the unmount was
   unsuccessful, then the filesystem will be placed back into full service.

   The DM_EVENT_UNMOUNT event should really be asynchronous, because the
   application has no control over whether or not the unmount succeeds.	 (The
   DMAPI spec defined it that way because asynchronous events aren't always
   guaranteed to be delivered.)

   Since the filesystem is already unmounted in the successful case, the
   DM_EVENT_UNMOUNT event can't make available any vnode to be used in
   subsequent sid/hanp/hlen/token calls by the application.  The event will
   hang around until the application does a DM_RESP_CONTINUE, but the handle
   within the event is unusable by the application.
*/

void
dm_send_unmount_event(
	vfs_t		*vfsp,
	vnode_t		*vp,		/* NULL if unmount successful */
	dm_right_t	vfsp_right,
	mode_t		mode,
	int		retcode,	/* errno, if unmount failed */
	int		flags)
{
	dm_namesp_event_t	*np;
	dm_tokevent_t	*tevp;
	dm_tokdata_t	*tdp1;

	/* If the unmount failed, put the filesystem back into full service,
	   allowing blocked handle references to finish.  If it succeeded, put
	   the filesystem into the DM_STATE_UNMOUNTED state and fail all
	   blocked DM_NO_TOKEN handle accesses.
	*/

	if (retcode != 0) {	/* unmount was unsuccessful */
		dm_change_fsys_entry(vfsp, DM_STATE_MOUNTED);
	} else {
		dm_change_fsys_entry(vfsp, DM_STATE_UNMOUNTED);
	}

	/* If the event wasn't in the filesystem dm_eventset_t, just remove
	   the filesystem from the list of DMAPI filesystems and return.
	*/

	if (flags & DM_FLAGS_UNWANTED) {
		if (retcode == 0)
			dm_remove_fsys_entry(vfsp);
		return;
	}

	/* Calculate the size of the event in bytes and allocate zeroed memory
	   for it.
	*/

	tdp1 = dm_vfs_data(vfsp, vp, vfsp_right);
	if (tdp1 == NULL)
		return;

	tevp = dm_evt_create_tevp(DM_EVENT_UNMOUNT, HANDLE_SIZE(tdp1),
		(void **)&np);
	if (tevp == NULL) {
		kmem_cache_free(dm_tokdata_cachep, tdp1);
		return;
	}

	dm_add_handle_to_event(tevp, tdp1);

	/* Now copy in all the dm_namesp_event_t specific fields. */

	np->ne_handle1.vd_offset = sizeof(*np);
	np->ne_handle1.vd_length = HANDLE_SIZE(tdp1);
	memcpy((char *) np + np->ne_handle1.vd_offset, &tdp1->td_handle,
			np->ne_handle1.vd_length);
	np->ne_mode = mode;
	np->ne_retcode = retcode;

	/* Since DM_EVENT_UNMOUNT is effectively asynchronous, queue the
	   message and ignore any error return for DM_EVENT_UNMOUNT.
	*/

	(void)dm_enqueue_normal_event(vfsp, tevp, flags);

	if (retcode == 0)
		dm_remove_fsys_entry(vfsp);

	dm_evt_rele_tevp(tevp, 0);
}


/* Generate the given namespace event and wait for a reply (if synchronous) or
   until the event has been queued (asynchronous).  The caller must guarantee
   that at least one vnode within the filesystem has had its reference count
   bumped so that the filesystem can't disappear while the event is
   outstanding.
*/

int
dm_send_namesp_event(
	dm_eventtype_t	event,
	bhv_desc_t	*bdp1,
	dm_right_t	vp1_right,
	bhv_desc_t	*bdp2,
	dm_right_t	vp2_right,
	char		*name1,
	char		*name2,
	mode_t		mode,
	int		retcode,
	int		flags)
{
	dm_namesp_event_t	*np;
	dm_tokevent_t	*tevp;
	dm_tokdata_t	*tdp1 = NULL;	/* primary handle for event */
	dm_tokdata_t	*tdp2 = NULL;	/* additional handle for event */
	vfs_t		*sidvfsp;	/* vfs event must be registered on */
	size_t		nextoff;
	int		error;
	vnode_t		*vp1;

	vp1 = BHV_TO_VNODE(bdp1);
	sidvfsp = vp1->v_vfsp;

	switch (event) {
	case DM_EVENT_PREUNMOUNT:
		/*
		 *  PREUNMOUNT - Send the file system handle in handle1,
		 *  and the handle for the root dir in the second.  Otherwise
		 *  it's a normal sync message; i.e. succeeds or fails
		 *  depending on the app's return code.
		 *	bdp1 and bdp2 are both the root dir of mounted FS
		 *	vp1_right is the filesystem right.
		 *	vp2_right is the root inode right.
		 */

		if (flags & DM_FLAGS_UNWANTED) {
			dm_change_fsys_entry(sidvfsp, DM_STATE_UNMOUNTING);
			return(0);
		}
		tdp1 = dm_vfs_data(sidvfsp, vp1, vp1_right);
		if (tdp1 == NULL)
			return ENOMEM;
		tdp2 = dm_vp_data(BHV_TO_VNODE(bdp2), vp2_right, /* reference held */ 1);
		if (tdp2 == NULL) {
			kmem_cache_free(dm_tokdata_cachep, tdp1);
			return ENOMEM;
		}
		break;

	case DM_EVENT_NOSPACE:
		/* vp1_right is the filesystem right. */

		tdp1 = dm_vfs_data(sidvfsp, vp1, vp1_right);
		if (tdp1 == NULL)
			return ENOMEM;
		tdp2 = dm_vp_data(BHV_TO_VNODE(bdp2), vp2_right, /* reference held */ 1); /* additional info - not in the spec */
		if (tdp2 == NULL) {
			kmem_cache_free(dm_tokdata_cachep, tdp1);
			return ENOMEM;
		}
		break;

	default:
		/* All other events only pass in vnodes and don't require any
		   special cases.
		*/

		tdp1 = dm_vp_data(vp1, vp1_right, /* reference held */ 1);
		if (tdp1 == NULL)
			return ENOMEM;
		if (bdp2) {
			tdp2 = dm_vp_data(BHV_TO_VNODE(bdp2), vp2_right, /* reference held */ 1);
			if (tdp2 == NULL) {
				kmem_cache_free(dm_tokdata_cachep, tdp1);
				return ENOMEM;
			}
		}
	}

	/* Calculate the size of the event in bytes and allocate zeroed memory
	   for it.
	*/

	tevp = dm_evt_create_tevp(event,
		HANDLE_SIZE(tdp1) + (bdp2 ? HANDLE_SIZE(tdp2) : 0) +
		(name1 ? strlen(name1) + 1 : 0) +
		(name2 ? strlen(name2) + 1 : 0), (void **)&np);
	if (tevp == NULL) {
		if (tdp1)
			kmem_cache_free(dm_tokdata_cachep, tdp1);
		if (tdp2)
			kmem_cache_free(dm_tokdata_cachep, tdp2);
		return(ENOMEM);
	}

	dm_add_handle_to_event(tevp, tdp1);
	if (bdp2)
		dm_add_handle_to_event(tevp, tdp2);

	/* Now copy in all the dm_namesp_event_t specific fields. */

	np->ne_handle1.vd_offset = sizeof(*np);
	np->ne_handle1.vd_length = HANDLE_SIZE(tdp1);
	memcpy((char *) np + np->ne_handle1.vd_offset, &tdp1->td_handle,
			np->ne_handle1.vd_length);
	nextoff = GETNEXTOFF(np->ne_handle1);
	if (bdp2) {
		np->ne_handle2.vd_offset = nextoff;
		np->ne_handle2.vd_length = HANDLE_SIZE(tdp2);
		memcpy((char *)np + np->ne_handle2.vd_offset, &tdp2->td_handle,
				np->ne_handle2.vd_length);
		nextoff = GETNEXTOFF(np->ne_handle2);
	}
	if (name1) {
		np->ne_name1.vd_offset = nextoff;
		np->ne_name1.vd_length = strlen(name1) + 1;
		memcpy((char *)np + np->ne_name1.vd_offset, name1,
				np->ne_name1.vd_length);
		nextoff = GETNEXTOFF(np->ne_name1);
	}
	if (name2) {
		np->ne_name2.vd_offset = nextoff;
		np->ne_name2.vd_length = strlen(name2) + 1;
		memcpy((char *)np + np->ne_name2.vd_offset, name2,
				np->ne_name2.vd_length);
	}
	np->ne_mode = mode;
	np->ne_retcode = retcode;

	/* Queue the message and wait for the reply. */

	error = dm_enqueue_normal_event(sidvfsp, tevp, flags);

	/* If no errors occurred, we must leave with the same rights we had
	   upon entry.	If errors occurred, we must leave with no rights.
	*/

	dm_evt_rele_tevp(tevp, error);

	if (!error && event == DM_EVENT_PREUNMOUNT) {
		dm_change_fsys_entry(sidvfsp, DM_STATE_UNMOUNTING);
	}

	return(error);
}


/*
 *  Send a message of type "DM_EVENT_USER".  Since no vnode is involved, we
 *  don't have to worry about rights here.
 */

int
dm_send_msg(
	dm_sessid_t	targetsid,
	dm_msgtype_t	msgtype,		/* SYNC or ASYNC */
	size_t		buflen,
	void		*bufp)
{
	dm_tokevent_t	*tevp;
	int		sync;
	void		*msgp;
	int		error;

	if (buflen > DM_MAX_MSG_DATA)
		return(E2BIG);
	if (msgtype == DM_MSGTYPE_ASYNC) {
		sync = 0;
	} else if (msgtype == DM_MSGTYPE_SYNC) {
		sync = 1;
	} else {
		return(EINVAL);
	}

	tevp = dm_evt_create_tevp(DM_EVENT_USER, buflen, (void **)&msgp);
	if (tevp == NULL)
		return ENOMEM;

	if (buflen && copy_from_user(msgp, bufp, buflen)) {
		dm_evt_rele_tevp(tevp, 0);
		return(EFAULT);
	}

	/* Enqueue the request and wait for the reply. */

	error = dm_enqueue_sendmsg_event(targetsid, tevp, sync);

	/* Destroy the tevp and return the reply.  (dm_pending is not
	   supported here.)
	*/

	dm_evt_rele_tevp(tevp, error);

	return(error);
}


/*
 *  Send a message of type "DM_EVENT_USER".  Since no vnode is involved, we
 *  don't have to worry about rights here.
 */

int
dm_create_userevent(
	dm_sessid_t	sid,
	size_t		msglen,
	void		*msgdatap,
	dm_token_t	*tokenp)		/* return token created */
{
	dm_tokevent_t	*tevp;
	dm_token_t	token;
	int		error;
	void		*msgp;

	if (msglen > DM_MAX_MSG_DATA)
		return(E2BIG);

	tevp = dm_evt_create_tevp(DM_EVENT_USER, msglen, (void **)&msgp);
	if (tevp == NULL)
		return(ENOMEM);

	if (msglen && copy_from_user(msgp, msgdatap, msglen)) {
		dm_evt_rele_tevp(tevp, 0);
		return(EFAULT);
	}

	/* Queue the message.  If that didn't work, free the tevp structure. */

	if ((error = dm_enqueue_user_event(sid, tevp, &token)) != 0)
		dm_evt_rele_tevp(tevp, 0);

	if (!error && copy_to_user(tokenp, &token, sizeof(token)))
		error = EFAULT;

	return(error);
}
