| 1 | /* GNU test program (ksb and mjb) */ | 
|---|
| 2 |  | 
|---|
| 3 | /* Modified to run with the GNU shell Apr 25, 1988 by bfox. */ | 
|---|
| 4 |  | 
|---|
| 5 | /* Copyright (C) 1987-2005 Free Software Foundation, Inc. | 
|---|
| 6 |  | 
|---|
| 7 | This file is part of GNU Bash, the Bourne Again SHell. | 
|---|
| 8 |  | 
|---|
| 9 | Bash is free software; you can redistribute it and/or modify it under | 
|---|
| 10 | the terms of the GNU General Public License as published by the Free | 
|---|
| 11 | Software Foundation; either version 2, or (at your option) any later | 
|---|
| 12 | version. | 
|---|
| 13 |  | 
|---|
| 14 | Bash is distributed in the hope that it will be useful, but WITHOUT ANY | 
|---|
| 15 | WARRANTY; without even the implied warranty of MERCHANTABILITY or | 
|---|
| 16 | FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License | 
|---|
| 17 | for more details. | 
|---|
| 18 |  | 
|---|
| 19 | You should have received a copy of the GNU General Public License along | 
|---|
| 20 | with Bash; see the file COPYING.  If not, write to the Free Software | 
|---|
| 21 | Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ | 
|---|
| 22 |  | 
|---|
| 23 | /* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching | 
|---|
| 24 | binary operators. */ | 
|---|
| 25 | /* #define PATTERN_MATCHING */ | 
|---|
| 26 |  | 
|---|
| 27 | #if defined (HAVE_CONFIG_H) | 
|---|
| 28 | #  include <config.h> | 
|---|
| 29 | #endif | 
|---|
| 30 |  | 
|---|
| 31 | #include <stdio.h> | 
|---|
| 32 |  | 
|---|
| 33 | #include "bashtypes.h" | 
|---|
| 34 |  | 
|---|
| 35 | #if !defined (HAVE_LIMITS_H) | 
|---|
| 36 | #  include <sys/param.h> | 
|---|
| 37 | #endif | 
|---|
| 38 |  | 
|---|
| 39 | #if defined (HAVE_UNISTD_H) | 
|---|
| 40 | #  include <unistd.h> | 
|---|
| 41 | #endif | 
|---|
| 42 |  | 
|---|
| 43 | #include <errno.h> | 
|---|
| 44 | #if !defined (errno) | 
|---|
| 45 | extern int errno; | 
|---|
| 46 | #endif /* !errno */ | 
|---|
| 47 |  | 
|---|
| 48 | #if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H) | 
|---|
| 49 | #  include <sys/file.h> | 
|---|
| 50 | #endif /* !_POSIX_VERSION */ | 
|---|
| 51 | #include "posixstat.h" | 
|---|
| 52 | #include "filecntl.h" | 
|---|
| 53 |  | 
|---|
| 54 | #include "bashintl.h" | 
|---|
| 55 |  | 
|---|
| 56 | #include "shell.h" | 
|---|
| 57 | #include "pathexp.h" | 
|---|
| 58 | #include "test.h" | 
|---|
| 59 | #include "builtins/common.h" | 
|---|
| 60 |  | 
|---|
| 61 | #include <glob/strmatch.h> | 
|---|
| 62 |  | 
|---|
| 63 | #if !defined (STRLEN) | 
|---|
| 64 | #  define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) | 
|---|
| 65 | #endif | 
|---|
| 66 |  | 
|---|
| 67 | #if !defined (STREQ) | 
|---|
| 68 | #  define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0) | 
|---|
| 69 | #endif /* !STREQ */ | 
|---|
| 70 |  | 
|---|
| 71 | #if !defined (R_OK) | 
|---|
| 72 | #define R_OK 4 | 
|---|
| 73 | #define W_OK 2 | 
|---|
| 74 | #define X_OK 1 | 
|---|
| 75 | #define F_OK 0 | 
|---|
| 76 | #endif /* R_OK */ | 
|---|
| 77 |  | 
|---|
| 78 | #define EQ      0 | 
|---|
| 79 | #define NE      1 | 
|---|
| 80 | #define LT      2 | 
|---|
| 81 | #define GT      3 | 
|---|
| 82 | #define LE      4 | 
|---|
| 83 | #define GE      5 | 
|---|
| 84 |  | 
|---|
| 85 | #define NT      0 | 
|---|
| 86 | #define OT      1 | 
|---|
| 87 | #define EF      2 | 
|---|
| 88 |  | 
|---|
| 89 | /* The following few defines control the truth and false output of each stage. | 
|---|
| 90 | TRUE and FALSE are what we use to compute the final output value. | 
|---|
| 91 | SHELL_BOOLEAN is the form which returns truth or falseness in shell terms. | 
|---|
| 92 | Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */ | 
|---|
| 93 | #define TRUE 1 | 
|---|
| 94 | #define FALSE 0 | 
|---|
| 95 | #define SHELL_BOOLEAN(value) (!(value)) | 
|---|
| 96 |  | 
|---|
| 97 | #define TEST_ERREXIT_STATUS     2 | 
|---|
| 98 |  | 
|---|
| 99 | static procenv_t test_exit_buf; | 
|---|
| 100 | static int test_error_return; | 
|---|
| 101 | #define test_exit(val) \ | 
|---|
| 102 | do { test_error_return = val; longjmp (test_exit_buf, 1); } while (0) | 
|---|
| 103 |  | 
|---|
| 104 | /* We have to use access(2) for machines running AFS, because it's | 
|---|
| 105 | not a Unix file system.  This may produce incorrect answers for | 
|---|
| 106 | non-AFS files.  I hate AFS. */ | 
|---|
| 107 | #if defined (AFS) | 
|---|
| 108 | #  define EACCESS(path, mode)   access(path, mode) | 
|---|
| 109 | #else | 
|---|
| 110 | #  define EACCESS(path, mode)   test_eaccess(path, mode) | 
|---|
| 111 | #endif /* AFS */ | 
|---|
| 112 |  | 
|---|
| 113 | static int pos;         /* The offset of the current argument in ARGV. */ | 
|---|
| 114 | static int argc;        /* The number of arguments present in ARGV. */ | 
|---|
| 115 | static char **argv;     /* The argument list. */ | 
|---|
| 116 | static int noeval; | 
|---|
| 117 |  | 
|---|
| 118 | static void test_syntax_error __P((char *, char *)) __attribute__((__noreturn__)); | 
|---|
| 119 | static void beyond __P((void)) __attribute__((__noreturn__)); | 
|---|
| 120 | static void integer_expected_error __P((char *)) __attribute__((__noreturn__)); | 
|---|
| 121 |  | 
|---|
| 122 | static int test_stat __P((char *, struct stat *)); | 
|---|
| 123 |  | 
|---|
| 124 | static int unary_operator __P((void)); | 
|---|
| 125 | static int binary_operator __P((void)); | 
|---|
| 126 | static int two_arguments __P((void)); | 
|---|
| 127 | static int three_arguments __P((void)); | 
|---|
| 128 | static int posixtest __P((void)); | 
|---|
| 129 |  | 
|---|
| 130 | static int expr __P((void)); | 
|---|
| 131 | static int term __P((void)); | 
|---|
| 132 | static int and __P((void)); | 
|---|
| 133 | static int or __P((void)); | 
|---|
| 134 |  | 
|---|
| 135 | static int filecomp __P((char *, char *, int)); | 
|---|
| 136 | static int arithcomp __P((char *, char *, int, int)); | 
|---|
| 137 | static int patcomp __P((char *, char *, int)); | 
|---|
| 138 |  | 
|---|
| 139 | static void | 
|---|
| 140 | test_syntax_error (format, arg) | 
|---|
| 141 | char *format, *arg; | 
|---|
| 142 | { | 
|---|
| 143 | builtin_error (format, arg); | 
|---|
| 144 | test_exit (TEST_ERREXIT_STATUS); | 
|---|
| 145 | } | 
|---|
| 146 |  | 
|---|
| 147 | /* | 
|---|
| 148 | * beyond - call when we're beyond the end of the argument list (an | 
|---|
| 149 | *      error condition) | 
|---|
| 150 | */ | 
|---|
| 151 | static void | 
|---|
| 152 | beyond () | 
|---|
| 153 | { | 
|---|
| 154 | test_syntax_error (_("argument expected"), (char *)NULL); | 
|---|
| 155 | } | 
|---|
| 156 |  | 
|---|
| 157 | /* Syntax error for when an integer argument was expected, but | 
|---|
| 158 | something else was found. */ | 
|---|
| 159 | static void | 
|---|
| 160 | integer_expected_error (pch) | 
|---|
| 161 | char *pch; | 
|---|
| 162 | { | 
|---|
| 163 | test_syntax_error (_("%s: integer expression expected"), pch); | 
|---|
| 164 | } | 
|---|
| 165 |  | 
|---|
| 166 | /* A wrapper for stat () which disallows pathnames that are empty strings | 
|---|
| 167 | and handles /dev/fd emulation on systems that don't have it. */ | 
|---|
| 168 | static int | 
|---|
| 169 | test_stat (path, finfo) | 
|---|
| 170 | char *path; | 
|---|
| 171 | struct stat *finfo; | 
|---|
| 172 | { | 
|---|
| 173 | if (*path == '\0') | 
|---|
| 174 | { | 
|---|
| 175 | errno = ENOENT; | 
|---|
| 176 | return (-1); | 
|---|
| 177 | } | 
|---|
| 178 | if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0) | 
|---|
| 179 | { | 
|---|
| 180 | #if !defined (HAVE_DEV_FD) | 
|---|
| 181 | intmax_t fd; | 
|---|
| 182 | int r; | 
|---|
| 183 |  | 
|---|
| 184 | if (legal_number (path + 8, &fd) && fd == (int)fd) | 
|---|
| 185 | { | 
|---|
| 186 | r = fstat ((int)fd, finfo); | 
|---|
| 187 | if (r == 0 || errno != EBADF) | 
|---|
| 188 | return (r); | 
|---|
| 189 | } | 
|---|
| 190 | errno = ENOENT; | 
|---|
| 191 | return (-1); | 
|---|
| 192 | #else | 
|---|
| 193 | /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a | 
|---|
| 194 | trailing slash.  Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx. | 
|---|
| 195 | On most systems, with the notable exception of linux, this is | 
|---|
| 196 | effectively a no-op. */ | 
|---|
| 197 | char pbuf[32]; | 
|---|
| 198 | strcpy (pbuf, DEV_FD_PREFIX); | 
|---|
| 199 | strcat (pbuf, path + 8); | 
|---|
| 200 | return (stat (pbuf, finfo)); | 
|---|
| 201 | #endif /* !HAVE_DEV_FD */ | 
|---|
| 202 | } | 
|---|
| 203 | #if !defined (HAVE_DEV_STDIN) | 
|---|
| 204 | else if (STREQN (path, "/dev/std", 8)) | 
|---|
| 205 | { | 
|---|
| 206 | if (STREQ (path+8, "in")) | 
|---|
| 207 | return (fstat (0, finfo)); | 
|---|
| 208 | else if (STREQ (path+8, "out")) | 
|---|
| 209 | return (fstat (1, finfo)); | 
|---|
| 210 | else if (STREQ (path+8, "err")) | 
|---|
| 211 | return (fstat (2, finfo)); | 
|---|
| 212 | else | 
|---|
| 213 | return (stat (path, finfo)); | 
|---|
| 214 | } | 
|---|
| 215 | #endif /* !HAVE_DEV_STDIN */ | 
|---|
| 216 | return (stat (path, finfo)); | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | /* Do the same thing access(2) does, but use the effective uid and gid, | 
|---|
| 220 | and don't make the mistake of telling root that any file is | 
|---|
| 221 | executable. */ | 
|---|
| 222 | int | 
|---|
| 223 | test_eaccess (path, mode) | 
|---|
| 224 | char *path; | 
|---|
| 225 | int mode; | 
|---|
| 226 | { | 
|---|
| 227 | struct stat st; | 
|---|
| 228 |  | 
|---|
| 229 | if (test_stat (path, &st) < 0) | 
|---|
| 230 | return (-1); | 
|---|
| 231 |  | 
|---|
| 232 | if (current_user.euid == 0) | 
|---|
| 233 | { | 
|---|
| 234 | /* Root can read or write any file. */ | 
|---|
| 235 | if (mode != X_OK) | 
|---|
| 236 | return (0); | 
|---|
| 237 |  | 
|---|
| 238 | /* Root can execute any file that has any one of the execute | 
|---|
| 239 | bits set. */ | 
|---|
| 240 | if (st.st_mode & S_IXUGO) | 
|---|
| 241 | return (0); | 
|---|
| 242 | } | 
|---|
| 243 |  | 
|---|
| 244 | if (st.st_uid == current_user.euid)   /* owner */ | 
|---|
| 245 | mode <<= 6; | 
|---|
| 246 | else if (group_member (st.st_gid)) | 
|---|
| 247 | mode <<= 3; | 
|---|
| 248 |  | 
|---|
| 249 | if (st.st_mode & mode) | 
|---|
| 250 | return (0); | 
|---|
| 251 |  | 
|---|
| 252 | errno = EACCES; | 
|---|
| 253 | return (-1); | 
|---|
| 254 | } | 
|---|
| 255 |  | 
|---|
| 256 | /* Increment our position in the argument list.  Check that we're not | 
|---|
| 257 | past the end of the argument list.  This check is supressed if the | 
|---|
| 258 | argument is FALSE.  Made a macro for efficiency. */ | 
|---|
| 259 | #define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0) | 
|---|
| 260 | #define unary_advance() do { advance (1); ++pos; } while (0) | 
|---|
| 261 |  | 
|---|
| 262 | /* | 
|---|
| 263 | * expr: | 
|---|
| 264 | *      or | 
|---|
| 265 | */ | 
|---|
| 266 | static int | 
|---|
| 267 | expr () | 
|---|
| 268 | { | 
|---|
| 269 | if (pos >= argc) | 
|---|
| 270 | beyond (); | 
|---|
| 271 |  | 
|---|
| 272 | return (FALSE ^ or ());               /* Same with this. */ | 
|---|
| 273 | } | 
|---|
| 274 |  | 
|---|
| 275 | /* | 
|---|
| 276 | * or: | 
|---|
| 277 | *      and | 
|---|
| 278 | *      and '-o' or | 
|---|
| 279 | */ | 
|---|
| 280 | static int | 
|---|
| 281 | or () | 
|---|
| 282 | { | 
|---|
| 283 | int value, v2; | 
|---|
| 284 |  | 
|---|
| 285 | value = and (); | 
|---|
| 286 | if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2]) | 
|---|
| 287 | { | 
|---|
| 288 | advance (0); | 
|---|
| 289 | v2 = or (); | 
|---|
| 290 | return (value || v2); | 
|---|
| 291 | } | 
|---|
| 292 |  | 
|---|
| 293 | return (value); | 
|---|
| 294 | } | 
|---|
| 295 |  | 
|---|
| 296 | /* | 
|---|
| 297 | * and: | 
|---|
| 298 | *      term | 
|---|
| 299 | *      term '-a' and | 
|---|
| 300 | */ | 
|---|
| 301 | static int | 
|---|
| 302 | and () | 
|---|
| 303 | { | 
|---|
| 304 | int value, v2; | 
|---|
| 305 |  | 
|---|
| 306 | value = term (); | 
|---|
| 307 | if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2]) | 
|---|
| 308 | { | 
|---|
| 309 | advance (0); | 
|---|
| 310 | v2 = and (); | 
|---|
| 311 | return (value && v2); | 
|---|
| 312 | } | 
|---|
| 313 | return (value); | 
|---|
| 314 | } | 
|---|
| 315 |  | 
|---|
| 316 | /* | 
|---|
| 317 | * term - parse a term and return 1 or 0 depending on whether the term | 
|---|
| 318 | *      evaluates to true or false, respectively. | 
|---|
| 319 | * | 
|---|
| 320 | * term ::= | 
|---|
| 321 | *      '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename | 
|---|
| 322 | *      '-'('G'|'L'|'O'|'S'|'N') filename | 
|---|
| 323 | *      '-t' [int] | 
|---|
| 324 | *      '-'('z'|'n') string | 
|---|
| 325 | *      '-o' option | 
|---|
| 326 | *      string | 
|---|
| 327 | *      string ('!='|'='|'==') string | 
|---|
| 328 | *      <int> '-'(eq|ne|le|lt|ge|gt) <int> | 
|---|
| 329 | *      file '-'(nt|ot|ef) file | 
|---|
| 330 | *      '(' <expr> ')' | 
|---|
| 331 | * int ::= | 
|---|
| 332 | *      positive and negative integers | 
|---|
| 333 | */ | 
|---|
| 334 | static int | 
|---|
| 335 | term () | 
|---|
| 336 | { | 
|---|
| 337 | int value; | 
|---|
| 338 |  | 
|---|
| 339 | if (pos >= argc) | 
|---|
| 340 | beyond (); | 
|---|
| 341 |  | 
|---|
| 342 | /* Deal with leading `not's. */ | 
|---|
| 343 | if (argv[pos][0] == '!' && argv[pos][1] == '\0') | 
|---|
| 344 | { | 
|---|
| 345 | value = 0; | 
|---|
| 346 | while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0') | 
|---|
| 347 | { | 
|---|
| 348 | advance (1); | 
|---|
| 349 | value = 1 - value; | 
|---|
| 350 | } | 
|---|
| 351 |  | 
|---|
| 352 | return (value ? !term() : term()); | 
|---|
| 353 | } | 
|---|
| 354 |  | 
|---|
| 355 | /* A paren-bracketed argument. */ | 
|---|
| 356 | if (argv[pos][0] == '(' && argv[pos][1] == '\0') /* ) */ | 
|---|
| 357 | { | 
|---|
| 358 | advance (1); | 
|---|
| 359 | value = expr (); | 
|---|
| 360 | if (argv[pos] == 0) /* ( */ | 
|---|
| 361 | test_syntax_error (_("`)' expected"), (char *)NULL); | 
|---|
| 362 | else if (argv[pos][0] != ')' || argv[pos][1]) /* ( */ | 
|---|
| 363 | test_syntax_error (_("`)' expected, found %s"), argv[pos]); | 
|---|
| 364 | advance (0); | 
|---|
| 365 | return (value); | 
|---|
| 366 | } | 
|---|
| 367 |  | 
|---|
| 368 | /* are there enough arguments left that this could be dyadic? */ | 
|---|
| 369 | if ((pos + 3 <= argc) && test_binop (argv[pos + 1])) | 
|---|
| 370 | value = binary_operator (); | 
|---|
| 371 |  | 
|---|
| 372 | /* Might be a switch type argument */ | 
|---|
| 373 | else if (argv[pos][0] == '-' && argv[pos][2] == '\0') | 
|---|
| 374 | { | 
|---|
| 375 | if (test_unop (argv[pos])) | 
|---|
| 376 | value = unary_operator (); | 
|---|
| 377 | else | 
|---|
| 378 | test_syntax_error (_("%s: unary operator expected"), argv[pos]); | 
|---|
| 379 | } | 
|---|
| 380 | else | 
|---|
| 381 | { | 
|---|
| 382 | value = argv[pos][0] != '\0'; | 
|---|
| 383 | advance (0); | 
|---|
| 384 | } | 
|---|
| 385 |  | 
|---|
| 386 | return (value); | 
|---|
| 387 | } | 
|---|
| 388 |  | 
|---|
| 389 | static int | 
|---|
| 390 | filecomp (s, t, op) | 
|---|
| 391 | char *s, *t; | 
|---|
| 392 | int op; | 
|---|
| 393 | { | 
|---|
| 394 | struct stat st1, st2; | 
|---|
| 395 | int r1, r2; | 
|---|
| 396 |  | 
|---|
| 397 | if ((r1 = test_stat (s, &st1)) < 0) | 
|---|
| 398 | { | 
|---|
| 399 | if (op == EF) | 
|---|
| 400 | return (FALSE); | 
|---|
| 401 | } | 
|---|
| 402 | if ((r2 = test_stat (t, &st2)) < 0) | 
|---|
| 403 | { | 
|---|
| 404 | if (op == EF) | 
|---|
| 405 | return (FALSE); | 
|---|
| 406 | } | 
|---|
| 407 |  | 
|---|
| 408 | switch (op) | 
|---|
| 409 | { | 
|---|
| 410 | case OT: return (r1 < r2 || (r2 == 0 && st1.st_mtime < st2.st_mtime)); | 
|---|
| 411 | case NT: return (r1 > r2 || (r1 == 0 && st1.st_mtime > st2.st_mtime)); | 
|---|
| 412 | case EF: return ((st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino)); | 
|---|
| 413 | } | 
|---|
| 414 | return (FALSE); | 
|---|
| 415 | } | 
|---|
| 416 |  | 
|---|
| 417 | static int | 
|---|
| 418 | arithcomp (s, t, op, flags) | 
|---|
| 419 | char *s, *t; | 
|---|
| 420 | int op, flags; | 
|---|
| 421 | { | 
|---|
| 422 | intmax_t l, r; | 
|---|
| 423 | int expok; | 
|---|
| 424 |  | 
|---|
| 425 | if (flags & TEST_ARITHEXP) | 
|---|
| 426 | { | 
|---|
| 427 | l = evalexp (s, &expok); | 
|---|
| 428 | if (expok == 0) | 
|---|
| 429 | return (FALSE);         /* should probably longjmp here */ | 
|---|
| 430 | r = evalexp (t, &expok); | 
|---|
| 431 | if (expok == 0) | 
|---|
| 432 | return (FALSE);         /* ditto */ | 
|---|
| 433 | } | 
|---|
| 434 | else | 
|---|
| 435 | { | 
|---|
| 436 | if (legal_number (s, &l) == 0) | 
|---|
| 437 | integer_expected_error (s); | 
|---|
| 438 | if (legal_number (t, &r) == 0) | 
|---|
| 439 | integer_expected_error (t); | 
|---|
| 440 | } | 
|---|
| 441 |  | 
|---|
| 442 | switch (op) | 
|---|
| 443 | { | 
|---|
| 444 | case EQ: return (l == r); | 
|---|
| 445 | case NE: return (l != r); | 
|---|
| 446 | case LT: return (l < r); | 
|---|
| 447 | case GT: return (l > r); | 
|---|
| 448 | case LE: return (l <= r); | 
|---|
| 449 | case GE: return (l >= r); | 
|---|
| 450 | } | 
|---|
| 451 |  | 
|---|
| 452 | return (FALSE); | 
|---|
| 453 | } | 
|---|
| 454 |  | 
|---|
| 455 | static int | 
|---|
| 456 | patcomp (string, pat, op) | 
|---|
| 457 | char *string, *pat; | 
|---|
| 458 | int op; | 
|---|
| 459 | { | 
|---|
| 460 | int m; | 
|---|
| 461 |  | 
|---|
| 462 | m = strmatch (pat, string, FNMATCH_EXTFLAG|FNMATCH_IGNCASE); | 
|---|
| 463 | return ((op == EQ) ? (m == 0) : (m != 0)); | 
|---|
| 464 | } | 
|---|
| 465 |  | 
|---|
| 466 | int | 
|---|
| 467 | binary_test (op, arg1, arg2, flags) | 
|---|
| 468 | char *op, *arg1, *arg2; | 
|---|
| 469 | int flags; | 
|---|
| 470 | { | 
|---|
| 471 | int patmatch; | 
|---|
| 472 |  | 
|---|
| 473 | patmatch = (flags & TEST_PATMATCH); | 
|---|
| 474 |  | 
|---|
| 475 | if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0'))) | 
|---|
| 476 | return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2)); | 
|---|
| 477 |  | 
|---|
| 478 | else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0') | 
|---|
| 479 | return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0)); | 
|---|
| 480 |  | 
|---|
| 481 | else if (op[0] == '!' && op[1] == '=' && op[2] == '\0') | 
|---|
| 482 | return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0)); | 
|---|
| 483 |  | 
|---|
| 484 | else if (op[2] == 't') | 
|---|
| 485 | { | 
|---|
| 486 | switch (op[1]) | 
|---|
| 487 | { | 
|---|
| 488 | case 'n': return (filecomp (arg1, arg2, NT));           /* -nt */ | 
|---|
| 489 | case 'o': return (filecomp (arg1, arg2, OT));           /* -ot */ | 
|---|
| 490 | case 'l': return (arithcomp (arg1, arg2, LT, flags));   /* -lt */ | 
|---|
| 491 | case 'g': return (arithcomp (arg1, arg2, GT, flags));   /* -gt */ | 
|---|
| 492 | } | 
|---|
| 493 | } | 
|---|
| 494 | else if (op[1] == 'e') | 
|---|
| 495 | { | 
|---|
| 496 | switch (op[2]) | 
|---|
| 497 | { | 
|---|
| 498 | case 'f': return (filecomp (arg1, arg2, EF));           /* -ef */ | 
|---|
| 499 | case 'q': return (arithcomp (arg1, arg2, EQ, flags));   /* -eq */ | 
|---|
| 500 | } | 
|---|
| 501 | } | 
|---|
| 502 | else if (op[2] == 'e') | 
|---|
| 503 | { | 
|---|
| 504 | switch (op[1]) | 
|---|
| 505 | { | 
|---|
| 506 | case 'n': return (arithcomp (arg1, arg2, NE, flags));   /* -ne */ | 
|---|
| 507 | case 'g': return (arithcomp (arg1, arg2, GE, flags));   /* -ge */ | 
|---|
| 508 | case 'l': return (arithcomp (arg1, arg2, LE, flags));   /* -le */ | 
|---|
| 509 | } | 
|---|
| 510 | } | 
|---|
| 511 |  | 
|---|
| 512 | return (FALSE);       /* should never get here */ | 
|---|
| 513 | } | 
|---|
| 514 |  | 
|---|
| 515 |  | 
|---|
| 516 | static int | 
|---|
| 517 | binary_operator () | 
|---|
| 518 | { | 
|---|
| 519 | int value; | 
|---|
| 520 | char *w; | 
|---|
| 521 |  | 
|---|
| 522 | w = argv[pos + 1]; | 
|---|
| 523 | if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */ | 
|---|
| 524 | ((w[0] == '>' || w[0] == '<') && w[1] == '\0') ||         /* <, > */ | 
|---|
| 525 | (w[0] == '!' && w[1] == '=' && w[2] == '\0'))             /* != */ | 
|---|
| 526 | { | 
|---|
| 527 | value = binary_test (w, argv[pos], argv[pos + 2], 0); | 
|---|
| 528 | pos += 3; | 
|---|
| 529 | return (value); | 
|---|
| 530 | } | 
|---|
| 531 |  | 
|---|
| 532 | #if defined (PATTERN_MATCHING) | 
|---|
| 533 | if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0') | 
|---|
| 534 | { | 
|---|
| 535 | value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE); | 
|---|
| 536 | pos += 3; | 
|---|
| 537 | return (value); | 
|---|
| 538 | } | 
|---|
| 539 | #endif | 
|---|
| 540 |  | 
|---|
| 541 | if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0) | 
|---|
| 542 | { | 
|---|
| 543 | test_syntax_error (_("%s: binary operator expected"), w); | 
|---|
| 544 | /* NOTREACHED */ | 
|---|
| 545 | return (FALSE); | 
|---|
| 546 | } | 
|---|
| 547 |  | 
|---|
| 548 | value = binary_test (w, argv[pos], argv[pos + 2], 0); | 
|---|
| 549 | pos += 3; | 
|---|
| 550 | return value; | 
|---|
| 551 | } | 
|---|
| 552 |  | 
|---|
| 553 | static int | 
|---|
| 554 | unary_operator () | 
|---|
| 555 | { | 
|---|
| 556 | char *op; | 
|---|
| 557 | intmax_t r; | 
|---|
| 558 |  | 
|---|
| 559 | op = argv[pos]; | 
|---|
| 560 | if (test_unop (op) == 0) | 
|---|
| 561 | return (FALSE); | 
|---|
| 562 |  | 
|---|
| 563 | /* the only tricky case is `-t', which may or may not take an argument. */ | 
|---|
| 564 | if (op[1] == 't') | 
|---|
| 565 | { | 
|---|
| 566 | advance (0); | 
|---|
| 567 | if (pos < argc) | 
|---|
| 568 | { | 
|---|
| 569 | if (legal_number (argv[pos], &r)) | 
|---|
| 570 | { | 
|---|
| 571 | advance (0); | 
|---|
| 572 | return (unary_test (op, argv[pos - 1])); | 
|---|
| 573 | } | 
|---|
| 574 | else | 
|---|
| 575 | return (FALSE); | 
|---|
| 576 | } | 
|---|
| 577 | else | 
|---|
| 578 | return (unary_test (op, "1")); | 
|---|
| 579 | } | 
|---|
| 580 |  | 
|---|
| 581 | /* All of the unary operators take an argument, so we first call | 
|---|
| 582 | unary_advance (), which checks to make sure that there is an | 
|---|
| 583 | argument, and then advances pos right past it.  This means that | 
|---|
| 584 | pos - 1 is the location of the argument. */ | 
|---|
| 585 | unary_advance (); | 
|---|
| 586 | return (unary_test (op, argv[pos - 1])); | 
|---|
| 587 | } | 
|---|
| 588 |  | 
|---|
| 589 | int | 
|---|
| 590 | unary_test (op, arg) | 
|---|
| 591 | char *op, *arg; | 
|---|
| 592 | { | 
|---|
| 593 | intmax_t r; | 
|---|
| 594 | struct stat stat_buf; | 
|---|
| 595 |  | 
|---|
| 596 | switch (op[1]) | 
|---|
| 597 | { | 
|---|
| 598 | case 'a':                   /* file exists in the file system? */ | 
|---|
| 599 | case 'e': | 
|---|
| 600 | return (test_stat (arg, &stat_buf) == 0); | 
|---|
| 601 |  | 
|---|
| 602 | case 'r':                   /* file is readable? */ | 
|---|
| 603 | return (EACCESS (arg, R_OK) == 0); | 
|---|
| 604 |  | 
|---|
| 605 | case 'w':                   /* File is writeable? */ | 
|---|
| 606 | return (EACCESS (arg, W_OK) == 0); | 
|---|
| 607 |  | 
|---|
| 608 | case 'x':                   /* File is executable? */ | 
|---|
| 609 | return (EACCESS (arg, X_OK) == 0); | 
|---|
| 610 |  | 
|---|
| 611 | case 'O':                   /* File is owned by you? */ | 
|---|
| 612 | return (test_stat (arg, &stat_buf) == 0 && | 
|---|
| 613 | (uid_t) current_user.euid == (uid_t) stat_buf.st_uid); | 
|---|
| 614 |  | 
|---|
| 615 | case 'G':                   /* File is owned by your group? */ | 
|---|
| 616 | return (test_stat (arg, &stat_buf) == 0 && | 
|---|
| 617 | (gid_t) current_user.egid == (gid_t) stat_buf.st_gid); | 
|---|
| 618 |  | 
|---|
| 619 | case 'N': | 
|---|
| 620 | return (test_stat (arg, &stat_buf) == 0 && | 
|---|
| 621 | stat_buf.st_atime <= stat_buf.st_mtime); | 
|---|
| 622 |  | 
|---|
| 623 | case 'f':                   /* File is a file? */ | 
|---|
| 624 | if (test_stat (arg, &stat_buf) < 0) | 
|---|
| 625 | return (FALSE); | 
|---|
| 626 |  | 
|---|
| 627 | /* -f is true if the given file exists and is a regular file. */ | 
|---|
| 628 | #if defined (S_IFMT) | 
|---|
| 629 | return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0); | 
|---|
| 630 | #else | 
|---|
| 631 | return (S_ISREG (stat_buf.st_mode)); | 
|---|
| 632 | #endif /* !S_IFMT */ | 
|---|
| 633 |  | 
|---|
| 634 | case 'd':                   /* File is a directory? */ | 
|---|
| 635 | return (test_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode))); | 
|---|
| 636 |  | 
|---|
| 637 | case 's':                   /* File has something in it? */ | 
|---|
| 638 | return (test_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0); | 
|---|
| 639 |  | 
|---|
| 640 | case 'S':                   /* File is a socket? */ | 
|---|
| 641 | #if !defined (S_ISSOCK) | 
|---|
| 642 | return (FALSE); | 
|---|
| 643 | #else | 
|---|
| 644 | return (test_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode)); | 
|---|
| 645 | #endif /* S_ISSOCK */ | 
|---|
| 646 |  | 
|---|
| 647 | case 'c':                   /* File is character special? */ | 
|---|
| 648 | return (test_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode)); | 
|---|
| 649 |  | 
|---|
| 650 | case 'b':                   /* File is block special? */ | 
|---|
| 651 | return (test_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode)); | 
|---|
| 652 |  | 
|---|
| 653 | case 'p':                   /* File is a named pipe? */ | 
|---|
| 654 | #ifndef S_ISFIFO | 
|---|
| 655 | return (FALSE); | 
|---|
| 656 | #else | 
|---|
| 657 | return (test_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode)); | 
|---|
| 658 | #endif /* S_ISFIFO */ | 
|---|
| 659 |  | 
|---|
| 660 | case 'L':                   /* Same as -h  */ | 
|---|
| 661 | case 'h':                   /* File is a symbolic link? */ | 
|---|
| 662 | #if !defined (S_ISLNK) || !defined (HAVE_LSTAT) | 
|---|
| 663 | return (FALSE); | 
|---|
| 664 | #else | 
|---|
| 665 | return ((arg[0] != '\0') && | 
|---|
| 666 | (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode)); | 
|---|
| 667 | #endif /* S_IFLNK && HAVE_LSTAT */ | 
|---|
| 668 |  | 
|---|
| 669 | case 'u':                   /* File is setuid? */ | 
|---|
| 670 | return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0); | 
|---|
| 671 |  | 
|---|
| 672 | case 'g':                   /* File is setgid? */ | 
|---|
| 673 | return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0); | 
|---|
| 674 |  | 
|---|
| 675 | case 'k':                   /* File has sticky bit set? */ | 
|---|
| 676 | #if !defined (S_ISVTX) | 
|---|
| 677 | /* This is not Posix, and is not defined on some Posix systems. */ | 
|---|
| 678 | return (FALSE); | 
|---|
| 679 | #else | 
|---|
| 680 | return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0); | 
|---|
| 681 | #endif | 
|---|
| 682 |  | 
|---|
| 683 | case 't':   /* File fd is a terminal? */ | 
|---|
| 684 | if (legal_number (arg, &r) == 0) | 
|---|
| 685 | return (FALSE); | 
|---|
| 686 | return ((r == (int)r) && isatty ((int)r)); | 
|---|
| 687 |  | 
|---|
| 688 | case 'n':                   /* True if arg has some length. */ | 
|---|
| 689 | return (arg[0] != '\0'); | 
|---|
| 690 |  | 
|---|
| 691 | case 'z':                   /* True if arg has no length. */ | 
|---|
| 692 | return (arg[0] == '\0'); | 
|---|
| 693 |  | 
|---|
| 694 | case 'o':                   /* True if option `arg' is set. */ | 
|---|
| 695 | return (minus_o_option_value (arg) == 1); | 
|---|
| 696 | } | 
|---|
| 697 |  | 
|---|
| 698 | /* We can't actually get here, but this shuts up gcc. */ | 
|---|
| 699 | return (FALSE); | 
|---|
| 700 | } | 
|---|
| 701 |  | 
|---|
| 702 | /* Return TRUE if OP is one of the test command's binary operators. */ | 
|---|
| 703 | int | 
|---|
| 704 | test_binop (op) | 
|---|
| 705 | char *op; | 
|---|
| 706 | { | 
|---|
| 707 | if (op[0] == '=' && op[1] == '\0') | 
|---|
| 708 | return (1);         /* '=' */ | 
|---|
| 709 | else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0')  /* string <, > */ | 
|---|
| 710 | return (1); | 
|---|
| 711 | else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0') | 
|---|
| 712 | return (1);         /* `==' and `!=' */ | 
|---|
| 713 | #if defined (PATTERN_MATCHING) | 
|---|
| 714 | else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!')) | 
|---|
| 715 | return (1); | 
|---|
| 716 | #endif | 
|---|
| 717 | else if (op[0] != '-' || op[2] == '\0' || op[3] != '\0') | 
|---|
| 718 | return (0); | 
|---|
| 719 | else | 
|---|
| 720 | { | 
|---|
| 721 | if (op[2] == 't') | 
|---|
| 722 | switch (op[1]) | 
|---|
| 723 | { | 
|---|
| 724 | case 'n':             /* -nt */ | 
|---|
| 725 | case 'o':             /* -ot */ | 
|---|
| 726 | case 'l':             /* -lt */ | 
|---|
| 727 | case 'g':             /* -gt */ | 
|---|
| 728 | return (1); | 
|---|
| 729 | default: | 
|---|
| 730 | return (0); | 
|---|
| 731 | } | 
|---|
| 732 | else if (op[1] == 'e') | 
|---|
| 733 | switch (op[2]) | 
|---|
| 734 | { | 
|---|
| 735 | case 'q':             /* -eq */ | 
|---|
| 736 | case 'f':             /* -ef */ | 
|---|
| 737 | return (1); | 
|---|
| 738 | default: | 
|---|
| 739 | return (0); | 
|---|
| 740 | } | 
|---|
| 741 | else if (op[2] == 'e') | 
|---|
| 742 | switch (op[1]) | 
|---|
| 743 | { | 
|---|
| 744 | case 'n':             /* -ne */ | 
|---|
| 745 | case 'g':             /* -ge */ | 
|---|
| 746 | case 'l':             /* -le */ | 
|---|
| 747 | return (1); | 
|---|
| 748 | default: | 
|---|
| 749 | return (0); | 
|---|
| 750 | } | 
|---|
| 751 | else | 
|---|
| 752 | return (0); | 
|---|
| 753 | } | 
|---|
| 754 | } | 
|---|
| 755 |  | 
|---|
| 756 | /* Return non-zero if OP is one of the test command's unary operators. */ | 
|---|
| 757 | int | 
|---|
| 758 | test_unop (op) | 
|---|
| 759 | char *op; | 
|---|
| 760 | { | 
|---|
| 761 | if (op[0] != '-') | 
|---|
| 762 | return (0); | 
|---|
| 763 |  | 
|---|
| 764 | switch (op[1]) | 
|---|
| 765 | { | 
|---|
| 766 | case 'a': case 'b': case 'c': case 'd': case 'e': | 
|---|
| 767 | case 'f': case 'g': case 'h': case 'k': case 'n': | 
|---|
| 768 | case 'o': case 'p': case 'r': case 's': case 't': | 
|---|
| 769 | case 'u': case 'w': case 'x': case 'z': | 
|---|
| 770 | case 'G': case 'L': case 'O': case 'S': case 'N': | 
|---|
| 771 | return (1); | 
|---|
| 772 | } | 
|---|
| 773 |  | 
|---|
| 774 | return (0); | 
|---|
| 775 | } | 
|---|
| 776 |  | 
|---|
| 777 | static int | 
|---|
| 778 | two_arguments () | 
|---|
| 779 | { | 
|---|
| 780 | if (argv[pos][0] == '!' && argv[pos][1] == '\0') | 
|---|
| 781 | return (argv[pos + 1][0] == '\0'); | 
|---|
| 782 | else if (argv[pos][0] == '-' && argv[pos][2] == '\0') | 
|---|
| 783 | { | 
|---|
| 784 | if (test_unop (argv[pos])) | 
|---|
| 785 | return (unary_operator ()); | 
|---|
| 786 | else | 
|---|
| 787 | test_syntax_error (_("%s: unary operator expected"), argv[pos]); | 
|---|
| 788 | } | 
|---|
| 789 | else | 
|---|
| 790 | test_syntax_error (_("%s: unary operator expected"), argv[pos]); | 
|---|
| 791 |  | 
|---|
| 792 | return (0); | 
|---|
| 793 | } | 
|---|
| 794 |  | 
|---|
| 795 | #define ANDOR(s)  (s[0] == '-' && !s[2] && (s[1] == 'a' || s[1] == 'o')) | 
|---|
| 796 |  | 
|---|
| 797 | /* This could be augmented to handle `-t' as equivalent to `-t 1', but | 
|---|
| 798 | POSIX requires that `-t' be given an argument. */ | 
|---|
| 799 | #define ONE_ARG_TEST(s)         ((s)[0] != '\0') | 
|---|
| 800 |  | 
|---|
| 801 | static int | 
|---|
| 802 | three_arguments () | 
|---|
| 803 | { | 
|---|
| 804 | int value; | 
|---|
| 805 |  | 
|---|
| 806 | if (test_binop (argv[pos+1])) | 
|---|
| 807 | { | 
|---|
| 808 | value = binary_operator (); | 
|---|
| 809 | pos = argc; | 
|---|
| 810 | } | 
|---|
| 811 | else if (ANDOR (argv[pos+1])) | 
|---|
| 812 | { | 
|---|
| 813 | if (argv[pos+1][1] == 'a') | 
|---|
| 814 | value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]); | 
|---|
| 815 | else | 
|---|
| 816 | value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]); | 
|---|
| 817 | pos = argc; | 
|---|
| 818 | } | 
|---|
| 819 | else if (argv[pos][0] == '!' && argv[pos][1] == '\0') | 
|---|
| 820 | { | 
|---|
| 821 | advance (1); | 
|---|
| 822 | value = !two_arguments (); | 
|---|
| 823 | } | 
|---|
| 824 | else if (argv[pos][0] == '(' && argv[pos+2][0] == ')') | 
|---|
| 825 | { | 
|---|
| 826 | value = ONE_ARG_TEST(argv[pos+1]); | 
|---|
| 827 | pos = argc; | 
|---|
| 828 | } | 
|---|
| 829 | else | 
|---|
| 830 | test_syntax_error (_("%s: binary operator expected"), argv[pos+1]); | 
|---|
| 831 |  | 
|---|
| 832 | return (value); | 
|---|
| 833 | } | 
|---|
| 834 |  | 
|---|
| 835 | /* This is an implementation of a Posix.2 proposal by David Korn. */ | 
|---|
| 836 | static int | 
|---|
| 837 | posixtest () | 
|---|
| 838 | { | 
|---|
| 839 | int value; | 
|---|
| 840 |  | 
|---|
| 841 | switch (argc - 1)     /* one extra passed in */ | 
|---|
| 842 | { | 
|---|
| 843 | case 0: | 
|---|
| 844 | value = FALSE; | 
|---|
| 845 | pos = argc; | 
|---|
| 846 | break; | 
|---|
| 847 |  | 
|---|
| 848 | case 1: | 
|---|
| 849 | value = ONE_ARG_TEST(argv[1]); | 
|---|
| 850 | pos = argc; | 
|---|
| 851 | break; | 
|---|
| 852 |  | 
|---|
| 853 | case 2: | 
|---|
| 854 | value = two_arguments (); | 
|---|
| 855 | pos = argc; | 
|---|
| 856 | break; | 
|---|
| 857 |  | 
|---|
| 858 | case 3: | 
|---|
| 859 | value = three_arguments (); | 
|---|
| 860 | break; | 
|---|
| 861 |  | 
|---|
| 862 | case 4: | 
|---|
| 863 | if (argv[pos][0] == '!' && argv[pos][1] == '\0') | 
|---|
| 864 | { | 
|---|
| 865 | advance (1); | 
|---|
| 866 | value = !three_arguments (); | 
|---|
| 867 | break; | 
|---|
| 868 | } | 
|---|
| 869 | /* FALLTHROUGH */ | 
|---|
| 870 | default: | 
|---|
| 871 | value = expr (); | 
|---|
| 872 | } | 
|---|
| 873 |  | 
|---|
| 874 | return (value); | 
|---|
| 875 | } | 
|---|
| 876 |  | 
|---|
| 877 | /* | 
|---|
| 878 | * [: | 
|---|
| 879 | *      '[' expr ']' | 
|---|
| 880 | * test: | 
|---|
| 881 | *      test expr | 
|---|
| 882 | */ | 
|---|
| 883 | int | 
|---|
| 884 | test_command (margc, margv) | 
|---|
| 885 | int margc; | 
|---|
| 886 | char **margv; | 
|---|
| 887 | { | 
|---|
| 888 | int value; | 
|---|
| 889 | int code; | 
|---|
| 890 |  | 
|---|
| 891 | USE_VAR(margc); | 
|---|
| 892 |  | 
|---|
| 893 | code = setjmp (test_exit_buf); | 
|---|
| 894 |  | 
|---|
| 895 | if (code) | 
|---|
| 896 | return (test_error_return); | 
|---|
| 897 |  | 
|---|
| 898 | argv = margv; | 
|---|
| 899 |  | 
|---|
| 900 | if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0') | 
|---|
| 901 | { | 
|---|
| 902 | --margc; | 
|---|
| 903 |  | 
|---|
| 904 | if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1])) | 
|---|
| 905 | test_syntax_error (_("missing `]'"), (char *)NULL); | 
|---|
| 906 |  | 
|---|
| 907 | if (margc < 2) | 
|---|
| 908 | test_exit (SHELL_BOOLEAN (FALSE)); | 
|---|
| 909 | } | 
|---|
| 910 |  | 
|---|
| 911 | argc = margc; | 
|---|
| 912 | pos = 1; | 
|---|
| 913 |  | 
|---|
| 914 | if (pos >= argc) | 
|---|
| 915 | test_exit (SHELL_BOOLEAN (FALSE)); | 
|---|
| 916 |  | 
|---|
| 917 | noeval = 0; | 
|---|
| 918 | value = posixtest (); | 
|---|
| 919 |  | 
|---|
| 920 | if (pos != argc) | 
|---|
| 921 | test_syntax_error (_("too many arguments"), (char *)NULL); | 
|---|
| 922 |  | 
|---|
| 923 | test_exit (SHELL_BOOLEAN (value)); | 
|---|
| 924 | } | 
|---|