
/*
 * ctrlsubs.c: version 3.3 of 6/4/85
 *
 *  ctrlsubs.c -- VIOS Control request subroutines
 *  copyright (c) American Information Systems Corporation
 *	Daniel Steinberg	November, 1984
 */

#include "vinc/viosconf.h"
#include "vinc/viostatus.h"
#include "vinc/devfuncs.h"
#include "vinc/poolfuncs.h"
#include "vinc/viocmds.h"


    PDD_PTR
pname_search (name)
    register char *name;

/* pname_search (name) -- Search for Physical Device name
 *
 *	in:	name		string containing device name to find
 *	return:	(PDD_PTR)	bufptr to Physical Device Descriptor, or NULL
 *	thrown:	(NONE)
 *
 *	Search the Physical Device Descriptor list for a PDD whose name
 *	matches 'name' and return it, if found.  If a name match is found,
 *	but the device is marked for removal (on i/o completion or detach),
 *	then NULL is returned.
 */
{
    register PDD_PTR ptr;		/* first of linked device list */
    register PDD *p;

    ptr = (PDD_PTR) Pdevices.next;	/* point to first physical device */
    while (ptr NE NULL)
	{
	p = *ptr;		/* dereference ptr */
	if ( strcmp(name, *Devname(p)) EQ 0 )
	    {
	    if ( Rundown(p) )  ptr = NULL;
	    return(ptr);	/* this one matches */
	    }
	ptr = Next(p);		/* point to next device, if any */
	}
    return(NULL);		/* not found */
}

    IMP_MODULE **				/* throws: V_NO_DEVICE */
imp_search (str)
    register char *str;

/* imp_search (str) -- Find the implementation module for a physical device
 *
 *	in:	str		Address of device name string
 *	return:	(IMP_MODULE**)	Bufptr to appropriate implementation module
 *	thrown:	V_NO_DEVICE	no name match
 *
 *	Compares the first letters of the string at 'str' with the names
 *	of the existing implementation modules.  If there is a match,
 *	returns the implementation module descriptor address.
 *
 *	Implementation module names consist of a few characters followed by a
 *	colon, and this syntax is enforced.  Lower case matches upper case for
 *	module names.
 *
 *	If there is no colon in 'str', or if there is no match of the portion
 *	of 'str' up to and including the colon with an implementation module
 *	name, throw V_NO_DEVICE.
 */
{
    register IMP_MODULE **ptr;
    register IMP_MODULE *p;
    register int len;

    /* Locate the terminating colon, then search for a match */

    for (len=0; str[len] NE '\0'; )	/* Loop until break or end of str */
	{
	if (str[len++] EQ ':')
	    {
	    /* 'len' is now the number of characters to match */

	    ptr = (IMP_MODULE**) Imp_modules.next;	/* Point to 1st */

	    while (ptr NE NULL)
		{
		p = *ptr;		/* dereference ptr */
		if ( strncmp(str, *Imp_name(p), len) EQ 0 )
		    {
		    return(ptr);	/* Name matches...return imp module */
		    }
		ptr = Next(p);		/* Try next one, if any */
		} /*while*/

	    break;	/* Imp module not found...break out of for loop */
	    } /*if*/
	} /*for*/
    
    throw (V_NO_DEVICE);	/* No colon or no match */
}

    VDD_PTR					/* throws: V_NO_DEVICE */
vdname_search (name, ros)
    register char *name;
    register OS_IDENT ros;

/* vdname_search (name, ros) -- Find a virtual device with matching name/os
 *
 *	in:	name		string containing device name to find
 *	in:	ros		target os_id
 *	return:	(VDD_PTR)	bufptr to Virtual Device Descriptor
 *	thrown:	V_NO_DEVICE	name/os match not found
 *
 *	Search the Virtual Device Descriptor list for a VDD whose name and owner
 *	match 'name' and 'ros' and return it, if found.  Uses vname_search()
 *	(below) to do the table searching but specifically disallows the
 *	location, by a target O.S., of the Hyper-Exec devices.
 *	This routine is used, for example, to locate a device for an O.S.
 *	i/o request, while the other would check for generalized name conflict.
 *
 *	Throws V_NO_DEVICE on failure, rather than returning NULL.
 */
{
    register VDD_PTR ptr;		/* first of linked device list */

    if ( ((ptr = vname_search(name,ros)) EQ NULL) OR
	( (ros NE HYPER_ID) AND (Ownerid(*ptr) EQ HYPER_ID) ) )
		throw (V_NO_DEVICE);	/* OS may not find Hyper-Exec VDs */

    return(ptr);	/* this one matches */
}

    VDD_PTR
vname_search (name, tos)
    register char *name;
    OS_IDENT tos;

/* vname_search (name, ros) -- Find a virtual device with matching name/os
 *
 *	in:	name		string containing device name to find
 *	in:	tos		target os_id
 *	return:	(VDD_PTR)	bufptr to Virtual Device Descriptor, or NULL
 *	thrown:	(NONE)
 *
 *	Search the Virtual Device Descriptor list for a VDD whose name and owner
 *	match 'name' and 'tos' and return it, if found (or NULL, if not).
 *	If either a device or the target are HYPER_ID, a name match is enough
 *	to qualify the device.
 *	Devices marked for removal are never found.
 *	See also vdname_search() (above).
 */
{
    register VDD_PTR ptr;		/* first of linked device list */
    register VDD *p;

    ptr = (VDD_PTR) Vdevices.next;	/* point to first Virtual device */
    while (ptr NE NULL)
	{
	p = *ptr;		/* dereference ptr */
	if ( ( (tos EQ HYPER_ID) OR (Ownerid(p) EQ HYPER_ID) OR
						    (tos EQ Ownerid(p)))
		    AND
		(strcmp(name, *Devname(p)) EQ 0) )
	    {
	    if ( Rundown(p) )  ptr = NULL;
	    return(ptr);	/* this one matches */
	    }
	ptr = Next(p);		/* point to next device, if any */
	}
    return(NULL);		/* not found */
}

    CLASS_MODULE **				/* throws: V_BAD_PARAMS */
cls_search (class)
    register UB8 class;

/* cls_search (class) -- Find the class handling module for a given class
 *
 *	in:	class		Class number to match
 *	return:(CLASS_MODULE**)	Bufptr to appropriate class handling module
 *	thrown:	V_BAD_PARAMS	no class match
 *
 *	Compares the device class given by 'class' with the classes
 *	of the existing class handling modules.  If there is a match,
 *	returns the class handling module descriptor address.
 *
 *	If no match is found, throw V_BAD_PARAMS.
 */
{
    register CLASS_MODULE **ptr;
    register CLASS_MODULE *p;

    ptr = (CLASS_MODULE**) Class_modules.next;		/* Point to 1st */

    while (ptr NE NULL)
	{
	if ( C_class(p = *ptr) EQ class )
	    {
	    return(ptr);	/* Name matches...return class module */
	    }
	ptr = Next(p);		/* Try next one, if any */
	} /*while*/

    throw (V_BAD_PARAMS);	/* No match */
}

    							/* throw: V_NO_DEVICE */
checkvd (vdd, ros)
    register VDD_PTR vdd;
    OS_IDENT ros;

/* checkvd (vdd, ros) -- Verify that a VDD from an I/O packet is valid
 *
 * 	in:	vdd		VDD bufptr (from pkt->device)
 *	in:	ros		OS id (from pkt->os)
 *	return:	(NONE)
 *	thrown:	V_NO_DEVICE	VDD not valid
 *
 *	Verifies that the Virtual Device Descriptor 'vdd' is a valid
 *	bufptr and is linked into the Virtual Device list.  If it is,
 *	checks further that the O.S. of the device matches the request
 *	O.S. ('ros') (unless 'ros' is the HYPER_ID) and that the device
 *	is not marked for removal.
 *
 *	If any of the above checks fail, V_NO_DEVICE is thrown.
 */
{
    register VDD *vd;
    register VDD_PTR tmp;

    				/* chk_pool returns FALSE if not bufptr */
    if ( (chk_pool(vdd)) AND (Devtype(vd = *vdd) EQ VDTYPE) )
	{
	tmp = (VDD_PTR) Vdevices.next;
	while (tmp NE NULL)
	    {
	    if (tmp EQ vdd)		/* found the VDD */
		if ( ((ros EQ HYPER_ID) OR (ros EQ Ownerid(vd))) AND
			(Devname(vd) NE NULL) AND
			(NOT Rundown(vd)) )
		    return;		/* valid device */
		else
		    break;		/* don't keep looking */
	    tmp = Next(*tmp);
	    } /*while*/
	} /*if (bufptr)*/

    throw(V_NO_DEVICE);		/* bad VDD, not in list, or wrong owner */
}

    							/* throw: V_NO_DEVICE */
checkpd (pdd)
    register PDD_PTR pdd;

/* checkpd (pdd) -- Verify that a PDD from an I/O packet is valid
 *
 * 	in:	pdd		PDD bufptr (from pkt->device)
 *	return:	(NONE)
 *	thrown:	V_NO_DEVICE	PDD not valid
 *
 *	Verifies that the Physical Device Descriptor 'pdd' is a valid
 *	bufptr and is linked into the Physical Device list.  If it is,
 *	checks further that the device is not marked for removal.
 *
 *	If any of the above checks fail, V_NO_DEVICE is thrown.
 */
{
    register PDD *pd;
    register PDD_PTR tmp;

    				/* chk_pool returns FALSE if not bufptr */
    if ( (chk_pool(pdd)) AND (Devtype(pd = *pdd) EQ PDTYPE) )
	{
	tmp = (PDD_PTR) Pdevices.next;
	while (tmp NE NULL)
	    {
	    if (tmp EQ pdd)		/* found the PDD */
		{
		if (Rundown(pd))
		    break;		/* can't access if marked to remove */
		else
		    return;		/* valid device */
		}
	    tmp = Next(*tmp);
	    } /*while*/
	} /*if (bufptr)*/

    throw(V_NO_DEVICE);		/* bad PDD, or not in list */
}


rem_units(vdd)
    VDD_PTR vdd;

/* rem_units (vdd) -- Remove all virtual device unit assignments from a VDD
 *
 *	in:	vdd		targer Virtual Device Descriptor
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Removes all of the device unit assignments from the given 'vdd',
 *	namely, Primary Input, Primary Output, Error Input, Journal Input,
 *	Journal Output, and, if Record-Sequential Class, EOT Input.
 *	Uses clr_unit() to clear assignments so that pending removals
 *	on detach will be initiated.
 */
{
    register VDD *vd;

    if (Devclass(vd = *vdd) EQ REC_SEQ)
	while (Recs_eot(vd) NE NULL)  pop_unit(vdd);	/* pop eot_inputs */

    clr_unit (&Primaryoutput(vd));	/* deassign all other units */
    clr_unit (&Primaryinput(vd));
    clr_unit (&Errorinput(vd));
    clr_unit (&Journaloutput(vd));
    clr_unit (&Journalinput(vd));
}


rem_references(vdd)
    VDD_PTR vdd;

/* rem_references (vdd) -- Remove all device assignments that point to a device
 *
 *	in:	vdd		targer Virtual Device Descriptor
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Search through the Virtual Device List and clear all unit assignments
 *	that reference the target 'vdd', until the attachment count goes to
 *	zero (and so could loop forever if not accurate).
 *	Does not process device removal after attachments goes to zero.
 *
 *	This routine (and pop_unit()) would have to be checked very carefully
 *	if it was to be called with a Physical Device Descriptor.
 */
{
    register VDD *p;
    register VDD_PTR ptr;
    register VDD_PTR *u;

    ptr = (VDD_PTR) Vdevices.next;
    while ( Attachments(*vdd) AND (ptr NE NULL) )
	{
	p = *ptr;
	u = &Primaryinput(p);	/* primary input may be pushed or set */

	while ( *u EQ vdd )
	    {
	    if ( (Devclass(p) EQ REC_SEQ) AND (Recs_eot(p) NE NULL) )
		pop_unit(ptr);	/* pop primary input unit */
	    else
		clr_unit(u);	/* clear primary input unit */
	    }
	if ( *(u = (&Primaryoutput(p))) EQ vdd ) clr_unit(u);
	if ( *(u = (&Errorinput(p))) EQ vdd ) clr_unit(u);
	if ( *(u = (&Journaloutput(p))) EQ vdd ) clr_unit(u);
	if ( *(u = (&Journalinput(p))) EQ vdd ) clr_unit(u);
	ptr = Next(p);
	}
}


clr_unit (unit)
    register VDD_PTR *unit;	/* ptr to unit assignment to clear */

/* clr_unit (unit) -- Clear a vdd unit assignment
 *
 *	mod:	unit		Ptr to target unit slot of VDD / Returned NULL
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Clears the unit assignment of the vdd slot pointed to by 'unit'.
 *	If the assigned device is ready for removal, that, too, is performed.
 *
 *	If the device slot is already clear (*unit == NULL), nothing happens.
 *
 *	The unit slot is returned cleared.
 */
{
    register VDD_PTR vdd;	/* vdd of unit assignment */
    register VDD *vd;

    if ( (vdd = *unit) NE NULL )
	{
	*unit = NULL;		/* clear assignment */
	vd = *vdd;		/* dereference ptr */

	/* decr number of ptrs to it...if zero check for remove on deassign */
	/* if rem_on_dea, remove it or set flag to remove when i/o done */
	if ( (--Attachments(vd) EQ 0) AND Detachremove(vd) )
	    {
	    if (Outstanding_IO(vd))  Rundown(vd) = Compremove(vd) = TRUE;
	    else rem_dev(vdd);
	    }
	}
}


rem_dev (dd)
    register VDD_PTR dd;

/* rem_dev (dd) -- Unlink and deallocate a physical or virtual device
 *
 * 	in:	dd		target VDD or PDD
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Calls either rem_vdev() or rem_pdev() to remove the device.
 *	Both the attachment and i/o counts must have already been reduced
 *	to zero before rem_dev() is called.
 */
{
    if ( Devtype(*dd) EQ VDTYPE )  rem_vdev(dd);
    else  rem_pdev(dd);
}


rem_pdev (pdd)
    register PDD_PTR pdd;

/* rem_pdev (pdd) -- Unlink and deallocate a physical device
 *
 * 	in:	pdd		target PDD
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	The physical device descriptor is removed by:
 *		1) Calling the device dispatcher with the PDREMOVE command
 *		   to clean up and deallocate any auxilliary structures.
 *		2) Unqueueing the descriptor and releasing its name string
 *		   and it back into system pool.
 *
 *	If the device handler throws an error back, then there was an error
 *	detaching the device.  A typical error would be if the host was busy
 *	and there was no pool from which a temporary i/o packet could be
 *	allocated.  In this case, reset the device flags to
 *	'Detachremove' so that a future I/O Request can remove the device.
 *
 *	Both the attachment and i/o counts must have already been reduced
 *	to zero before rem_pdev() is called.
 */
{

#ifdef DEBUG4   /*************************************************************/
    if ( Attachments(*pdd) OR Outstanding_IO(*pdd) OR
						(Devtype(*pdd) NE PDTYPE) )
    error("Can't rem_pdev attached PDD @ %x", *pdd);
#endif /* DEBUG4 *************************************************************/

    if (catch() EQ 0)			/* catch errors thrown from handler */
	{
	(*Dispatcher(*pdd))(PDREMOVE,pdd,NULL);	/* clean up device */
	uncatch();
	uq_item (pdd, &Pdevices);		/* take off PDD list */
	free (Devname(*pdd));		/* free device name */
	vfree (pdd);			/* free device descriptor */
	}
    else
	{
#ifdef DEBUG1   /*************************************************************/
	errpri("Physical device %s still connected\n", (char*)*(Devname(*pdd)));
#endif /* DEBUG1 *************************************************************/

	Compremove(*pdd) = Rundown(*pdd) = FALSE;	/* reset flags so */
	Detachremove(*pdd) = TRUE;		/* future i/o can work */
	}
}


rem_vdev(vdd)
    register VDD_PTR vdd;

/* rem_vdev (vdd) -- Unlink and deallocate a virtual device
 *
 * 	in:	vdd		target VDD
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	The virtual device descriptor is removed by:
 *		1) Calling rem_units() to deassign all unit attachments
 *		   (this is done now rather than earlier in case the
 *		    device had outstanding i/o that might have required
 *		    auxilliary units after i/o (e.g., error-input).
 *		2) Calling the class-dependent virtual device removal routine.
 *		   (this step is skipped if the class handler is NULL...
 *		    which occurs if removing a temporary vdd)
 *		3) Unqueueing the descriptor and releasing its name string
 *		   and it back into system pool.
 *
 *	Both the attachment and i/o counts must have already been reduced
 *	to zero before rem_vdev() is called.
 */
{

#ifdef DEBUG4   /*************************************************************/
    if ( Attachments(*vdd) OR Outstanding_IO(*vdd) OR
						(Devtype(*vdd) NE VDTYPE) )
    error("Can't rem_vdev attached VDD @ %x", *vdd);
#endif /* DEBUG4 *************************************************************/

    rem_units(vdd);			/* deassign all units */

    if (Classdispatcher(*vdd) NE NULL)		/* if there is a dispatcher */
	(*Classdispatcher(*vdd)) (VDREMOVE, NULL, vdd);	/* class-dependent */

    uq_item (vdd, &Vdevices);		/* take off VDD list */
    free (Devname(*vdd));		/* free device name */
    vfree (vdd);			/* free device descriptor */
}


push_unit (vdd, newdev, pushdev)
    VDD_PTR vdd;	/* VDD to manipulate */
    VDD_PTR newdev;	/* VDD to push into pri_input */
    VDD_PTR pushdev;	/* initialized VDD to become eot_input target */

/* push_unit (vdd, newdev, pushdev) -- Push an eot_input redirection
 *
 *	in:	vdd		VDD to manipulate
 *	in:	newdev		VDD to push into primary-input
 *	in:	pushdev		initialized VDD to become eot-input target
 *
 *	Push the current primary-input of a Record-Sequential device in
 *	the following way:
 *		1) Copy the primary- and eot- inputs of 'vdd' to 'pushdev'.
 *		   'Pushdev' is required as a parameter so that allocation
 *		    failures occur at an outside level.
 *		   *** ^^ That might change ^^ ***
 *		2) Set the primary-input of 'vdd' to 'newdev'.
 *		   *** (Handling of active i/o requests must be dealt with) ***
 *		3) Set the eot-input of 'vdd' to 'pushdev'.
 *		4) Mark 'pushdev' to be removed when the attachment count
 *		   drops to zero and link it to the virtual device list.
 *
 *	The input devices MUST ALL be Record-Sequential Class!
 *
 *	See also pop_unit() (naturally).
 */
{
    register VDD *vd;
    register VDD *pushd;

#ifdef DEBUG4   /*************************************************************/
    if ( (Devclass(*vdd) NE REC_SEQ) OR
	 (Devclass(*newdev) NE REC_SEQ) OR
	 (Devclass(*pushdev) NE REC_SEQ) )
	    error("Can't push a dev that isn't REC_SEQ...vdd @ %x", *vdd);
#endif /* DEBUG4 *************************************************************/

    vd = *vdd;		/* dereference ptrs */
    pushd = *pushdev;

    reassign (&(Primaryinput(pushd)),Primaryinput(vd));	/* copy old primary */
    reassign (&(Recs_eot(pushd)), Recs_eot(vd));	/* copy old eot */
    reassign (&(Primaryinput(vd)), newdev);		/* set new primary */
    reassign (&(Recs_eot(vd)), pushdev);		/* set new eot_input */
    Detachremove(pushd) = TRUE;			/* mark pushed vd as temp */
    q_item (pushdev, &Vdevices);		/* and add it to VDD list */
}


pop_unit (vdd)
    register VDD_PTR vdd;

/* pop_unit (vdd) -- Pop an eot_input redirection
 *
 *	in:	vdd		Target virtual device
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	If 'vdd' is not a Record-Sequential device, or if it does not
 *	have any eot-input attachments, this routine is a no-op.
 *	Otherwise, the primary-input is popped as follows:
 *		1) Assign 'eotp' as a fake unit attachment to the eot-input
 *		   of 'vdd'.
 *		2) Reassign the eot-input of 'vdd' to the eot-input of 'eotp'.
 *		   This re-establishes the stack of pushed attachments.
 *		3) Clear the eot-input of 'eotp' so that it won't be popped
 *		   when the temporary VDD is released.
 *		4) Reassign the primary-input of 'vdd' to that of 'eotp'.
 *		5) Clear the fake attachment of 'eotp'.  This should be the
 *		   last attachment to a temporary VDD that will then be
 *		   removed by rem_vdev() which will, in turn, clear all of
 *		   its unit attachments.
 *
 *	See also push_unit().
 */
{
    register VDD *vd;
    VDD_PTR newp;		/* new primary input */
    VDD_PTR eotp;		/* original eot input */
    register VDD_PTR *eota;	/* ptr to original eot input slot */

    vd = *vdd;			/* dereference ptr */
    eota = &Recs_eot(vd);	/* point to eot slot */

    if ( (Devclass(vd) EQ REC_SEQ) AND (*eota NE NULL) )
	{
	eotp = NULL;		/* clear so that reassign doesn't crash */
	reassign (&eotp, *eota);	/* keep eot dev attachments non-zero */
				  /* by reassigning rather than just copying */
	newp = Primaryinput(*eotp);	/* get new primary input device */
					/* set new eot to eot of eot */
	reassign (eota, Recs_eot(*eotp));

			/* clear temp-vd eot now so it won't pop later */
	clr_unit (&Recs_eot(*eotp));

#ifdef DEBUG3   /*************************************************************/
	if (newp EQ NULL)  error("Pop_unit() popped NULL", NULL);
#endif /* DEBUG3 *************************************************************/

	reassign (&Primaryinput(vd), newp);	/* set new primary */
	clr_unit (&eotp);			/* clear pointer to temp vd */
	} /*if-pushed input*/
}


reassign (unit, newvdd)
    register VDD_PTR *unit;
    register VDD_PTR newvdd;

/* reassign (unit, newvdd) -- Re-assign a virtual device unit assignment
 *
 *	mod:	unit		Ptr to target unit slot of VDD / Return 'newvdd'
 *	in:	newvdd		VDD of new device to redirect to  'unit'
 *	return:	(NONE)
 *	thrown:	(NONE)
 *
 *	Reassigns the slot '*unit' by:
 *		1) Clearing the old assignment with clr_unit().
 *		2) Setting the new assignment to 'newvdd' and, if not NULL,
 *		   incrementing the attachment count of 'newvdd'.
 */
{
    clr_unit(unit);		/* clear old assignment */
    if (newvdd NE NULL)
	{
	*unit = newvdd;			/* set new unit */
	Attachments(*newvdd)++;		/* count one more attachment */
	}
}

	    						/* throw: V_CIRCULAR */
trc_input (nxtdev, vdd)
    register VDD_PTR nxtdev;
    register VDD_PTR vdd;

/* trc_input (nxtdev, vdd) -- Trace input device chains to prevent circles
 *
 *	in:	nxtdev		VDD of device to check
 *	in:	vdd		VDD to watch out for (other than 'nxtdev')
 *	return:	(NONE)
 *	thrown:	V_CIRCULAR	if 'vdd' is anywhere on 'nxtdev's chain
 *
 *	Trace, recursively, down all possible input device chains of 'nxtdev'
 *	until the end, ensuring that 'vdd' does not show up anywhere.
 *	Trc_input() would be called before allowing 'vdd' to be attached
 *	to an input stream of 'nxtdev'.
 *
 *	Throw V_CIRCULAR if 'vdd' is already on the input chain of 'nxtdev'.
 */
{
    register VDD *nxtd;

    if (nxtdev EQ vdd)  throw(V_CIRCULAR);	/* got back to first device */

    if ( (nxtdev NE NULL) AND (Devtype(nxtd = *nxtdev) EQ VDTYPE) )
	{
	trc_input (Primaryinput(nxtd), vdd);	/* trace primary input */
	trc_input (Errorinput(nxtd), vdd);	/* trace error input */

	if (Devclass(nxtd) EQ REC_SEQ)
	    trc_input (Recs_eot(nxtd), vdd);	/* trace eot input */
	}
}

	    						/* throw: V_CIRCULAR */
trc_output (nxtdev, vdd)
    register VDD_PTR nxtdev;
    register VDD_PTR vdd;

/* trc_output (nxtdev, vdd) -- Trace output device chains to prevent circles
 *
 *	in:	nxtdev		VDD of device to check
 *	in:	vdd		VDD to watch out for (other than 'nxtdev')
 *	return:	(NONE)
 *	thrown:	V_CIRCULAR	if 'vdd' is anywhere on 'nxtdev's chain
 *
 *	Trace, recursively, down all possible output device chains of 'nxtdev'
 *	until the end, ensuring that 'vdd' does not show up anywhere.
 *	Trc_output() would be called before allowing 'vdd' to be attached
 *	to an output stream of 'nxtdev'.
 *
 *	Throw V_CIRCULAR if 'vdd' is already on the output chain of 'nxtdev'.
 */
{
    register VDD *nxtd;

    if (nxtdev EQ vdd)  throw(V_CIRCULAR);	/* got back to first device */

    if ( (nxtdev NE NULL) AND (Devtype(nxtd = *nxtdev) EQ VDTYPE) )
	{
	trc_output (Primaryoutput(nxtd), vdd);	/* primary output */
	trc_output (Journaloutput(nxtd), vdd);	/* secondary output */
	}
}

    VDD_PTR						/* throw: ALL_FAIL */
vddalloc (osid)
    OS_IDENT osid;

/* vddalloc (osid) -- Allocate and initialize a Virtual device descriptor
 *
 *	in:	osid		New device opearting system id
 *	return:	(VDD_PTR)	VDD bufptr of new virtual device
 *	thrown:	ALL_FAIL	Pool allocation failure
 *		???		*** eventually....error if os device table full
 *
 *	Allocate a new VDD and clear it.  Then set the O.S. id, device type bit
 *	(virtual), and mark it read/write.
 *	Calling routines might have to reset the read/write status.
 *
 *	This routine assumes that NULL == 0  and that sizeof(char) == 1.
 *
 *	Valloc() throws ALL_FAIL if the VDD could not be allocated from pool.
 */
{
    register VDD *vd;
    register int i;
    register char *c;
    register VDD_PTR vdd;

#ifdef DEBUG4   /*************************************************************/
    if ( (NULL NE 0) OR (sizeof(char) NE 1) )
	error("Init error in vddalloc()", NULL);
#endif /* DEBUG4 *************************************************************/

    c = (char*) (vd = *(vdd = (VDD_PTR) valloc(sizeof(VDD))));	/* allocate */

    for (i=0; i<sizeof(VDD); i++)
	*c++ = '\0';			/* init to all zeroes */

    Ownerid(vd) = osid;
    Devtype(vd) = VDTYPE;
    Readable(vd) = TRUE;	/* Set read/write (a reasonable assumption) */
    Writeable(vd) = TRUE;
    return(vdd);
}

    PDD_PTR						/* throw: ALL_FAIL */
pddalloc (routine)
    int (*routine)();

/* pddalloc (routine) -- Allocate and initialize a Physical device descriptor
 *
 *	in:	routine		Device dispatch routine address
 *	return:	(PDD_PTR)	PDD bufptr of new physical device
 *	thrown:	ALL_FAIL	Pool allocation failure
 *
 *	Allocate a new PDD and clear it.  Then set the device type bit
 *	(physical), and set the dispatcher address.
 *	Calling routines and/or the device handler must set the read/write
 *	status.  Note that the device create handler is NOT called from here.
 *
 *	Valloc() throws ALL_FAIL if the PDD could not be allocated from pool.
 */
{
    register PDD *pd;
    register int i;
    register char *c;
    register PDD_PTR pdd;

    c = (char*) (pd = *(pdd = (PDD_PTR) valloc(sizeof(PDD))));	/* allocate */

    for (i=0; i<sizeof(PDD); i++)
	*c++ = '\0';			/* init to all zeroes */
    				/* This assumes that NULL == 0 */

    Dispatcher(pd) = routine;	/* Set initial i/o handler address */
    Devtype(pd) = PDTYPE;	/* Set physical device type */
    return(pdd);
}

