adding GNU dc ("desk calculator")
[unix-history] / bin / test / test.c
CommitLineData
15637ed4
RG
1/*
2 * GNU test program (ksb and mjb)
3 *
4 * Modified to run with the GNU shell Apr 25, 1988 by bfox.
5 *
6 *??? -G file is owned by same gid; the effective gid is checked
7 * Chet Ramey, CWRU 3/23/89
8 *
9 * Fixed a BSD dependency (_doprnt()) in the port to AIX 2.2
10 * Chet Ramey, CWRU 5/3/89
11 */
12
13/* Copyright (C) 1987,1989 Free Software Foundation, Inc.
14
15This file is part of GNU Bash, the Bourne Again SHell.
16
17Bash is free software; you can redistribute it and/or modify it under
18the terms of the GNU General Public License as published by the Free
19Software Foundation; either version 1, or (at your option) any later
20version.
21
22Bash is distributed in the hope that it will be useful, but WITHOUT ANY
23WARRANTY; without even the implied warranty of MERCHANTABILITY or
24FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25for more details.
26
27You should have received a copy of the GNU General Public License along
28with Bash; see the file COPYING. If not, write to the Free Software
29Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
30
31#include <stdio.h>
32#include <varargs.h>
33
34#ifndef SONY
35#include <fcntl.h>
36#endif
37
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <sys/file.h>
41
42#ifndef R_OK
43#define R_OK 4
44#define W_OK 2
45#define X_OK 1
46#define F_OK 0
47#endif
48
49#ifndef lint
50static char *rcsid = "$Id: gtest.c,v 1.10 88/07/02 13:34:45 afb Exp Locker: afb $";
51#endif
52
53/* The following few defines control the truth and false output of each stage.
54 TRUE and FALSE are what we use to compute the final output value.
55 SHELL_BOOLEAN is the form which returns truth or falseness in shell terms.
56 TRUTH_OR is how to do logical or with TRUE and FALSE.
57 TRUTH_AND is how to do logical and with TRUE and FALSE..
58 Default is TRUE = 1, FALSE = 0, TRUTH_OR = a | b, TRUTH_AND = a & b,
59 SHELL_BOOLEAN = (!value). */
60#define TRUE 1
61#define FALSE 0
62#define SHELL_BOOLEAN(value) (!(value))
63#define TRUTH_OR(a, b) ((a) | (b))
64#define TRUTH_AND(a, b) ((a) & (b))
65
66
67/* Define STANDALONE to get the /bin/test version. Otherwise, we are
68 making this for the shell. */
69/* #define STANDALONE */
70
71#ifdef STANDALONE
72#define test_exit(val) exit (val)
73#else
74#include <setjmp.h>
75jmp_buf test_exit_buf;
76int test_error_return = 0;
77#define test_exit(val) test_error_return = val, longjmp (test_exit_buf, 1)
78#endif /* STANDALONE */
79
80#ifdef SYSV
81int sys_v = 1;
82#else
83int sys_v = 0;
84#endif
85
86static int pos; /* position in list */
87static int argc; /* number of args from command line */
88static char **argv; /* the argument list */
89
90static void
91test_syntax_error (format, arg)
92 char *format, *arg;
93{
94 (void) fprintf (stderr, "%s: ", argv[0]);
95 (void) fprintf (stderr, format, arg);
96 test_exit (SHELL_BOOLEAN (FALSE));
97}
98
99test_io_error (name)
100 char *name;
101{
102 extern int errno;
103 int old_errno = errno;
104 fprintf (stderr, "%s: ", argv[0]);
105 errno = old_errno;
106 perror (name);
107 test_exit (SHELL_BOOLEAN (FALSE));
108}
109
110/*
111 * advance - increment our position in the argument list. Check that
112 * we're not past the end of the argument list. This check is
113 * supressed if the argument is FALSE. made a macro for efficiency.
114 */
115#ifndef lint
116#define advance(f) (++pos, f && (pos < argc ? 0 : beyond()))
117#endif
118
119#if !defined(advance)
120static int
121advance (f)
122 int f;
123{
124 ++pos;
125 if (f && pos >= argc)
126 beyond ();
127}
128
129#endif
130
131#define unary_advance() (advance (1),++pos)
132
133/*
134 * beyond - call when we're beyond the end of the argument list (an
135 * error condition)
136 */
137static int
138beyond ()
139{
140 test_syntax_error ("argument expected\n", (char *)NULL);
141}
142
143/*
144 * int_expt_err - when an integer argument was expected, but something else
145 * was found.
146 */
147static void
148int_expt_err (pch)
149 char *pch;
150{
151 test_syntax_error ("integer expression expected %s\n", pch);
152}
153
154/*
155 * isint - is the argument whose position in the argument vector is 'm' an
156 * integer? Convert it for me too, returning it's value in 'pl'.
157 */
158static int
159isint (m, pl)
160 int m;
161 long *pl;
162{
163 extern long atol ();
164 register char *pch;
165
166 pch = argv[m];
167
168 /* Skip leading whitespace characters. */
169 while (*pch == '\t' || *pch == ' ')
170 pch++;
171
172 /* accept negative numbers but not '-' alone */
173 if ('-' == *pch)
174 if ('\000' == *++pch)
175 return 0;
176
177 while ('\000' != *pch)
178 {
179 switch (*pch)
180 {
181 case '0':
182 case '1':
183 case '2':
184 case '3':
185 case '4':
186 case '5':
187 case '6':
188 case '7':
189 case '8':
190 case '9':
191 break;
192 default:
193 return (FALSE);
194 }
195 ++pch;
196 }
197 *pl = atol (argv[m]);
198 return (TRUE);
199}
200
201/*
202 * age_of - find the age of the given file. Return YES or NO depending
203 * on whether the file exists, and if it does, fill in the age with
204 * the modify time.
205 */
206static int
207age_of (posit, age)
208 int posit;
209 long *age;
210{
211 struct stat stat_buf;
212
213 if (stat (argv[posit], &stat_buf) < 0)
214 {
215 return (FALSE);
216 }
217 *age = stat_buf.st_mtime;
218 return (TRUE);
219}
220
221/*
222 * term - parse a term and return 1 or 0 depending on whether the term
223 * evaluates to true or false, respectively.
224 *
225 * term ::=
226 * '-'('h'|'d'|'f'|'r'|'s'|'w'|'c'|'b'|'p'|'u'|'g'|'k') filename
227 * '-'('L'|'x') filename
228 * '-t' [ int ]
229 * '-'('z'|'n') string
230 * string
231 * string ('!='|'=') string
232 * <int> '-'(eq|ne|le|lt|ge|gt) <int>
233 * file '-'(nt|ot|ef) file
234 * '(' <expr> ')'
235 * int ::=
236 * '-l' string
237 * positive and negative integers
238 */
239static int
240term ()
241{
242 int expr ();
243 auto struct stat stat_buf, stat_spare;
244 auto long int l, r;
245 auto int l_is_l, r_is_l; /* are the left and right integer
246 * expressions of the form '-l string'?
247 */
248 auto int value, fd;
249
250 if (pos >= argc)
251 beyond ();
252
253 /* Deal with leading "not". */
254 if (pos < argc && '!' == argv[pos][0] && '\000' == argv[pos][1])
255 {
256 advance (1);
257
258 /* This has to be rewritten to handle the TRUTH and FALSE stuff. */
259 return (!term ());
260 }
261
262 if ('(' == argv[pos][0] && '\000' == argv[pos][1])
263 {
264 advance (1);
265 value = expr ();
266 if (')' != argv[pos][0] || '\000' != argv[pos][1])
267 test_syntax_error ("argument expected, found %s\n", argv[pos]);
268 advance (0);
269 return (TRUE == (value));
270 }
271 /* are there enough arguments left that this could be dyadic? */
272 if (pos + 3 <= argc)
273 {
274 register int op;
275 if ('-' == argv[pos][0] && 'l' == argv[pos][1] &&
276 '\000' == argv[pos][2])
277 {
278 l_is_l = 1;
279 op = pos + 2;
280 advance (0);
281 }
282 else
283 {
284 l_is_l = 0;
285 op = pos + 1;
286 }
287 if ('-' == argv[op + 1][0] && 'l' == argv[op + 1][1]
288 && '\000' == argv[op + 1][2])
289 {
290 r_is_l = 1;
291 advance (0);
292 }
293 else
294 r_is_l = 0;
295
296 if ('-' == argv[op][0])
297 {
298 /* check for eq, nt, and stuff */
299 switch (argv[op][1])
300 {
301 default:
302 break;
303 case 'l':
304 if ('t' == argv[op][2] && '\000' == argv[op][3])
305 {
306 /* lt */
307 if (l_is_l)
308 l = strlen (argv[op - 1]);
309 else
310 {
311 if (!isint (op - 1, &l))
312 int_expt_err ("before -lt");
313 }
314
315 if (r_is_l)
316 r = strlen (argv[op + 2]);
317 else
318 {
319 if (!isint (op + 1, &r))
320 int_expt_err ("after -lt");
321 }
322 pos += 3;
323 return (TRUE == (l < r));
324 }
325
326 if ('e' == argv[op][2] && '\000' == argv[op][3])
327 {
328 /* le */
329 if (l_is_l)
330 l = strlen (argv[op - 1]);
331 else
332 {
333 if (!isint (op - 1, &l))
334 int_expt_err ("before -le");
335 }
336 if (r_is_l)
337 r = strlen (argv[op + 2]);
338 else
339 {
340 if (!isint (op + 1, &r))
341 int_expt_err ("after -le");
342 }
343 pos += 3;
344 return (TRUE == (l <= r));
345 }
346 break;
347
348 case 'g':
349
350 if ('t' == argv[op][2] && '\000' == argv[op][3])
351 {
352 /* gt integer greater than */
353 if (l_is_l)
354 l = strlen (argv[op - 1]);
355 else
356 {
357 if (!isint (op - 1, &l))
358 int_expt_err ("before -gt");
359 }
360 if (r_is_l)
361 r = strlen (argv[op + 2]);
362 else
363 {
364 if (!isint (op + 1, &r))
365 int_expt_err ("after -gt");
366 }
367 pos += 3;
368 return (TRUE == (l > r));
369 }
370
371 if ('e' == argv[op][2] && '\000' == argv[op][3])
372 {
373 /* ge - integer greater than or equal to */
374 if (l_is_l)
375 l = strlen (argv[op - 1]);
376 else
377 {
378 if (!isint (op - 1, &l))
379 int_expt_err ("before -ge");
380 }
381 if (r_is_l)
382 r = strlen (argv[op + 2]);
383 else
384 {
385 if (!isint (op + 1, &r))
386 int_expt_err ("after -ge");
387 }
388 pos += 3;
389 return (TRUE == (l >= r));
390 }
391 break;
392
393 case 'n':
394 if ('t' == argv[op][2] && '\000' == argv[op][3])
395 {
396 /* nt - newer than */
397 pos += 3;
398 if (l_is_l || r_is_l)
399 test_syntax_error ("-nt does not accept -l\n",
400 (char *)NULL);
401 if (age_of (op - 1, &l) && age_of (op + 1, &r))
402 return (TRUE == (l > r));
403 else
404 return (FALSE);
405 }
406
407 if ('e' == argv[op][2] && '\000' == argv[op][3])
408 {
409 /* ne - integer not equal */
410 if (l_is_l)
411 l = strlen (argv[op - 1]);
412 else
413 {
414 if (!isint (op - 1, &l))
415 int_expt_err ("before -ne");
416 }
417 if (r_is_l)
418 r = strlen (argv[op + 2]);
419 else
420 {
421 if (!isint (op + 1, &r))
422 int_expt_err ("after -ne");
423 }
424 pos += 3;
425 return (TRUE == (l != r));
426 }
427 break;
428
429 case 'e':
430 if ('q' == argv[op][2] && '\000' == argv[op][3])
431 {
432 /* eq - integer equal */
433 if (l_is_l)
434 l = strlen (argv[op - 1]);
435 else
436 {
437 if (!isint (op - 1, &l))
438 int_expt_err ("before -eq");
439 }
440 if (r_is_l)
441 r = strlen (argv[op + 2]);
442 else
443 {
444 if (!isint (op + 1, &r))
445 int_expt_err ("after -eq");
446 }
447 pos += 3;
448 return (TRUE == (l == r));
449 }
450
451 if ('f' == argv[op][2] && '\000' == argv[op][3])
452 {
453 /* ef - hard link? */
454 pos += 3;
455 if (l_is_l || r_is_l)
456 test_syntax_error ("-ef does not accept -l\n",
457 (char *)NULL);
458 if (stat (argv[op - 1], &stat_buf) < 0)
459 return (FALSE);
460 if (stat (argv[op + 1], &stat_spare) < 0)
461 return (FALSE);
462 return (TRUE ==
463 (stat_buf.st_dev == stat_spare.st_dev &&
464 stat_buf.st_ino == stat_spare.st_ino));
465 }
466 break;
467
468 case 'o':
469 if ('t' == argv[op][2] && '\000' == argv[op][3])
470 {
471 /* ot - older than */
472 pos += 3;
473 if (l_is_l || r_is_l)
474 test_syntax_error ("-nt does not accept -l\n",
475 (char *)NULL);
476 if (age_of (op - 1, &l) && age_of (op + 1, &r))
477 return (TRUE == (l < r));
478 return (FALSE);
479 }
480 break;
481 }
482 }
483
484 if ('=' == argv[op][0] && '\000' == argv[op][1])
485 {
486 value = (0 == strcmp (argv[pos], argv[pos + 2]));
487 pos += 3;
488 return (TRUE == value);
489 }
490 if ('!' == argv[op][0] && '=' == argv[op][1] && '\000' == argv[op][2])
491 {
492 value = 0 != strcmp (argv[pos], argv[pos + 2]);
493 pos += 3;
494 return (TRUE == value);
495 }
496 }
497
498 /* Might be a switch type argument */
499 if ('-' == argv[pos][0] && '\000' == argv[pos][2] /* && pos < argc-1 */ )
500 {
501 switch (argv[pos][1])
502 {
503 default:
504 break;
505
506 /* All of the following unary operators use unary_advance (), which
507 checks to make sure that there is an argument, and then advances
508 pos right past it. This means that pos - 1 is the location of the
509 argument. */
510
511 case 'r': /* file is readable? */
512 unary_advance ();
513 value = -1 != access (argv[pos - 1], R_OK);
514 return (TRUE == value);
515
516 case 'w': /* File is writeable? */
517 unary_advance ();
518 value = -1 != access (argv[pos - 1], W_OK);
519 return (TRUE == value);
520
521 case 'x': /* File is executable? */
522 unary_advance ();
523 value = -1 != access (argv[pos - 1], X_OK);
524 return (TRUE == value);
525
526 case 'O': /* File is owned by you? */
527 unary_advance ();
528 if (stat (argv[pos - 1], &stat_buf) < 0)
529 return (FALSE);
530
531 return (TRUE == (geteuid () == stat_buf.st_uid));
532
533 case 'f': /* File is a file? */
534 unary_advance ();
535 if (stat (argv[pos - 1], &stat_buf) < 0)
536 return (FALSE);
537
538 /*
539 * Under SYSV, -f is true if the given file exists
540 * and is a regular file. Other places, this checks
541 * to see if the given file is not a directory.
542 */
543 if (sys_v)
544 return (TRUE == ((S_IFREG == (stat_buf.st_mode & S_IFMT)) ||
545 (0 == (stat_buf.st_mode & S_IFMT))));
546 else
547 return (TRUE == (S_IFDIR != (stat_buf.st_mode & S_IFMT)));
548
549 case 'd': /* File is a directory? */
550 unary_advance ();
551 if (stat (argv[pos - 1], &stat_buf) < 0)
552 return (FALSE);
553
554 return (TRUE == (S_IFDIR == (stat_buf.st_mode & S_IFMT)));
555
556 case 's': /* File has something in it? */
557 unary_advance ();
558 if (stat (argv[pos - 1], &stat_buf) < 0)
559 return (FALSE);
560
561 return (TRUE == (stat_buf.st_size > (off_t) 0));
562
563#ifdef S_IFSOCK
564 case 'S': /* File is a socket? */
565 unary_advance ();
566 if (stat (argv[pos - 1], &stat_buf) < 0)
567 return (FALSE);
568
569 return (TRUE == (S_IFSOCK == (stat_buf.st_mode & S_IFMT)));
570#endif
571
572 case 'c': /* File is character special? */
573 unary_advance ();
574 if (stat (argv[pos - 1], &stat_buf) < 0)
575 return (FALSE);
576
577 return (TRUE == (S_IFCHR == (stat_buf.st_mode & S_IFMT)));
578
579 case 'b': /* File is block special? */
580 unary_advance ();
581 if (stat (argv[pos - 1], &stat_buf) < 0)
582 return (FALSE);
583
584 return (TRUE == (S_IFBLK == (stat_buf.st_mode & S_IFMT)));
585
586 case 'p': /* File is a named pipe? */
587 unary_advance ();
588#ifndef S_IFIFO
589 return (FALSE);
590#else
591 if (stat (argv[pos - 1], &stat_buf) < 0)
592 return (FALSE);
593 return (TRUE == (S_IFIFO == (stat_buf.st_mode & S_IFMT)));
594#endif /* S_IFIFO */
595
596 case 'L': /* Same as -h */
597 /*FALLTHROUGH*/
598
599 case 'h': /* File is a symbolic link? */
600 unary_advance ();
601#ifndef S_IFLNK
602 return (FALSE);
603#else
604 if (lstat (argv[pos - 1], &stat_buf) < 0)
605 return (FALSE);
606
607 return (TRUE == (S_IFLNK == (stat_buf.st_mode & S_IFMT)));
608#endif /* S_IFLNK */
609
610 case 'u': /* File is setuid? */
611 unary_advance ();
612 if (stat (argv[pos - 1], &stat_buf) < 0)
613 return (FALSE);
614
615 return (TRUE == (0 != (stat_buf.st_mode & S_ISUID)));
616
617 case 'g': /* File is setgid? */
618 unary_advance ();
619 if (stat (argv[pos - 1], &stat_buf) < 0)
620 return (FALSE);
621
622 return (TRUE == (0 != (stat_buf.st_mode & S_ISGID)));
623
624 case 'k': /* File has sticky bit set? */
625 unary_advance ();
626 if (stat (argv[pos - 1], &stat_buf) < 0)
627 return (FALSE);
628 return (TRUE == (0 != (stat_buf.st_mode & S_ISVTX)));
629
630 case 't': /* File (fd) is a terminal? (fd) defaults to stdout. */
631 advance (0);
632 if (pos < argc && isint (pos, &r))
633 {
634 advance (0);
635 return (TRUE == (isatty ((int) r)));
636 }
637 return (TRUE == (isatty (1)));
638
639 case 'n': /* True if arg has some length. */
640 unary_advance ();
641 return (TRUE == (0 != strlen (argv[pos - 1])));
642 break;
643
644 case 'z': /* True if arg has no length. */
645 unary_advance ();
646 return (TRUE == (0 == strlen (argv[pos - 1])));
647 }
648 }
649 value = 0 != strlen (argv[pos]);
650 advance (0);
651 return value;
652}
653
654/*
655 * and:
656 * and '-a' term
657 * term
658 */
659static int
660and ()
661{
662 auto int value;
663
664 value = term ();
665 while (pos < argc && '-' == argv[pos][0] && 'a' == argv[pos][1] && '\000' == argv[pos][2])
666 {
667 advance (0);
668 value = TRUTH_AND (value, term ());
669 }
670 return (TRUE == value);
671}
672
673/*
674 * or:
675 * or '-o' and
676 * and
677 */
678static int
679or ()
680{
681 auto int value;
682
683 value = and ();
684 while (pos < argc && '-' == argv[pos][0] && 'o' == argv[pos][1] && '\000' == argv[pos][2])
685 {
686 advance (0);
687 value = TRUTH_OR (value, and ());
688 }
689 return (TRUE == value);
690}
691
692/*
693 * expr:
694 * or
695 */
696int
697expr ()
698{
699 auto int value;
700
701 if (pos >= argc)
702 beyond ();
703
704 value = FALSE;
705
706 return value ^ or (); /* Same with this. */
707}
708
709/*
710 * [:
711 * '[' expr ']'
712 * test:
713 * test expr
714 */
715int
716#ifdef STANDALONE
717main (margc, margv)
718#else
719test_command (margc, margv)
720#endif /* STANDALONE */
721 int margc;
722 char **margv;
723{
724 auto int value;
725 int expr ();
726#ifndef STANDALONE
727 int code = setjmp (test_exit_buf);
728
729 if (code)
730 return (test_error_return);
731#endif /* STANDALONE */
732
733 argv = margv;
734
735 if (margv[0] && strcmp (margv[0], "[") == 0)
736 {
737 --margc;
738
739 if (margc < 2)
740 test_exit (SHELL_BOOLEAN (FALSE));
741
742 if (margv[margc] && strcmp (margv[margc], "]") != 0)
743 test_syntax_error ("missing `]'\n", (char *)NULL);
744 }
745
746 argc = margc;
747 pos = 1;
748
749 if (pos >= argc)
750 test_exit (SHELL_BOOLEAN (FALSE));
751
752 value = expr ();
753 if (pos != argc)
754 test_syntax_error ("too many arguments\n", (char *)NULL);
755
756 test_exit (SHELL_BOOLEAN (value));
757}