test: accept "==" as a synonym for "="
[platform/upstream/coreutils.git] / src / test.c
1 /* GNU test program (ksb and mjb) */
2
3 /* Modified to run with the GNU shell by bfox. */
4
5 /* Copyright (C) 1987-2005, 2007-2011 Free Software Foundation, Inc.
6
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 /* Define TEST_STANDALONE to get the /bin/test version.  Otherwise, you get
21    the shell builtin version. */
22
23 #include <config.h>
24 #include <stdio.h>
25 #include <sys/types.h>
26
27 #define TEST_STANDALONE 1
28
29 #ifndef LBRACKET
30 # define LBRACKET 0
31 #endif
32
33 /* The official name of this program (e.g., no `g' prefix).  */
34 #if LBRACKET
35 # define PROGRAM_NAME "["
36 #else
37 # define PROGRAM_NAME "test"
38 #endif
39
40 #include "system.h"
41 #include "quote.h"
42 #include "stat-time.h"
43 #include "strnumcmp.h"
44
45 #if HAVE_SYS_PARAM_H
46 # include <sys/param.h>
47 #endif
48
49 /* Exit status for syntax errors, etc.  */
50 enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE };
51
52 #if defined TEST_STANDALONE
53 # define test_exit(val) exit (val)
54 #else
55    static jmp_buf test_exit_buf;
56    static int test_error_return = 0;
57 # define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
58 #endif /* !TEST_STANDALONE */
59
60 static int pos;         /* The offset of the current argument in ARGV. */
61 static int argc;        /* The number of arguments present in ARGV. */
62 static char **argv;     /* The argument list. */
63
64 static bool test_unop (char const *s);
65 static bool unary_operator (void);
66 static bool binary_operator (bool);
67 static bool two_arguments (void);
68 static bool three_arguments (void);
69 static bool posixtest (int);
70
71 static bool expr (void);
72 static bool term (void);
73 static bool and (void);
74 static bool or (void);
75
76 static void test_syntax_error (char const *format, char const *arg)
77      ATTRIBUTE_NORETURN;
78 static void beyond (void) ATTRIBUTE_NORETURN;
79
80 static void
81 test_syntax_error (char const *format, char const *arg)
82 {
83   fprintf (stderr, "%s: ", argv[0]);
84   fprintf (stderr, format, arg);
85   fputc ('\n', stderr);
86   fflush (stderr);
87   test_exit (TEST_FAILURE);
88 }
89
90 /* Increment our position in the argument list.  Check that we're not
91    past the end of the argument list.  This check is supressed if the
92    argument is false.  */
93
94 static void
95 advance (bool f)
96 {
97   ++pos;
98
99   if (f && pos >= argc)
100     beyond ();
101 }
102
103 static void
104 unary_advance (void)
105 {
106   advance (true);
107   ++pos;
108 }
109
110 /*
111  * beyond - call when we're beyond the end of the argument list (an
112  *      error condition)
113  */
114 static void
115 beyond (void)
116 {
117   test_syntax_error (_("missing argument after %s"), quote (argv[argc - 1]));
118 }
119
120 /* If the characters pointed to by STRING constitute a valid number,
121    return a pointer to the start of the number, skipping any blanks or
122    leading '+'.  Otherwise, report an error and exit.  */
123 static char const *
124 find_int (char const *string)
125 {
126   char const *p;
127   char const *number_start;
128
129   for (p = string; isblank (to_uchar (*p)); p++)
130     continue;
131
132   if (*p == '+')
133     {
134       p++;
135       number_start = p;
136     }
137   else
138     {
139       number_start = p;
140       p += (*p == '-');
141     }
142
143   if (ISDIGIT (*p++))
144     {
145       while (ISDIGIT (*p))
146         p++;
147       while (isblank (to_uchar (*p)))
148         p++;
149       if (!*p)
150         return number_start;
151     }
152
153   test_syntax_error (_("invalid integer %s"), quote (string));
154 }
155
156 /* Find the modification time of FILE, and stuff it into *MTIME.
157    Return true if successful.  */
158 static bool
159 get_mtime (char const *filename, struct timespec *mtime)
160 {
161   struct stat finfo;
162   bool ok = (stat (filename, &finfo) == 0);
163 #ifdef lint
164   static struct timespec const zero;
165   *mtime = zero;
166 #endif
167   if (ok)
168     *mtime = get_stat_mtime (&finfo);
169   return ok;
170 }
171
172 /* Return true if S is one of the test command's binary operators.  */
173 static bool
174 binop (char const *s)
175 {
176   return ((STREQ (s,   "=")) || (STREQ (s,  "!=")) || (STREQ (s, "==")) ||
177           (STREQ (s,   "-nt")) ||
178           (STREQ (s, "-ot")) || (STREQ (s, "-ef")) || (STREQ (s, "-eq")) ||
179           (STREQ (s, "-ne")) || (STREQ (s, "-lt")) || (STREQ (s, "-le")) ||
180           (STREQ (s, "-gt")) || (STREQ (s, "-ge")));
181 }
182
183 /*
184  * term - parse a term and return 1 or 0 depending on whether the term
185  *      evaluates to true or false, respectively.
186  *
187  * term ::=
188  *      '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename
189  *      '-'('L'|'x') filename
190  *      '-t' int
191  *      '-'('z'|'n') string
192  *      string
193  *      string ('!='|'=') string
194  *      <int> '-'(eq|ne|le|lt|ge|gt) <int>
195  *      file '-'(nt|ot|ef) file
196  *      '(' <expr> ')'
197  * int ::=
198  *      '-l' string
199  *      positive and negative integers
200  */
201 static bool
202 term (void)
203 {
204   bool value;
205   bool negated = false;
206
207   /* Deal with leading `not's.  */
208   while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
209     {
210       advance (true);
211       negated = !negated;
212     }
213
214   if (pos >= argc)
215     beyond ();
216
217   /* A paren-bracketed argument. */
218   if (argv[pos][0] == '(' && argv[pos][1] == '\0')
219     {
220       int nargs;
221
222       advance (true);
223
224       for (nargs = 1;
225            pos + nargs < argc && ! STREQ (argv[pos + nargs], ")");
226            nargs++)
227         if (nargs == 4)
228           {
229             nargs = argc - pos;
230             break;
231           }
232
233       value = posixtest (nargs);
234       if (argv[pos] == 0)
235         test_syntax_error (_("')' expected"), NULL);
236       else
237         if (argv[pos][0] != ')' || argv[pos][1])
238           test_syntax_error (_("')' expected, found %s"), argv[pos]);
239       advance (false);
240     }
241
242   /* Are there enough arguments left that this could be dyadic?  */
243   else if (4 <= argc - pos && STREQ (argv[pos], "-l") && binop (argv[pos + 2]))
244     value = binary_operator (true);
245   else if (3 <= argc - pos && binop (argv[pos + 1]))
246     value = binary_operator (false);
247
248   /* It might be a switch type argument.  */
249   else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0')
250     {
251       if (test_unop (argv[pos]))
252         value = unary_operator ();
253       else
254         test_syntax_error (_("%s: unary operator expected"), argv[pos]);
255     }
256   else
257     {
258       value = (argv[pos][0] != '\0');
259       advance (false);
260     }
261
262   return negated ^ value;
263 }
264
265 static bool
266 binary_operator (bool l_is_l)
267 {
268   int op;
269   struct stat stat_buf, stat_spare;
270   /* Is the right integer expression of the form '-l string'? */
271   bool r_is_l;
272
273   if (l_is_l)
274     advance (false);
275   op = pos + 1;
276
277   if ((op < argc - 2) && STREQ (argv[op + 1], "-l"))
278     {
279       r_is_l = true;
280       advance (false);
281     }
282   else
283     r_is_l = false;
284
285   if (argv[op][0] == '-')
286     {
287       /* check for eq, nt, and stuff */
288       if ((((argv[op][1] == 'l' || argv[op][1] == 'g')
289             && (argv[op][2] == 'e' || argv[op][2] == 't'))
290            || (argv[op][1] == 'e' && argv[op][2] == 'q')
291            || (argv[op][1] == 'n' && argv[op][2] == 'e'))
292           && !argv[op][3])
293         {
294           char lbuf[INT_BUFSIZE_BOUND (uintmax_t)];
295           char rbuf[INT_BUFSIZE_BOUND (uintmax_t)];
296           char const *l = (l_is_l
297                            ? umaxtostr (strlen (argv[op - 1]), lbuf)
298                            : find_int (argv[op - 1]));
299           char const *r = (r_is_l
300                            ? umaxtostr (strlen (argv[op + 2]), rbuf)
301                            : find_int (argv[op + 1]));
302           int cmp = strintcmp (l, r);
303           bool xe_operator = (argv[op][2] == 'e');
304           pos += 3;
305           return (argv[op][1] == 'l' ? cmp < xe_operator
306                   : argv[op][1] == 'g' ? cmp > - xe_operator
307                   : (cmp != 0) == xe_operator);
308         }
309
310       switch (argv[op][1])
311         {
312         default:
313           break;
314
315         case 'n':
316           if (argv[op][2] == 't' && !argv[op][3])
317             {
318               /* nt - newer than */
319               struct timespec lt, rt;
320               bool le, re;
321               pos += 3;
322               if (l_is_l || r_is_l)
323                 test_syntax_error (_("-nt does not accept -l"), NULL);
324               le = get_mtime (argv[op - 1], &lt);
325               re = get_mtime (argv[op + 1], &rt);
326               return le && (!re || timespec_cmp (lt, rt) > 0);
327             }
328           break;
329
330         case 'e':
331           if (argv[op][2] == 'f' && !argv[op][3])
332             {
333               /* ef - hard link? */
334               pos += 3;
335               if (l_is_l || r_is_l)
336                 test_syntax_error (_("-ef does not accept -l"), NULL);
337               return (stat (argv[op - 1], &stat_buf) == 0
338                       && stat (argv[op + 1], &stat_spare) == 0
339                       && stat_buf.st_dev == stat_spare.st_dev
340                       && stat_buf.st_ino == stat_spare.st_ino);
341             }
342           break;
343
344         case 'o':
345           if ('t' == argv[op][2] && '\000' == argv[op][3])
346             {
347               /* ot - older than */
348               struct timespec lt, rt;
349               bool le, re;
350               pos += 3;
351               if (l_is_l || r_is_l)
352                 test_syntax_error (_("-ot does not accept -l"), NULL);
353               le = get_mtime (argv[op - 1], &lt);
354               re = get_mtime (argv[op + 1], &rt);
355               return re && (!le || timespec_cmp (lt, rt) < 0);
356             }
357           break;
358         }
359
360       /* FIXME: is this dead code? */
361       test_syntax_error (_("unknown binary operator"), argv[op]);
362     }
363
364   if (argv[op][0] == '=' && (!argv[op][1] ||
365        ((argv[op][1] == '=') && !argv[op][2])))
366     {
367       bool value = STREQ (argv[pos], argv[pos + 2]);
368       pos += 3;
369       return value;
370     }
371
372   if (STREQ (argv[op], "!="))
373     {
374       bool value = !STREQ (argv[pos], argv[pos + 2]);
375       pos += 3;
376       return value;
377     }
378
379   /* Not reached.  */
380   abort ();
381 }
382
383 static bool
384 unary_operator (void)
385 {
386   struct stat stat_buf;
387
388   switch (argv[pos][1])
389     {
390     default:
391       return false;
392
393       /* All of the following unary operators use unary_advance (), which
394          checks to make sure that there is an argument, and then advances
395          pos right past it.  This means that pos - 1 is the location of the
396          argument. */
397
398     case 'a':                   /* file exists in the file system? */
399     case 'e':
400       unary_advance ();
401       return stat (argv[pos - 1], &stat_buf) == 0;
402
403     case 'r':                   /* file is readable? */
404       unary_advance ();
405       return euidaccess (argv[pos - 1], R_OK) == 0;
406
407     case 'w':                   /* File is writable? */
408       unary_advance ();
409       return euidaccess (argv[pos - 1], W_OK) == 0;
410
411     case 'x':                   /* File is executable? */
412       unary_advance ();
413       return euidaccess (argv[pos - 1], X_OK) == 0;
414
415     case 'O':                   /* File is owned by you? */
416       unary_advance ();
417       return (stat (argv[pos - 1], &stat_buf) == 0
418               && (geteuid () == stat_buf.st_uid));
419
420     case 'G':                   /* File is owned by your group? */
421       unary_advance ();
422       return (stat (argv[pos - 1], &stat_buf) == 0
423               && (getegid () == stat_buf.st_gid));
424
425     case 'f':                   /* File is a file? */
426       unary_advance ();
427       /* Under POSIX, -f is true if the given file exists
428          and is a regular file. */
429       return (stat (argv[pos - 1], &stat_buf) == 0
430               && S_ISREG (stat_buf.st_mode));
431
432     case 'd':                   /* File is a directory? */
433       unary_advance ();
434       return (stat (argv[pos - 1], &stat_buf) == 0
435               && S_ISDIR (stat_buf.st_mode));
436
437     case 's':                   /* File has something in it? */
438       unary_advance ();
439       return (stat (argv[pos - 1], &stat_buf) == 0
440               && 0 < stat_buf.st_size);
441
442     case 'S':                   /* File is a socket? */
443       unary_advance ();
444       return (stat (argv[pos - 1], &stat_buf) == 0
445               && S_ISSOCK (stat_buf.st_mode));
446
447     case 'c':                   /* File is character special? */
448       unary_advance ();
449       return (stat (argv[pos - 1], &stat_buf) == 0
450               && S_ISCHR (stat_buf.st_mode));
451
452     case 'b':                   /* File is block special? */
453       unary_advance ();
454       return (stat (argv[pos - 1], &stat_buf) == 0
455               && S_ISBLK (stat_buf.st_mode));
456
457     case 'p':                   /* File is a named pipe? */
458       unary_advance ();
459       return (stat (argv[pos - 1], &stat_buf) == 0
460               && S_ISFIFO (stat_buf.st_mode));
461
462     case 'L':                   /* Same as -h  */
463       /*FALLTHROUGH*/
464
465     case 'h':                   /* File is a symbolic link? */
466       unary_advance ();
467       return (lstat (argv[pos - 1], &stat_buf) == 0
468               && S_ISLNK (stat_buf.st_mode));
469
470     case 'u':                   /* File is setuid? */
471       unary_advance ();
472       return (stat (argv[pos - 1], &stat_buf) == 0
473               && (stat_buf.st_mode & S_ISUID));
474
475     case 'g':                   /* File is setgid? */
476       unary_advance ();
477       return (stat (argv[pos - 1], &stat_buf) == 0
478               && (stat_buf.st_mode & S_ISGID));
479
480     case 'k':                   /* File has sticky bit set? */
481       unary_advance ();
482       return (stat (argv[pos - 1], &stat_buf) == 0
483               && (stat_buf.st_mode & S_ISVTX));
484
485     case 't':                   /* File (fd) is a terminal? */
486       {
487         long int fd;
488         char const *arg;
489         unary_advance ();
490         arg = find_int (argv[pos - 1]);
491         errno = 0;
492         fd = strtol (arg, NULL, 10);
493         return (errno != ERANGE && 0 <= fd && fd <= INT_MAX && isatty (fd));
494       }
495
496     case 'n':                   /* True if arg has some length. */
497       unary_advance ();
498       return argv[pos - 1][0] != 0;
499
500     case 'z':                   /* True if arg has no length. */
501       unary_advance ();
502       return argv[pos - 1][0] == '\0';
503     }
504 }
505
506 /*
507  * and:
508  *      term
509  *      term '-a' and
510  */
511 static bool
512 and (void)
513 {
514   bool value = true;
515
516   while (true)
517     {
518       value &= term ();
519       if (! (pos < argc && STREQ (argv[pos], "-a")))
520         return value;
521       advance (false);
522     }
523 }
524
525 /*
526  * or:
527  *      and
528  *      and '-o' or
529  */
530 static bool
531 or (void)
532 {
533   bool value = false;
534
535   while (true)
536     {
537       value |= and ();
538       if (! (pos < argc && STREQ (argv[pos], "-o")))
539         return value;
540       advance (false);
541     }
542 }
543
544 /*
545  * expr:
546  *      or
547  */
548 static bool
549 expr (void)
550 {
551   if (pos >= argc)
552     beyond ();
553
554   return or ();         /* Same with this. */
555 }
556
557 /* Return true if OP is one of the test command's unary operators. */
558 static bool
559 test_unop (char const *op)
560 {
561   if (op[0] != '-')
562     return false;
563
564   switch (op[1])
565     {
566     case 'a': case 'b': case 'c': case 'd': case 'e':
567     case 'f': case 'g': case 'h': case 'k': case 'n':
568     case 'o': case 'p': case 'r': case 's': case 't':
569     case 'u': case 'w': case 'x': case 'z':
570     case 'G': case 'L': case 'O': case 'S': case 'N':
571       return true;
572     default:
573       return false;
574     }
575 }
576
577 static bool
578 one_argument (void)
579 {
580   return argv[pos++][0] != '\0';
581 }
582
583 static bool
584 two_arguments (void)
585 {
586   bool value;
587
588   if (STREQ (argv[pos], "!"))
589     {
590       advance (false);
591       value = ! one_argument ();
592     }
593   else if (argv[pos][0] == '-'
594            && argv[pos][1] != '\0'
595            && argv[pos][2] == '\0')
596     {
597       if (test_unop (argv[pos]))
598         value = unary_operator ();
599       else
600         test_syntax_error (_("%s: unary operator expected"), argv[pos]);
601     }
602   else
603     beyond ();
604   return (value);
605 }
606
607 static bool
608 three_arguments (void)
609 {
610   bool value;
611
612   if (binop (argv[pos + 1]))
613     value = binary_operator (false);
614   else if (STREQ (argv[pos], "!"))
615     {
616       advance (true);
617       value = !two_arguments ();
618     }
619   else if (STREQ (argv[pos], "(") && STREQ (argv[pos + 2], ")"))
620     {
621       advance (false);
622       value = one_argument ();
623       advance (false);
624     }
625   else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o"))
626     value = expr ();
627   else
628     test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
629   return (value);
630 }
631
632 /* This is an implementation of a Posix.2 proposal by David Korn. */
633 static bool
634 posixtest (int nargs)
635 {
636   bool value;
637
638   switch (nargs)
639     {
640       case 1:
641         value = one_argument ();
642         break;
643
644       case 2:
645         value = two_arguments ();
646         break;
647
648       case 3:
649         value = three_arguments ();
650         break;
651
652       case 4:
653         if (STREQ (argv[pos], "!"))
654           {
655             advance (true);
656             value = !three_arguments ();
657             break;
658           }
659         if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")"))
660           {
661             advance (false);
662             value = two_arguments ();
663             advance (false);
664             break;
665           }
666         /* FALLTHROUGH */
667       case 5:
668       default:
669         if (nargs <= 0)
670           abort ();
671         value = expr ();
672     }
673
674   return (value);
675 }
676
677 #if defined TEST_STANDALONE
678
679 void
680 usage (int status)
681 {
682   if (status != EXIT_SUCCESS)
683     fprintf (stderr, _("Try `%s --help' for more information.\n"),
684              program_name);
685   else
686     {
687       fputs (_("\
688 Usage: test EXPRESSION\n\
689   or:  test\n\
690   or:  [ EXPRESSION ]\n\
691   or:  [ ]\n\
692   or:  [ OPTION\n\
693 "), stdout);
694       fputs (_("\
695 Exit with the status determined by EXPRESSION.\n\
696 \n\
697 "), stdout);
698       fputs (HELP_OPTION_DESCRIPTION, stdout);
699       fputs (VERSION_OPTION_DESCRIPTION, stdout);
700       fputs (_("\
701 \n\
702 An omitted EXPRESSION defaults to false.  Otherwise,\n\
703 EXPRESSION is true or false and sets exit status.  It is one of:\n\
704 "), stdout);
705       fputs (_("\
706 \n\
707   ( EXPRESSION )               EXPRESSION is true\n\
708   ! EXPRESSION                 EXPRESSION is false\n\
709   EXPRESSION1 -a EXPRESSION2   both EXPRESSION1 and EXPRESSION2 are true\n\
710   EXPRESSION1 -o EXPRESSION2   either EXPRESSION1 or EXPRESSION2 is true\n\
711 "), stdout);
712       fputs (_("\
713 \n\
714   -n STRING            the length of STRING is nonzero\n\
715   STRING               equivalent to -n STRING\n\
716   -z STRING            the length of STRING is zero\n\
717   STRING1 = STRING2    the strings are equal\n\
718   STRING1 != STRING2   the strings are not equal\n\
719 "), stdout);
720       fputs (_("\
721 \n\
722   INTEGER1 -eq INTEGER2   INTEGER1 is equal to INTEGER2\n\
723   INTEGER1 -ge INTEGER2   INTEGER1 is greater than or equal to INTEGER2\n\
724   INTEGER1 -gt INTEGER2   INTEGER1 is greater than INTEGER2\n\
725   INTEGER1 -le INTEGER2   INTEGER1 is less than or equal to INTEGER2\n\
726   INTEGER1 -lt INTEGER2   INTEGER1 is less than INTEGER2\n\
727   INTEGER1 -ne INTEGER2   INTEGER1 is not equal to INTEGER2\n\
728 "), stdout);
729       fputs (_("\
730 \n\
731   FILE1 -ef FILE2   FILE1 and FILE2 have the same device and inode numbers\n\
732   FILE1 -nt FILE2   FILE1 is newer (modification date) than FILE2\n\
733   FILE1 -ot FILE2   FILE1 is older than FILE2\n\
734 "), stdout);
735       fputs (_("\
736 \n\
737   -b FILE     FILE exists and is block special\n\
738   -c FILE     FILE exists and is character special\n\
739   -d FILE     FILE exists and is a directory\n\
740   -e FILE     FILE exists\n\
741 "), stdout);
742       fputs (_("\
743   -f FILE     FILE exists and is a regular file\n\
744   -g FILE     FILE exists and is set-group-ID\n\
745   -G FILE     FILE exists and is owned by the effective group ID\n\
746   -h FILE     FILE exists and is a symbolic link (same as -L)\n\
747   -k FILE     FILE exists and has its sticky bit set\n\
748 "), stdout);
749       fputs (_("\
750   -L FILE     FILE exists and is a symbolic link (same as -h)\n\
751   -O FILE     FILE exists and is owned by the effective user ID\n\
752   -p FILE     FILE exists and is a named pipe\n\
753   -r FILE     FILE exists and read permission is granted\n\
754   -s FILE     FILE exists and has a size greater than zero\n\
755 "), stdout);
756       fputs (_("\
757   -S FILE     FILE exists and is a socket\n\
758   -t FD       file descriptor FD is opened on a terminal\n\
759   -u FILE     FILE exists and its set-user-ID bit is set\n\
760   -w FILE     FILE exists and write permission is granted\n\
761   -x FILE     FILE exists and execute (or search) permission is granted\n\
762 "), stdout);
763       fputs (_("\
764 \n\
765 Except for -h and -L, all FILE-related tests dereference symbolic links.\n\
766 Beware that parentheses need to be escaped (e.g., by backslashes) for shells.\n\
767 INTEGER may also be -l STRING, which evaluates to the length of STRING.\n\
768 "), stdout);
769       fputs (_("\
770 \n\
771 NOTE: [ honors the --help and --version options, but test does not.\n\
772 test treats each of those as it treats any other nonempty STRING.\n\
773 "), stdout);
774       printf (USAGE_BUILTIN_WARNING, _("test and/or ["));
775       emit_ancillary_info ();
776     }
777   exit (status);
778 }
779 #endif /* TEST_STANDALONE */
780
781 #if !defined TEST_STANDALONE
782 # define main test_command
783 #endif
784
785 #define AUTHORS \
786   proper_name ("Kevin Braunsdorf"), \
787   proper_name ("Matthew Bradburn")
788
789 /*
790  * [:
791  *      '[' expr ']'
792  * test:
793  *      test expr
794  */
795 int
796 main (int margc, char **margv)
797 {
798   bool value;
799
800 #if !defined TEST_STANDALONE
801   int code;
802
803   code = setjmp (test_exit_buf);
804
805   if (code)
806     return (test_error_return);
807 #else /* TEST_STANDALONE */
808   initialize_main (&margc, &margv);
809   set_program_name (margv[0]);
810   setlocale (LC_ALL, "");
811   bindtextdomain (PACKAGE, LOCALEDIR);
812   textdomain (PACKAGE);
813
814   initialize_exit_failure (TEST_FAILURE);
815   atexit (close_stdout);
816 #endif /* TEST_STANDALONE */
817
818   argv = margv;
819
820   if (LBRACKET)
821     {
822       /* Recognize --help or --version, but only when invoked in the
823          "[" form, when the last argument is not "]".  Use direct
824          parsing, rather than parse_long_options, to avoid accepting
825          abbreviations.  POSIX allows "[ --help" and "[ --version" to
826          have the usual GNU behavior, but it requires "test --help"
827          and "test --version" to exit silently with status 0.  */
828       if (margc == 2)
829         {
830           if (STREQ (margv[1], "--help"))
831             usage (EXIT_SUCCESS);
832
833           if (STREQ (margv[1], "--version"))
834             {
835               version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
836                            (char *) NULL);
837               test_exit (EXIT_SUCCESS);
838             }
839         }
840       if (margc < 2 || !STREQ (margv[margc - 1], "]"))
841         test_syntax_error (_("missing `]'"), NULL);
842
843       --margc;
844     }
845
846   argc = margc;
847   pos = 1;
848
849   if (pos >= argc)
850     test_exit (TEST_FALSE);
851
852   value = posixtest (argc - 1);
853
854   if (pos != argc)
855     test_syntax_error (_("extra argument %s"), quote (argv[pos]));
856
857   test_exit (value ? TEST_TRUE : TEST_FALSE);
858 }