dos.library: Fix ReadArgs() processing of '?' commands, that lead to MungWall hits
[AROS.git] / rom / dos / readargs.c
blobe816a76caab220958cb08863e2b56b5a5b9499e5
2 /*
3 Copyright © 1995-2011, The AROS Development Team. All rights reserved.
4 $Id$
6 Desc:
7 Lang: english
8 */
10 #include <aros/debug.h>
12 #include <exec/memory.h>
13 #include <proto/exec.h>
14 #include <dos/rdargs.h>
15 #include <dos/dosextens.h>
16 #include <proto/utility.h>
18 #include "dos_intern.h"
20 #ifdef TEST
21 # include <stdio.h>
22 # include <proto/dos.h>
23 # undef ReadArgs
24 # undef AROS_LH3
25 # define AROS_LH3(t,fn,a1,a2,a3,bt,bn,o,lib) t fn (a1,a2,a3)
26 # undef AROS_LHA
27 # define AROS_LHA(t,n,r) t n
28 # undef AROS_LIBFUNC_INIT
29 # define AROS_LIBFUNC_INIT
30 # undef AROS_LIBFUNC_EXIT
31 # define AROS_LIBFUNC_EXIT
32 #endif
34 /*****************************************************************************
36 NAME */
37 #include <proto/dos.h>
39 AROS_LH3(struct RDArgs *, ReadArgs,
41 /* SYNOPSIS */
42 AROS_LHA(CONST_STRPTR, template, D1),
43 AROS_LHA(IPTR *, array, D2),
44 AROS_LHA(struct RDArgs *, rdargs, D3),
46 /* LOCATION */
47 struct DosLibrary *, DOSBase, 133, Dos)
49 /* FUNCTION
50 Parses the commandline, a given string or Input() and fills
51 an argument array according to the options template given.
52 The array must be initialized to the wanted defaults before
53 each call to ReadArgs(). If the rdargs argument is NULL
54 ReadArgs() tries to parse the commandline and continues
55 on the input channel if it just consists of a single '?',
56 prompting the user for input.
58 INPUTS
59 template - Template string. The template string is given as
60 a number of options separated by ',' and modified
61 by '/' modifiers, e.g. 'NAME,WIDTH/N,HEIGHT/N'
62 means get a name string and two numbers (width and
63 height). The possible modifiers are:
64 /S Option is a switch. It may be either set or
65 left out.
66 /T Option is a boolean value. Requires an argument
67 which may be "ON", "YES" (setting the respective
68 argument to 1), "OFF" or "NO" (setting the
69 respective argument to 0).
70 /N Option is a number. Strings are not allowed.
71 If the option is optional, a pointer to the
72 actual number is returned. This is how you know
73 if it was really given. The number is always of type
74 LONG.
75 /A Argument is required. If it is left out ReadArgs()
76 fails.
77 /K The keyword must be given when filling the option.
78 Normally it's skipped.
79 /M Multiple strings or, when used in combination with /N,
80 numbers. The result is returned as an array of pointers
81 to strings or LONGs, and is terminated with NULL. /M
82 eats all strings that don't fit into any other option.
83 If there are unfilled /A arguments after parsing they
84 steal strings from /M. This makes it possible to, for
85 example, write a Copy command template like
86 'FROM/A/M,TO/A'. There may be only one /M option in a
87 template.
88 /F Eats the rest of the line even if there are option
89 keywords in it.
90 array - Array to be filled with the result values. The array must
91 be intialized to the default values before calling
92 ReadArgs().
93 rdargs - An optional RDArgs structure determinating the type of
94 input to process.
96 RESULT
97 A handle for the memory allocated by ReadArgs(). Must be freed
98 with FreeArgs() later.
100 SEE ALSO
101 FreeArgs(), Input()
103 *****************************************************************************/
105 AROS_LIBFUNC_INIT
107 /* Allocated resources */
108 struct DAList *dalist = NULL;
109 UBYTE *flags = NULL;
110 STRPTR strbuf = NULL, iline = NULL;
111 STRPTR *multvec = NULL, *argbuf = NULL;
112 CONST_STRPTR numstr;
113 ULONG multnum = 0, multmax = 0;
114 LONG strbuflen;
116 /* Some variables */
117 CONST_STRPTR cs1;
118 STRPTR s1, s2, *newmult;
119 ULONG arg, numargs, nextarg;
120 LONG it, item, chars;
121 struct CSource lcs, *cs;
122 TEXT argbuff[256]; /* Maximum BCPL string length + ASCIIZ */
124 ASSERT_VALID_PTR(template);
125 ASSERT_VALID_PTR(array);
126 ASSERT_VALID_PTR_OR_NULL(rdargs);
128 /* Get pointer to process structure. */
129 struct Process *me = (struct Process *) FindTask(NULL);
131 /* Error recovery. C has no exceptions. This is a simple replacement. */
132 LONG error;
134 #undef ERROR
135 #define ERROR(a) { error=a; goto end; }
137 /* Template options */
138 #define REQUIRED 0x80 /* /A */
139 #define KEYWORD 0x40 /* /K */
140 #define MULTIPLE 0x20 /* /M */
141 #define TYPEMASK 0x07
142 #define NORMAL 0x00 /* No option */
143 #define SWITCH 0x01 /* /S, implies /K */
144 #define TOGGLE 0x02 /* /T, implies /K */
145 #define NUMERIC 0x03 /* /N */
146 #define REST 0x04 /* /F */
148 /* Flags for each possible character. */
149 static const UBYTE argflags[] =
151 REQUIRED, 0, 0, 0, 0, REST, 0, 0, 0, 0, KEYWORD, 0, MULTIPLE,
152 NUMERIC, 0, 0, 0, 0, SWITCH | KEYWORD, TOGGLE | KEYWORD, 0, 0,
153 0, 0, 0, 0
156 /* Allocate readargs structure (and private internal one) */
157 if (!rdargs)
159 rdargs = (struct RDArgs *) AllocVec(sizeof(struct RDArgs),
160 MEMF_ANY | MEMF_CLEAR);
161 if (rdargs)
163 rdargs->RDA_Flags |= RDAF_ALLOCATED_BY_READARGS;
167 dalist = (struct DAList *) AllocVec(sizeof(struct DAList),
168 MEMF_ANY | MEMF_CLEAR);
170 if (rdargs == NULL || dalist == NULL)
172 ERROR(ERROR_NO_FREE_STORE);
175 /* Init character source. */
176 if (rdargs->RDA_Source.CS_Buffer)
178 cs = &rdargs->RDA_Source;
180 else
182 BOOL notempty = TRUE;
183 BPTR input = Input();
185 D(bug("[ReadArgs] Input: 0x%p\n", input));
187 * Take arguments from input stream. They were injected there by either
188 * runcommand.c or createnewproc.c (see vbuf_inject() routine).
189 * This is described in Guru Book.
191 argbuff[0] = 0;
192 lcs.CS_Buffer = &argbuff[0];
195 * Special kludge for interactive filehandles (i. e. CLI windows).
196 * Read data only if filehandle's buffer is not empty. Otherwise
197 * read will cause opening CLI window and waiting for user's input.
198 * As a consequence we still can use ReadArgs() on input redirected
199 * from a file, even if we are started from Workbench (hypothetical
200 * situation).
201 * This prevents opening a CLI window if the program was started from
202 * Workbench and redirected its Input() and Output() to own window,
203 * but still called ReadArgs() after redirection for some reason.
204 * Streams redirection is widely used in AROS startup code.
206 if (IsInteractive(input))
208 struct FileHandle *fh = BADDR(input);
210 notempty = (fh->fh_Pos != fh->fh_End);
213 if (notempty)
214 FGets(input, lcs.CS_Buffer, sizeof(argbuff));
216 D(bug("[ReadArgs] Line: %s\n", argbuff));
218 cs1 = lcs.CS_Buffer;
220 for (; *cs1 != '\0'; ++cs1);
222 lcs.CS_Length = cs1 - lcs.CS_Buffer;
223 lcs.CS_CurChr = 0;
225 cs = &lcs;
228 /* Check for optional reprompting */
229 if (!(rdargs->RDA_Flags & RDAF_NOPROMPT))
231 /* Check commandline for a single '?' */
232 cs1 = cs->CS_Buffer;
234 /* Skip leading whitespace */
235 while (*cs1 == ' ' || *cs1 == '\t')
237 cs1++;
240 /* Check for '?' */
241 if (*cs1++ == '?')
243 /* Skip whitespace */
244 while (*cs1 == ' ' || *cs1 == '\t')
246 cs1++;
249 /* Check for EOL */
250 if (*cs1 == '\n' || !*cs1)
252 /* Only a single '?' on the commandline. */
253 BPTR input = Input();
254 BPTR output = Output();
255 ULONG isize = 0, ibuf = 0;
256 LONG c;
257 ULONG helpdisplayed = FALSE;
259 /* Prompt for more input */
261 D(bug("[ReadArgs] Only ? found\n"));
262 D(bug("[ReadArgs] rdargs=0x%p\n", rdargs));
263 D(if (rdargs) bug ("[ReadArds] rdargs->RDA_ExtHelp=0x%p\n", rdargs->RDA_ExtHelp);)
265 if (FPuts(output, template) || FPuts(output, ": "))
267 ERROR(me->pr_Result2);
270 if (!Flush(output))
272 ERROR(me->pr_Result2);
275 do {
276 /* Read a line in. */
277 for (c = 0; c != '\n';)
279 if (c == '\n')
281 iline[isize] = '\0'; /* end of string */
282 break;
284 if (isize >= ibuf)
286 /* Buffer too small. Get a new one. */
287 STRPTR newiline;
289 ibuf += 256;
291 newiline = (STRPTR) AllocVec(ibuf, MEMF_ANY);
293 if (newiline == NULL)
295 ERROR(ERROR_NO_FREE_STORE);
298 if (iline != NULL)
299 CopyMemQuick(iline, newiline, isize);
301 FreeVec(iline);
303 iline = newiline;
306 /* Read character */
307 c = FGetC(input);
309 /* Check and write it. */
310 if (c == EOF && me->pr_Result2)
312 ERROR(me->pr_Result2);
315 /* Fix short buffers to have a trailing '\n' */
316 if (c == EOF || c == '\0')
317 c = '\n';
319 iline[isize++] = c;
321 iline[isize] = '\0'; /* end of string */
323 D(iline[isize] = 0; bug("[ReadArgs] Size %d, line: '%s'\n", isize, iline));
325 /* if user entered single ? again or some string ending
326 with space and ? either display template again or
327 extended help if it's available */
328 if ((isize == 1 || (isize > 1 && iline[isize-2] == ' '))
329 && iline[isize-1] == '?' )
331 helpdisplayed = TRUE;
332 isize = 0;
333 if(rdargs->RDA_ExtHelp != NULL)
335 if (FPuts(output, rdargs->RDA_ExtHelp) || FPuts(output, ": "))
336 ERROR(me->pr_Result2);
338 else if (FPuts(output, template) || FPuts(output, ": "))
340 ERROR(me->pr_Result2);
343 if (!Flush(output))
345 ERROR(me->pr_Result2);
348 else
349 helpdisplayed = FALSE;
351 while(helpdisplayed);
353 /* Prepare input source for new line. */
354 cs->CS_Buffer = iline;
355 cs->CS_Length = isize;
361 * Get enough space for string buffer.
362 * It's always smaller than the size of the input line+1.
365 strbuflen = cs->CS_Length + 1;
366 strbuf = (STRPTR) AllocVec(strbuflen, MEMF_ANY);
368 if (strbuf == NULL)
370 ERROR(ERROR_NO_FREE_STORE);
373 /* Count the number of items in the template (number of ','+1). */
374 numargs = 1;
375 cs1 = template;
377 while (*cs1)
379 if (*cs1++ == ',')
381 numargs++;
385 /* Use this count to get space for temporary flag array and result
386 * buffer. */
387 flags = (UBYTE *) AllocVec(numargs + 1, MEMF_CLEAR);
389 argbuf = (STRPTR *) AllocVec((numargs + 1) * sizeof(STRPTR), MEMF_CLEAR);
391 if (flags == NULL || argbuf == NULL)
393 ERROR(ERROR_NO_FREE_STORE);
396 /* Fill the flag array. */
397 cs1 = template;
398 s2 = flags;
400 while (*cs1)
402 /* A ',' means: goto next item. */
403 if (*cs1 == ',')
405 s2++;
408 /* In case of a '/' use the next character as option. */
409 if (*cs1++ == '/')
411 UBYTE argc = ToUpper(*cs1);
412 if (argc >= 'A' && argc <= 'Z')
413 *s2 |= argflags[argc - 'A'];
417 /* Add a dummy so that the whole line is processed. */
418 *++s2 = MULTIPLE;
421 * Now process commandline for the first time:
422 * Go from left to right and fill all items that need filling.
423 * If an item is given as 'OPTION=VALUE' or 'OPTION VALUE' fill
424 * it out of turn.
426 s1 = strbuf;
428 for (arg = 0; arg <= numargs; arg = nextarg)
430 nextarg = arg + 1;
432 D(bug("[ReadArgs] s1=&strbuf[%d], %d left\n", s1-strbuf, strbuflen));
434 /* Skip /K options and options that are already done. */
435 if (flags[arg] & KEYWORD || argbuf[arg] != NULL)
437 continue;
440 #if 0 /* stegerg: if so a template of CLOSE/S,QUICK/S,COMMAND/F would
441 not work correctly if command line for example is
442 "CLOSE QUICK" it would all end up being eaten by COMMAND/F
443 argument */
445 /* If the current option is of type /F do not look for keywords */
446 if ((flags[arg] & TYPEMASK) != REST)
447 #endif
450 /* Get item. Quoted items are never keywords. */
451 it = ReadItem(s1, strbuflen, cs);
453 if (it == ITEM_UNQUOTED)
455 /* Not quoted. Check if it's a keyword. */
456 item = FindArg(template, s1);
458 if (item >= 0 && item < numargs && argbuf[item] == NULL)
461 * It's a keyword. Fill it and retry the current option
462 * at the next turn
464 nextarg = arg;
465 arg = item;
467 /* /S /T may not be given as 'OPTION=VALUE'. */
468 if ((flags[item] & TYPEMASK) != SWITCH
469 && (flags[item] & TYPEMASK) != TOGGLE)
471 /* Get value. */
472 it = ReadItem(s1, strbuflen, cs);
474 if (it == ITEM_EQUAL)
476 it = ReadItem(s1, strbuflen, cs);
482 /* Check returncode of ReadItem(). */
483 if (it == ITEM_EQUAL)
485 ERROR(ERROR_BAD_TEMPLATE);
487 else if (it == ITEM_ERROR)
489 ERROR(me->pr_Result2);
491 else if (it == ITEM_NOTHING)
493 break;
497 /* /F takes all the rest */
498 if ((flags[arg] & TYPEMASK) == REST)
500 argbuf[arg] = s1;
502 do {
503 /* Skip part already read above by ReadItem() */
504 while (*s1)
506 s1++;
507 strbuflen--;
510 *s1++ = ' ';
511 strbuflen--;
513 it = ReadItem(s1, strbuflen, cs);
514 } while (it == ITEM_QUOTED || it == ITEM_UNQUOTED);
516 s1[-1] = 0;
517 it = ITEM_NOTHING;
518 break;
521 if (flags[arg] & MULTIPLE)
523 /* All /M arguments are stored in a buffer. */
524 if (multnum >= multmax)
526 /* Buffer too small. Get a new one. */
527 multmax += 16;
529 newmult = (STRPTR *) AllocVec(multmax * sizeof(char *),
530 MEMF_ANY);
531 if (newmult == NULL)
533 ERROR(ERROR_NO_FREE_STORE);
536 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
537 multnum * sizeof(char *));
539 FreeVec(multvec);
541 multvec = newmult;
544 /* Put string into the buffer. */
545 multvec[multnum++] = s1;
547 while (*s1++)
548 --strbuflen;
549 /* Account for the \000 at the end. */
550 --strbuflen;
552 /* /M takes more than one argument, so retry. */
553 nextarg = arg;
555 else if ((flags[arg] & TYPEMASK) == SWITCH
556 || (flags[arg] & TYPEMASK) == TOGGLE)
558 /* /S or /T just set a flag */
559 argbuf[arg] = (char *) ~0;
561 else /* NORMAL || NUMERIC */
563 /* Put argument into argument buffer. */
564 argbuf[arg] = s1;
566 while (*s1++)
567 --strbuflen;
568 /* Account for the \000 at the end. */
569 --strbuflen;
572 if (cs->CS_CurChr >= cs->CS_Length)
573 break; /* end of input */
576 /* Unfilled /A options steal Arguments from /M */
577 for (arg = numargs; arg-- > 0;)
579 if (flags[arg] & REQUIRED && argbuf[arg] == NULL
580 && !(flags[arg] & MULTIPLE))
582 if (flags[arg] & KEYWORD)
584 /* /K/A argument, which insists on keyword
585 * being used, cannot be satisfied */
587 ERROR(ERROR_TOO_MANY_ARGS); /* yes, strange error number,
588 * but it translates to "wrong
589 * number of arguments" */
593 if (multnum == 0)
595 /* No arguments left? Oh dear! */
596 ERROR(ERROR_REQUIRED_ARG_MISSING);
599 argbuf[arg] = multvec[--multnum];
603 /* Put the rest of /M where it belongs */
604 for (arg = 0; arg < numargs; arg++)
606 if (flags[arg] & MULTIPLE)
608 if (flags[arg] & REQUIRED && multnum == 0)
610 ERROR(ERROR_REQUIRED_ARG_MISSING);
613 if (multnum != 0)
615 /* NULL terminate it. */
616 if (multnum >= multmax)
618 multmax += 16;
620 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
621 MEMF_ANY);
623 if (newmult == NULL)
625 ERROR(ERROR_NO_FREE_STORE);
628 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
629 multnum * sizeof(char *));
631 FreeVec(multvec);
633 multvec = newmult;
636 multvec[multnum++] = NULL;
637 argbuf[arg] = (STRPTR) multvec;
639 else
641 /* Shouldn't be necessary, but some buggy software relies on this */
642 argbuf[arg] = NULL;
645 break;
649 /* There are some arguments left? Return error. */
650 if (multnum != 0 && arg == numargs)
652 ERROR(ERROR_TOO_MANY_ARGS);
656 * The commandline is processed now. Put the results in the result array.
657 * Convert /N arguments by the way.
659 for (arg = 0; arg < numargs; arg++)
661 /* Just for the arguments given. */
662 if (argbuf[arg] != NULL)
664 if (flags[arg] & MULTIPLE)
666 array[arg] = (IPTR) argbuf[arg];
668 if ((flags[arg] & TYPEMASK) == NUMERIC)
670 STRPTR *p;
671 LONG *q;
673 if (multnum * 2 > multmax)
675 multmax = multnum * 2;
676 newmult = (STRPTR *) AllocVec(multmax * sizeof(STRPTR),
677 MEMF_ANY);
679 if (newmult == NULL)
681 ERROR(ERROR_NO_FREE_STORE);
684 CopyMemQuick((ULONG *) multvec, (ULONG *) newmult,
685 multnum * sizeof(char *));
687 FreeVec(multvec);
689 multvec = newmult;
692 array[arg] = (IPTR) multvec;
693 p = multvec;
694 q = (LONG *) (multvec + multnum);
696 while (*p)
698 /* Convert /N argument. */
699 chars = StrToLong(*p, q);
701 if (chars <= 0 || (*p)[chars])
703 /* Conversion failed. */
704 ERROR(ERROR_BAD_NUMBER);
707 /* Put the result where it belongs. */
708 *p = (STRPTR) q;
709 p++;
710 q += sizeof(IPTR) / sizeof(LONG);
714 else
716 switch (flags[arg] & TYPEMASK)
718 case NORMAL:
719 case REST:
720 case SWITCH:
721 /* Simple arguments are just copied. */
722 array[arg] = (IPTR) argbuf[arg];
723 break;
725 case TOGGLE:
726 /* /T logically inverts the argument. */
727 array[arg] = array[arg] ? 0 : ~0;
728 break;
730 case NUMERIC:
731 /* Convert /N argument. */
732 /* Abuse the argbuf buffer. It's not needed anymore. */
733 numstr = (CONST_STRPTR)argbuf[arg];
734 chars = StrToLong(numstr, (LONG *)&argbuf[arg]);
736 if (chars <= 0 || numstr[chars] != '\0')
738 /* Conversion failed. */
739 ERROR(ERROR_BAD_NUMBER);
742 /* Put the result where it belongs. */
743 array[arg] = (IPTR) &argbuf[arg];
744 break;
748 else
750 if (flags[arg] & MULTIPLE)
752 /* Shouldn't be necessary, but some buggy software relies on this.
753 * IBrowse's URL field isn't set to zero.
755 array[arg] = (IPTR)NULL;
760 /* All OK. */
761 error = 0;
762 end:
763 /* Cleanup and return. */
764 FreeVec(iline);
765 FreeVec(flags);
767 if (error)
769 /* ReadArgs() failed. Clean everything up. */
770 if (rdargs)
772 if (rdargs->RDA_Flags & RDAF_ALLOCATED_BY_READARGS)
774 FreeVec(rdargs);
778 FreeVec(dalist);
779 FreeVec(argbuf);
780 FreeVec(strbuf);
781 FreeVec(multvec);
783 me->pr_Result2 = error;
785 return NULL;
787 else
789 /* All went well. Prepare result and return. */
790 rdargs->RDA_DAList = (IPTR) dalist;
791 dalist->ArgBuf = argbuf;
792 dalist->StrBuf = strbuf;
793 dalist->MultVec = multvec;
794 return rdargs;
796 AROS_LIBFUNC_EXIT
797 } /* ReadArgs */
799 #ifdef TEST
800 # include <dos/dos.h>
801 # include <dos/rdargs.h>
802 # include <utility/tagitem.h>
804 # include <proto/dos.h>
806 char cmlargs[] = "TEST/A";
808 char usage[] =
809 "This is exthelp for test\n"
810 "Enter something";
812 #define CML_TEST 0
813 #define CML_END 1
815 LONG cmlvec[CML_END];
818 main(int argc, char **argv)
820 struct RDArgs *rdargs;
822 if ((rdargs = AllocDosObject(DOS_RDARGS, NULL)))
824 rdargs->RDA_ExtHelp = usage; /* FIX: why doesn't this work? */
826 if (!(ReadArgs(cmlargs, cmlvec, rdargs)))
828 PrintFault(IoErr(), "AROS boot");
829 FreeDosObject(DOS_RDARGS, rdargs);
830 exit(RETURN_FAIL);
833 else
835 PrintFault(ERROR_NO_FREE_STORE, "AROS boot");
836 exit(RETURN_FAIL);
839 FreeArgs(rdargs);
840 FreeDosObject(DOS_RDARGS, rdargs);
842 return 0;
843 } /* main */
845 #endif /* TEST */