dd: clarify meaning of multiplication factors; put xM in order
[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-2008 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 "euidaccess.h"
42 #include "quote.h"
43 #include "stat-time.h"
44 #include "strnumcmp.h"
45
46 #if HAVE_SYS_PARAM_H
47 # include <sys/param.h>
48 #endif
49
50 /* Exit status for syntax errors, etc.  */
51 enum { TEST_TRUE, TEST_FALSE, TEST_FAILURE };
52
53 #if defined TEST_STANDALONE
54 # define test_exit(val) exit (val)
55 #else
56    static jmp_buf test_exit_buf;
57    static int test_error_return = 0;
58 # define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
59 #endif /* !TEST_STANDALONE */
60
61 static int pos;         /* The offset of the current argument in ARGV. */
62 static int argc;        /* The number of arguments present in ARGV. */
63 static char **argv;     /* The argument list. */
64
65 static bool test_unop (char const *s);
66 static bool unary_operator (void);
67 static bool binary_operator (bool);
68 static bool two_arguments (void);
69 static bool three_arguments (void);
70 static bool posixtest (int);
71
72 static bool expr (void);
73 static bool term (void);
74 static bool and (void);
75 static bool or (void);
76
77 static void test_syntax_error (char const *format, char const *arg)
78      ATTRIBUTE_NORETURN;
79 static void beyond (void) ATTRIBUTE_NORETURN;
80
81 static void
82 test_syntax_error (char const *format, char const *arg)
83 {
84   fprintf (stderr, "%s: ", argv[0]);
85   fprintf (stderr, format, arg);
86   fputc ('\n', stderr);
87   fflush (stderr);
88   test_exit (TEST_FAILURE);
89 }
90
91 /* Increment our position in the argument list.  Check that we're not
92    past the end of the argument list.  This check is supressed if the
93    argument is false.  */
94
95 static inline void
96 advance (bool f)
97 {
98   ++pos;
99
100   if (f && pos >= argc)
101     beyond ();
102 }
103
104 static inline void
105 unary_advance (void)
106 {
107   advance (true);
108   ++pos;
109 }
110
111 /*
112  * beyond - call when we're beyond the end of the argument list (an
113  *      error condition)
114  */
115 static void
116 beyond (void)
117 {
118   test_syntax_error (_("missing argument after %s"), quote (argv[argc - 1]));
119 }
120
121 /* If the characters pointed to by STRING constitute a valid number,
122    return a pointer to the start of the number, skipping any blanks or
123    leading '+'.  Otherwise, report an error and exit.  */
124 static char const *
125 find_int (char const *string)
126 {
127   char const *p;
128   char const *number_start;
129
130   for (p = string; isblank (to_uchar (*p)); p++)
131     continue;
132
133   if (*p == '+')
134     {
135       p++;
136       number_start = p;
137     }
138   else
139     {
140       number_start = p;
141       p += (*p == '-');
142     }
143
144   if (ISDIGIT (*p++))
145     {
146       while (ISDIGIT (*p))
147         p++;
148       while (isblank (to_uchar (*p)))
149         p++;
150       if (!*p)
151         return number_start;
152     }
153
154   test_syntax_error (_("invalid integer %s"), quote (string));
155 }
156
157 /* Find the modification time of FILE, and stuff it into *MTIME.
158    Return true if successful.  */
159 static bool
160 get_mtime (char const *filename, struct timespec *mtime)
161 {
162   struct stat finfo;
163   bool ok = (stat (filename, &finfo) == 0);
164 #ifdef lint
165   static struct timespec const zero;
166   *mtime = zero;
167 #endif
168   if (ok)
169     *mtime = get_stat_mtime (&finfo);
170   return ok;
171 }
172
173 /* Return true if S is one of the test command's binary operators.  */
174 static bool
175 binop (char const *s)
176 {
177   return ((STREQ (s,   "=")) || (STREQ (s,  "!=")) || (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     {
366       bool value = STREQ (argv[pos], argv[pos + 2]);
367       pos += 3;
368       return value;
369     }
370
371   if (STREQ (argv[op], "!="))
372     {
373       bool value = !STREQ (argv[pos], argv[pos + 2]);
374       pos += 3;
375       return value;
376     }
377
378   /* Not reached.  */
379   abort ();
380 }
381
382 static bool
383 unary_operator (void)
384 {
385   struct stat stat_buf;
386
387   switch (argv[pos][1])
388     {
389     default:
390       return false;
391
392       /* All of the following unary operators use unary_advance (), which
393          checks to make sure that there is an argument, and then advances
394          pos right past it.  This means that pos - 1 is the location of the
395          argument. */
396
397     case 'a':                   /* file exists in the file system? */
398     case 'e':
399       unary_advance ();
400       return stat (argv[pos - 1], &stat_buf) == 0;
401
402     case 'r':                   /* file is readable? */
403       unary_advance ();
404       return euidaccess (argv[pos - 1], R_OK) == 0;
405
406     case 'w':                   /* File is writable? */
407       unary_advance ();
408       return euidaccess (argv[pos - 1], W_OK) == 0;
409
410     case 'x':                   /* File is executable? */
411       unary_advance ();
412       return euidaccess (argv[pos - 1], X_OK) == 0;
413
414     case 'O':                   /* File is owned by you? */
415       unary_advance ();
416       return (stat (argv[pos - 1], &stat_buf) == 0
417               && (geteuid () == stat_buf.st_uid));
418
419     case 'G':                   /* File is owned by your group? */
420       unary_advance ();
421       return (stat (argv[pos - 1], &stat_buf) == 0
422               && (getegid () == stat_buf.st_gid));
423
424     case 'f':                   /* File is a file? */
425       unary_advance ();
426       /* Under POSIX, -f is true if the given file exists
427          and is a regular file. */
428       return (stat (argv[pos - 1], &stat_buf) == 0
429               && S_ISREG (stat_buf.st_mode));
430
431     case 'd':                   /* File is a directory? */
432       unary_advance ();
433       return (stat (argv[pos - 1], &stat_buf) == 0
434               && S_ISDIR (stat_buf.st_mode));
435
436     case 's':                   /* File has something in it? */
437       unary_advance ();
438       return (stat (argv[pos - 1], &stat_buf) == 0
439               && 0 < stat_buf.st_size);
440
441     case 'S':                   /* File is a socket? */
442       unary_advance ();
443       return (stat (argv[pos - 1], &stat_buf) == 0
444               && S_ISSOCK (stat_buf.st_mode));
445
446     case 'c':                   /* File is character special? */
447       unary_advance ();
448       return (stat (argv[pos - 1], &stat_buf) == 0
449               && S_ISCHR (stat_buf.st_mode));
450
451     case 'b':                   /* File is block special? */
452       unary_advance ();
453       return (stat (argv[pos - 1], &stat_buf) == 0
454               && S_ISBLK (stat_buf.st_mode));
455
456     case 'p':                   /* File is a named pipe? */
457       unary_advance ();
458       return (stat (argv[pos - 1], &stat_buf) == 0
459               && S_ISFIFO (stat_buf.st_mode));
460
461     case 'L':                   /* Same as -h  */
462       /*FALLTHROUGH*/
463
464     case 'h':                   /* File is a symbolic link? */
465       unary_advance ();
466       return (lstat (argv[pos - 1], &stat_buf) == 0
467               && S_ISLNK (stat_buf.st_mode));
468
469     case 'u':                   /* File is setuid? */
470       unary_advance ();
471       return (stat (argv[pos - 1], &stat_buf) == 0
472               && (stat_buf.st_mode & S_ISUID));
473
474     case 'g':                   /* File is setgid? */
475       unary_advance ();
476       return (stat (argv[pos - 1], &stat_buf) == 0
477               && (stat_buf.st_mode & S_ISGID));
478
479     case 'k':                   /* File has sticky bit set? */
480       unary_advance ();
481       return (stat (argv[pos - 1], &stat_buf) == 0
482               && (stat_buf.st_mode & S_ISVTX));
483
484     case 't':                   /* File (fd) is a terminal? */
485       {
486         long int fd;
487         char const *arg;
488         unary_advance ();
489         arg = find_int (argv[pos - 1]);
490         errno = 0;
491         fd = strtol (arg, NULL, 10);
492         return (errno != ERANGE && 0 <= fd && fd <= INT_MAX && isatty (fd));
493       }
494
495     case 'n':                   /* True if arg has some length. */
496       unary_advance ();
497       return argv[pos - 1][0] != 0;
498
499     case 'z':                   /* True if arg has no length. */
500       unary_advance ();
501       return argv[pos - 1][0] == '\0';
502     }
503 }
504
505 /*
506  * and:
507  *      term
508  *      term '-a' and
509  */
510 static bool
511 and (void)
512 {
513   bool value = true;
514
515   for (;;)
516     {
517       value &= term ();
518       if (! (pos < argc && STREQ (argv[pos], "-a")))
519         return value;
520       advance (false);
521     }
522 }
523
524 /*
525  * or:
526  *      and
527  *      and '-o' or
528  */
529 static bool
530 or (void)
531 {
532   bool value = false;
533
534   for (;;)
535     {
536       value |= and ();
537       if (! (pos < argc && STREQ (argv[pos], "-o")))
538         return value;
539       advance (false);
540     }
541 }
542
543 /*
544  * expr:
545  *      or
546  */
547 static bool
548 expr (void)
549 {
550   if (pos >= argc)
551     beyond ();
552
553   return or ();         /* Same with this. */
554 }
555
556 /* Return true if OP is one of the test command's unary operators. */
557 static bool
558 test_unop (char const *op)
559 {
560   if (op[0] != '-')
561     return false;
562
563   switch (op[1])
564     {
565     case 'a': case 'b': case 'c': case 'd': case 'e':
566     case 'f': case 'g': case 'h': case 'k': case 'n':
567     case 'o': case 'p': case 'r': case 's': case 't':
568     case 'u': case 'w': case 'x': case 'z':
569     case 'G': case 'L': case 'O': case 'S': case 'N':
570       return true;
571     }
572
573   return false;
574 }
575
576 static bool
577 one_argument (void)
578 {
579   return argv[pos++][0] != '\0';
580 }
581
582 static bool
583 two_arguments (void)
584 {
585   bool value;
586
587   if (STREQ (argv[pos], "!"))
588     {
589       advance (false);
590       value = ! one_argument ();
591     }
592   else if (argv[pos][0] == '-'
593            && argv[pos][1] != '\0'
594            && argv[pos][2] == '\0')
595     {
596       if (test_unop (argv[pos]))
597         value = unary_operator ();
598       else
599         test_syntax_error (_("%s: unary operator expected"), argv[pos]);
600     }
601   else
602     beyond ();
603   return (value);
604 }
605
606 static bool
607 three_arguments (void)
608 {
609   bool value;
610
611   if (binop (argv[pos + 1]))
612     value = binary_operator (false);
613   else if (STREQ (argv[pos], "!"))
614     {
615       advance (true);
616       value = !two_arguments ();
617     }
618   else if (STREQ (argv[pos], "(") && STREQ (argv[pos + 2], ")"))
619     {
620       advance (false);
621       value = one_argument ();
622       advance (false);
623     }
624   else if (STREQ (argv[pos + 1], "-a") || STREQ (argv[pos + 1], "-o"))
625     value = expr ();
626   else
627     test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
628   return (value);
629 }
630
631 /* This is an implementation of a Posix.2 proposal by David Korn. */
632 static bool
633 posixtest (int nargs)
634 {
635   bool value;
636
637   switch (nargs)
638     {
639       case 1:
640         value = one_argument ();
641         break;
642
643       case 2:
644         value = two_arguments ();
645         break;
646
647       case 3:
648         value = three_arguments ();
649         break;
650
651       case 4:
652         if (STREQ (argv[pos], "!"))
653           {
654             advance (true);
655             value = !three_arguments ();
656             break;
657           }
658         if (STREQ (argv[pos], "(") && STREQ (argv[pos + 3], ")"))
659           {
660             advance (false);
661             value = two_arguments ();
662             advance (false);
663             break;
664           }
665         /* FALLTHROUGH */
666       case 5:
667       default:
668         if (nargs <= 0)
669           abort ();
670         value = expr ();
671     }
672
673   return (value);
674 }
675
676 #if defined TEST_STANDALONE
677 # include "long-options.h"
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_bug_reporting_address ();
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, and when the last argument is not "]".  POSIX
824          allows "[ --help" and "[ --version" to have the usual GNU
825          behavior, but it requires "test --help" and "test --version"
826          to exit silently with status 0.  */
827       if (margc < 2 || !STREQ (margv[margc - 1], "]"))
828         {
829           parse_long_options (margc, margv, PROGRAM_NAME, PACKAGE_NAME, VERSION,
830                               usage, AUTHORS, (char const *) NULL);
831           test_syntax_error (_("missing `]'"), NULL);
832         }
833
834       --margc;
835     }
836
837   argc = margc;
838   pos = 1;
839
840   if (pos >= argc)
841     test_exit (TEST_FALSE);
842
843   value = posixtest (argc - 1);
844
845   if (pos != argc)
846     test_syntax_error (_("extra argument %s"), quote (argv[pos]));
847
848   test_exit (value ? TEST_TRUE : TEST_FALSE);
849 }