/*
 *	Name:				memobj.c
 *
 * Description:	Support for memory objects
 *
 * Copyright:		1992-1993 by David Jones.
 *
 * Distribution:
 *		This program is free software; you can redistribute it and/or modify
 *		it under the terms of the GNU General Public License as published by
 *		the Free Software Foundation; either version 2 of the License, or
 *		(at your option) any later version.
 *
 *		This program is distributed in the hope that it will be useful,
 *		but WITHOUT ANY WARRANTY; without even the implied warranty of
 *		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *		GNU General Public License for more details.
 *
 *		You should have received a copy of the GNU General Public License
 *		along with this program; if not, write to:
 *
 *				The Free Software Foundation		David Jones
 *				675 Mass Ave							6730 Tooney Drive
 *				Cambridge, MA							Orleans, Ontario
 *				02139										K1C 6R4
 *				USA										Canada
 *
 *	Usenet:	gnu@prep.ai.mit.edu					aa457@freenet.carleton.ca
 *	Fidonet:												1:163/109.8
 *
 *		$Log: $
 *
 */

#include <exec/types.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <dos/dos.h>
#include <proto/utility.h>
#include <exec/memory.h>
#include <exec/semaphores.h>
#include <utility/tagitem.h>
#include <string.h>
#include <stdarg.h>
#include "xferq.h"
#include "xferqint.h"
#include "xferq_pragmas.h"
#include "execlists.h"

#include <proto/intuition.h>
#include <intuition/intuition.h>

extern struct Library *XferqBase;

/*
 *	The following constants are used to "group" memory allocations to
 * improve performance and reduce fragmentation.
 */

#define	HEAP_QUANTUM		8192	/* Size of each memory block */
#define	STRING_QUANTUM		16		/* Number of chars added to string */

/*
 *		Level 1 private data structures.
 */

struct MemHeader Memory;
struct MinList MemList;
struct SignalSemaphore ObjectLock;
struct MinList MemUsed[XQO_MAX], MemFree[XQO_MAX];
long NumUsed[XQO_MAX], NumFree[XQO_MAX];

#define	OFFSET(s,f)	(&((struct s *)0)->f)
#define	ADDRESS(ptr, off) ((char *)ptr + off)

char *ObjNames[] = {
	NULL,
	"NetAddress",
	"Sequence",
	"String",
	"Session",
	"WorkNode",
	"ErrorNode",
	"AnchorPath",
	"SiteQueue",
	"SessNode",
	"VarList",
	"VarListEntry"
};


struct FieldDesc AddrDesc[] = {
	"NETWORK", XQ_Network, XQO_LONG, OFFSET(NetAddress, type),
	"ZONE", XQ_ZoneStr, XQO_STRING, OFFSET(NetAddress, addr.fido.zone),
	"NET", XQ_NetStr, XQO_STRING, OFFSET(NetAddress, addr.fido.net),
	"NODE", XQ_NodeStr, XQO_STRING, OFFSET(NetAddress, addr.fido.node),
	"POINT", XQ_PointStr, XQO_STRING, OFFSET(NetAddress, addr.fido.point),
	"DOMAIN", XQ_Domain, XQO_STRING, OFFSET(NetAddress, addr.fido.domain),
	"USERNAME", XQ_UserName, XQO_STRING, OFFSET(NetAddress, userName),
	"UUCPNAME", XQ_UucpName, XQO_STRING, OFFSET(NetAddress, addr.machine),
	"FQDANAME", XQ_FqdaName, XQO_STRING, OFFSET(NetAddress, addr.fqda),
	"TEXTNAME", XQ_TextName, XQO_STRING, OFFSET(NetAddress, addr.text),
	"", XQ_Zone, XQO_NUMSTR, OFFSET(NetAddress, addr.fido.zone),
	"", XQ_Net, XQO_NUMSTR, OFFSET(NetAddress, addr.fido.net),
	"", XQ_Node, XQO_NUMSTR, OFFSET(NetAddress, addr.fido.node),
	"", XQ_Point, XQO_NUMSTR, OFFSET(NetAddress, addr.fido.point),
	NULL, TAG_DONE, XQO_NULL, 0
};

struct FieldDesc WorkNodeDesc[] = {
	"NAME", XQ_Name, XQO_STRING, OFFSET(WorkNode, node.ln_Name),
	"ASNAME", XQ_AsName, XQO_STRING, OFFSET(WorkNode, asname),
	"PRI", XQ_Pri, XQO_BYTE, OFFSET(WorkNode, node.ln_Pri),
	"FLAGS", XQ_Flags, XQO_WORD, OFFSET(WorkNode, userFlags),
	"STATUS", XQ_Status, XQO_BYTE, OFFSET(WorkNode, status),
	"SITE", XQ_Site, XQO_ADDRESS, OFFSET(WorkNode, addr),
	NULL, TAG_DONE, XQO_NULL, 0
};

struct FieldDesc SessionDesc[] = {
	"SESSFLAGS", XQ_SessFlags, XQO_WORD, OFFSET(Session, flags),
	NULL, TAG_DONE, XQO_NULL, 0
};

struct FieldDesc SeqDesc[] = {
	"SEQ", XQ_Sequence, XQO_LONG, 0,
	NULL, TAG_DONE, XQO_NULL, 0
};


struct ObjectDesc ObjectDesc[] = {
	{
	"NUL", 0,
	NULL, NULL,
	NULL,
	NULL, NULL, NULL, NULL
	},
	
	{
	"ADR", sizeof(struct NetAddress),
	NULL, NULL,
	AddrDesc,
	NULL, NULL, NULL, CanonAddress
	},
	
	{
	"SEQ", 4,
	NULL, NULL,
	SeqDesc,
	NULL, NULL, NULL, NULL
	},
	
	{
	"STR", 1,
	NULL, NULL,
	NULL,
	NULL, NULL, NULL, NULL
	},
	
	{
	"SES", sizeof(struct Session),
	PostCreateSession, PreDropSession,
	SessionDesc,
	NULL, NULL, NULL, NULL
	},
	
	{
	"WKN", sizeof(struct WorkNode),
	NULL, NULL,
	WorkNodeDesc,
	PreExamWork, PostExamWork, PreModifyWork, PostModifyWork }
};


#ifdef DEBUG
int GetCount(void *object)
{
struct XfqObject *xo;

	xo = object;
	xo--;
	return xo->useCount;
}


void CheckCount(char *func, void *object, int count)
{
int realcount;

	realcount = GetCount(object);
	if (realcount != count) {
		kprintf("Bad usecount in %s, object %08lx, expected %ld, got %ld\n",
			func, object, count, realcount);
	}
}
#endif


/*
 *		Level 1: Sub-heap memory management.
 *
 *	This level allows allocation/deallocation of memory blocks without
 *	fragmenting the system heap.
 */

void DropMemory(void)
/*
 *	Does:	Returns all memory to the system.
 */
{
struct MinNode *mh, *tmh;

	mh = FIRST(&MemList);
	while (tmh = NEXT(mh)) {
		FreeVec(mh);
		mh = tmh;
	}
}


void* ExpandMemory(ULONG size)
/*
 *	In:	size				Number of bytes required.
 *
 *	Does:	Expands the variable size memory pool.
 *			Returns non-NULL if successful.
 */
{
struct MemChunk *mc;
struct MinNode *mn;
long *p, len;

	if (size < HEAP_QUANTUM) {
		size = HEAP_QUANTUM;
	}
	mn = AllocVec(size + sizeof(struct MinNode) + 4, MEMF_PUBLIC);
#ifdef DEBUG
	kprintf("Expanded memory at %08lx\n", mn);
#endif
	if (mn) {
		/*
		 *		Align mn to a multiple of 8 bytes.  Aaarrrrggghhh!!! :-(
		 */
		mc = (struct MemChunk *)
			(((ULONG)mn + sizeof(struct MinNode) + 4) & -8);
		/*
		 *		Clear the memory to a known pattern to facilitate debugging.
		 */
		p = (long *)mc;
		len = size >> 2;
		while (len--) {
			*p++ = 0x20202020;
		}
		/* The following is based on the example in the RKM under
			Allocate() */
		if (Memory.mh_Node.ln_Name) {
			Deallocate(&Memory, mc, size);
		}
		else {
			Memory.mh_Node.ln_Type = NT_MEMORY;
			Memory.mh_Node.ln_Name = "XferQ heap memory";
			Memory.mh_First = mc;
			Memory.mh_Lower = mc;
			Memory.mh_Upper = (APTR)((char *)mc + size);
			Memory.mh_Free = size;
	
			mc->mc_Next = NULL;
			mc->mc_Bytes = size;
		}
		ADDHEAD(&MemList, mn);
	}
	return mn;
}


ULONG InitMemory(void)
/*
 *	Does:	Initializes the memory object manager
 *
 */
{
int i;

	/* Init the memory list and lock */
	InitSemaphore(&ObjectLock);
	NEWLIST(&MemList);

	/* Create pools for all constant objects */
	for (i = 1; i < XQO_MAX; i++) {
		NEWLIST(&MemUsed[i]);
		NEWLIST(&MemFree[i]);
		NumUsed[i] = NumFree[i] = 0;
	}
	
	/*
	 *		Set up MemHeader.  To start with, allocate a single block.
	 *		If ln_Name is NULL, then ExpandMemory() will take special
	 *		action to prevent Deallocate() on an empty MemHeader.
	 */
	Memory.mh_Node.ln_Name = NULL;
	return (ULONG)ExpandMemory(HEAP_QUANTUM);
}


void ClearMem(void *mem, LONG size)
/*
 *	In:	mem				Pointer to memory block to clear
 *			size				Size of block to clear
 *
 *	Does:	Writes zeroes into the memory block so described.
 *			It is assumed that the memory and size are long-aligned.
 *
 */
{
long *lp;

	lp = mem;
	while (size > 0) {
		*lp++ = 0;
		size -= 4;
	}
}


void *AllocObject(ULONG size, ULONG type)
/*
 *	In:	size				Size of object to allocate
 *			type				Type of object to allocate
 *
 * Does:	Allocates memory for an object.
 */
{
struct XfqObject *obj;
ULONG newsize;

	ObtainSemaphore(&ObjectLock);
	if (type == XQO_STRING || (!(obj = REMHEAD(&MemFree[type])))) {
		/*
		 *		Compute correct size.  For strings, add one (null terminator).
		 *		For public objects, take size from table.  For private objects,
		 *		size must be provided.
		 */
		if (type == XQO_STRING) {
			size++;
		}
		else if (type <= XQO_WORKNODE) {
			size = ObjectDesc[type].size;
		}
		newsize = size + sizeof(struct XfqObject);
		obj = Allocate(&Memory, newsize);
		if (!obj) {
			ExpandMemory(newsize);
			obj = Allocate(&Memory, newsize);
			if (!obj) {
				ReleaseSemaphore(&ObjectLock);
				return NULL;
			}
		}
		obj->size = newsize;
		obj->type = type;
	}
	else {
		size = obj->size - sizeof(struct XfqObject);
		NumFree[type]--;
	}
	/* Link into Used queue */
	ADDHEAD(&MemUsed[type], obj);
	NumUsed[type]++;
	
	obj->useCount = 1;
	obj++;
	ClearMem(obj, size);
	
	ReleaseSemaphore(&ObjectLock);
	return obj;
}


void FreeObject(void *object)
/*
 *	In:	object			Pointer to memory to free
 *
 * Does:	Frees a variable size object.
 */
{
int type;
struct XfqObject *xo;

	if (object) {
		ObtainSemaphore(&ObjectLock);
		xo = object;
		xo--;
		type = xo->type;
		REMOVE(xo);
		NumUsed[type]--;
		
		if (type == XQO_STRING) {
			Deallocate(&Memory, xo, xo->size);
		}
		else {
			ADDHEAD(&MemFree[type], xo);
			NumFree[type]++;
		}
		ReleaseSemaphore(&ObjectLock);
	}
}


ULONG PValid(void *p)
{
long l;

	l = (long)p;
	if ((l & 1) || l <= 1024) {
		return FALSE;
	}
	return TypeOfMem(p);
}


void PCheck(char *fname, ...)
{
va_list ap;
char *arg;
void *val;
int good;

	good = TRUE;
	va_start(ap, fname);
	while (arg = va_arg(ap, char *)) {
		val = va_arg(ap, void *);
		if (!PValid(val)) {
			good = FALSE;
		}
	}
	if (!good) {
		Wait(0);
	}
}


int OValid(struct XfqObject *xo)
{

	if (!PValid(xo)) {
		return FALSE;
	}
	if (!PValid(xo->link.mln_Succ)) {
		return FALSE;
	}
	if (!PValid(xo->link.mln_Pred)) {
		return FALSE;
	}
	if (xo->size > 8192) {
		return FALSE;
	}
	/*
	 *		Any more than 8192 instances of an object probably indicates
	 *		a bug in your program.  Either that, or you have one HUGE
	 *		system :-)
	 */
	if (xo->useCount > 0x2000 || xo->useCount == 0) {
		return FALSE;
	}
	if (!xo->type || xo->type >= XQO_MAX) {
		return FALSE;
	}
	return TRUE;
}


void MemDump(BPTR fh, void *base, long size)
{
long *lp;
char *cp;
int r, j;

	lp = base;
	cp = base;
	if (!PValid(cp) || !PValid(cp + ((size + 1) & -2))) {
		return;
	}
	for (r = 0; r < size; r += 16) {
		if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) {
			return;
		}
		FPrintf(fh, "%08lx: %08lx %08lx %08lx %08lx   ",
			lp, lp[0], lp[1], lp[2], lp[3]);
		for (j = 0; j < 16; j++) {
			FPutC(fh, (cp[j] & 0x7F) < 0x20 ? '.' : cp[j] & 0x7F);
		}
		lp += 4;
		cp += 16;
		FPutC(fh, '\n');
	}
}


void CoreDump(void)
{
BPTR fh;
int i;
struct XfqObject *xo, *txo;
long *lp, *tlp;

	fh = Open("XFERQ:core", MODE_NEWFILE);
	if (!fh) {
		return;
	}
	FPrintf(fh, "Type  Used  Free  UsedHead  UsedTail  FreeHead  FreeTail\n");
	for (i = 1; i < XQO_MAX; i++) {
		FPrintf(fh, "%3ld   %4ld  %4ld  %08lx  %08lx  %08lx  %08lx\n",
			i, NumUsed[i], NumFree[i],
			MemUsed[i].mlh_Head, MemUsed[i].mlh_TailPred,
			MemFree[i].mlh_Head, MemFree[i].mlh_TailPred);
	}
	FPrintf(fh, "\n");
	lp = FIRST(&MemList);
	while (PValid(lp) && (tlp = NEXT(lp))) {
		MemDump(fh, lp, lp[-1]);
		lp = tlp;
	}
	if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) {
		goto killme;
	}
	for (i = 1; i < XQO_MAX; i++) {
		FPrintf(fh, "\ntype %ld\n========\n", i);
		txo = xo = FIRST(&MemUsed[i]);
		if (PValid(xo)) {
			while (PValid(xo) && (txo = NEXT(xo))) {
				if (OValid(xo)) {
					MemDump(fh, xo, xo->size);
				}
				else {
					MemDump(fh, xo, 32);
				}
				if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) {
					goto killme;
				}
				xo = txo;
				FPutC(fh, '\n');
			}
		}
		if (txo) {	/* error dumping in forward direction */
			xo = LAST(&MemUsed[i]);
			while (PValid(xo) && (txo = PREV(xo))) {
				if (OValid(xo)) {
					MemDump(fh, xo, xo->size);
				}
				else {
					MemDump(fh, xo, 32);
				}
				if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) {
					goto killme;
				}
				xo = txo;
				FPutC(fh, '\n');
			}
		}
	}
killme:
	Close(fh);
}


struct EasyStruct WatchES = {
	sizeof(struct EasyStruct),
	0,
	"XferQ Watchdog",
	"Object integrity violation detected!\n"
	"%s(%08lx)\nCore dumped in XFERQ:core",
	"Acknowledged"
};

struct IntuitionBase *IntuitionBase;

void Watchdog(char *oper, void *obj)
{
struct XfqObject *xo;

	xo = obj;
	xo--;
	if (OValid(xo)) {
		return;
	}
	CoreDump();
	if (IntuitionBase = (struct IntuitionBase *)
		OpenLibrary("intuition.library", 0)) {
		EasyRequest(NULL, &WatchES, NULL, oper, obj);
		CloseLibrary((struct Library *)IntuitionBase);
	}
	Wait(0);
}


/*
 *		Level 2: Management of objects allowing multiple use.
 */


void *LibCopyObject(void *object)
/*
 *	In:	object	A0		Pointer to object to copy
 *
 * Does:	Makes a copy of the object, preventing contention.
 */
{
struct XfqObject *xo;

	if (object) {
		Watchdog("CopyObject", object);
		ObtainSemaphore(&ObjectLock);
	
		xo = (struct XfqObject *)object;
		xo--;
		
		xo->useCount++;
		ReleaseSemaphore(&ObjectLock);
	}
	return object;
}


void *__saveds __asm LIBCopyObject(register __a0 void *obj)
{
void *newobj;

	newobj = LibCopyObject(obj);
	SetErrorTags(NULL);
	return newobj;
}


void *DupObject(void *object)
/*
 *	In:	object			Pointer to object to duplicate.
 *
 *	Does:	Makes a copy of the object and returns a pointer to the copy.
 */
{
int type, size;
void *newObj;
struct FieldDesc *fd;
void **objP;

	ObtainSemaphore(&ObjectLock);
	Watchdog("DupObject", object);
	/* Create a new object with no data */
	type = ObjectType(object);
	size = ObjectSize(object) - sizeof(struct XfqObject);
	newObj = AllocObject(size, type);
	if (!newObj) {
		ReleaseSemaphore(&ObjectLock);
		return NULL;
	}
	
	/* Since read copies don't change the data, just blast it over. */
	CopyMem(object, newObj, size);
	
	/* But be sure to lock the fields. */
	fd = ObjectDesc[type].fields;
	if (fd) {
		while (fd->type) {
			if (fd->type < XQO_MAX) {
				objP = (void **)ADDRESS(object, fd->offset);
				LibCopyObject(*objP);
			}
			fd++;
		}
	}
	ReleaseSemaphore(&ObjectLock);
	return newObj;
}


void LibDropObject(void *object)
/*
 *	In:	object	A0		Pointer to object to drop
 *
 *	Does:	Terminates access to the object.
 */
{
struct XfqObject *xo;
struct FieldDesc *fd;

	if (object) {
		Watchdog("DropObject", object);	
		ObtainSemaphore(&ObjectLock);
	
		/* Decrement usecount */
		xo = (struct XfqObject *)object;
		xo--;
		xo->useCount--;
	
		/* If zero, drop the object's fields */
		if (!xo->useCount) {
			if (ObjectDesc[xo->type].preDrop) {
				(*ObjectDesc[xo->type].preDrop)(object);
			}
			fd = ObjectDesc[xo->type].fields;
			if (fd) {
				while (fd->type) {
					if (fd->type < XQO_MAX) {
						LibDropObject(*(void **)ADDRESS(object, fd->offset));
					}
					fd++;
				}
			}
			FreeObject(object);
		}
	
		ReleaseSemaphore(&ObjectLock);
	}
}


void __saveds __asm LIBDropObject(register __a0 void *obj)
{

	LibDropObject(obj);
	SetErrorTags(NULL);
}


/*
 *		String routines.  These make use of object routines at level 2.
 */

char *CopyStringN(char *string, ULONG size)
/*
 *	In:	string			Pointer to string to copy
 *			size				Number of bytes to copy
 *
 *	Does:	Makes a copy of the string and returns it.
 */
{
char *newStr;

	/* AllocObject a string of the required size */
	newStr = AllocObject(size + 1, XQO_STRING);
	
	/* Copy the data and add the null terminator (which strncpy() is
		not guaranteed to provide) */
	if (newStr) {
		strncpy(newStr, string, size);
		newStr[size] = '\0';
	}
	else {
		XfqSetLocalError(XQERROR_NOMEM);
	}

	return newStr;
}


char *CopyString(char *string)
/*
 *	In:	string			Pointer to string to copy
 *
 * Does:	Makes a copy of the string and returns it.
 */
{

	if (string) {
		return CopyStringN(string, strlen(string));
	}
	else {
		return NULL;
	}
}


char *NeedString(char *string, ULONG len)
/*
 *	In:	string			Strings to (possibly) lengthen
 *			len				Additional length required
 *
 * Does:	Determines if len characters can be added to the string.  If
 *			not, then the string is lengthened.
 */
{
LONG newSize;
char *newStr;

	if (!string) {
		return AllocObject(len + STRING_QUANTUM, XQO_STRING);
	}
	Watchdog("NeedString", string);
	newSize = len + strlen(string);
	
	/* If the result can't fit, then allocate a new string, copy the
		old data over, and drop the old string. */
	if (newSize >= ObjectSize(string) - sizeof(struct XfqObject)) {
		newStr = CopyStringN(string, newSize + STRING_QUANTUM);
		if (!newStr) {
			return NULL;
		}
		LibDropObject(string);
		string = newStr;
	}
	return string;
}


/*
 *		Level 3: High-level create/examine/modify
 */


void *LibCreateObject(ULONG type,
	struct TagItem *tags)
/*
 *	In:	type		D0		Type of object to create
 *			tags		A0		Taglist giving defaults
 *
 * Does:	Creates a new object of the given type.  The object and tag
 *			list are passed to XfqModifyObject().
 */
{
ULONG dataSize;
void *object;

	dataSize = GetTagData(XQ_Size, 0, tags);
	object = AllocObject(dataSize, type);
	if (object) {
	
		/* Call post-create to set up defaults */
		if (ObjectDesc[type].postCreate) {
			(*ObjectDesc[type].postCreate)(object);
		}
	
		/* Modify the fields */
		if (tags) {
			object = LibModifyObject(object, tags);
		}
	}
	
	return object;
}


void *__saveds __asm LIBCreateObject(register __d0 ULONG type,
	register __a0 struct TagItem *tags)
{
void *obj;

	obj = LibCreateObject(type, tags);
	SetErrorTags(tags);
	return obj;
}


void *LibCreateObjectTags(ULONG type, Tag tag, ...)
{

	return LibCreateObject(type, (struct TagItem *)&tag);
}


BOOL ExamObject(void *object,
	BOOL (*wfunc)(void *, struct FieldDesc *, long), void *dest)
/*
 *	In:	object			Pointer to object to examine
 *			wfunc				Function to call to write value
 *			dest				Data for use by wfunc
 *
 * Does:	Copies data from the object to another entity.  The write
 *			to the other entity is performed by wfunc.
 */
{
int type;
long data;
char *srcP;
struct FieldDesc *fd;
short result;

	if (!object) {
		return FALSE;
	}
	Watchdog("ExamObject", object);
	type = ObjectType(object);
	if (ObjectDesc[type].preExamine) {
		if (!((*ObjectDesc[type].preExamine)(object))) {
			return FALSE;
		}
	}
	fd = ObjectDesc[type].fields;
	while (fd->name) {
		srcP = ADDRESS(object, fd->offset);
		
		switch (fd->type) {
		case XQO_BYTE:
			data = *(signed char *)srcP;
			break;
		case XQO_WORD:
			data = *(short *)srcP;
			break;
		case XQO_LONG:
			data = *(long *)srcP;
			break;
		case XQO_NUMSTR:
			if (*(char **)srcP) {
				GetNumber(*(char **)srcP, 10, 0, &data);
			}
			else {
				data = 0;
			}
			break;
		case XQO_STRING:
		default:
			/*
			 *		Write agent must XfqCopyObject().
			 */
			data = *(long *)srcP;
			break;
		}
		result = (*wfunc)(dest, fd, data);
		if (result == XODIP_ERROR) {
			return FALSE;
		}
		fd++;
	}
	if (ObjectDesc[type].postExamine) {
		return (*ObjectDesc[type].postExamine)(object);
	}
	return TRUE;
}


short TagWFunc(struct TagItem *tags, struct FieldDesc *fd, long data)
/*
 *	In:	tags				Pointer to taglist to write to
 *			fd					Pointer to field descriptor
 *			data				Data item to write
 *
 *	Does:	This is the taglist write function.  It writes the object data
 *			into the variable indicated in the tags.
 */
{
long *p;
struct TagItem *tag;

	tag = FindTagItem(fd->tag, tags);
	if (tag) {
		if (fd->type == XQO_STRING || fd->type < XQO_MAX) {
			LibCopyObject((void *)data);
		}
		p = (long *)tag->ti_Data;
		*p = data;
		return XODIP_OK;
	}
	else {
		return XODIP_NOTFOUND;
	}
}

		
ULONG LibExamObject(void *object,
	struct TagItem *tags)
/*
 *	In:	object	A0		Pointer to object to examine
 *			tags		A1		Taglist giving fields to examine
 *
 * Does:	Copies data from the object to the pointers given by the tag
 *			list.
 */
{
BOOL result;

	result = ExamObject(object, TagWFunc, tags);
	return result;
}


ULONG __saveds __asm LIBExamObject(register __a0 void *obj,
	register __a1 struct TagItem *tags)
{
ULONG result;

	result = LibExamObject(obj, tags);
	SetErrorTags(tags);
	return result;
}


ULONG LibExamObjectTags(void *object, Tag tag, ...)
{

	return LibExamObject(object, (struct TagItem *)&tag);
}


short VarListWFunc(struct VarList *vl, struct FieldDesc *fd, long data)
/*
 *	In:	vl					Pointer to varlist to write to
 *			fd					Pointer to field descriptor structure
 *			data				Data item to write
 *
 * Does:	Writes a data item into a varlist.
 */
{
short result;

	switch (fd->type) {
	case XQO_BYTE:
	case XQO_WORD:
	case XQO_LONG:
		if (!AddVarListNum(vl, fd->name, strlen(fd->name), data)) {
			return XODIP_ERROR;
		}
		break;
	case XQO_STRING:
		/*
		 *		Kludge: don't write null string into varlist.
		 *		Fix when varlists semantics are defined, if ever.
		 */
		if (!data) {
			data = (long)"";
		}
		result = AddVarListStr(vl, fd->name, strlen(fd->name), (char *)data,
			strlen((char *)data));
		if (!result) {
			return XODIP_ERROR;
		}
		break;
	}
	return XODIP_OK;
}


BOOL ExamObjectVarList(void *object, struct VarList *vl)
/*
 *	In:	object			Pointer to object to examine
 *			vl					Taglist giving fields to examine
 *
 * Does:	Copies data from the object to the given varlist.
 *
 */
{

	return ExamObject(object, VarListWFunc, vl);
}


void *ModifyObject(void *object,
	BOOL (*rfunc)(void *, struct FieldDesc *, long *),
	void *src, BOOL noCopyStrs)
/*
 *	In:	object			Pointer to object to modify
 *			rfunc				Function used to read data from entity
 *			src				Entity-specific data
 *			noCopyStrs		Flag: TRUE = suppress copy of strings
 *
 * Does:	Copies data from the entity to the object.
 */
{
int type;
long data;
char *destP;
void *backup;
BOOL success, madeBackup;
short result;
struct FieldDesc *fd;
struct XfqObject *xo;

	if (!object) {
		return NULL;
	}
	Watchdog("ModifyObject", object);
	
	/*
	 * Make backup in case modify fails.  In effect, all mods are done
	 *	to the backup to leave the original intact in case it fails.
	 *	If all goes well, the original is junked and the backup becomes
	 *	the working copy.
	 *
	 * Optimization/bug fix:
	 *		If there are no string components to the object, then mods
	 *		cannot fail, so don't make a backup.  This also allows sessions
	 *		to exist (their sessLists get screwed otherwise :-( ).
	 *
	 *		If the object is being shared (useCount > 1) then always copy.
	 *		If noCopyStrs is in effect, then may not need to copy.
	 */
	madeBackup = FALSE;
	type = ObjectType(object);
	fd = ObjectDesc[type].fields;
	
	xo = (struct XfqObject *)object;
	xo--;
	if (xo->useCount > 1) {
		madeBackup = TRUE;
	}
	else if (!noCopyStrs) {
		while (fd->name) {
			if (fd->type == XQO_STRING) {
				madeBackup = TRUE;
				break;
			}
			fd++;
		}
	}
	if (madeBackup) {
		backup = DupObject(object);
		if (!backup) {
			return NULL;
		}
	}
	else {
		backup = object;
	}

	/*
	 * Call pre-modify
	 */
	if (ObjectDesc[type].preModify) {
		if (!(*ObjectDesc[type].preModify)(backup)) {
			LibDropObject(backup);
			return NULL;
		}
	}
	
	success = TRUE;
	fd = ObjectDesc[type].fields;
	
	while (fd->name && success) {
		result = (*rfunc)(src, fd, &data);
		if (result == XODIP_OK) {
			destP = ADDRESS(backup, fd->offset);
			switch (fd->type) {
			case XQO_BYTE: {
				signed char *byteP;

					byteP = (signed char *)destP;
					*byteP = data;
					break;
				}
			case XQO_WORD: {
				short *wordP;

					wordP = (short *)destP;
					*wordP = data;
					break;
				}
			case XQO_NUMSTR: {
				char **strP;
				char numbuf[12];
				int numlen;
				
					numlen = PutNumber(numbuf, 10, 0, data);
					numbuf[numlen] = '\0';
					
					strP = (char **)destP;
					LibDropObject(*strP);
					*strP = CopyString(numbuf);
					if (!*strP) {
						success = FALSE;
					}
					break;
				}
			case XQO_STRING: {
				char **strP;
		
					strP = (char **)destP;
					LibDropObject(*strP);
					if (noCopyStrs) {
						*strP = LibCopyObject((char *)data);
					}
					else {
						*strP = CopyString((char *)data);
						if (!*strP && data) {
							success = FALSE;
						}
					}
					break;
				}
			case XQO_LONG: {
				long *longP;

					longP = (long *)destP;
					*longP = data;
					break;
				}
			default: {
				void **voidP;

					voidP = (void **)destP;
					LibDropObject(*voidP);
					*voidP = LibCopyObject((void *)data);
					break;
				}
			}
		}
		else if (result == XODIP_ERROR) {
			success = FALSE;
		}
		fd++;
	}
	
	/* Call post-modify */
	if (success && ObjectDesc[type].postModify) {
		success = (*ObjectDesc[type].postModify)(backup);
	}
	
	if (!success) {
		if (madeBackup) {
			LibDropObject(backup);
		}
		return NULL;
	}
	else {
		if (madeBackup) {
			LibDropObject(object);
		}
		return backup;
	}
}


short TagRFunc(struct TagItem *tags, struct FieldDesc *fd, long *data)
/*
 *	In:	tags				Pointer to taglist to read from
 *			fd					Pointer to object field description
 *			data				Pointer to write data to
 *
 * Does:	Copies data out of a taglist.
 */
{
struct TagItem *tag;

	tag = FindTagItem(fd->tag, tags);
	if (tag) {
		*data = tag->ti_Data;
		return XODIP_OK;
	}
	else {
		return XODIP_NOTFOUND;
	}
}


void *LibModifyObject(void *object,
	struct TagItem *tags)
/*
 *	In:	object	A0		Pointer to object to modify
 *			tags		A1		Taglist giving modifications
 *
 * Does:	Copies data from the tag items to the object.
 */
{
void *newObj;
BOOL noCopyStrs;

	/*
	 * Determine method of string modification
	 */
	noCopyStrs = GetTagData(XQ_NoCopyStrs, FALSE, tags);
	
	newObj = ModifyObject(object, TagRFunc, tags, noCopyStrs);
	return newObj;
}


void *__saveds __asm LIBModifyObject(register __a0 void *obj,
	register __a1 struct TagItem *tags)
{
void *newobj;

	newobj = LibModifyObject(obj, tags);
	SetErrorTags(tags);
	return newobj;
}


void *LibModifyObjectTags(void *obj, Tag tag, ...)
{

	return LibModifyObject(obj, (struct TagItem *)&tag);
}


short VarListRFunc(struct VarList *vl, struct FieldDesc *fd, long *data)
/*
 *	In:	vl					Pointer to varlist to read from
 *			fd					Pointer to object field description
 *			data				Pointer to write data to
 *
 * Does:	Copies data out of a varlist.
 */
{
BOOL result;

	switch (fd->type) {
	case XQO_BYTE:
	case XQO_WORD:
	case XQO_LONG:
		result = GetVarListNum(vl, fd->name, strlen(fd->name), data);
		return (short)((result) ? XODIP_OK : XODIP_NOTFOUND);
	case XQO_STRING:
		*data = (long)GetVarListStr(vl, fd->name, strlen(fd->name));
		return (short)((*data) ? XODIP_OK : XODIP_NOTFOUND);
	}
}


void *ModifyObjectVarList(void *object, struct VarList *vl)
/*
 *	In:	object			Pointer to object to modify
 *			vl					Varlist containing modifications
 *
 * Does:	Copies data from the tag items to the object.
 */
{

	return ModifyObject(object, VarListRFunc, vl, TRUE);
}


