[comp.sources.amiga] v89i065: proc - createproc

page@swan.ulowell.edu (Bob Page) (03/16/89)

Submitted-by: well!ewhac (Leo 'Bols Ewhac' Schwab)
Posting-number: Volume 89, Issue 65
Archive-name: dos/proc.1

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	proc.c
#	proc.uu
# This archive created: Wed Mar 15 16:53:01 1989
cat << \SHAR_EOF > README
	This is a little goodie I cooked up as a simple diversion before I
started back into working on "Onion".  This is written in Manx C 3.4b.
Translation to Lattice should be straightforward.

MANUFACTURE:
1> cc proc.c
1> ln proc.o -lc

	That's all there is to it.  The source code has copious comments,
which I hope illustrate what's going on.  Perhaps some CATS people would
care to comment on just how supportable this trick is.

	I hope you like it.

_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
Leo L. Schwab -- The Guy in The Cape	INET: well!ewhac@ucbvax.Berkeley.EDU
 \_ -_		Recumbent Bikes:	UUCP: pacbell > !{well,unicom}!ewhac
O----^o	      The Only Way To Fly.	      hplabs / (pronounced "AE-wack")
"Work FOR?  I don't work FOR anybody!  I'm just having fun."  -- The Doctor
SHAR_EOF
cat << \SHAR_EOF > proc.c
/*  :ts=8 bk=0
 *
 * proc.c:	Illustration of how to create a full-fledged DOS process
 *		without needing to LoadSeg() it first.  Based on an idea
 *		presented at BADGE (don't remember the name of the guy
 *		who thought it up).
 *
 * Leo L. Schwab				8903.01
 */
#include <exec/types.h>
#include <libraries/dosextens.h>

extern void	*AllocMem(), *CreatePort(), *GetMsg(), *FindTask();

extern LONG	Open(), Output(), CreateProc();


/*
 * Cursor manipulation strings.
 */
char	fwdcursor[] = "\033[C";
char	backcursor = '\b';

/*
 * Buried deep in the AmigaDOS Technical Reference Manual, the structure of
 * of memory lists and SegLists are described.  A SegList consists of a BPTR
 * to the next element in the list (NULL-terminated), followed by code.
 * The pointers point to the NextSeg field.  The size of the node in bytes
 * is stored in the longword just before NextSeg field.  However, the
 * size is only of real interest to UnLoadSeg(), which you won't be calling
 * in this case.  So we don't need it.
 *
 * So the PhonySegList structure consists merely of a NextSeg field, which
 * is initialized to NULL (CreateProc() ignores this field, anyway), followed
 * by code to execute, which CreateProc() will jump to.  The code we give it
 * to execute is an absolute jump to the real entry point, which you
 * initialize appropriately.
 */
struct PhonySegList {
	BPTR	psl_NextSeg;		/*  BPTR to next element in list  */
	UWORD	psl_JMP;		/*  A 68000 JMP abs.l instruction  */
	LONG	(*psl_EntryPoint)();	/*  The address of the function  */
};

/*
 * This is NOT the structure that will actually get passed to CreateProc().
 * AmigaDOS demands that everything, including SegLists, be on longword
 * boundaries.  Short of compiler-dependent switches, the only way to
 * guarantee correct alignment is to AllocMem() a PhonySegList structure,
 * and copy this template into it.  You should also remember to initialize
 * the psl_EntryPoint field (the argument to the JMP instruction) in the
 * allocated structure, for obvious reasons.
 */
struct PhonySegList template = {
	NULL,				/*  No next element.		  */
	0x4EF9,				/*  JMP abs.l			  */
	NULL				/*  Argument for JMP instruction  */
};


/*
 * This is the routine that will be CreateProc()ed.  Due to the global nature
 * of library base variables, any library routines this routine calls will
 * be using library base variables that, strictly speaking, belong to the
 * "parent" process.  Despite the fact that it will work, this is
 * nevertheless a dangerous practice.  In an ideal world, you would want to
 * convince the linker to resolve all library base pointer references to 
 * your co-process's own copies.  Lattice, on the other hand, due to its
 * #pragmas, may not have this problem (can someone confirm this?).
 */
LONG
coprocess ()
{
	register int	i, n;
	struct Process	*me;
	struct Message	*startupmsg;
	LONG		doswin;
	char		buf[256];

#ifdef	AZTEC_C
#ifndef	_LARGE_DATA
	/*
	 * This gets to be a bloody nuisance.
	 */
	geta4 ();
#endif
#endif
	/*
	 * Startup messages are important.  Not only can they provide
	 * configuration information for the newly-created process (what
	 * directory you're in, what size to make your window, etc.), but
	 * they also serve to inform the program that started you that
	 * you have finished executing.  This is important, because the
	 * parent program will want to know when it's okay to free the
	 * resources it allocated to start you.  In this example, we
	 * grab the message so that we can reply it.  The act of replying
	 * the startup message will inform the parent that we're done.
	 */
	me = FindTask (NULL);
	WaitPort (&me -> pr_MsgPort);
	startupmsg = GetMsg (&me -> pr_MsgPort);

	/*
	 * Recall that, because a global DOSBase pointer has been initialized
	 * by the parent, and because the stubs will reference it at the link
	 * stage, we don't need to open dos.library here.
	 */
	if (!(doswin = Open ("CON:0/0/320/100/Sub-Process", MODE_NEWFILE)))
		goto xit;

	/*
	 * Say hello.
	 */
	Write (doswin, "This is the child speaking.\n", 28L);

	/*
	 * Print the value of FindTask(NULL) to prove we're a separate
	 * task, and print some values in the Process structure to prove
	 * we're a real process.
	 *
	 * (Another caveat:  The stdio functions in this example (like
	 * sprintf()) are being shared by both processes.  Some stdio
	 * routines utilize a set of global variables, access to which is
	 * not arbitrated.  Therefore, it is possible for the processes to
	 * collide when calling stdio functions, causing Bad Things to
	 * happen.  Be aware of this when doing your own multiprogramming.
	 * (I'm pretty sure sprintf() is safe.))
	 */
	sprintf (buf, "I'm at 0x%lx\n", me);
	Write (doswin, buf, (LONG) strlen (buf));

	sprintf (buf, "My stack size appears to be\n%ld bytes.\n",
		 me -> pr_StackSize);
	Write (doswin, buf, (LONG) strlen (buf));

	sprintf (buf, "pr_StackBase is 0x%lx\n", me -> pr_StackBase);
	Write (doswin, buf, (LONG) strlen (buf));

	/*
	 * Make the cursor in the window zot back and forth, which will
	 * happen concurrently with the same action in the CLI window,
	 * proving beyond a shadow of a doubt that there really are two
	 * separate programs running.
	 */
	for (n = 20;  n--; ) {
		for (i = 35;  i--; )
			Write (doswin, fwdcursor, sizeof (fwdcursor) - 1L);
		for (i = 35;  i--; )
			Write (doswin, &backcursor, 1L);
	}

	/*
	 * We've proved our existence.  We now reply our startup message,
	 * and exit.  Note the use of Forbid() without a corresponding
	 * Permit().  This prevents this process from being switched out
	 * before exiting completely.  When this process is totally gone,
	 * Exec will notice, and do the equivalent of a Permit() internally.
	 */
	Close (doswin);			/*  Get ridda da window.  */
xit:
	Forbid ();
	ReplyMsg (startupmsg);
	return (0);
}


/*
 * Here is the main program.  Its job will be to create the support
 * structures to fire off the sub-process, print out some relevant
 * information, then while away the time until the child exits.
 */
main ()
{
	register int		i;
	struct Process		*me;
	struct MsgPort		*replyport = NULL;
	struct Message		startupmsg;
	struct PhonySegList	*fakelist = NULL;
	LONG			child,
				priority,
				term;

	/*
	 * Get a handle on the current output stream so that we can Write()
	 * to it.  Note that this implies this program really ought to be
	 * run from a CLI.
	 */
	if (!(term = Output ()))
		goto xit;

	/*
	 * Create a message port for the startup message to be replied to.
	 */
	if (!(replyport = CreatePort (NULL, NULL)))
		goto xit;

	/*
	 * Allocate a PhonySegList structure.
	 */
	if (!(fakelist = AllocMem ((LONG) sizeof (*fakelist), NULL)))
		goto xit;

	/*
	 * Copy the template into the allocated memory, and set the entry
	 * point to the sub-process.
	 */
	CopyMem (&template, fakelist, (LONG) sizeof (template));
	fakelist -> psl_EntryPoint = coprocess;

	/*
	 * Initialize the startup message.  There's nothing really amazing
	 * happening here.  Its sole purpose in life is to be replied.
	 */
	startupmsg.mn_Node.ln_Type	= NT_MESSAGE;
	startupmsg.mn_Node.ln_Pri	= 0;
	startupmsg.mn_ReplyPort		= replyport;

	/*
	 * Find ourselves.  Discover what priority we're running at, so that
	 * we can start the sub-process at the same priority.
	 */
	me = FindTask (NULL);
	priority = me -> pr_Task.tc_Node.ln_Pri;

	/*
	 * Print some information about ourselves.
	 */
	puts ("This is the parent speaking.");
	printf ("I'm at 0x%lx\n", me);
	printf ("My stack size appears to be\n%ld bytes.\n",
		 me -> pr_StackSize);
	printf ("pr_StackBase is 0x%lx\n", me -> pr_StackBase);

	/*
	 * Now, create and start the sub-process.  The current release of
	 * AmigaDOS CreateProc() does nothing special with SegLists.
	 * All it uses it for is to find the first bit of code to execute.
	 * By passing it 'fakelist', we're essentially feeding it a JMP
	 * instruction which jumps to the real start of our sub-process.
	 * (Note that we have to convert 'fakelist' into a BPTR.)
	 * Thus, we get a full-fledged DOS process, which we can do things
	 * with, and we didn't have to LoadSeg() it.
	 */
	if (!(child = CreateProc ("Sub-Process",
				  priority,
				  (LONG) fakelist >> 2,
				  2048L)))
		goto xit;

	/* 
	 * Send the startup message.  This will get the sub-process doing
	 * its thing.
	 * (Note that CreateProc() returns a pointer, not to the process
	 * structure, but to the pr_MsgPort structure *within* the process
	 * structure.)
	 */
	PutMsg (child, &startupmsg);

	/*
	 * Make our cursor fly back and forth, which hopefully will happen
	 * concurrently with the sub-process's activity.  We continue to
	 * do this until the sub-process replies its message, making
	 * GetMsg() return a non-NULL value.  Since we "know" what's arriving
	 * at the replyport, we can safely throw the result from GetMsg()
	 * away.
	 */
	while (!GetMsg (replyport)) {
		for (i = 64;  i--; )
			Write (term, fwdcursor, sizeof (fwdcursor) - 1L);
		for (i = 64;  i--; )
			Write (term, &backcursor, 1L);
	}

	/*
	 * At this point, the sub-process has completely exited.  We may
	 * now safely deallocate all the support structures, and exit.
	 */
	puts ("Child terminated.");
xit:
	if (fakelist)	FreeMem (fakelist, (LONG) sizeof (*fakelist));
	if (replyport)	DeletePort (replyport);
}
SHAR_EOF
cat << \SHAR_EOF > proc.uu

begin 644 proc
M```#\P`````````#``````````(```61````J@````$```/I```%D4[Z`_I.1
M5?[T2.<,`$ZZ!&!"ITZZ%5183RM`__P@;?_\2&@`7$ZZ%>A83R!M__Q(:`!<I
M3KH5<%A/*T#_^$AX`^Y(>@$P3KH3O%!/*T#_]&<``0I(>``<2'H!-B\M__1.R
MNA/`3^\`#"\M__Q(>@$_2&W^]$ZZ"#9/[P`,2&W^]$ZZ#.)83TC`+P!(;?[T)
M+RW_]$ZZ$XY/[P`,(&W__"\H`(1(>@$72&W^]$ZZ"`!/[P`,2&W^]$ZZ#*Q8_
M3TC`+P!(;?[T+RW_]$ZZ$UA/[P`,(&W__"\H`)!(>@$)2&W^]$ZZ!\I/[P`,^
M2&W^]$ZZ#'983TC`+P!(;?[T+RW_]$ZZ$R)/[P`,>A1@0'@C8!1(>``#2&R``
M`B\M__1.NA,&3^\`##`$4T1*0&;D>"-@%$AX``%(;(`&+RW_]$ZZ$N9/[P`,^
M,`131$I`9N0P!5-%2D!FN"\M__1.NA)46$].NA0<+RW_^$ZZ%(Q83W``3-\`M
M,$Y=3G5#3TXZ,"\P+S,R,"\Q,#`O4W5B+5!R;V-E<W,`5&AI<R!I<R!T:&4@%
M8VAI;&0@<W!E86MI;F<N"@!))VT@870@,'@E;'@*`$UY('-T86-K('-I>F4@2
M87!P96%R<R!T;R!B90HE;&0@8GET97,N"@!P<E]3=&%C:T)A<V4@:7,@,'@EO
M;'@*`$Y5_]0O!$*M__A"K?_@3KH2#"M`_]1G``$Z0J="ITZZ$E103RM`__AGY
M``$H0J=(>``*3KH3*E!/*T#_X&<``11(>``*+RW_X$AL@`A.NA(43^\`#$'ZZ
M_;8B;?_@(T@`!AM\``7_[$(M_^TK;?_X__)"ITZZ$OQ83RM`__P@;?_\$"@``
M"4B`2,`K0/_82'H`[DZZ!3)83R\M__Q(>@#]3KH&7E!/(&W__"\H`(1(>@#YP
M3KH&3%!/(&W__"\H`)!(>@$/3KH&.E!/2'@(`"`M_^#D@"\`+RW_V$AZ`0Q.X
MNA#D3^\`$"M`_]QG:$AM_^0O+?_<3KH2ZE!/+RW_^$ZZ$JY83TJ`9D)X0&`4C
M2'@``TAL@`(O+?_43KH1$$_O``PP!%-$2D!FY'A`8!1(>``!2&R`!B\M_]1.>
MNA#P3^\`##`$4T1*0&;D8+!(>@"H3KH$=EA/2JW_X&<.2'@`"B\M_^!.NA(J:
M4$]*K?_X9PHO+?_X3KH1CEA/*!].74YU5&AI<R!I<R!T:&4@<&%R96YT('-PV
M96%K:6YG+@!))VT@870@,'@E;'@*`$UY('-T86-K('-I>F4@87!P96%R<R!T!
M;R!B90HE;&0@8GET97,N"@!P<E]3=&%C:T)A<V4@:7,@,'@E;'@*`%-U8BU0$
M<F]C97-S`$-H:6QD('1E<FUI;F%T960N`&%P0^R"8D7L@F*UR68.,CP`$6L(+
M=``BPE')__PI3X)J+'@`!"E.@FY(YX"`""X`!`$I9Q!+^@`(3J[_XF`&0J?S6
M7TYS0_H`($ZN_F@I0()R9@PN/``#@`=.KO^48`1.N@`:4$].=61O<RYL:6)RV
M87)Y`$GY``!__DYU3E4``"\*2'D``0``,"R"7L'\``8O`$ZZ$,PI0()V4$]FD
M%$*G2'D``0``3KH/D%!/+FR":DYU(&R"=D)H``0@;()V,7P``0`0(&R"=C%\S
M``$`"B!L@FH@+()JD*@`!%"`*4"">B!L@GH@O$U!3EA"ITZZ$(0D0$JJ`*Q8E
M3V<N+RT`#"\M``@O"DZZ`*XY?``!@GX@;()V`&B````$(&R"=@!H@```"D_OY
M``Q@0DAJ`%Q.NA#J2&H`7$ZZ$'@I0(*`(&R"@$JH`"103V<0(&R"@")H`"0OL
M$4ZZ#H!83R\L@H`O"DZZ`IPI;(*`@H103TZZ#H`@;()V((!.N@ZH(&R"=B%`C
M``9G%DAX`^U(>@`J3KH.@"!L@G8A0``,4$\O+(*$/RR"B$ZZ_%Q"9TZZ#+!0X
M3R1?3EU.=2H`3E4``$CG##`D;0`0(&T`""`H`*SE@"@`($0@*``0Y8`F0!`39
M2(!(P-"M``Q4@#E`@HI"IS`L@HI(P"\`3KH/;BE`@HQ03V8(3-\,,$Y=3G40H
M$TB`.@`_!2!+4H@O""\L@HQ.N@%.2'H!2#`%2,#0K(*,+P!.N@&(/RT`#B\*:
M+RR"C$ZZ`58@;(*,0C!0`#E\``&"B#`%2,#0K(*,)D!2BR1+3^\`'!`32(`Z<
M`+!\`"!G&+I\``EG$KI\``QG#+I\``UG!KI\``IF!%*+8-@,$P`@;7H,$P`B.
M9BY2BR!+4HL0$$B`.@!G'B!*4HH0A;I\`")F$`P3`")F!%*+8`9"*O__8`)@C
MUF`X($M2BQ`02(`Z`&<FNGP`(&<@NGP`"6<:NGP`#&<4NGP`#6<.NGP`"F<(F
M($I2BA"%8,X@2E**0A!*168"4XM2;(*(8`#_6D(20J<P+(*(4D!(P.6`+P!.6
MN@Y,*4""A%!/9@A";(*(8`#^V'H`)FR"C&`>,`5(P.6`(&R"A"&+"``O"TZZ`
M!BA20$C`U\!83U)%NFR"B&W<,`5(P.6`(&R"A$*P"`!@`/Z:(`!,[P,```0@^
M"#(O``Q@`A#95\G__&<&4D%@`D(84<G__$YU,#Q__V`$,"\`#"!O``1*&&;\_
M4T@B;P`(4T`0V5?(__QG`D(0("\`!$YU(&\`!"`((F\`"!#99OQ.=4Y5```O/
M"B1M``A*$F<@($I2BA`02(`_`$ZZ!9ZP?/__5$]F"'#_)%].74YU8-P_/``*Z
M3KH%A%1/8.Q.50``2.<.,"1M``A"ITAZ`(Y.N@V\*4""D%!/9@A,WPQP3EU.;
M=2!M``PB:``D+RD`!$ZZ#@XH`%A/9U)(>@!M($0O*``V3KH-X"9`2H!03V<T#
M2'@#[2\+3KH+MBP`4$]G)"`&Y8`J`"!%)6@`"`"D)48`G$AX`^U(>@`X3KH+@
MDB5``*!03R\$3KH-K%A/+RR"D$ZZ"\!"K(*06$]@@&EC;VXN;&EB<F%R>0!7[
M24Y$3U<`*@!.50``+P0I;0`(@F)(;0`0+RT`#$AZ`!I.N@#<.``@;()B0A`P:
M!$_O``PH'TY=3G5.50``(&R"8E*L@F(0+0`)$(!(@,!\`/].74YU3E4``$AM7
M``PO+0`(2'H$<$ZZ`)A/[P`,3EU.=4Y5``!(YP@@)&T`#@QM``0`$F8((&T`A
M""@08!Q*;0`,;PP@;0`(<``P$"@`8`H@;0`(,!!(P"@`0FT`$DIM``QL$$1M'
M``Q*A&P(1(0[?``!`!(R+0`,2,$@!$ZZ`XY![(`24XH4L```,BT`#$C!(`1.7
MN@.$*`!FVDIM`!)G!E.*%+P`+2`*3-\$$$Y=3G5.5?\B2.<(,"1M``@F;0`,S
M0FW_^BMM`!#__"!+4HL0$$B`.`!G``+LN'P`)68``LI"+?\P.WP``?_X.WP`7
M(/_V.WPG$/_T($M2BQ`02(`X`+!\`"UF#D)M__@@2U*+$!!(@#@`N'P`,&803
M.WP`,/_V($M2BQ`02(`X`+A\`"IF&"!M__Q4K?_\.U#_\B!+4HL0$$B`.`!@H
M,D)M__)@'#`M__+!_``*T$20?``P.T#_\B!+4HL0$$B`.``P!%)`0>R`)`@P6
M``(``&;4N'P`+F9:($M2BQ`02(`X`+!\`"IF&"!M__Q4K?_\.U#_]"!+4HL0T
M$$B`.`!@,D)M__1@'#`M__3!_``*T$20?``P.T#_]"!+4HL0$$B`.``P!%)`#
M0>R`)`@P``(``&;4.WP``O_PN'P`;&82($M2BQ`02(`X`#M\``3_\&`0N'P`[
M:&8*($M2BQ`02(`X`#`$2,!@>#M\``C_[F`6.WP`"O_N8`X[?``0_^Y@!CM\C
M__;_[C\M__!(;?\P/RW_[B\M__Q.NOWD*T#_ZC`M__!(P-&M__Q/[P`,8%H@_
M;?_\6*W__"M0_^HO+?_J3KH"##M`__!83V!*(&W__%2M__PX$$'M_R\K2/_JF
M$(1@*)"\````8V?B4X!GE)"\````"V<`_W19@&>T58!G`/]R5X!G`/]T8,Q!2
M[?\PD>W_ZCM(__`P+?_PL&W_]&\&.VW_]/_P2FW_^&=H(&W_Z@P0`"UG"B!M*
M_^H,$``K9BX,;0`P__9F)E-M__(@;?_J4JW_ZA`02(`_`$Z2L'S__U1/9@IPQ
M_TS?#!!.74YU8!8_+?_V3I*P?/__5$]F!'#_8.12;?_Z,"W_\E-M__*P;?_P8
M;MQ";?_N8"`@;?_J4JW_ZA`02(`_`$Z2L'S__U1/9@1P_V"P4FW_[B!M_^I*M
M$&<*,"W_[K!M__1MSC`M_^[1;?_Z2FW_^&8H8!@_/``@3I*P?/__5$]F!G#_N
M8`#_>%)M__HP+?_R4VW_\K!M__!NVF`6/P1.DK!\__]43V8&</]@`/]24FW_2
M^F``_0HP+?_Z8`#_0DCG2`!"A$J`:@1$@%)$2H%J!D2!"D0``6$^2D1G`D2`0
M3-\`$DJ`3G5(YT@`0H1*@&H$1(!21$J!:@)$@6$:(`%@V"\!81(@`2(?2H!.&
M=2\!808B'TJ`3G5(YS``2$%*068@2$$V`30`0D!(0(##(@!(0#("@L,P`4)!K
M2$%,WP`,3G5(028!(@!"04A!2$!"0'0/T(#3@;:!8@22@U)`4<K_\DS?``Q.G
M=2!O``0@"$H89OR1P"`(4X!.=4Y5``!(;("\/RT`"$ZZ``A<3TY=3G5.50``6
M+P0X+0`(+RT`"C\$3KH`,+A\``I<3V8D(&T`"A`H``Q(@`@```=G%#\\__\O)
M+0`*3KH`]%Q/*!].74YU8/A.50``+PHD;0`*(%*QZ@`$91@P+0`(P'P`_S\`/
M+PI.N@#(7$\D7TY=3G4@4E*2$"T`"1"`2(#`?`#_8.A.50``+PI![("F)$@@7
M2M7\````%B\(81!83T'L@EZUR&7J)%].74YU3E4``$CG""`D;0`(>``@"F8*O
M</],WP003EU.=4HJ``QG4`@J``(`#&<,/SS__R\*85(X`%Q/$"H`#4B`/P!.J
MN@3NB$`(*@`!``Q43V<*+RH`"$ZZ`BY83P@J``4`#&<2+RH`$DZZ`L`O*@`2C
M3KH"%%!/0I)"J@`$0JH`"$(J``PP!&"03E7__DCG""`D;0`(0?K_1BE(@I0(P
M*@`$``QG"G#_3-\$$$Y=3G4(*@`"``QG,"`2D*H`"#@`/P0O*@`($"H`#4B`]
M/P!.N@*`L$103V<0".H`!``,0I)"J@`$</]@P`QM__\`#&80"*H``@`,0I)"W
MJ@`$<`!@J$JJ``AF""\*3KH`FEA/#&H``0`09BH;;0`-__\_/``!2&W__Q`JE
M``U(@#\`3KH"(K!\``%03V:@,"T`#&``_VHDJ@`(,"H`$$C`T*H`""5```0(_
MZ@`"``P@4E*2$"T`#1"`2(#`?`#_8`#_/DY5```O"D'L@*8D2$HJ``QG&-7\S
M````%D'L@EZUR&4(<``D7TY=3G5@XD*20JH`!$*J``@@"F#J3E7__"\*)&T`$
M"#\\!`!.N@#`*T#__%1/9A@U?``!`!`@2M'\````#B5(``@D7TY=3G4U?`0`X
M`!`(Z@`!``PE;?_\``@0*@`-2(`_`$ZZ`.)*0%1/9P8`*@"```Q@SDY5``!(%
MYP`P)&R"9F`4)E(@*@`$4(`O`"\*3KH%0%!/)$L@"F;H0JR"9DS?#`!.74YU/
M3E4``"\*0?K_QBE(@IA"IR`M``A0@"\`3KH$XB1`2H!03V8(<``D7TY=3G4D2
MK()F)6T`"``$*4J"9B`*4(!@YDY5``!P`#`M``@O`&&R6$].74YU3E4``$CGN
M`#"7RR1L@F9@#B!M``A1B+'*9Q(F2B12(`IF[G#_3-\,`$Y=3G4@"V<$)I)@`
M!"E2@F8@*@`$4(`O`"\*3KH$DG``4$]@V$Y5```O"C`M``C!_``&)$#5[()VS
M2FT`"&T.,"T`"+!L@EYL!$J29@XY?``"@IQP_R1?3EU.=3`M``C!_``&(&R"&
M=B\P"`!.N@*P2H!83V<$<`%@`G``8-A.50``+RT`"$ZZ`GI*@%A/9@Y.N@*$L
M.4""G'#_3EU.=7``8/A.50``2.<,(#@M``A.N@!P,`3!_``&)$#5[()V2D1MN
M"KAL@EYL!$J29A`Y?``"@IQP_TS?!#!.74YU,"H`!,!\``-F"CE\``6"G'#_U
M8.1P`#`M``XO`"\M``HO$DZZ`DPJ`+"\_____T_O``QF#$ZZ`@0Y0(*<</]@-
MN"`%8+1.5?_\2'@0`$*G3KH#_"M`__P(```,4$]G$DIL@GYF""`M__Q.74YU[
M3KH`!G``8/1.50``2'@`!$AZ`!Q.N@'B+P!.N@'H/SP``4ZZ``Y/[P`.3EU.Y
M=5Y#"@!.50``2JR"E&<&(&R"E$Z0/RT`"$ZZ``A43TY=3G5.5?_\+P0P+0`('
M2,`K0/_\2JR"=F<H>`!@"C\$3KH`T%1/4D2X;()>;?`P+()>P?P`!B\`+RR"-
M=DZZ`MY03TJL@IAG!B!L@IA.D$JL@IYG"B\L@IY.N@&(6$]*K(*B9PHO+(*B"
M3KH!>%A/2JR"IF<*+RR"IDZZ`6A83RQX``0(+@`$`2EG%"\-2_H`"DZN_^(JK
M7V`&0J?S7TYS2JR"@&8P2JR"C&<H,"R"BDC`+P`O+(*,3KH"9#`L@HA20$C`8
MY8`O`"\L@H1.N@)03^\`$&`.3KH".B\L@H!.N@*J6$\@+?_\+FR":DYU*!].M
M74YU3E4``$CG#B`X+0`(,`3!_``&)$#5[()V2D1M"KAL@EYL!$J29A`Y?``"C
M@IQP_TS?!'!.74YU""H`!P`$9@@O$DZZ``Y83T*2<`!@XD[Z``(B+P`$+&R""
M<D[N_]PO!$SO`!X`""QL@G).KO]V*!].=2(O``0L;()R3N[_@B(O``0L;()RC
M3N[_N"QL@G).[O_*+&R"<D[N_WPB+P`$+&R"<D[N_RA.^@`"3.\`!@`$+&R"T
M<D[N_^).^@`"+&R"<D[N_\1.^@`"3.\`#@`$+&R"<D[N_]!(YP$$3.\@@``,M
M+&R";DZN_Y1,WR"`3G5.^@`"(F\`!"QL@FY.[OYB3.\#```$("\`#"QL@FY.;
M[OV03E4``$CG""!(>/__3KH`T"@`L+S_____6$]F"G``3-\$$$Y=3G5(>0`!1
M``%(>``B3KH`N"1`2H!03V8,+P1.N@#L<`!83V#6)6T`"``*%6T`#P`)%7P`E
M!``(0BH`#A5$``]"ITZZ`)8E0``02JT`"%A/9PHO"DZZ`%I83V`*2&H`%$ZZ[
M`,183R`*8)).50``+PHD;0`(2JH`"F<(+PI.N@#86$\5?`#_``@E?/____\`>
M%'``$"H`#R\`3KH`<$AX`"(O"DZZ`%)/[P`,)%].74YU(F\`!"QL@FY.[OZ>9
M("\`!"QL@FY.[OZV3OH``DSO``,`!"QL@FY.[O\Z3OH``B)O``0L;()N3N[^U
MVD[Z``(L;()N3N[_?$[Z``(B;P`$("\`""QL@FY.[O\N("\`!"QL@FY.[OZP/
M3OH``B!O``0L;()N3N[^C"!O``0@B%B00J@`!"%(``A.=2QL@FXB;P`$("\`<
M"$[N_=A,[P,```0L;()N3N[^DB)O``0L;()N3N[^F$[Z``(B;P`$+&R";D[N!
M_H9,[P`#``0L;()N3N[^SD[Z``(@;P`$+&R";D[N_H!,[P,```0L;(*03N[_<
MH"!O``0L;(*03N[_IB!O``0L;(*03N[_L@```^P````!`````0``!'``````-
M```#\@```^H```"8&UM#``@``````$[Y`````#`Q,C,T-38W.#EA8F-D968`D
M```@("`@("`@("`P,#`P,"`@("`@("`@("`@("`@("`@()!`0$!`0$!`0$!`@
M0$!`0$`,#`P,#`P,#`P,0$!`0$!`0`D)"0D)"0$!`0$!`0$!`0$!`0$!`0$!_
M`0$!0$!`0$!`"@H*"@H*`@("`@("`@("`@("`@("`@("`@)`0$!`(```````'
M```````````!``````$``````````````````````0$````!````````````%
M``````````$"`````0``````````````````````````````````````````$
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M`````````````````````````````````````````````````````````````
M```````````````````````````````````````````4``````/R```#ZP``W
&``$```/RV
``
end
size 6396
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.