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')