/*  :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);
}
