[comp.sources.amiga] v02i015: dres - an object-oriented resource library

page@swan.ulowell.edu (Bob Page) (10/25/88)

Submitted-by: dillon@cory.berkeley.edu (Matt Dillon)
Posting-number: Volume 2, Issue 15
Archive-name: util/dres.docs

# This is a shell archive.  Remove anything before this line
# then unpack it by saving it in a file and typing "sh file"
# (Files unpacked will be owned by you and have default permissions).
# This archive contains the following files:
#	ipc.doc
#	lists.doc
#	memory.doc
#	misc.doc
#	qints.doc
#	res.doc
#	runlib.doc
#	timedate.doc
#
if `test ! -s ipc.doc`
then
echo "writing ipc.doc"
cat > ipc.doc << '\Rogue\Monster\'

			IPC.DOC  (DRES.LIBRARY)

			    Matthew Dillon
			  25 September 1988


	Special note.  I've tried my best, but this document is somewhat
	lacking in the exact mechanics and implementation of IPC.  Please
	refer to the source module to resolve such questions.

    The IPC section of the DRES.LIBRARY pertains to the sending of commands
and retrieval of results between arbitrary processes without conflict.
Processes which use IPC come in three flavors:

    (1) A process which is able to accept and process IPC commands
    (2) A process which might send IPC commands to other processes
    (3) A process which does both.

    The IPC mechanism implemented by this library is inherently asychronous
in nature (messages and ports), but also provides a synchronous call
specially tailored for cases (2) and (3).  Doing IPC commands synchronously
has certain advantages, the greatest of which is that the implementation by
application software is greatly simplified.  The synchronous call gets
around possible deadlocks in case (3) by interrupting itself and calling a
handler if incomming messages occur while it is waiting for the sent
message to complete.

    The greatest feature of this IPC mechanism is a great flexibility in
who allocates and deallocates the message buffers.  Both the source and
destination processes have a wide range of options concerning message
buffers.  Unless otherwise specified, an allocated copy of the message
buffer is automatically made and used which allows the send or reply message
buffer used by the process to be immediately discarded.  Additionaly, a
destination IPCPort can be flagged to force incomming messages to be
allocated no matter what the sender requests.

    NOTE:   The term 'allocation of messages' inherently means that if the
message is allocated, the user of the message can munge it however he likes
(exception: IF_GLOBAL messages that are propogated through all IPC ports).
Allocated messages are automatically FreeMem()d by the IPC subsystem or
by the receiver.   Also, WHEN DUPLICATION OF MESSAGES OCCUR, THE ALLOCATED
MEMORY IS OF THE SAME TYPE AS THE ORIGINAL BUFFER.

    NOTE:   Do not get confused by the many different ways messages buffers
can be allocated.  A message buffer will never be allocated-duplicate more
than once.  For example, if the source specifies that it is passing an
allocated buffer, the further duplication is done by the IPC subsystem.

    -The sender can allocate the message buffer himself and pass that,
     pass a temporary buffer which the IPC subsystem duplicates, or pass
     a static buffer to the destination without the overhead of allocation
     and duplication.

    -The receiver of a particular message can determine whether that message
     was allocated and FreeMem() it himself, can allow the IPC subsystem
     to free allocated messages, and can even force all incomming messages
     on his IPC port to be allocated no matter what the sender specifies.
     Forcing the IPC subsystem to allocate otherwise static messages is
     a very useful ability to have.  For instance, the parser employed by
     the receiver of a message might destroy or modify the message buffer
     during parser, while the original sender of the message might have
     used a static string or otherwise expected the buffer not to be
     screwed up.  By the receiver being able to specify that he WANTS the
     buffer to be an allocated duplicate because he intends to modify it
     without permission is a good thing.  Even though the sender passes
     a static buffer to the IPC subsystem, that subsystem will allocate
     and duplicate it for the receiver.

    -In his reply to the sender, the receiver has the same sort of control
     as sender in terms of how the reply message buffer is allocated.  After
     getting the reply back, the original sender can determine whether the
     reply buffer was allocated and FreeMem() it himself, or allow the IPC
     subsystem to free it when he is through with the reply.

    -Finally, the receiver of the original message can even free his own
     reply buffer when the original sender is through with it!

    So in general, one can use either the IPC subsystem's automatic buffer
copying feature (the default), use no buffer copying at all, or use one's
own scheme.

    MOST OF THESE ALLOCATION FEATURES ARE IMPLEMENTED THROUGH FLAGS.  READ
THE DOCS BELOW ON OPENIPC() and SENDIPC().


		       APPLICATION NAME DOMAINS

    An application for cases (1) or (3) must create a receiver port for
IPC messages with the OpenIPC() call.  OpenIPC() takes a name and flags
argument.  The Flags argument is discussed in OpenIPC() below.  You do
not need a port to send messages, only to receive them.

    The name of the port determines the type of messages it will receive.
The format is:	<application_name>.<domain> , for example: "dmouse.CMD"
Currently, only the .CMD domain has a defined standard.  It does not matter
whether multiple copies of the application are running, they can share the
same port name.  You could have, say, two invocations of your favorite
editor each with five active projects.	Support for this is discussed in
the ReplyIPC() call below.

    While this port exists, other tasks may send IPC messages to it.  You
must extract the messages (GetMsg()), process it, and then ReplyIPC() the
message to return it to the original sender.

    The CloseIPC() call will shutdown an IPC port that you had previously
openning and return any pending messages by trying to pass them to the
next application of the same name (if none, an error is eventually returned
to the sender).

				.CMD ports

    Messages sent over .CMD ports are formatted as null-terminated
ascii-text.  The send buffer (msg->TBuf) is formatted as follows:

    project\0command\0		(\0 = NUL = ascii 0)

    The \0 MUST be included as part of the buffer and buffer length.  The
project name allows the application to identify which of its possibly
multiple projects is to be affected by the command.  a NULL project name
(\0command\0) is allowed and refers to the 'active' project in the
application.  The project name is ignored by applications which do not
support the notion of projects. The format of the command depends on the
application, but must be in ascii and terminate with a \0 (ascii 0).

    The logical extension for the specification of multiple project names
is to allow comma delimited names and wildcards (* and ?).  For example,
"*\0command\0" would refer to all projects, "a.c,b.c\0command\0" would
refer to two specific projects.  This might not be supported by all
applications.

    The format of the reply buffer depends on the success of the operation.
If the command was successful, the reply buffer will either be NULL or
contain some ascii/text response ending in \0.	If the command was not
successful, IF_ERROR will be set in msg->RFlags and the reply buffer will
be either NULL or contain some ascii/text error message ending in \0.
The msg->Error integer error code will be non-zero if IF_ERROR was set,
possibly non-zero if IF_ERROR was not set to indicate a warning (IF_ERROR
always means Total-Failure).   If msg->Error is not set by the application
which returns the error condition, it will be set by the IPC subsystem.



ParseCmd							ParseCmd

    argc = ParseCmd(buf, &argv, varget, varfree, &error, NULL);
    int argc;
    char **argv;	    (note, address passed)
    char *((*varget)());
    void (*varfree)();
    long error; 	    (note, address passed)

    (note:  Last NULL argument (0L) is required for future compatibility).

    THIS COMMAND CAN EASILY BE USED WITH NON-IPC RELATED ROUTINES!

    Do DME-type parsing on a string buffer.  The buffer is terminated with
    a \0 but may contain a \n at the end (which is ignored) to be compatible
    with fgets() as well as gets().  NOTE that if the string is terminated
    with a \<LF><NUL> i.e. "\\n\0", the backslash will override the standard
    ignorance of the \n and parse it into the result.

    DME-type parsing delimites fields by spaces except those enclosed in
    matching parenthesis.  Multiple levels of parenthesis is tracked and
    the OUTERMOST SET REMOVED.	Other special characters include shift-6
    (^), backslash (\), and dollar ($):

	 form	    examples	    function

	^<char>     ^c ^d ^L	    embed a control character, case is ignored
				    you may not embed ^@ (0)

	\<char>     \\ \( \^        override the meaning of the next character,
				    allows embedding special characters.

	$(varname)                  inline string variable, var name can be
				    anything except ')'.

	$varname		    inline string variable, var name
				    restricted to alphanumeric and '_'.

    Example:	"echo (this (is a) test) $file/x a mess ^g \(hi! \)ug\\"
    Result: argv[0] =	"echo"
	    argv[1] =	"this (is a) test"
	    argv[2] =	"dnet:foo/x"        (assumes $file == 'dnet:foo')
	    argv[3] =	"a"
	    argv[4] =	"mess"
	    argv[5] =	"<CONTROL-G>"       (i.e. ascii code 7)
	    argv[6] =	"(hi!"
	    argv[7] =	")ug\"
	    argv[8] =	NULL

    Note: offset -1 of each argument contains a status byte.  Currently,
    bit 0 is defined to indicate whether the argument was paren'd or not.
    This cannot otherwise be told as the highest level of parenthesis are
    stripped.	(argv[0][-1] & 1) == 0, (argv[1][-1] & 1) == 1.

				--------

    buf 	-\0 or \n\0 terminated buffer holding the command to parse.
		 The buffer is not modified and may be discarded after the
		 call returns.

    &argv	-address of a variable which will be initialized to point
		 to an allocated array of pointers to strings exactly the
		 way the main() argv works.

    varget	-Pointer to a subroutine which when given a \0 terminated
		 variable name will return a \0 terminated string or NULL
		 if that variable does not exist.

		 varget(varname)
		 char *varname;

		 This argument may be NULL indicating string variables are
		 not supported.  The routine may return non-allocated
		 strings (i.e. static storage or string constants) in which
		 case there is no need for a varfree() function.

    varfree	-Pointer to a subroutine which when given the string
		 pointer returned by varget() will free it.  This call
		 is made immediately after varget() is called and returns
		 nothing.  You may pass NULL for this argument if no free
		 function exists (varget was NULL or returned static
		 strings).

    &error	-You must supply a pointer to a longword which will be
		 returned holding the error, if any, and the index where
		 the error occured, if any.  The index is stored in the
		 lower 16 bits and the error # stored in the upper 16 bits:

		 1 << 16  = NO MEMORY
		 2 << 16  = STRING VARIABLE NOT FOUND (varget() returned
			    NULL), the lower 16 bits contain the index into
			    buf.

    NULL	-This argument is reserved for future expansion and must
		 be NULL (0L).

    NOTE:   The returned arg list may be munged however you wish, including
	    the pointers themselves, just as long as you pass the original
	    argv pointer (i.e. the entries may be munged) to FreeParseCmd().

	    0 is always returned on error, and error will be set properly.
	    Otherwise, argc is returned and error will hold 0.

	    The original buffer is not modified in any way.

    NOTE:   Since the outer set of parenthesis () are stripped, offset -1
	    of each arguments provides information as to whether the
	    argument was quoted or not.  If bit 0 is set, the argument
	    was quoted,  I.E.:	    (a) b c
		(av[0][-1] & 1) = 1     av[0] = "a"
		(av[1][-1] & 1) = 0     av[1] = "b"
		(av[2][-1] & 1) = 0     av[2] = "c"


FreeParseCmd							FreeParseCmd

    (void) FreeParseCmd(argv)

    This routine frees the storage referenced by the argv... the argv array
    and the original strings are all freed.  It is ok if Individual entries
    in the argv array are munged.


OpenIPC 							OpenIPC

    port = OpenIPC(name, flags)
    PORT *port;
    char *name;
    long flags;

    Create an IPC port of the specified name suitable for receiving IPC
    commands on.  If the port could not be created due to lack of memory,
    NULL is returned, else an EXEC port is returned.  The port is setup
    as PA_SIGNAL and takes up a signal bit in your signal set.	If the
    application does not wish a signal-port, it may FreeSignal() the
    signal and change the flags to something other than PA_SIGNAL.

    Due to the fact that the signal was allocated for the calling task,
    the port is normally usable only from the calling task.  One can
    change the ownership and port characterstics but always remember that
    CloseIPC() can only be called from the owning task.

    IF_ALLOC	If IF_ALLOC is specified, *ALL* incomming message buffers
		will be forced to be allocated even if the sender of the
		message specifies IF_NOCOPY.  I.E. you, as the receiver of
		IPC messages on this port can specify this flag to guarentee
		the msg->TBuf is allocated and thus modify the contents
		of the buffer itself without confusing the original sender
		(who may have specified a static string)

		Note:	However, if this message is to be passed on via
		IF_GLOBAL or IF_NOTFND, you probably do not want to munge
		the buffer.

    IF_GLOBAL	If IF_GLOBAL is specified, this port will accept global
		messages.  Global messages are always of the .CMD form
		though the sender could easily put stuff beyond the second
		\0 in the message buffer destined for specialized routines.

		If not specified, IF_GLOBAL messages will NEVER propogate
		through this port.


CloseIPC							CloseIPC

    (void) CloseIPC(port)
    PORT *port;

    Delete an IPC port previously obtained by OpenIPC().  The owning task
    must make this call since it will FreeSignal() the allocated signal.
    Note that if the port flags are set to something other than PA_SIGNAL,
    no attempt will be made to free the possibly non-existant signal bit.

    This call will pass any pending messages on to the next application
    of the same name, and if no other exists, will return the messages
    with an error.  Pending messages with IF_GLOBAL set continue their
    journey through the IPC ports flagged to accept IF_GLOBAL messages.


SendIPC 							SendIPC

    msg = SendIPC(appname, buf, len, flags)
    IPCMSG *msg;
    char *appname;
    APTR buf;
    long len, flags;

    This routine starts an asynchronous IPC request.  It allocates and
    initializes an IPCMSG and reply port and sends the message to the
    destination application (appname).  The IPCMSG is returned and it's
    msg->RFlags holds the IF_ALLOCMSG internal flag to cause the IPCMSG
    and replyport to be deallocated when you are through with the reply.
    NOTE!  The returned IPCMSG has already been sent to the destination
    and is not owned by you.  The idea is to then CheckMsg() and/or
    WaitMsg() on it.  The ANode filed of the IPCMSG, however, *IS* owned
    by you and you may use it however you wish.

    The buffer and buffer length you pass SendIPC() is used to allocate
    a copy of the buffer (which is deallocated automatically when the
    destination ReplyIPC()s).  This means that you can discard/reuse your
    buffer as soon as the call returns.  However, if you specify the
    IF_NOCOPY flag SendIPC() will use your buffer pointer and you must
    be sure not to modify its contents until the message is returned to you.

    If you specify IF_ALLOC it is assumed your buffer pointer and length
    were AllocMem()d and the system will FreeMem() them when the destination
    is through processing your message and calls ReplyIPC().

    ** See SendIPC2() for info on how to handle the message when it comes
    back.

    * If the 'name' is NULL, the first application in the application
    list is called.  This is usually used in conjunction with IF_GLOBAL
    to indicate that the message should propogate to all IPC ports in the
    system.

SendIPC2							SendIPC2

    msg = SendIPC2(name, msg)

    This routine is called by SendIPC().  This assumes you already have an
    IPC message structure that you wish to use.  The msg argument is
    returned.  The reply fields, including msg->Error and msg->Confirm,
    are zerod.

    The IPCMSG must be initialized as follows:

    msg->Msg.mn_ReplyPort	-must hold reply port for the message
    msg->Msg.mn_Length		-must hold length of IPCMSG structure ONLY
				 if you had specified the IF_ALLOCMSG in
				 msg->TFlags
    msg->Msg.TBuf		-holds the buffer pointer to the message
				 or NULL
    msg->Msg.TLen		-holds the length of the message
    msg->Msg.TFlags		-holds flags associated with the message:
	    IF_ALLOC		* automatic deallocation on reply.  This flag
				  is set automatically if you do not specify
				  IF_NOCOPY.  If NEITHER IF_NOCOPY or IF_ALLOC
				  are specified, the IPC subsystem will
				  allocate a duplicate of your buffer, place
				  it in TBuf, and set this flag for you.
	    IF_GLOBAL		* force propogate to all applications in
				  the system before returning to the sender.
	    IF_NOCOPY		* force IPC system to use the buffer you
				  placed in TBuf.  Otherwise, the IPC system
				  will allocate and copy your buffer into
				  its own and place that in TBuf.

	    IF_ALLOCMSG 	* IPC system will deallocate the IPCMSG
				  itself when FreeIPC() is called.  This
				  flag is set automatically when the IPC
				  system has allocate the IPCMSG in the
				  first place.

    WARNING!	Upon return, msg->TBuf will not necessarily point to your
		buffer, even if you specified IF_NOCOPY or IF_ALLOC.

		   Handling the message when it comes back

    After you make the call, you must wait for the message to complete
    via WaitMsg and/or CheckMsg().  Note that WaitMsg() will remove the
    message from its reply port.  If you do not use WaitMsg(), you must
    remember to remove the message before continuing.

    Then, process the reply (msg->RBuf, msg->RLen, and msg->RFlags).
    The msg->RBuf IS ALLOWED TO BE NULL.  msg->RFlags will indicate
    any errors:

	IF_ERROR|IF_NOTFND|IF_NOAPP:	no application of that name exists

	IF_ERROR|IF_NOTFND	   :	application exists, but the requested
					project does not exist.  OR the
					command does not exist for this
					application.

	IF_ERROR		    :	The application exists and can handle
					the project & command, but an error
					occured during interpretation of the
					command.

	IF_ALLOC		    :	The reply buffer was allocated.  You
					have the option of FreeMem()ing it
					yourself or allowing the system to
					do so.	If you do it, you must set
					RBuf = NULL.

    NOTE:   msg->Error does not need to be set by the receiver.  If
	    msg->Error is 0 and IF_ERROR is set, msg->Error will be set to
	    20 by the IPC subsystem.

    NOTE:   In the case of IF_ERROR, the reply buffer might still be
	    non-NULL and contain an error message.

    When through dealing with the reply, you MUST call FreeIPC(msg).  This
    routine will deallocate the reply buffer if msg->RFlags had IF_ALLOC
    set by the destination and also free the message it self if msg->TFlags
    contained IF_ALLOCMSG set by SendIPC() or the user before a SendIPC2().

    Additionaly, if msg->Confirm was set by the destination, this function
    vector will be called with the message pointer as an argument to allow
    the destination to deallocate its reply.  NOTE!!! If the message has
    IF_GLOBAL set in msg->TFlags, msg->Confirm should NOT be touched by
    receiving applications.


DoIPC2								DoIPC2

    (void)  DoIPC2(name, msg, collfunc, collport)
    long flags
    char *name;
    IPCMSG *msg;
    void (*collfunc)();
    PORT *collport;

    This is the hall mark of the IPC library and allows one to send
    SEMI-SYNCHRONOUS messages while still retaining the ability to
    process incomming messages on your own IPC port (case #3).  The IPC
    message msg must be setup as in SendIPC2().  The collision function
    and collision port may be NULL if you wish.

    This command sends the message and then waits for the reply.  If while
    waiting for the reply a message comes in on the collision port (usually
    your IPC port), the collision vector will be called with the collision
    port as an argument, allowing you to process incomming messages while
    waiting for one you sent.  By properly designing your software, you
    can allow multiply stacked DoIPC2() calls.

    When the message finally comes back, this call returns.  The application
    must still deal with its reply buffer and call IPCFree() when through.

    NOTE: The collision function is called as a subroutine of this routine
    and thus if it is a general command-handling routine remember that
    your command interpreter might have to be synchronously reentrant.
    DoIPC2() is certainly reentrant.  A4 and A5 are not screwed up by
    DoIPC2() so you do not have to reload your data segment base register
    from the collision vector routine if using the small data model.
    (Compatible with both Aztec and Lattice C).


ReplyIPC							ReplyIPC

    (void) ReplyIPC(msg, buf, len, flags)
    IPCMSG *msg;
    APTR buf;	    (may be NULL & len would then be 0)
    long len;
    long flags;

    You must reply to all IPC messages you receive on your IPC port by
    calling ReplyIPC().  ReplyIPC() works almost exactly like IPCSend(),
    but deals with the RBuf, RFlags, and RLen fields of the IPC structure.
    Normally, the IPC system will allocate a duplicate of your reply buffer
    and stick that in msg->RBuf which allows you to throw away your buffer
    after calling ReplyIPC().  Various flags override this feature:

    IF_ALLOC	    -	The buffer is automatically deallocated when the
			source calls FreeIPC().  If this flag exists the
			IPC system will not allocate a duplicate buffer.

    IF_NOCOPY	    -	The IPC system will not allocate a duplicate buffer.

	Normally, (1) neither of these flags is specified, or (2) IF_ALLOC
	is specified.  Specifying IF_ALLOC alone means that the IPC system
	assumes you have AllocMem()d the buffer yourself and want it
	automatically freed when the source (original sender) calls
	FreeIPC().

	Specifying both IF_ALLOC and IF_NOCOPY is equivalent to specifying
	just IF_ALLOC.	Specifying just IF_NOCOPY means that the IPC system
	will use your buffer pointer and will NOT attempt to deallocate it.
	In this case, you are responsible for keeping the buffer intact
	until the source calls FreeIPC().  This is possible through the
	use of the msg->Confirm vector.   ** you cannot use msg->Confirm
	for IF_GLOBAL packets!	Because there is only one vector entry and
	possibly multiple users of the message.

	NOTE:  The buffer pointer placed in msg->RBuf will either be yours
	or the allocated duplicate.  msg->RBuf will be set to NULL when (if)
	the system deallocates it.

    If you specify IF_NOTFND, your buffer and length are completely
    ignored and the message is passed on to the next application program
    with the same name.  IF THE ORIGINATOR SPECIFIED IF_GLOBAL WHEN HE
    SENT THE MESSAGE, AND YOU DID NOT SPECIFY IF_NOTFND, the message
    will be passed on to the next application anyway and your reply will be
    passed on along with it.  In the same way, if you were not the first
    application to get this message (it was passed to you), the
    msg->RBuf/RLen/RFlags fields may already have something in them. If
    your reply buffer pointer is not the same address as the one already in
    msg->RBuf and msg->RFlags held IF_ALLOC, the old buffer in msg->RBuf
    will be deallocated before processing continues with your ReplyIPC().

    ** Warning, msg->RBuf will be replaced in the same manner msg->TBuf
       is by an allocated duplicate unless you specify otherwise.


FreeIPC 							FreeIPC

    (void) FreeIPC(msg)
    IPCMSG *msg;

    This routine must be called when the original sender of an IPC message
    has sent the message, waited for the reply, removed the message from
    the reply port, and processed the reply buffer.  This serves to free
    the message and any associated buffers as specified below:

    If msg->RFlags has IF_ALLOC set the reply buffer is immediately
    deallocated and msg->RBuf set to NULL.

    If msg->Confirm is non-NULL the vector will be called with the IPCMSG
    as an argument.

    If msg->TFlags holds IF_ALLOCMSG, the msg will be FreeMem()d with the
    length msg->Msg.mn_Length.	IF_ALLOCMSG is not usually set by the
    user... it is set by SendIPC() which allocates a message/replyport
    and wants both to be deallocated on completion.




\Rogue\Monster\
else
  echo "will not over write ipc.doc"
fi
if [ `wc -c ipc.doc | awk '{printf $1}'` -ne 24710 ]
then
echo `wc -c ipc.doc | awk '{print "Got " $1 ", Expected " 24710}'`
fi
if `test ! -s lists.doc`
then
echo "writing lists.doc"
cat > lists.doc << '\Rogue\Monster\'

				 LISTS.DOC

    These are general list handling routines that should have been provided
in EXEC.  In addition to basic additions, I provide support for structures
whos Nodes are not at the base of the structure.  These are the infamous
'sptr's below.  Such routines take a pointer to the structure, add an
offset to it to get to the Node, do the specified operation, then subtract
the offset to return to the 'sptr's actual base.  In otherwords, it allows
you to deal with such structures without having to do these hacks in your
source.

GetHead 						    GetHead
GetSucc 						    GetSucc

    node = GetHead(list)
    node = GetSucc(node)    (physically the same routine as GetHead()).

    This routine returns the successor of a node in the list.  If a list is
    specified, the first node in the list is returned.	NULL is returned if
    the list is empty or one has reached the end of the list.

GetTail 						    GetTail

    node = GetTail(list)

    This routine returns the last node in the list, or NULL if the list
    is empty.

GetPred 						    GetPred

    node = GetPred(node)

    This routine returns the previous node from the given node, or NULL
    if we are at the head of the list.

GetHeadOff						    GetHeadOff

    sptr = GetHeadOff(list, offset)

    This routine retrieves the first node in the list and subtracts the
    specified offset to get to the base of the structure (where 'offset'
    is the offset into the structure where the Node has been placed rather
    than placing it as the first object in the structure).

    NULL is returned is the list is empty.  NOTE!  Unlike GetHead(), only
    a valid list pointer may be specified here.

GetTailOff						    GetTailOff

    sptr = GetTailOff(list, offset)

    This routine works the same as GetHeadOff() but works on the last
    node in the list.

GetSuccOff						    GetSuccOff

    sptr = GetSuccOff(sptr, offset)

    Given a structure pointer where the Node is offset from the structure
    base, add the offset to the pointer, determine the successor if any,
    then subtract the offset from the successor to get back to the
    structure base for the successor.

    NULL is returned if there is no successor.

GetPredOff						    GetPredOff

    sptr = GetPredOff(sptr, offset)

    This routine works the same as GetSuccOff() but works on the previous
    node rather than the next.	NULL is returned if we are at the head
    of the list;

EnqueueLong						    EnqueueLong

    (void) EnqueueLong(list, startnode, node, valoff)

    This routine queues a node into a list beginning its search at the
    startnode (first element if startnode == NULL).  The list is assumed
    to be sorted by the longword valoff from the node structure.  I.E.
    If you had a graphic structure with a Node at its base and where
    coordinates were stored in longwords, you insert nodes sorted by
    one of the coordinates into a list.

    valoff is the offset from the node base where the longword value
    the list is sorted by exists.

EnqueueOffLong						    EnqueueOffLong

    (void) EnqueueOffLong(list, startnode, sptr, off, valoff)

    This works just like EnqueueLong() but an extra parameter, 'off', is
    provided to inform the routine where in the structure the Node is.
    Does that make sense?

SearchFwdNode						    SearchFwdNode

    retval = SearchFwdNode(node, function, arg)
    MINNODE *node;
    long (*function)();
    long arg;

    This routine searches a list in the forward direction beginning at
    the specified node, calling the function vector for every node.  The
    function is called C-fashion (A4 and A5 are intact to support the
    small code model and D2/D3 may be destroyed in addition to standard
    scratch variables to support Aztec C).  The function is given two
    arguments:	the node, and the arg passed to SearchFwdNode():

	(*function)(current_srch_node, arg)

    The search is terminated if the function vector returns a non-zero
    value.  If the search reaches the end of the list, NULL is returned.
    NOTE: you may pass a NULL as the node to SearchFwdNode() which results
    in a NULL being immediately returned.

SearchRvsNode						    SearchRvsNode

    retval = SearchFwdNode(node, function, arg)

    This function works exactly the same as SearchFwdNode() but works
    in reverse begining at the specified node.

SearchFwdList						    SearchFwdList

    retval = SearchFwdList(list, function, arg)

    This function works exactly the same as SearchFwdNode() but you give
    it a pointer to a list rather than to a node.

    A valid list pointer must be specified, but the list may be empty.

SearchRvsList						    SearchRvsList

    retval = SearchRvsList(list, function, arg)

    This function works exactly the same as SearchRvsNode() but you give
    it a pointer to a list rather than to a node.

    A valid list pointer must be specified, but the list may be empty.

SearchFwdNodeOff					    SearchFwdNodeOff
SearchRvsNodeOff					    SearchRvsNodeOff
SearchFwdListOff					    SearchFwdListOff
SearchRvsListOff					    SearchRvsListOff

    rval    =	SearchFwdNodeOff(sptr, function, off, arg)
    rval    =	SearchRvsNodeOff(sptr, function, off, arg)
    rval    =	SearchFwdListOff(list, function, off, arg)
    rval    =	SearchRvsListOff(list, function, off, arg)

    These routines work like those shown above, but an additional
    argument, an offset, is supplied.  This is the offset into the
    structure pointer where the Node structure is embedded.  In otherwords,
    the other routines were simply special cases of this one with off == 0.

    Note that for *NodeOff() routines a pointer to a structure containing
    the Node structure in it somewhere is supplied, whereas in the
    *ListOff() routines a pointer to the list base is given (this hasn't
    changed).



\Rogue\Monster\
else
  echo "will not over write lists.doc"
fi
if [ `wc -c lists.doc | awk '{printf $1}'` -ne 5861 ]
then
echo `wc -c lists.doc | awk '{print "Got " $1 ", Expected " 5861}'`
fi
if `test ! -s memory.doc`
then
echo "writing memory.doc"
cat > memory.doc << '\Rogue\Monster\'

				MEMORY.DOC

    These are general memory handling routines which will perform operations
    as quickly as possible.  They are super-optimized to use long word and
    multiple-register moves whenever possible.

BZero								    BZero

    (void) = BZero(buf, bytes)
    APTR buf;
    long bytes;

    Zero out an area of memory.  That was simple!  This is simply a special
    case of BSet()

BSet								    BSet

    (void) = BSet(buf, bytes, value)
    APTR buf;
    long bytes;
    long value; 	(only lower 8 bits used)

    Jam a byte value into an area of memory.


BMov								    BMov

    (void) = BMov(src, dest, bytes)
    APTR src, dest;
    long bytes;

    Move a block of memory from the source to the destination.	An
    ascending or decending copy is used depending on how the source and
    destination overlap, if at all.  Both are optimized to use multiple-
    register moves when possible, longword moves, or byte moves if the
    two blocks are hopelessly out of alignment.

BCmp								    BCmp

    BOOL   = BCmp(src, dest, bytes)
    APTR src, dest;
    long bytes;

    Compare two blocks of memory.  (1) is returned on success, (0) on
    failure.  Currently, this routine is not optimized at all.


\Rogue\Monster\
else
  echo "will not over write memory.doc"
fi
if [ `wc -c memory.doc | awk '{printf $1}'` -ne 1234 ]
then
echo `wc -c memory.doc | awk '{print "Got " $1 ", Expected " 1234}'`
fi
if `test ! -s misc.doc`
then
echo "writing misc.doc"
cat > misc.doc << '\Rogue\Monster\'

				  MISC.DOC

    These are miscellanious useful routines for your enjoyment.


WildCmp 							WildCmp

    CBOOL  = WildCmp(wildcard, filename)
    char *wildcard;
    char *filename;

    The wildcard characters supported are * and ?.  The wildcard is
    compared against the filename and (1) returned on success, (0) on
    failure.


WaitMsg 							WaitMsg

    msg    = WaitMsg(msg)
    EXECMSG *msg;

    This routines Waits for a message to be returned just like WaitIO()
    waits for an io request to complete.  The message is REMOVED from the
    reply port after it has returned.

    the message must have a valid mn_ReplyPort() and must have been queued
    with PutMsg(), which sets the ln_Type to NT_MESSAGE, and replied with
    ReplyMsg(), which sets the ln_Type to NT_REPLYMSG.


CheckMsg							CheckMsg

    msg/NULL = CheckMsg(msg)
    EXECMSG *msg;

    This routine checks to see if the message has been returned.  The
    same restrictions apply as for WaitMsg().  the message is NOT removed.
    The message is returned if it has been returned (not removed), or
    NULL otherwise.


CheckPort							CheckPort

    msg/NULL = CheckPort(port)
    EXECMSG *msg;
    PORT *port;

    This routine works like WaitPort(), but (1) does not block, and
    (2) does not remove the message.  If the port is not empty the first
    message is returned (not removed), else NULL is returned if the port
    is empty.


LockAddr							LockAddr

    (void) LockAddr(&lock)
    long lock[2];

    The lock structure (8 bytes) MUST initially be 0.  This routine obtains
    an exclusive lock on the structure and blocks until that lock can be
    obtained.  The structure must be word-aligned.

    Unless it is forced to block, this subroutine is extremely fast.  This
    routine does not allocate any signals, but uses the reserved EXEC
    semaphore signal.


LockAddrB							LockAddrB

    (void) LockAddr(bitno, &lock)
    long bitno;
    long lock[2];

    The lock structure actually supports up to 8 independant exclusive
    locks.  LockAddr() is a special case which uses bit # 0.  This call
    works as in LockAddr() but you may specify the bit you wish to lock
    (0 to 7).  If you specify 0, this call is equivalent to LockAddr().


UnLockAddr							UnLockAddr

    (void) UnLockAddr(&lock)
    long lock[2];

    Remove an exclusive lock you had previously obtained.  If other tasks
    are waiting for this lock, ALL are awakened even though only one will
    get the lock next.	This ensures that the highest priority task will
    get the lock next.

    This routine is extremely fast if nobody else is waiting for the lock,
    else it has to Signal() them.  You MUST have previously obtained the
    lock.


UnLockAddrB							UnLockAddrB

    (void) UnLockAddrB(bitno, &lock)
    long bitno;
    long lock[2];

    Again, this routine works the same as UnLockAddr() with the exception
    that you may specify one of the 8 bits to unlock (0 to 7).


DoSyncMsg							DoSyncMsg

    (void) DoSyncMsg(port, msg)
    PORT *port;
    EXECMSG *msg;

    This routine PutMsg()s a message and waits for it to be returned.  This
    routine creates its own reply port on the stack and stuffs it into
    mn_ReplyPort for you.  Thus, virtually no setup is required to use
    this routine.


FindName2							FindName2

    node/NULL = FindName2(list, name)

    This routine is identical to the EXEC FindName() call with the exception
    that it ignores nodes whos ln_Name fields are NULL.  ln_Name fields in
    the list nodes must contain either NULL or a valid string pointer.


GetTaskData							GetTaskData

    ptr = GetTaskData(name, bytes)
    APTR ptr;
    char *name;
    long bytes;

    This routine retrieves/allocates task-private named storage.  For a
    specific name, the first GetTaskData() call will allocate the specified
    # of bytes and zero them.  Space to hold the name itself is also
    allocated (i.e. you can use a temporary buffer to hold 'name' when you
    make this call).  Future calls return the pointer to the already
    allocated storage without modifying it.

    The storage is automatically freed if the TASK is removed... note that
    the task is not normally removed when a C program exits back into a
    CLI enviroment... it uses the CLI's task to run the program.  The
    task's memory list is used to implement this function.


FreeTaskData							FreeTaskData

    (void) FreeTaskData(name)
    char *name;

    If the task-private name exists, the storage associated with it is
    freed.  This works with memlist entries allocated with GetTaskData()
    or by the user, assuming the ln_Name field points to a valid string.

    (note:  FreeEntry() is used after the associated MemList structure is
     unlinked frlom the list.  ln_Name is not specifically freed but the
     way GetTaskData() works, the second entry is actually the storage
     associated with the ln_Name)


\Rogue\Monster\
else
  echo "will not over write misc.doc"
fi
if [ `wc -c misc.doc | awk '{printf $1}'` -ne 4956 ]
then
echo `wc -c misc.doc | awk '{print "Got " $1 ", Expected " 4956}'`
fi
if `test ! -s qints.doc`
then
echo "writing qints.doc"
cat > qints.doc << '\Rogue\Monster\'

				 QINTS.DOC

    I call them Q-Interrupts to tell them apart from other types of
interrupts that exist on the Amiga.  QInts provide a general purpose
priority enhanced local task-interrupt system based on exceptions.

    Essentially, one can associate a Q interrupt with any set of EXEC
signals.  When an associated signal comes in, a Q interrupt occurs.  The
interrupt is taken immediately if the current task Q-priority is lower
than the Q-priority of the interrupt, else it is queued until the
priority becomes lower.

    When a Q-interrupt is taken, the task's Q-priority is raised to that
of the Q-interrupt while the interrupt is being processed.  Additionaly,
the application software may set the task's Q-priority at any time,
usually to prevent interrupts from occuring in critical sections.

OpenQInts							OpenQInts

    handle = OpenQInts()
    long handle;

    This routine initializes a handle for the Q interrupt system.  All
    32 EXEC signals may be controlled through this one handle.	NULL is
    returned on error, else a non-zero handle is returned.

CloseQInts							CloseQInts

    (void) = CloseQInts(handle)
    long handle;

    This routine shuts down the handle, including the removal of any
    active Q Interrupt vectors.

SetQPri 							SetQPri

    (void) = SetQPri(handle, pri)
    long handle;
    long pri;

    This routine sets the current Q-interrupt priority for the task...
    actually all interrupts associated with the handle but this is
    usually all the interrupts period.

    Any Q-interrupts with higher priority can occur, while interrupts
    with lower or equal priority will be queued until you SetQPri() to
    a lower value.

    'pri' has a range -127 to 127

SetQVector							SetQVector

    oldvec = SetQvector(handle, vector, signo, arg, pri)
    void (*oldvec)();
    void (*vector)();
    long handle;
    long signo;
    long arg;
    long pri;

    This routine applied or removes or changes the priority of a
    Q interrupt.  If vector is NULL, the interrupt is removed for the
    specified signo.  If vector is not NULL the interrupt is set for
    the specified signo, replacing any previous interrupt vector and
    priority that existed.

    The interrupt has a priority of 'pri', (-127 to 127).

    The vector may be a C routine.  The OpenQInts() routine saved A4/A5
    and these registers are automatically re-loaded before the vector
    is called to support the small-data model.	Additionaly, D2 and D3
    Parameters are also scratch to support Aztec C.  Both Aztec and Lattice
    C are thus supported.  Parameters to the service routine are passed on
    the stack:

	(*vector)(arg)

    Specifically, the argument you gave SetQVector().

    NOTE:   SetQVector() may be called from a Q-interrupt.


\Rogue\Monster\
else
  echo "will not over write qints.doc"
fi
if [ `wc -c qints.doc | awk '{printf $1}'` -ne 2789 ]
then
echo `wc -c qints.doc | awk '{print "Got " $1 ", Expected " 2789}'`
fi
if `test ! -s res.doc`
then
echo "writing res.doc"
cat > res.doc << '\Rogue\Monster\'


			      RESOURCE.DOC

	       AMIGA RESOURCES AS IMPLEMENTED BY DRES.LIBRARY

    The first order of business is to avoid some confusion.  The Amiga
    already has resources .... EXEC resources, which are used mainly to
    arbitrate low level hardware.  The resources I am talking about is an
    object oriented system and has nothing to do with EXEC resources.  All
    references to 'resources' in this document refer to this object
    oriented system.

    What is a resources?  A resource is a means of accessing one or more
    objects through a generalized interface with as much flexibility as
    possible.  A specific object in this system has certain capabilities
    and is referenced by a pointer to something.  For instance, an
    intuition Window could be a resource.  Each and every resource in the
    system may be flagged as follows (any combination of flags):

    SHARABLE	-   Multiple references to the resource are allowed.  If not
		    sharable, duplication of a resource results in two
		    distinct resources with different data areas rather
		    than two pointers to a shared data area.

		    Duplication of non-sharable resources via DupRes()
		    results in a copy of that resource's data including any
		    changes made to that data.	GetRes()ing it results in
		    a pristine copy.

		    Duplication of a shareable resource returns the common
		    data area whether you DupRes() it or GetRes() it.

    GLOBAL	-   A global resource can be accessed by any task in the
		    system.  NOTE:  Private resources can still be
		    sharable, but only by a DupRes() call or a GetRes()
		    call from a task's private resource list.

		    Also, any resources tagged as GLOBAL in a task's private
		    resource list are immediately accessable to other tasks
		    via GetRes().  Searching other task's private lists for
		    global resources is always done last.

    VIRTUAL	-   A virtual resource is one that has been algorithmically
		    generated.	Most resources are virtual resources.  For
		    example, an intuition Window would be a virtual resource.

		    Non virtual resources are the 'raw' resources on disk.
		    For instance, a "_List" resource would be a raw resource
		    but when you GetRes(name, "List") note that obviously
		    some conversion code is required to go from "_List" to
		    "List", in this case the code initializes the list
		    pointers.

		    Most of the time, the existance of one or more
		    algorithms (subroutines) to generate a resource is
		    completely transparent to the routine requesting the
		    resource.  Using the above example, the task requesting
		    the resource simply asks for a "List" and is oblivious
		    to what actually must be performed to give him that
		    List.

    SWAPABLE	-   While not being referenced (all references fall to 0),
		    rather than delete the resource, it should be swapped to
		    disk (read: any filesystem device) and then swapped
		    back in when accessed.  This does not mean a resource
		    is automatically swapped when references fall to 0,
		    just that it *might* be swapped.

		    This only applies to SHARABLE resources, as you cannot
		    DupRes() a non-shared resource when the references fall
		    to 0 (nothing to dup), and GetRes() returns a pristine
		    copy.

		    SHARABLE resources on the otherhand are assumed to be
		    of a more permanent nature.  As in the LOCKED flag
		    below, this flag guarentees the resource will not become
		    pristine on the next reference after previous usage.

    LOCKED	-   While not being referenced, rather than delete or swap
		    the resource, it should stay in memory.  This only
		    applies to SHARABLE resources.


		    RESOURCE NAMES AND ALGORITHMIC GENERATION

    The identification of a resource consists of two parts.  (1) The name
    of the resource, and (2) the structure of the resource.  The name of
    the resource is arbitrary while the structure of the resource is some
    agreed upon standard.  The two parts are stuck together with an
    intervening period like so:

	    CHArlIE.Window

    For the above example, we have a resource named 'CHArlIE' of structure
    'Window'... an intuition window pointer would be the result of
    accessing this particular resource.  However, it is not possible to
    store a Window structure on disk verbatim since there are lots of
    pointers and other dependancies with intuition.  On disk, the resource
    might actually be:

	    CHArlIE.NewWindow

    But we want a Window structure... if the program requests
    CHArlIE.Window there must be some means of algorithmically translating
    a NewWindow to a Window.  These algorithms are provided by yet a
    third resource named:

	    NewWindow.Window.CODE

    The name is 'NewWindow.Window' and the type is 'CODE'.  This resource
    is a procedure which translates a NewWindow to a Window by calling
    OpenWindow().  The translation procedure is recursive, thus allowing
    any manner of 'patching' for reasons given above, for maintaining
    compatibility with older structures, and for convenience.

    Finally, a resource is owned by a specific task.  One can pass
    resources around but it must be done with cooperation from the
    library.

    CODE resources are discussed later on.  Now that you have an idea how
    resources work, we shall discuss the library calls currently available:



			   RESOURCE LIBRARY CALLS

    Beginning with the easiest of the calls and ending with the more
    difficult.	NOTE: Resource names are limited to 31 characters,
    Resource types are limited to 31 characters.

    resptr= GetRes(resnametype)             char *resnametype;

	This function retrieves the requested resource, doing any
	translations required to get the resource into the requested
	type.  NULL is returned if the resource could not be found
	or could not be translated to the requested type.

	In-Memory private resources are searched first, then the private
	resource files for the task, then In-Memory system resources, then
	the global resource files for the system.  Openning an already-open
	resource causes one of two actions depending on whether the resource
	is shared or not.  If shared, the reference count is simply
	incremented, otherwise a private copy of the resource is made.

	Example:    Win = GetRes("Charlie.Window");

    resptr2 = DupRes(resptr1)               APTR resptr1, resptr2;

	This call duplicates a resource.  If the resource is shared
	resptr2 will be the same as resptr1 and the reference count will
	be bumped.  Otherwise, a new resource data area is allocated and
	the old copied to the new.

	Things like fixing pointers and such within a resource that is
	physically duplicated are handled by the interface code (since
	raw resources do not contain pointers, only VIRTUAL resources
	will contain pointers and all VIRTUAL resources have some
	interface code).

    error = FreeRes(resptr)

	Free a resource that you retrieved via GetRes().  Only the task
	that owns the resource may free it (though several tasks may own
	a resource through duplication and ownership changes).	1 is
	returned on success, 0 on error.

	That is, if task #1 GetRes()'s the resource and duplicates it once,
	then task #2 duplicates it once, task #1 must call FreeRes() twice
	and task #2 must call FreeRes() once.  Ownership is strictly
	tracked.

    numres= FreeAllRes(task)

	Free all resources associated with the specified task (NULL for
	self).

    error = ChownRes(resptr, fromtask, totask)

	Change ownership of a resource from the source task to the
	destination task.  The resource must be owned by the source
	task or an error will occur (0 return value).  1 is returned
	on success.  NULL may be specified for either or both tasks
	and means the calling task.

    handle= UnLinkAllRes(task)

	Unlink all resources associated with the specified task (NULL
	for self) and return a handle representing those resources.

	This is useful for shells and such to keep their resources from
	getting removed by commands they run.  Combined with the NUNLINK
	flag one can pass resources to a Command and keep the rest out of
	reach.

    (void)  ReLinkAllRes(handle, task)

	Link all the resources represented by the handle to the specified
	task (NULL for self).

    oldfl = SetResFlags(resptr, newflags, flagsmask)

	Modify the flags associated with a resource.  NOTE:  If multiple
	references to a shared resource exist, all are modified.  Some
	modifications may be disallowed by the system.	This call is
	normally used to modify the LOCKED and SWAPABLE flags (note that
	the SWAPABLE flag can be changed back and forth only for resource
	which support it).

    error = AddRes(resnametype, flags, ptr, ctlcode);
						    char *resnametype;
						    long flags;
						    APTR ptr;
						    long (*ctlcode)();

	Add a resource to the system.  The resource is placed either in
	the task's privately accessable in-memory resource list or
	in the system global accessable in-memory resource list depending
	on the specified flags.  One may now GetRes() the resource.

	ONLY VIRTUAL RESOURCES MAY BE ADDED IN THIS WAY.  The VIRTUAL
	and LOCKED flags are automatically set.

	If flags specifies a VIRTUAL resource

    error = RemRes(resname)

	Remove the specified resource.	An error will occur and the resource
	will not be removed (1) if it does not exist in memory, or (2) it
	is currently being referenced.	(Note: the resource is removed even
	if it is swapped or locked).

    error = GetResInfo(resname, &flags, &bytes)

	Get information on a resource.	Information may not exist for a
	resource if it is not currently in memory and would have to be
	translated to get to the right type.

    error = GetResList(wildcard, from, &av, &ac);

	from:	mask, bit 0  search private list
			  1  search system list
			  2  <not used>
			  3  search in-memory private list
			  4  search in-memory global list

	Return an ARGC/ARGV list of resource names.  Note that some names
	might be duplicated if searching multiple lists.  Restricting the
	search to in-memory lists give resources which are already in
	memory (but might be swapped out or removed at any time for those
	with no references)

			     RESOURCE FILES

    error = GetFileList(wildcard, from, &av, &ac);

	from:	mask, bit 0  search private list
			  1  search system list
			  2  search swap list

	Return an ARGC/ARGV list of the files which match the specified
	wildcard from the private list, system list, or system swap dir
	list (in which case you get directory names).  This list has been
	allocated, and can be freed as follows:

	    Loop through all entries for (i = 0; i < ac; ++i)
					FreeMem(av[i], strlen(av[i])+1);
	    Free the array itself:  FreeMem(av, sizeof(char *) * (ac+1));

    num   = AddPrivResFile(filename, pri)

	Add a file name to the list of resource files for this task.  These
	are scanned before global files when a resource is requested.  All
	files in the list are write protected (i.e. a shared lock is kept
	for each file).  Thus, such files cannot be updated until removed
	from the list.

	Can also be used to modify the priority of an existing file

    num   = RemPrivResFiles(wildcard)

	Remove zero or more file names from the list of resource files for
	this task.  A wildcard pattern (* and ?) is accepted.

	Note for command processors:	commands you run might execute
	this command for *.

    num   = AddGlobResFile(filename, pri)

	Same as AddPrivResFile() but applies to the system list, which is
	searched last and by any requesting task.  Wildcard file names are
	NOT accepted.  The file need not exist at this time, and references
	to unmounted volumes are allowed.

	Can also be used to modify the priority of an existing entry

    num   = RemGlobResFiles(wildcard)

	Remove zero or more resource files from the global list.  Again,
	a wildcard filename is accepted.

    num   = AddResSwapDir(dirname, pri, maxkbytes)      char *dirname;
							char pri;
							long maxkbytes

	Add a directory to the list of directories the resource system
	can swap to.  The maximum number of KBytes of material allowed
	in the directory should be specified.  You can also use this
	call to modify the priority and maxkbytes for an entry.

	The highest priority directories are used before lower priority
	directories.  Not all directories need be mounted, but if a swapin
	occurs from an unmounted directory a requester will appear.

	A lock is kept on each specified directory.

    num   = RemResSwapDirs(wildcard)

	Remove directories associated with the resource swap areas.


			    RESOURCE FILE IFF FORMAT


\Rogue\Monster\
else
  echo "will not over write res.doc"
fi
if [ `wc -c res.doc | awk '{printf $1}'` -ne 12714 ]
then
echo `wc -c res.doc | awk '{print "Got " $1 ", Expected " 12714}'`
fi
if `test ! -s runlib.doc`
then
echo "writing runlib.doc"
cat > runlib.doc << '\Rogue\Monster\'

				DRES.LIBRARY

			    OVERVIEW OF FUNCTIONS

    This document is meant to give a simple overview of library functions,
    not to provide complete explanations.  Please refer to the separate
    document files for various command groups for more detailed information.

    * All function arguments are 32 bits unless otherwise specified

    * Any Address register may be used to hold the library base pointer
      when calling library routines from assembly.


				  IPC

    ipcport =	OpenIPC(name, 0)
    (void)  =   CloseIPC(ipcport)
    ipcmsg  =	SendIPC(name, buf, len, flags)
    ipcmsg  =	SendIPC2(name, ipcmsg)
    (void)  =   ReplyIPC(msg, buf, len, flags)
    (void)  =   FreeIPC(msg)
    (void)  =   DoIPC2(name, msg, handler, ipcport)

    argc    =	ParseCmd(str, &argv, varget, varfree, &error, NULL)
		FreeParseCmd(argv)

				 QINTS

    handle  =	OpenQInts()
    (void)  =   CloseQInts(handle)
    (void)  =   SetQPri(handle, pri)
    oldvect =	SetQVector(handle, vector, signo, arg, pri)

				 MISC

    BOOL    =	WildCmp(wildcard, filename)
    msg     =	WaitMsg(msg)
    msg/NULL=	CheckMsg(msg)
    msg/NULL=	CheckPort(port)
    (void)  =   LockAddr(&varlock)      (long varlock[2] = { 0,0 })
    (void)  =   LockAddrB(bitno, &varlock)
    (void)  =   UnLockAddr(&varlock)
    (void)  =   UnLockAddrB(bitno, &varlock)
    (void)  =   DoSyncMsg(port, msg)    (do not setup reply port for message)
    node/NULL=	FindName2(list, name)   (ignores NULL ln_Name fields)
    ptr     =	GetTaskData(name, bytes)
    (void)  =   FreeTaskData(name)

				LISTS

    node    =	GetHead(list)
    node    =	GetTail(list)

    node    =	GetSucc(list/node)
    node    =	GetPred(node)

    sptr    =	GetHeadOff(list, offset)
    sptr    =	GetTailOff(list, offset)
    sptr    =	GetSuccOff(sptr, offset)
    sptr    =	GetPredOff(sptr, offset)

    (void)  =   EnqueueLong(list, start, node, valoff)
    (void)  =   EnqueueOffLong(list, start, sptr, off, valoff)

    rval    =	SearchFwdNode(node, function, arg)
    rval    =	SearchRvsNode(node, function, arg)
    rval    =	SearchFwdList(list, function, arg)
    rval    =	SearchRvsList(list, function, arg)
    rval    =	SearchFwdNodeOff(sptr, function, off, arg)
    rval    =	SearchRvsNodeOff(sptr, function, off, arg)
    rval    =	SearchFwdListOff(list, function, off, arg)
    rval    =	SearchRvsListOff(list, function, off, arg)

	    >>	function(sptr:4(sp)orA2, arg:8(sp)orD4)

				  MEMORY

    (void)  =   BZero(buf, bytes)
    (void)  =   BSet(buf, bytes, char)
    (void)  =   BMov(src, dest, bytes)
    BOOL    =	BCmp(src, dest, bytes)      0=failed, 1=success

				  TIMEDATE

    DOSBOOL =	SetFileDate(file, date)     0=failed, -1=success
    char *  =	DateToS(datestamp, buf, format)

				  RESOURCES (NOT IMPLEMENTED YET)

\Rogue\Monster\
else
  echo "will not over write runlib.doc"
fi
if [ `wc -c runlib.doc | awk '{printf $1}'` -ne 2799 ]
then
echo `wc -c runlib.doc | awk '{print "Got " $1 ", Expected " 2799}'`
fi
if `test ! -s timedate.doc`
then
echo "writing timedate.doc"
cat > timedate.doc << '\Rogue\Monster\'


			       TIMEDATE.DOC

    Useful routines...

SetFileDate							SetFileDate

    DOSBOOL =  SetFileDate(file, date)
    char *file;
    DATESTAMP *date;

    This routine sets the datestamp for a file by implementing the new
    ACTION_SETDATE Dos packet.	0 is returned on failure, non-zero (-1)
    on success.

    NOTE:   The routine will fail if the file has been locked, shared or
    otherwise.


DateToS 							DateToS

    buf = DateToS(date, buf, format)

    DATESTAMP *date;
    char *buf;
    char *format;

    This routine calculates the ascii time and date from the datestamp and
    jams it into the output buffer (which is returned) according to the
    specified format string.  If the format string is NULL, "D M Y h:m:s"
    is used.  Essentially, the characters D,M,Y,h,m, and s are replaced by
    their ascii date equivalents (day, month, year, hours, minutes, seconds)
    in the output buffer, with all other characters copied verbatim.


\Rogue\Monster\
else
  echo "will not over write timedate.doc"
fi
if [ `wc -c timedate.doc | awk '{printf $1}'` -ne 968 ]
then
echo `wc -c timedate.doc | awk '{print "Got " $1 ", Expected " 968}'`
fi
echo "Finished archive 1 of 1"
# if you want to concatenate archives, remove anything after this line
exit
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.