[comp.sys.atari.st] Extended Argument Passing Proposal

apratt@atari.UUCP (Allan Pratt) (11/21/87)

Dale Schumacher et al.  have been working on an improved
argument-passing protocol for GEMDOS.  This is their manifesto.  They
consulted me (and, I confess, bent to my will) about which of many
methods to use.  The one below gets the job done cleanly and is easy to
understand and implement.  It doesn't do anything with interrupts, and
doesn't use Pexec(load/nogo), because that doesn't work.  The only
"rule" this idea breaks is that it has the child reading from the
parent's data space, and on the ST that is no big deal.

This is an important topic because the Mark Williams trick of passing
ARGV in the environment is incomplete and messy.  It is incomplete
because you can't tell if the ARGV came from your parent or from a
higher ancestor with intervening shells that don't know about ARGV.
It is messy because it hauls all the command line arguments around
in the environment, involving lots of copying.

Here is the proposal.  I recommend discussion and eventual adoption of
it.  Note that mail should go to ...stag!syntel!dal, not to me.  I'm
afraid I don't have time to follow up on this as closely as I might
like.  Note that this is not Atari Official anything: I am posting this
because I am interested in developing a better standard, and to lend an
air of importance to the whole affair. 




                         -------------------------------

                 A STANDARD FOR EXTENDED ARGUMENT PASSING IN 'C'. 
                                        by
                                   David Parsons
                                  Dale Schumacher
                                   John Stanley

                         -------------------------------

    Why change argc/argv in the first place? Well, there are times where
(a) it would be nice to have more than 128 bytes of command-line
arguments, (b) it would be nice to pass arguments to a child process
without having the startup code in the child process retokenize (spaces
inside of an argument, tokens containing `>', `<', `*', and `?', etc.)
and (c) it's nice to have a more Un*x-like interface for argc/argv. 

    The method employed by Mark Williams involves making a mess of the
already confused environment string, and lacks validation that the ARGV
string did, in fact come from a process's parent.  We would like to
suggest a cleaner way of handling extended arguments.  Of course, the
normal command line image will have to be supported for programs which
don't understand the extended format, and the extended format must be
validated in some way, but these are fairly trivial problems. 

    We would prefer the following approach.  An environment variable
called "xArg" is placed in the environment.  The value of this variable
is 8 hexadecimal digits (0-9 and capital letters A-F).  This value
defines the address of the XARG structure in the parent's data space. 
The XARG structure is defined as:

	typedef struct
		{
		char	xarg_magic[4];    /* verification value "xArg" */
		int	xargc;            /* argc */
		char	**xargv;          /* argv */
		char	*xiovector;       /* i/o handle status */
		char	*xparent;         /* pointer to parent's basepage */
		}
		XARG;

    <xarg_magic> value is the constant "xArg" ($78417267), and serves to
validate the extended argument format. 
    <xargc> holds the normal <argc> value.
    <xargv> is a pointer into the parent's data space where the list of
argv[] argument pointers is stored.  Even though <argc> is available,
argv[argc] should be set to NULL to tie off the argument list.  Obviously,
the <argv> values would also reside in the parent's data space, and should
be copied by the child process as part of the startup procedure.
    <xiovector> points to a '\0' terminated string which describes the
state of the i/o handles.  Only the characters [CAPF?] are valid in the
<xiovector> string.  These represent [C]onsole, [A]ux port, [P]rinter,
[F]ile and [?]unknown.  This feature is used to allow easy implmentation
of an isatty() function.  To support this feature, creat(), open(), close(),
dup() and dup2() [aka: Fdup()/Fforce()] must maintain the values in the
iovector[] string.
    <xparent> points to the basepage of the process which set up this
XARG structure.  If this value is not the same as the <p_parent> pointer
in the current (child process) basepage, then the extended arguments are
not for the current process, but were passed along by a shell/program
which was ignorant of this argument passing scheme.

    The following source code is public domain and serves as a guide to
use of this extended argument passing method...

/*--------------------------------CUT--HERE-------------------------------*/
#include <stdio.h>
#include <osbind.h>
#include <string.h>
#include <basepage.h>

typedef struct
	{
	char	xarg_magic[4];    /* verification value "xArg" */
	int	xargc;            /* argc */
	char	**xargv;          /* argv */
	char	*xiovector;       /* i/o handle status */
	char	*xparent;         /* pointer to parent's basepage */
	}
	XARG;

#define MAXARG  64

#define	h_CON   (-1)
#define	h_AUX   (-2)
#define	h_PRN   (-3)

extern BASEPAGE *_base;
extern int      _argc;
extern char     **_argv;

char            iovector[MAXFILES] = "CCAP????????????????";

static char     cmdln[130];
static char     xmagic[] = "xArg";
static char     hex[] = "0123456789ABCDEF";

/*
 * Build TOS-style command line image from argv[] list.
 */
int makcmdln(cmdln, argv)
	char *cmdln;
	register char **argv;
	{
	register char *p, *q;
	register int n = 0, argc = 1;

	p = cmdln + 1;
	while(q = *++argv)              /* start at argv[1] */
		{
		while(*q)
			{
			*p++ = *q++;    /* copy, checking for limit */
			if(++n > 127)
				goto limit;
			}
		++argc;
		*p++ = ' ';             /* separate arguments with a space */
		if(++n > 127)
			goto limit;
		}
limit:  *--p = '\0';                    /* '\0' terminate, though not needed */
	cmdln[0] = n - 1;               /* store the string length */
	return(argc);                   /* return number of arguments */
	}

/*
 * Execute <program> with a variable number of arguments.
 */
long lspawn(program, arg0)
	char *program, *arg0;
	{
	XARG xarg;
	register XARG *xp = &xarg;
	register char *p, **q;
	register int n;
	register long rv;
	char xenv[16];

	q = &arg0;
	n = makcmdln(cmdln, q);
	/*
	 * initialize XARG struct
	 */
	strncpy(xp->xarg_magic, xmagic, 4);
	xp->xargc = n;
	xp->xargv = q;
	xp->xiovector = iovector;
	xp->xparent = _base;
	/*
	 * create environment variable "xArg=XXXXXXXX"
	 */
	strcpy(xenv, xmagic);
	p = strrchr(xenv, '\0');        /* move to terminating '\0' */
	*p++ = '=';
	rv = ((long) xp);
	for(n=8; n--; rv >>= 4)         /* convert long to ascii-hex */
		p[n] = hex[rv & 0xF];
	p[8] = '\0';
	/*
	 * install environment variable and execute program
	 */
	putenv(xenv);
	rv = Pexec(0, program, cmdln, NULL);
	putenv(xmagic);                 /* remove "xArg" from environment */
	return(rv);
	}

/*
 * Retrieve extended arguments, if possible, and set up argc and argv[].
 */
void _initargs(cmdline, cmdlen)
	char *cmdline;
	int cmdlen;
	{
	register XARG *xp;
	register char *p, **q;
	register int i, n;
	register long a;
	char *getenv(), *malloc();

	if(p = getenv(xmagic))
		{
		/*
		 * if the "xArg" variable exists, decode the address
		 * and assume that it points somewhere reasonable,
		 * though possibly not to a valid XARG struct
		 */
		for(a = 0L; *p; ++p)    /* convert ascii-hex to long */
			a = ((a << 4) | (0xF & strpos(hex, *p)));
		xp = ((XARG *) a);
		}
	if((p == NULL)                                  /* no extended args */
	|| (strncmp(xp->xarg_magic, xmagic, 4))         /* not XARG struct */
	|| (xp->xparent != _base->p_parent))            /* not right parent */
		{
		strncpy(cmdln, cmdline, cmdlen);	/* make working copy */
		cmdline[cmdlen] = '\0';
		q = malloc(MAXARG * sizeof(char *));    /* allocate argv */
		q[0] = "";                              /* argv[0] == "" */
		n = 1;
		/*
		 * parse command line image based on whitespace
		 */
		if(p = strtok(cmdln, " \t"))
			{
			do
				{
				q[n++] = p;
				}
				while((p = strtok(NULL, " \t"))
				   && (n < MAXARG));
			}
		q[n] = NULL;                            /* tie off argv */
		_argc = n;
		_argv = q;
		}
	else                                            /* EXTENDED ARGS! */
		{
		/*
		 * extended args are easy... just remember to copy the
		 * data, since it resides in your parent's data space
		 */
		_argc = n = xp->xargc;                  /* copy argc */
		i = ((n + 1) * sizeof(char *));
		_argv = q = malloc(i);
		blkcpy(q, xp->xargv, i);                /* copy argv */
		q[n] = NULL;
		do
			{
			*q = strdup(*q);                /* copy arguments */
			}
			while(*++q);
		if(xp->xiovector)                       /* copy iovector */
			strncpy(iovector, xp->xiovector, strlen(iovector));
		}
	}

/*
 * Create a new file <filename> with <pmode> permissions
 */
int creat(filename, pmode)
	register char *filename;
	register int pmode;
	{
	register int rv;

	rv = Fdelete(filename);
	if((rv == 0) || (rv == -33))    /* SUCCESS or FILE-NOT-FOUND */
		{
		if((rv = Fcreate(filename, pmode)) >= 0)
			iovector[rv] = 'F';
		}
	return(rv);
	}

/*
 * Open an existing file <filename> in mode <iomode>
 */
int open(filename, iomode)
	register char *filename;
	register int iomode;
	{
	register int rv;

	if((rv = Fopen(filename, iomode)) >= 0)
		iovector[rv] = 'F';
	return(rv);
	}

/*
 * Close the file associated with <handle>
 */
int close(handle)
	int handle;
	{
	register int rv;

	if(((rv = ((int) Fclose(handle))) == 0) && (handle > 5))
		iovector[handle] = '?';
	return(rv);
	}

/*
 * Return a new handle which refers to the same file as <handle>
 */
int dup(handle)
	register int handle;
	{
	register int h;

	if((h = ((int) Fdup(handle))) >= 0)
		iovector[h] = iovector[handle];
	return(h);
	}

/*
 * Force <h2> to refer to the same file as <h1>
 */
int dup2(h1, h2)
	register int h1, h2;
	{
	int rv;

	if((rv = ((int) Fforce(h2,h1))) == 0)
		{
		if(h1 >= 0)
			iovector[h2] = iovector[h1];
		else if(h1 == h_CON)
			iovector[h2] = 'C';
		else if(h1 == h_AUX)
			iovector[h2] = 'A';
		else if(h1 == h_PRN)
			iovector[h2] = 'P';
		}
	return(rv);
	}

/*
 * Return TRUE if <handle> refers to a character device
 */
int isatty(handle)
	int handle;
	{
	register char iot;

	if(handle < 0)
		return(TRUE);
	iot = iovector[handle];
	return((iot == 'C') || (iot == 'A') || (iot == 'P'));
	}
/*--------------------------------CUT--HERE-------------------------------*/

    The creat()..isatty() functions work like their Un*x counterparts.
Watch particularly the parameter order on dup2().  The lspawn() and
_initargs() functions implement the heart of the extended argument passing.

    lspawn() is patterned after the standard function execl().  It takes
a program name as its first argument, followed by a variable length list
of parameters ending in a NULL parameter, ie:

	rv = lspawn("grep", "grep", "foo", "foo.bar", NULL);

Notice that, by convention, the name of the program to be executed is
passed at the first argument, which becomes argv[0].  lspawn() returns
the exits status of the child process, or a TOS error code.  Since exit
status values are limited to a 16-bit word, they can only be word negative,
but TOS error codes will always be long negative.

                Dale Schumacher
                ..ihnp4!meccts!stag!syntel!dal
                (alias: Dalnefre')