bug? in getopt(3) + [PATCH] + testcase

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

bug? in getopt(3) + [PATCH] + testcase

0xef967c36
To wrap this up and get if off my plate, here is the updated patch,
and a testcase program together with what should be (imvho) the
correct output.

Please do NOT assume that I'm insisting or that I have any personal
interest to have this included, considered, etc.

The testcase is a stripped down version of a much messier thing which
was also handling getopt_long() and getopt_long_only().

The output of the testcase in is in the form:

<< "q" ["prog", "-pq", "a1", "a2"]
gopts: unknown option -- p
>> {prog} #Up <q> | {a1} {a2}

ie '<< input', '>> expected output' with the expected error messages
in between.

===== patch to lib/libc/stdlib/getopt_long.c ======

--- getopt-long.c~ 2020-03-12 02:23:29.028903616 +0200
+++ getopt-long.c 2020-03-15 23:46:07.988119523 +0200
@@ -418,15 +418,7 @@
  }
 
  if ((optchar = (int)*place++) == (int)':' ||
-    (optchar == (int)'-' && *place != '\0') ||
     (oli = strchr(options, optchar)) == NULL) {
- /*
- * If the user specified "-" and  '-' isn't listed in
- * options, return -1 (non-option) as per POSIX.
- * Otherwise, it is an unknown option character (or ':').
- */
- if (optchar == (int)'-' && *place == '\0')
- return (-1);
  if (!*place)
  ++optind;
  if (PRINT_ERROR)

====== gopts.c =====================================
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static void showargs(int ac, char **av, char *os){
        int c, i;
        if(ac > 0) printf("{%s} ", av[0]);
        for(optarg = 0; (c = getopt(ac, av, os)) != -1; optarg = 0)
                switch(c){
                case '?':
                        printf("#U%c ", optopt); break;
                case ':':
                        printf("#A%c ", optopt); break;
                default:
                        printf(c >= ' ' && c <= '~' ? "<%c>" :
                                c < 8 ? "<\\%d>" : "<\\x%02x", c);
                        if(optarg) printf("={%s}", optarg);
                        printf(" ");
                }
        printf("|");
        for(i = optind; i < ac; i++) printf(" {%s}", av[i]);
        printf("\n");
}
static void test(int oi, char *os, char **av, char *buf, int blen){
        int ac, l; char *s;
        printf("<< %s\n>> ", buf);
        for(ac = 0, s = buf; av[ac]; ac++, s += l + 1){
                l = strlen(av[ac]);
                memcpy(s, av[ac], l + 1);
                av[ac] = s;
        }
        optind = oi; showargs(ac, av, os);
        printf("\n");
        memset(buf, ':', blen); /* clobber it */
}
#define TEST(oi, os, ...) do{\
        char *av[] = { __VA_ARGS__, NULL };\
        static char buf[] = #os " [" #__VA_ARGS__ "]";\
        test(oi, os, av, buf, sizeof buf);\
}while(0)

int main(int ac, char **av){
        char *t;
        int oi = (t = getenv("OI")) ? *t - '0' : 1;
        if(ac > 1){ optind = oi; showargs(ac - 2, av + 2, av[1]); return 0; }

        dup2(1, 2);
        setvbuf(stdout, NULL, _IOLBF, 0);
        setvbuf(stderr, NULL, _IOLBF, 0);

        TEST(oi, "q", "prog", "-q-");
        TEST(oi, "-q", "prog", "-q--", "--", "arg");
        TEST(oi, "p-:", "prog", "-p--", "--", "arg");
        TEST(oi, "q-", "---", "--", "arg");
        TEST(oi, "q-", "---", "arg");

        TEST(oi, "p:", "prog", "-p");
        TEST(oi, "p", "prog", "-p", "arg");
        TEST(oi, "q", "prog", "-q", "arg");

        TEST(oi, "-q", "prog", "-q", "a1", "a2");
}

====== gopts.output ================================
<< "q" ["prog", "-q-"]
gogs-o: unknown option -- -
>> {prog} <q> #U- |

<< "-q" ["prog", "-q--", "--", "arg"]
gogs-o: unknown option -- -
gogs-o: unknown option -- -
>> {prog} <q> #U- #U- | {arg}

<< "p-:" ["prog", "-p--", "--", "arg"]
>> {prog} <p> <->={-} | {arg}

<< "q-" ["---", "--", "arg"]
>> {---} | {arg}

<< "q-" ["---", "arg"]
>> {---} | {arg}

<< "p:" ["prog", "-p"]
gogs-o: option requires an argument -- p
>> {prog} #Up |

<< "p" ["prog", "-p", "arg"]
>> {prog} <p> | {arg}

<< "q" ["prog", "-q", "arg"]
>> {prog} <q> | {arg}

<< "-q" ["prog", "-q", "a1", "a2"]
>> {prog} <q> <\1>={a1} <\1>={a2} |

Reply | Threaded
Open this post in threaded view
|

Re: bug? in getopt(3) + [PATCH] + testcase

Ingo Schwarze
Hi,

[hidden email] wrote on Wed, Mar 25, 2020 at 05:58:22PM +0200:

> To wrap this up and get if off my plate, here is the updated patch,
[...]

> --- getopt-long.c~ 2020-03-12 02:23:29.028903616 +0200
> +++ getopt-long.c 2020-03-15 23:46:07.988119523 +0200
> @@ -418,15 +418,7 @@
>   }
>  
>   if ((optchar = (int)*place++) == (int)':' ||
> -    (optchar == (int)'-' && *place != '\0') ||
>      (oli = strchr(options, optchar)) == NULL) {
> - /*
> - * If the user specified "-" and  '-' isn't listed in
> - * options, return -1 (non-option) as per POSIX.
> - * Otherwise, it is an unknown option character (or ':').
> - */
> - if (optchar == (int)'-' && *place == '\0')
> - return (-1);
>   if (!*place)
>   ++optind;
>   if (PRINT_ERROR)

After lots of research, code inspection, and testing, and after
some useful exchange of ideas with martijn@, i came to the conclusion
that this patch is indeed the way to go.

I'm now asking for OKs and for other feedback.

See below for a full rationale, and at the end of this mail for
adjustments to the test suite that i intend to commit afterwards.

In addition to the automated tests, the testing i have done includes
a full run of "make build" and "make release" in both base and
xenocara, which all succeeded.


RATIONALE:

Here is the main bug to fix:
   $ OPTS=ax ./obj/getopt-test -a- -x arg                                     <
  OPT(a)ARG(-a-)ARG(-x)ARG(arg)

This raises the question how '-' as an option character should be
handled.  As POSIX does not require that it be handled at all,
the criterion to decide is compatibility with other systems.
For that reason, i tested with:

 * 4.4BSD-Lite2 (compiled on OpenBSD)
 * glibc (on Debian 8 and 9 and compiled from glibc HEAD on OpenBSD),
   where Debian 9 was tested by martijn@
 * FreeBSD (compiled on OpenBSD)
 * NetBSD (compiled on OpenBSD)
 * DragonFly (compiled on OpenBSD)
 * Illumos (compiled on OpenBSD)
 * Oracle Solaris 11 (tested on the OpenCSW cluster)

The tests are intended to be exhaustive regarding all aspects of
the handling of '-' as an option character.


Overview of test results:
-------------------------
"yes" means: the '-' option is supported in this way
"arg" means: treated as an argument, not as an option in this case
"err" means: treated as an invalid option in this case
"--"  means: treated the same as isolated "--"
"bug" means: behaviour clearly makes no sense in this case
"oa"  means: (wrongfully) swallowing an option argument
all caps means: probably undesirable, see below as to why

One case that is unsupported on glibc and Solaris and should be
disregarded in comparisons is indicated by [square brackets].
Some cases of unambiguously buggy behaviour that should be
disregarded in comparisons are indicated by {curly braces}.
The case numbers are arbitrary numbers for referencing the
cases in the analysis below.

                   Ox    4.4  glibc  Fx  Nx   Dx  Illum Sol11 case number
isolated ("-")     yes   yes  [ARG]  yes yes  yes  [ARG ARG]  6
trailing ("-a-")   yes   yes   yes   yes yes  yes   yes yes   1
middle ("-a-b")    ERR   yes   yes   yes yes  yes   yes yes   4
leading ("--a")    ERR  {--}   yes   yes yes  yes  {BUG BUG}  7

not in optstring:
isolated ("-")     arg   arg   arg   arg arg  arg   arg arg   -
trailing ("-a-")  {BUG} {BUG}  err   err err  err   err err   3
middle ("-a-b")    err  {ARG}  err   err err  err   err err   3
leading ("--a")    err  {--}   err   err err  err  {BUG BUG}  7

with mandatory argument after space:
isolated ("-")     yes   yes  [ARG]  yes yes  yes  [ARG ARG]  6
trailing ("-a-")   yes   yes   yes   yes yes  yes   yes yes   2

with mandatory argument wthout space:
isolated ("--xy")  ERR  {--}   yes   yes yes  yes  {BUG BUG}  7
trailing ("-a-xy") ERR   yes   yes   yes yes  yes   yes yes   5

with optional argument, present:
isolated ("--xy")  ERR  {--}   yes   yes yes  yes  {BUG BUG}  7
trailing ("-a-xy") ERR   yes   yes   yes yes  yes   yes yes   5

with optional argument, not present:
isolated ("-")     yes  {OA}  [ARG]  yes yes {OA}  [ARG ARG]  8
trailing ("-a-")   yes  {OA}   yes   yes yes {OA}  {OA  OA}   8


Test commands
-------------
isolated ("-")     OPTS=abc- ./getopt-test - -c arg
trailing ("-a-")   OPTS=abc- ./getopt-test -a- -c arg
middle ("-a-b")    OPTS=abc- ./getopt-test -a-b -c arg
leading ("--a")    OPTS=abc- ./getopt-test --a -c arg

not in optstring:
isolated ("-")     OPTS=abc ./getopt-test - -c arg
trailing ("-a-")   OPTS=abc ./getopt-test -a- -c arg
middle ("-a-b")    OPTS=abc ./getopt-test -a-b -c arg
leading ("--a")    OPTS=abc ./getopt-test --a -c arg

with mandatory argument after space:
isolated ("-")     OPTS=abc-: ./getopt-test - -- -c arg
trailing ("-a-")   OPTS=abc-: ./getopt-test -a- -- -c arg

with mandatory argument wthout space:
isolated ("--xy")  OPTS=abc-: ./getopt-test --xy -c arg
trailing ("-a-xy") OPTS=abc-: ./getopt-test -a-xy -c arg

with optional argument, present:
isolated ("--xy")  OPTS=abc-:: ./getopt-test --xy -c arg
trailing ("-a-xy") OPTS=abc-:: ./getopt-test -a-xy -c arg

with optional argument, not present:
isolated ("-")     OPTS=abc-:: ./getopt-test - -c arg
trailing ("-a-")   OPTS=abc-:: ./getopt-test -a- -c arg


FreeBSD/NetBSD test results for convenience:
--------------------------------------------
OPT(-)OPT(c)ARG(arg)
OPT(a)OPT(-)OPT(c)ARG(arg)
OPT(a)OPT(-)OPT(b)OPT(c)ARG(arg)
OPT(-)OPT(a)OPT(c)ARG(arg)

not in optstring:
ARG(-)ARG(-c)ARG(arg)
OPT(a)ERR(?-)OPT(c)ARG(arg)
OPT(a)ERR(?-)OPT(b)OPT(c)ARG(arg)
ERR(?-)OPT(a)OPT(c)ARG(arg)

with mandatory argument:
OPT(---)OPT(c)ARG(arg)
OPT(a)OPT(---)OPT(c)ARG(arg)
OPT(-xy)OPT(c)ARG(arg)
OPT(a)OPT(-xy)OPT(c)ARG(arg)

with optional argument:
OPT(-xy)OPT(c)ARG(arg)
OPT(a)OPT(-xy)OPT(c)ARG(arg)
OPT(-)OPT(c)ARG(arg)
OPT(a)OPT(-)OPT(c)ARG(arg)


Analysis
--------
The following analysis progresses from the cases where there is
more agreement to the more controversial cases.

Let's first look at the cases where all the implementations agree.
Case 1 tells us that if optstring contains '-', the dash can
appear as the final option in an option group.
Case 2 tells us that if optstring contains '-:', the '-' option
indeed takes an argument.

Now let's look at cases where all implementations agree apart from
disagreements that are clearly bugs.  The two cases 3 tell us that
a dash in the middle or at the end of an option group is treated
as an invalid option character if optstring does not contain '-'.
The 4.4BSD and OpenBSD behaviour of first returning the initial,
valid option(s) in the group, then returning the entire group *again*
as an argument clearly cannot be right.

Now let's look at those cases where only one implementation disagrees.
Case 4 tells us that if optstring contains '-', the dash can appear
as an option character in the middle of an option group.  So OpenBSD
treating it as an unknown option in that position should be regarded
as a bug.  Even 4.4BSD accepted this.

The two cases 5 tell us that if optstring contains "-:", the '-'
option takes following characters as the argument if any follow
without intervening space.  So OpenBSD treating it as an unknown
option in this case should be regarded as a bug.  Again, even 4.4BSD
accepted this.

Now we get to the cases where more than one system disagrees
with the majority.  The two cases 6 tells us that if optstring
contains '-', the '-' option can appear in an argument of its own,
i.e. "-".  Glibc and Solaris do not support that, also invalidating
part of another case in the Glibc and Solaris columns (case 8).

Finally, let's get to the trickiest cases: the four cases 7,
where '-' is the first option in an option group.
4.4BSD mistreated "--anything" as just "--", completely disregarding
the "anything" part.  That clearly isn't desirable, and no other
system does that.  Solaris is even worse: it reports the '-' as an
error (even though it does support the '-' option when it occurs
in the middle or at the end of an option group), then, just like
4.4BSD, silently skips "anything".  However, all other systems
except OpenBSD agree that '-' can occur at the beginning of an
option group and can even take both a mandatory or an optional
argument in that position, as long as there is no intervening space
(because intervening space would result in the "--" option, which
has different semantics).

The final two cases 8 don't tell us anything new except that several
systems have bugs with handling "-::": that is supposed to indicate
an optional argument, i.e. one without intervening whitespace, and
yet 4.4BSD, DragonFly, and Solaris erroneously collect an option
argument even after intervening whitespace in at least some cases.
Fortunately, there is nothing to fix in OpenBSD with respect to
the cases 8.

So, we have discussed the complete set of cases, and the result of
the comparison is unambiguous: OpenBSD should not only accept "-"
as an isolated option and at the end of option groups, but also at
the beginning and in the middle of option groups, and it should
accept an option argument without intervening whitespace.
Besides, when '-' appears at the end of an option group but is not
in optstring, it should be treated as an invalid option.
This would make OpenBSD fully consistent with FreeBSD and NetBSD,
consistent with glibc except that glibc does not support isolated "-",
and consistent with DragonFly except for one bug specific to DragonFly.

Note that POSIX does not require '-' to be acceptable as an option
letter, but allows it as an extension, and all systems i looked at
support that to at least some degree.  POSIX does not mandate any
special behaviour if '-' is indeed supported as an option letter,
so the FreeBSD/NetBSD behaviour seems a permissible POSIX extension
to me, whereas it feels unclear whether our current behaviour is
permitted by POSIX: while we currently support '-' as an extension,
we then go back and introduce several ad-hoc exceptions (cannot
appear at certain positions in option groups, cannot take option
arguments without intervening whitespace, ...).

The patch below makes seven cases where we now disagree with FreeBSD
and NetBSD agree with them, and because it changes nothing where
we already agree with them and with glibc, it seems very unlikely
that this would cause disruption in ports.


Index: lib/libc/stdlib/getopt_long.c
===================================================================
RCS file: /cvs/src/lib/libc/stdlib/getopt_long.c,v
retrieving revision 1.30
diff -u -p -r1.30 getopt_long.c
--- lib/libc/stdlib/getopt_long.c 25 Jan 2019 00:19:25 -0000 1.30
+++ lib/libc/stdlib/getopt_long.c 30 Mar 2020 04:02:09 -0000
@@ -418,15 +418,7 @@ start:
  }
 
  if ((optchar = (int)*place++) == (int)':' ||
-    (optchar == (int)'-' && *place != '\0') ||
     (oli = strchr(options, optchar)) == NULL) {
- /*
- * If the user specified "-" and  '-' isn't listed in
- * options, return -1 (non-option) as per POSIX.
- * Otherwise, it is an unknown option character (or ':').
- */
- if (optchar == (int)'-' && *place == '\0')
- return (-1);
  if (!*place)
  ++optind;
  if (PRINT_ERROR)
Index: regress/lib/libc/getopt/getopt.sh
===================================================================
RCS file: /cvs/src/regress/lib/libc/getopt/getopt.sh,v
retrieving revision 1.1
diff -u -p -r1.1 getopt.sh
--- regress/lib/libc/getopt/getopt.sh 23 Mar 2020 03:01:21 -0000 1.1
+++ regress/lib/libc/getopt/getopt.sh 30 Mar 2020 04:02:09 -0000
@@ -53,15 +53,33 @@ test3_getopt()
 
 irc=0
 
-# isolated options without arguments
+# valid isolated options without arguments
 test3_getopt ax '-a -x arg' 'OPT(a)OPT(x)ARG(arg)'
+test3_getopt x- '- -x arg' 'OPT(-)OPT(x)ARG(arg)'
+
+# invalid isolated options without arguments
 test3_getopt ax '-a -y arg' 'OPT(a)ERR(?y)ARG(arg)'
 test1_getopt :ax '-a -y arg' 'OPT(a)ERR(?y)ARG(arg)'
+test2_getopt x '- -x arg' 'ARG(-)ARG(-x)ARG(arg)'
+test1_getopt -x '- -x arg' 'NONE(-)OPT(x)NONE(arg)'
+test3_getopt a- '-a - -x arg' 'OPT(a)OPT(-)ERR(?x)ARG(arg)'
+test1_getopt :a- '-a - -x arg' 'OPT(a)OPT(-)ERR(?x)ARG(arg)'
 
-# grouped options without arguments
+# valid grouped options without arguments
 test3_getopt ax '-ax arg' 'OPT(a)OPT(x)ARG(arg)'
+test3_getopt ax- '-a- -x arg' 'OPT(a)OPT(-)OPT(x)ARG(arg)'
+test3_getopt abx- '-a-b -x arg' 'OPT(a)OPT(-)OPT(b)OPT(x)ARG(arg)'
+test3_getopt ax- '--a -x arg' 'OPT(-)OPT(a)OPT(x)ARG(arg)'
+
+# invalid grouped options without arguments
 test3_getopt ax '-ay arg' 'OPT(a)ERR(?y)ARG(arg)'
 test1_getopt :ax '-ay arg' 'OPT(a)ERR(?y)ARG(arg)'
+test3_getopt ax '-a- -x arg' 'OPT(a)ERR(?-)OPT(x)ARG(arg)'
+test1_getopt :ax '-a- -x arg' 'OPT(a)ERR(?-)OPT(x)ARG(arg)'
+test3_getopt abx '-a-b -x arg' 'OPT(a)ERR(?-)OPT(b)OPT(x)ARG(arg)'
+test1_getopt :abx '-a-b -x arg' 'OPT(a)ERR(?-)OPT(b)OPT(x)ARG(arg)'
+test3_getopt ax '--a -x arg' 'ERR(?-)OPT(a)OPT(x)ARG(arg)'
+test1_getopt :ax '--a -x arg' 'ERR(?-)OPT(a)OPT(x)ARG(arg)'
 
 # non-option arguments terminating option processing
 test2_getopt ax '-a arg -x' 'OPT(a)ARG(arg)ARG(-x)'
@@ -71,13 +89,6 @@ test1_getopt -ax '-a -- -x' 'OPT(a)ARG(-
 test2_getopt ax '-a - -x' 'OPT(a)ARG(-)ARG(-x)'
 test1_getopt -ax '-a - -x arg' 'OPT(a)NONE(-)OPT(x)NONE(arg)'
 
-# the '-' option only works when isolated
-test3_getopt a- '-a - -x arg' 'OPT(a)OPT(-)ERR(?x)ARG(arg)'
-test1_getopt :a- '-a - -x arg' 'OPT(a)OPT(-)ERR(?x)ARG(arg)'
-test1_getopt --a '-a - -x arg' 'OPT(a)OPT(-)ERR(?x)NONE(arg)'
-test3_getopt ax '-a-x arg' 'OPT(a)ERR(?-)OPT(x)ARG(arg)'
-test3_getopt a-x '-a-x arg' 'OPT(a)ERR(?-)OPT(x)ARG(arg)'
-
 # the ':' option never works
 test1_getopt ::a '-:a arg' 'ERR(?:)OPT(a)ARG(arg)'
 test1_getopt :::a '-: arg -a' 'ERR(?:)ARG(arg)ARG(-a)'
@@ -85,19 +96,41 @@ test1_getopt :::a '-: arg -a' 'ERR(?:)AR
 # isolated options with arguments
 test3_getopt o: '-o' 'ERR(?o)'
 test1_getopt :o: '-o' 'ERR(:o)'
+test3_getopt o-: '-' 'ERR(?-)'
+test1_getopt :-: '-' 'ERR(:-)'
 test3_getopt o:x '-o arg -x arg' 'OPT(oarg)OPT(x)ARG(arg)'
+test3_getopt o:x '-oarg -x arg' 'OPT(oarg)OPT(x)ARG(arg)'
 test3_getopt o::x '-oarg -x arg' 'OPT(oarg)OPT(x)ARG(arg)'
 test2_getopt o::x '-o arg -x' 'OPT(o)ARG(arg)ARG(-x)'
 test1_getopt -o::x '-o arg1 -x arg2' 'OPT(o)NONE(arg1)OPT(x)NONE(arg2)'
+test3_getopt o:x '-o -x arg' 'OPT(o-x)ARG(arg)'
 test3_getopt o:x '-o -- -x arg' 'OPT(o--)OPT(x)ARG(arg)'
+test3_getopt x-: '- arg -x arg' 'OPT(-arg)OPT(x)ARG(arg)'
+test3_getopt x-: '--arg -x arg' 'OPT(-arg)OPT(x)ARG(arg)'
+test3_getopt x-:: '--arg -x arg' 'OPT(-arg)OPT(x)ARG(arg)'
+test2_getopt x-:: '- arg -x' 'OPT(-)ARG(arg)ARG(-x)'
+test1_getopt --::x '- arg1 -x arg2' 'OPT(-)NONE(arg1)OPT(x)NONE(arg2)'
+test3_getopt x-: '- -x arg' 'OPT(--x)ARG(arg)'
+test3_getopt x-: '- -- -x arg' 'OPT(---)OPT(x)ARG(arg)'
 
 # grouped options with arguments
 test3_getopt ao: '-ao' 'OPT(a)ERR(?o)'
 test1_getopt :ao: '-ao' 'OPT(a)ERR(:o)'
+test3_getopt a-: '-a-' 'OPT(a)ERR(?-)'
+test1_getopt :a-: '-a-' 'OPT(a)ERR(:-)'
 test3_getopt ao:x '-ao arg -x arg' 'OPT(a)OPT(oarg)OPT(x)ARG(arg)'
+test3_getopt ao:x '-aoarg -x arg' 'OPT(a)OPT(oarg)OPT(x)ARG(arg)'
 test3_getopt ao::x '-aoarg -x arg' 'OPT(a)OPT(oarg)OPT(x)ARG(arg)'
 test2_getopt ao::x '-ao arg -x' 'OPT(a)OPT(o)ARG(arg)ARG(-x)'
 test1_getopt -ao::x '-ao arg1 -x arg2' 'OPT(a)OPT(o)NONE(arg1)OPT(x)NONE(arg2)'
+test3_getopt ao:x '-ao -x arg' 'OPT(a)OPT(o-x)ARG(arg)'
 test3_getopt ao:x '-ao -- -x arg' 'OPT(a)OPT(o--)OPT(x)ARG(arg)'
+test3_getopt a-:x '-a- arg -x arg' 'OPT(a)OPT(-arg)OPT(x)ARG(arg)'
+test3_getopt a-:x '-a-arg -x arg' 'OPT(a)OPT(-arg)OPT(x)ARG(arg)'
+test3_getopt a-::x '-a-arg -x arg' 'OPT(a)OPT(-arg)OPT(x)ARG(arg)'
+test2_getopt a-::x '-a- arg -x' 'OPT(a)OPT(-)ARG(arg)ARG(-x)'
+test1_getopt -a-::x '-a- arg1 -x arg2' 'OPT(a)OPT(-)NONE(arg1)OPT(x)NONE(arg2)'
+test3_getopt a-:x '-a- -x arg' 'OPT(a)OPT(--x)ARG(arg)'
+test3_getopt a-:x '-a- -- -x arg' 'OPT(a)OPT(---)OPT(x)ARG(arg)'
 
 exit $irc