Magellan Linux

Diff of /trunk/mkinitrd-magellan/busybox/coreutils/test.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 532 by niro, Sat Sep 1 22:45:15 2007 UTC revision 816 by niro, Fri Apr 24 18:33:46 2009 UTC
# Line 12  Line 12 
12   *     modified by Herbert Xu to be used as built-in in ash.   *     modified by Herbert Xu to be used as built-in in ash.
13   *     modified by Erik Andersen <andersen@codepoet.org> to be used   *     modified by Erik Andersen <andersen@codepoet.org> to be used
14   *     in busybox.   *     in busybox.
15     *     modified by Bernhard Reutner-Fischer to be useable (i.e. a bit less bloaty).
16   *   *
17   * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.   * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
18   *   *
# Line 19  Line 20 
20   *     "This program is in the Public Domain."   *     "This program is in the Public Domain."
21   */   */
22    
23  #include "busybox.h"  #include "libbb.h"
 #include <unistd.h>  
 #include <ctype.h>  
 #include <errno.h>  
 #include <string.h>  
24  #include <setjmp.h>  #include <setjmp.h>
25    
26    /* This is a NOFORK applet. Be very careful! */
27    
28    /* test_main() is called from shells, and we need to be extra careful here.
29     * This is true regardless of PREFER_APPLETS and STANDALONE_SHELL
30     * state. */
31    
32    
33  /* test(1) accepts the following grammar:  /* test(1) accepts the following grammar:
34   oexpr ::= aexpr | aexpr "-o" oexpr ;   oexpr ::= aexpr | aexpr "-o" oexpr ;
35   aexpr ::= nexpr | nexpr "-a" aexpr ;   aexpr ::= nexpr | nexpr "-a" aexpr ;
# Line 43  Line 47 
47   operand ::= <any legal UNIX file name>   operand ::= <any legal UNIX file name>
48  */  */
49    
50    #define TEST_DEBUG 0
51    
52  enum token {  enum token {
53   EOI,   EOI,
54   FILRD,   FILRD,
# Line 85  enum token { Line 91  enum token {
91   RPAREN,   RPAREN,
92   OPERAND   OPERAND
93  };  };
94    #define is_int_op(a)      (((unsigned char)((a) - INTEQ)) <= 5)
95    #define is_str_op(a)      (((unsigned char)((a) - STREZ)) <= 5)
96    #define is_file_op(a)     (((unsigned char)((a) - FILNT)) <= 2)
97    #define is_file_access(a) (((unsigned char)((a) - FILRD)) <= 2)
98    #define is_file_type(a)   (((unsigned char)((a) - FILREG)) <= 5)
99    #define is_file_bit(a)    (((unsigned char)((a) - FILSUID)) <= 2)
100    
101    #if TEST_DEBUG
102    int depth;
103    #define nest_msg(...) do { \
104     depth++; \
105     fprintf(stderr, "%*s", depth*2, ""); \
106     fprintf(stderr, __VA_ARGS__); \
107    } while (0)
108    #define unnest_msg(...) do { \
109     fprintf(stderr, "%*s", depth*2, ""); \
110     fprintf(stderr, __VA_ARGS__); \
111     depth--; \
112    } while (0)
113    #define dbg_msg(...) do { \
114     fprintf(stderr, "%*s", depth*2, ""); \
115     fprintf(stderr, __VA_ARGS__); \
116    } while (0)
117    #define unnest_msg_and_return(expr, ...) do { \
118     number_t __res = (expr); \
119     fprintf(stderr, "%*s", depth*2, ""); \
120     fprintf(stderr, __VA_ARGS__, res); \
121     depth--; \
122     return __res; \
123    } while (0)
124    static const char *const TOKSTR[] = {
125     "EOI",
126     "FILRD",
127     "FILWR",
128     "FILEX",
129     "FILEXIST",
130     "FILREG",
131     "FILDIR",
132     "FILCDEV",
133     "FILBDEV",
134     "FILFIFO",
135     "FILSOCK",
136     "FILSYM",
137     "FILGZ",
138     "FILTT",
139     "FILSUID",
140     "FILSGID",
141     "FILSTCK",
142     "FILNT",
143     "FILOT",
144     "FILEQ",
145     "FILUID",
146     "FILGID",
147     "STREZ",
148     "STRNZ",
149     "STREQ",
150     "STRNE",
151     "STRLT",
152     "STRGT",
153     "INTEQ",
154     "INTNE",
155     "INTGE",
156     "INTGT",
157     "INTLE",
158     "INTLT",
159     "UNOT",
160     "BAND",
161     "BOR",
162     "LPAREN",
163     "RPAREN",
164     "OPERAND"
165    };
166    #else
167    #define nest_msg(...)   ((void)0)
168    #define unnest_msg(...) ((void)0)
169    #define dbg_msg(...)    ((void)0)
170    #define unnest_msg_and_return(expr, ...) return expr
171    #endif
172    
173  enum token_types {  enum token_types {
174   UNOP,   UNOP,
# Line 94  enum token_types { Line 178  enum token_types {
178   PAREN   PAREN
179  };  };
180    
181  static const struct t_op {  struct operator_t {
182   const char *op_text;   char op_text[4];
183   short op_num, op_type;   unsigned char op_num, op_type;
184  } ops[] = {  };
185   {  
186   "-r", FILRD, UNOP}, {  static const struct operator_t ops[] = {
187   "-w", FILWR, UNOP}, {   { "-r", FILRD   , UNOP   },
188   "-x", FILEX, UNOP}, {   { "-w", FILWR   , UNOP   },
189   "-e", FILEXIST, UNOP}, {   { "-x", FILEX   , UNOP   },
190   "-f", FILREG, UNOP}, {   { "-e", FILEXIST, UNOP   },
191   "-d", FILDIR, UNOP}, {   { "-f", FILREG  , UNOP   },
192   "-c", FILCDEV, UNOP}, {   { "-d", FILDIR  , UNOP   },
193   "-b", FILBDEV, UNOP}, {   { "-c", FILCDEV , UNOP   },
194   "-p", FILFIFO, UNOP}, {   { "-b", FILBDEV , UNOP   },
195   "-u", FILSUID, UNOP}, {   { "-p", FILFIFO , UNOP   },
196   "-g", FILSGID, UNOP}, {   { "-u", FILSUID , UNOP   },
197   "-k", FILSTCK, UNOP}, {   { "-g", FILSGID , UNOP   },
198   "-s", FILGZ, UNOP}, {   { "-k", FILSTCK , UNOP   },
199   "-t", FILTT, UNOP}, {   { "-s", FILGZ   , UNOP   },
200   "-z", STREZ, UNOP}, {   { "-t", FILTT   , UNOP   },
201   "-n", STRNZ, UNOP}, {   { "-z", STREZ   , UNOP   },
202   "-h", FILSYM, UNOP},    /* for backwards compat */   { "-n", STRNZ   , UNOP   },
203   {   { "-h", FILSYM  , UNOP   },    /* for backwards compat */
204   "-O", FILUID, UNOP}, {  
205   "-G", FILGID, UNOP}, {   { "-O" , FILUID , UNOP   },
206   "-L", FILSYM, UNOP}, {   { "-G" , FILGID , UNOP   },
207   "-S", FILSOCK, UNOP}, {   { "-L" , FILSYM , UNOP   },
208   "=", STREQ, BINOP}, {   { "-S" , FILSOCK, UNOP   },
209   "==", STREQ, BINOP}, {   { "="  , STREQ  , BINOP  },
210   "!=", STRNE, BINOP}, {   { "==" , STREQ  , BINOP  },
211   "<", STRLT, BINOP}, {   { "!=" , STRNE  , BINOP  },
212   ">", STRGT, BINOP}, {   { "<"  , STRLT  , BINOP  },
213   "-eq", INTEQ, BINOP}, {   { ">"  , STRGT  , BINOP  },
214   "-ne", INTNE, BINOP}, {   { "-eq", INTEQ  , BINOP  },
215   "-ge", INTGE, BINOP}, {   { "-ne", INTNE  , BINOP  },
216   "-gt", INTGT, BINOP}, {   { "-ge", INTGE  , BINOP  },
217   "-le", INTLE, BINOP}, {   { "-gt", INTGT  , BINOP  },
218   "-lt", INTLT, BINOP}, {   { "-le", INTLE  , BINOP  },
219   "-nt", FILNT, BINOP}, {   { "-lt", INTLT  , BINOP  },
220   "-ot", FILOT, BINOP}, {   { "-nt", FILNT  , BINOP  },
221   "-ef", FILEQ, BINOP}, {   { "-ot", FILOT  , BINOP  },
222   "!", UNOT, BUNOP}, {   { "-ef", FILEQ  , BINOP  },
223   "-a", BAND, BBINOP}, {   { "!"  , UNOT   , BUNOP  },
224   "-o", BOR, BBINOP}, {   { "-a" , BAND   , BBINOP },
225   "(", LPAREN, PAREN}, {   { "-o" , BOR    , BBINOP },
226   ")", RPAREN, PAREN}, {   { "("  , LPAREN , PAREN  },
227   0, 0, 0}   { ")"  , RPAREN , PAREN  },
228  };  };
229    
230  #ifdef CONFIG_FEATURE_TEST_64  
231  typedef int64_t arith_t;  #if ENABLE_FEATURE_TEST_64
232    typedef int64_t number_t;
233  #else  #else
234  typedef int arith_t;  typedef int number_t;
235  #endif  #endif
236    
 static char **t_wp;  
 static struct t_op const *t_wp_op;  
 static gid_t *group_array;  
 static int ngroups;  
   
 static enum token t_lex(char *s);  
 static arith_t oexpr(enum token n);  
 static arith_t aexpr(enum token n);  
 static arith_t nexpr(enum token n);  
 static int binop(void);  
 static arith_t primary(enum token n);  
 static int filstat(char *nm, enum token mode);  
 static arith_t getn(const char *s);  
 static int newerf(const char *f1, const char *f2);  
 static int olderf(const char *f1, const char *f2);  
 static int equalf(const char *f1, const char *f2);  
 static int test_eaccess(char *path, int mode);  
 static int is_a_group_member(gid_t gid);  
 static void initialize_group_array(void);  
   
 static jmp_buf leaving;  
237    
238  int bb_test(int argc, char **argv)  /* We try to minimize both static and stack usage. */
239  {  struct test_statics {
240   int res;   char **args;
241     /* set only by check_operator(), either to bogus struct
242     * or points to matching operator_t struct. Never NULL. */
243     const struct operator_t *last_operator;
244     gid_t *group_array;
245     int ngroups;
246     jmp_buf leaving;
247    };
248    
249   if (LONE_CHAR(argv[0], '[')) {  /* See test_ptr_hack.c */
250   --argc;  extern struct test_statics *const test_ptr_to_statics;
  if (NOT_LONE_CHAR(argv[argc], ']')) {  
  bb_error_msg("missing ]");  
  return 2;  
  }  
  argv[argc] = NULL;  
  } else if (strcmp(argv[0], "[[") == 0) {  
  --argc;  
  if (strcmp(argv[argc], "]]")) {  
  bb_error_msg("missing ]]");  
  return 2;  
  }  
  argv[argc] = NULL;  
  }  
251    
252   res = setjmp(leaving);  #define S (*test_ptr_to_statics)
253   if (res)  #define args            (S.args         )
254   return res;  #define last_operator   (S.last_operator)
255    #define group_array     (S.group_array  )
256    #define ngroups         (S.ngroups      )
257    #define leaving         (S.leaving      )
258    
259    #define INIT_S() do { \
260     (*(struct test_statics**)&test_ptr_to_statics) = xzalloc(sizeof(S)); \
261     barrier(); \
262    } while (0)
263    #define DEINIT_S() do { \
264     free(test_ptr_to_statics); \
265    } while (0)
266    
267   /* resetting ngroups is probably unnecessary.  it will  static number_t primary(enum token n);
  * force a new call to getgroups(), which prevents using  
  * group data fetched during a previous call.  but the  
  * only way the group data could be stale is if there's  
  * been an intervening call to setgroups(), and this  
  * isn't likely in the case of a shell.  paranoia  
  * prevails...  
  */  
  ngroups = 0;  
   
  /* Implement special cases from POSIX.2, section 4.62.4 */  
  switch (argc) {  
  case 1:  
  return 1;  
  case 2:  
  return *argv[1] == '\0';  
  case 3:  
  if (argv[1][0] == '!' && argv[1][1] == '\0') {  
  return *argv[2] != '\0';  
  }  
  break;  
  case 4:  
  if (argv[1][0] != '!' || argv[1][1] != '\0') {  
  if (t_lex(argv[2]), t_wp_op && t_wp_op->op_type == BINOP) {  
  t_wp = &argv[1];  
  return binop() == 0;  
  }  
  }  
  break;  
  case 5:  
  if (argv[1][0] == '!' && argv[1][1] == '\0') {  
  if (t_lex(argv[3]), t_wp_op && t_wp_op->op_type == BINOP) {  
  t_wp = &argv[2];  
  return binop() != 0;  
  }  
  }  
  break;  
  }  
   
  t_wp = &argv[1];  
  res = !oexpr(t_lex(*t_wp));  
   
  if (*t_wp != NULL && *++t_wp != NULL) {  
  bb_error_msg("%s: unknown operand", *t_wp);  
  return 2;  
  }  
  return res;  
 }  
268    
269    static void syntax(const char *op, const char *msg) NORETURN;
270  static void syntax(const char *op, const char *msg)  static void syntax(const char *op, const char *msg)
271  {  {
272   if (op && *op) {   if (op && *op) {
273   bb_error_msg("%s: %s", op, msg);   bb_error_msg("%s: %s", op, msg);
274   } else {   } else {
275   bb_error_msg("%s", msg);   bb_error_msg("%s: %s"+4, msg);
276   }   }
277   longjmp(leaving, 2);   longjmp(leaving, 2);
278  }  }
279    
 static arith_t oexpr(enum token n)  
 {  
  arith_t res;  
   
  res = aexpr(n);  
  if (t_lex(*++t_wp) == BOR) {  
  return oexpr(t_lex(*++t_wp)) || res;  
  }  
  t_wp--;  
  return res;  
 }  
   
 static arith_t aexpr(enum token n)  
 {  
  arith_t res;  
   
  res = nexpr(n);  
  if (t_lex(*++t_wp) == BAND)  
  return aexpr(t_lex(*++t_wp)) && res;  
  t_wp--;  
  return res;  
 }  
   
 static arith_t nexpr(enum token n)  
 {  
  if (n == UNOT)  
  return !nexpr(t_lex(*++t_wp));  
  return primary(n);  
 }  
   
 static arith_t primary(enum token n)  
 {  
  arith_t res;  
   
  if (n == EOI) {  
  syntax(NULL, "argument expected");  
  }  
  if (n == LPAREN) {  
  res = oexpr(t_lex(*++t_wp));  
  if (t_lex(*++t_wp) != RPAREN)  
  syntax(NULL, "closing paren expected");  
  return res;  
  }  
  if (t_wp_op && t_wp_op->op_type == UNOP) {  
  /* unary expression */  
  if (*++t_wp == NULL)  
  syntax(t_wp_op->op_text, "argument expected");  
  switch (n) {  
  case STREZ:  
  return strlen(*t_wp) == 0;  
  case STRNZ:  
  return strlen(*t_wp) != 0;  
  case FILTT:  
  return isatty(getn(*t_wp));  
  default:  
  return filstat(*t_wp, n);  
  }  
  }  
   
  if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {  
  return binop();  
  }  
   
  return strlen(*t_wp) > 0;  
 }  
   
 static int binop(void)  
 {  
  const char *opnd1, *opnd2;  
  struct t_op const *op;  
   
  opnd1 = *t_wp;  
  (void) t_lex(*++t_wp);  
  op = t_wp_op;  
   
  if ((opnd2 = *++t_wp) == (char *) 0)  
  syntax(op->op_text, "argument expected");  
   
  switch (op->op_num) {  
  case STREQ:  
  return strcmp(opnd1, opnd2) == 0;  
  case STRNE:  
  return strcmp(opnd1, opnd2) != 0;  
  case STRLT:  
  return strcmp(opnd1, opnd2) < 0;  
  case STRGT:  
  return strcmp(opnd1, opnd2) > 0;  
  case INTEQ:  
  return getn(opnd1) == getn(opnd2);  
  case INTNE:  
  return getn(opnd1) != getn(opnd2);  
  case INTGE:  
  return getn(opnd1) >= getn(opnd2);  
  case INTGT:  
  return getn(opnd1) > getn(opnd2);  
  case INTLE:  
  return getn(opnd1) <= getn(opnd2);  
  case INTLT:  
  return getn(opnd1) < getn(opnd2);  
  case FILNT:  
  return newerf(opnd1, opnd2);  
  case FILOT:  
  return olderf(opnd1, opnd2);  
  case FILEQ:  
  return equalf(opnd1, opnd2);  
  }  
  /* NOTREACHED */  
  return 1;  
 }  
   
 static int filstat(char *nm, enum token mode)  
 {  
  struct stat s;  
  unsigned int i;  
   
  if (mode == FILSYM) {  
 #ifdef S_IFLNK  
  if (lstat(nm, &s) == 0) {  
  i = S_IFLNK;  
  goto filetype;  
  }  
 #endif  
  return 0;  
  }  
   
  if (stat(nm, &s) != 0)  
  return 0;  
   
  switch (mode) {  
  case FILRD:  
  return test_eaccess(nm, R_OK) == 0;  
  case FILWR:  
  return test_eaccess(nm, W_OK) == 0;  
  case FILEX:  
  return test_eaccess(nm, X_OK) == 0;  
  case FILEXIST:  
  return 1;  
  case FILREG:  
  i = S_IFREG;  
  goto filetype;  
  case FILDIR:  
  i = S_IFDIR;  
  goto filetype;  
  case FILCDEV:  
  i = S_IFCHR;  
  goto filetype;  
  case FILBDEV:  
  i = S_IFBLK;  
  goto filetype;  
  case FILFIFO:  
 #ifdef S_IFIFO  
  i = S_IFIFO;  
  goto filetype;  
 #else  
  return 0;  
 #endif  
  case FILSOCK:  
 #ifdef S_IFSOCK  
  i = S_IFSOCK;  
  goto filetype;  
 #else  
  return 0;  
 #endif  
  case FILSUID:  
  i = S_ISUID;  
  goto filebit;  
  case FILSGID:  
  i = S_ISGID;  
  goto filebit;  
  case FILSTCK:  
  i = S_ISVTX;  
  goto filebit;  
  case FILGZ:  
  return s.st_size > 0L;  
  case FILUID:  
  return s.st_uid == geteuid();  
  case FILGID:  
  return s.st_gid == getegid();  
  default:  
  return 1;  
  }  
   
   filetype:  
  return ((s.st_mode & S_IFMT) == i);  
   
   filebit:  
  return ((s.st_mode & i) != 0);  
 }  
   
 static enum token t_lex(char *s)  
 {  
  struct t_op const *op = ops;  
   
  if (s == 0) {  
  t_wp_op = (struct t_op *) 0;  
  return EOI;  
  }  
  while (op->op_text) {  
  if (strcmp(s, op->op_text) == 0) {  
  t_wp_op = op;  
  return op->op_num;  
  }  
  op++;  
  }  
  t_wp_op = (struct t_op *) 0;  
  return OPERAND;  
 }  
   
280  /* atoi with error detection */  /* atoi with error detection */
281  static arith_t getn(const char *s)  //XXX: FIXME: duplicate of existing libbb function?
282    static number_t getn(const char *s)
283  {  {
284   char *p;   char *p;
285  #ifdef CONFIG_FEATURE_TEST_64  #if ENABLE_FEATURE_TEST_64
286   long long r;   long long r;
287  #else  #else
288   long r;   long r;
289  #endif  #endif
290    
291   errno = 0;   errno = 0;
292  #ifdef CONFIG_FEATURE_TEST_64  #if ENABLE_FEATURE_TEST_64
293   r = strtoll(s, &p, 10);   r = strtoll(s, &p, 10);
294  #else  #else
295   r = strtol(s, &p, 10);   r = strtol(s, &p, 10);
# Line 488  static arith_t getn(const char *s) Line 304  static arith_t getn(const char *s)
304   return r;   return r;
305  }  }
306    
307    /* UNUSED
308  static int newerf(const char *f1, const char *f2)  static int newerf(const char *f1, const char *f2)
309  {  {
310   struct stat b1, b2;   struct stat b1, b2;
# Line 512  static int equalf(const char *f1, const Line 329  static int equalf(const char *f1, const
329   stat(f2, &b2) == 0 &&   stat(f2, &b2) == 0 &&
330   b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);   b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino);
331  }  }
332    */
333    
334    
335    static enum token check_operator(char *s)
336    {
337     static const struct operator_t no_op = {
338     .op_num = -1,
339     .op_type = -1
340     };
341     const struct operator_t *op;
342    
343     last_operator = &no_op;
344     if (s == NULL) {
345     return EOI;
346     }
347    
348     op = ops;
349     do {
350     if (strcmp(s, op->op_text) == 0) {
351     last_operator = op;
352     return op->op_num;
353     }
354     op++;
355     } while (op < ops + ARRAY_SIZE(ops));
356    
357     return OPERAND;
358    }
359    
360    
361    static int binop(void)
362    {
363     const char *opnd1, *opnd2;
364     const struct operator_t *op;
365     number_t val1, val2;
366    
367     opnd1 = *args;
368     check_operator(*++args);
369     op = last_operator;
370    
371     opnd2 = *++args;
372     if (opnd2 == NULL)
373     syntax(op->op_text, "argument expected");
374    
375     if (is_int_op(op->op_num)) {
376     val1 = getn(opnd1);
377     val2 = getn(opnd2);
378     if (op->op_num == INTEQ)
379     return val1 == val2;
380     if (op->op_num == INTNE)
381     return val1 != val2;
382     if (op->op_num == INTGE)
383     return val1 >= val2;
384     if (op->op_num == INTGT)
385     return val1 >  val2;
386     if (op->op_num == INTLE)
387     return val1 <= val2;
388     if (op->op_num == INTLT)
389     return val1 <  val2;
390     }
391     if (is_str_op(op->op_num)) {
392     val1 = strcmp(opnd1, opnd2);
393     if (op->op_num == STREQ)
394     return val1 == 0;
395     if (op->op_num == STRNE)
396     return val1 != 0;
397     if (op->op_num == STRLT)
398     return val1 < 0;
399     if (op->op_num == STRGT)
400     return val1 > 0;
401     }
402     /* We are sure that these three are by now the only binops we didn't check
403     * yet, so we do not check if the class is correct:
404     */
405    /* if (is_file_op(op->op_num)) */
406     {
407     struct stat b1, b2;
408    
409     if (stat(opnd1, &b1) || stat(opnd2, &b2))
410     return 0; /* false, since at least one stat failed */
411     if (op->op_num == FILNT)
412     return b1.st_mtime > b2.st_mtime;
413     if (op->op_num == FILOT)
414     return b1.st_mtime < b2.st_mtime;
415     if (op->op_num == FILEQ)
416     return b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
417     }
418     return 1; /* NOTREACHED */
419    }
420    
421    
422    static void initialize_group_array(void)
423    {
424     ngroups = getgroups(0, NULL);
425     if (ngroups > 0) {
426     /* FIXME: ash tries so hard to not die on OOM,
427     * and we spoil it with just one xrealloc here */
428     /* We realloc, because test_main can be entered repeatedly by shell.
429     * Testcase (ash): 'while true; do test -x some_file; done'
430     * and watch top. (some_file must have owner != you) */
431     group_array = xrealloc(group_array, ngroups * sizeof(gid_t));
432     getgroups(ngroups, group_array);
433     }
434    }
435    
436    
437    /* Return non-zero if GID is one that we have in our groups list. */
438    //XXX: FIXME: duplicate of existing libbb function?
439    // see toplevel TODO file:
440    // possible code duplication ingroup() and is_a_group_member()
441    static int is_a_group_member(gid_t gid)
442    {
443     int i;
444    
445     /* Short-circuit if possible, maybe saving a call to getgroups(). */
446     if (gid == getgid() || gid == getegid())
447     return 1;
448    
449     if (ngroups == 0)
450     initialize_group_array();
451    
452     /* Search through the list looking for GID. */
453     for (i = 0; i < ngroups; i++)
454     if (gid == group_array[i])
455     return 1;
456    
457     return 0;
458    }
459    
460    
461  /* Do the same thing access(2) does, but use the effective uid and gid,  /* Do the same thing access(2) does, but use the effective uid and gid,
462     and don't make the mistake of telling root that any file is     and don't make the mistake of telling root that any file is
# Line 546  static int test_eaccess(char *path, int Line 491  static int test_eaccess(char *path, int
491   return -1;   return -1;
492  }  }
493    
494  static void initialize_group_array(void)  
495    static int filstat(char *nm, enum token mode)
496  {  {
497   ngroups = getgroups(0, NULL);   struct stat s;
498   if (ngroups > 0) {   unsigned i = i; /* gcc 3.x thinks it can be used uninitialized */
499   group_array = xmalloc(ngroups * sizeof(gid_t));  
500   getgroups(ngroups, group_array);   if (mode == FILSYM) {
501    #ifdef S_IFLNK
502     if (lstat(nm, &s) == 0) {
503     i = S_IFLNK;
504     goto filetype;
505     }
506    #endif
507     return 0;
508   }   }
509    
510     if (stat(nm, &s) != 0)
511     return 0;
512     if (mode == FILEXIST)
513     return 1;
514     if (is_file_access(mode)) {
515     if (mode == FILRD)
516     i = R_OK;
517     if (mode == FILWR)
518     i = W_OK;
519     if (mode == FILEX)
520     i = X_OK;
521     return test_eaccess(nm, i) == 0;
522     }
523     if (is_file_type(mode)) {
524     if (mode == FILREG)
525     i = S_IFREG;
526     if (mode == FILDIR)
527     i = S_IFDIR;
528     if (mode == FILCDEV)
529     i = S_IFCHR;
530     if (mode == FILBDEV)
531     i = S_IFBLK;
532     if (mode == FILFIFO) {
533    #ifdef S_IFIFO
534     i = S_IFIFO;
535    #else
536     return 0;
537    #endif
538     }
539     if (mode == FILSOCK) {
540    #ifdef S_IFSOCK
541     i = S_IFSOCK;
542    #else
543     return 0;
544    #endif
545     }
546     filetype:
547     return ((s.st_mode & S_IFMT) == i);
548     }
549     if (is_file_bit(mode)) {
550     if (mode == FILSUID)
551     i = S_ISUID;
552     if (mode == FILSGID)
553     i = S_ISGID;
554     if (mode == FILSTCK)
555     i = S_ISVTX;
556     return ((s.st_mode & i) != 0);
557     }
558     if (mode == FILGZ)
559     return s.st_size > 0L;
560     if (mode == FILUID)
561     return s.st_uid == geteuid();
562     if (mode == FILGID)
563     return s.st_gid == getegid();
564     return 1; /* NOTREACHED */
565  }  }
566    
567  /* Return non-zero if GID is one that we have in our groups list. */  
568  static int is_a_group_member(gid_t gid)  static number_t nexpr(enum token n)
569  {  {
570   int i;   number_t res;
571    
572   /* Short-circuit if possible, maybe saving a call to getgroups(). */   nest_msg(">nexpr(%s)\n", TOKSTR[n]);
573   if (gid == getgid() || gid == getegid())   if (n == UNOT) {
574   return 1;   res = !nexpr(check_operator(*++args));
575     unnest_msg("<nexpr:%lld\n", res);
576     return res;
577     }
578     res = primary(n);
579     unnest_msg("<nexpr:%lld\n", res);
580     return res;
581    }
582    
  if (ngroups == 0)  
  initialize_group_array();  
583    
584   /* Search through the list looking for GID. */  static number_t aexpr(enum token n)
585   for (i = 0; i < ngroups; i++)  {
586   if (gid == group_array[i])   number_t res;
  return 1;  
587    
588   return 0;   nest_msg(">aexpr(%s)\n", TOKSTR[n]);
589     res = nexpr(n);
590     dbg_msg("aexpr: nexpr:%lld, next args:%s\n", res, args[1]);
591     if (check_operator(*++args) == BAND) {
592     dbg_msg("aexpr: arg is AND, next args:%s\n", args[1]);
593     res = aexpr(check_operator(*++args)) && res;
594     unnest_msg("<aexpr:%lld\n", res);
595     return res;
596     }
597     args--;
598     unnest_msg("<aexpr:%lld, args:%s\n", res, args[0]);
599     return res;
600  }  }
601    
602    
603  /* applet entry point */  static number_t oexpr(enum token n)
604    {
605     number_t res;
606    
607  int test_main(int argc, char **argv)   nest_msg(">oexpr(%s)\n", TOKSTR[n]);
608     res = aexpr(n);
609     dbg_msg("oexpr: aexpr:%lld, next args:%s\n", res, args[1]);
610     if (check_operator(*++args) == BOR) {
611     dbg_msg("oexpr: next arg is OR, next args:%s\n", args[1]);
612     res = oexpr(check_operator(*++args)) || res;
613     unnest_msg("<oexpr:%lld\n", res);
614     return res;
615     }
616     args--;
617     unnest_msg("<oexpr:%lld, args:%s\n", res, args[0]);
618     return res;
619    }
620    
621    
622    static number_t primary(enum token n)
623  {  {
624   return bb_test(argc, argv);  #if TEST_DEBUG
625     number_t res = res; /* for compiler */
626    #else
627     number_t res;
628    #endif
629     const struct operator_t *args0_op;
630    
631     nest_msg(">primary(%s)\n", TOKSTR[n]);
632     if (n == EOI) {
633     syntax(NULL, "argument expected");
634     }
635     if (n == LPAREN) {
636     res = oexpr(check_operator(*++args));
637     if (check_operator(*++args) != RPAREN)
638     syntax(NULL, "closing paren expected");
639     unnest_msg("<primary:%lld\n", res);
640     return res;
641     }
642    
643     /* coreutils 6.9 checks "is args[1] binop and args[2] exist?" first,
644     * do the same */
645     args0_op = last_operator;
646     /* last_operator = operator at args[1] */
647     if (check_operator(args[1]) != EOI) { /* if args[1] != NULL */
648     if (args[2]) {
649     // coreutils also does this:
650     // if (args[3] && args[0]="-l" && args[2] is BINOP)
651     // return binop(1 /* prepended by -l */);
652     if (last_operator->op_type == BINOP)
653     unnest_msg_and_return(binop(), "<primary: binop:%lld\n");
654     }
655     }
656     /* check "is args[0] unop?" second */
657     if (args0_op->op_type == UNOP) {
658     /* unary expression */
659     if (args[1] == NULL)
660    // syntax(args0_op->op_text, "argument expected");
661     goto check_emptiness;
662     args++;
663     if (n == STREZ)
664     unnest_msg_and_return(args[0][0] == '\0', "<primary:%lld\n");
665     if (n == STRNZ)
666     unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
667     if (n == FILTT)
668     unnest_msg_and_return(isatty(getn(*args)), "<primary: isatty(%s)%lld\n", *args);
669     unnest_msg_and_return(filstat(*args, n), "<primary: filstat(%s):%lld\n", *args);
670     }
671    
672     /*check_operator(args[1]); - already done */
673     if (last_operator->op_type == BINOP) {
674     /* args[2] is known to be NULL, isn't it bound to fail? */
675     unnest_msg_and_return(binop(), "<primary:%lld\n");
676     }
677     check_emptiness:
678     unnest_msg_and_return(args[0][0] != '\0', "<primary:%lld\n");
679  }  }
680    
681    
682    int test_main(int argc, char **argv)
683    {
684     int res;
685     const char *arg0;
686    // bool negate = 0;
687    
688     arg0 = bb_basename(argv[0]);
689     if (arg0[0] == '[') {
690     --argc;
691     if (!arg0[1]) { /* "[" ? */
692     if (NOT_LONE_CHAR(argv[argc], ']')) {
693     bb_error_msg("missing ]");
694     return 2;
695     }
696     } else { /* assuming "[[" */
697     if (strcmp(argv[argc], "]]") != 0) {
698     bb_error_msg("missing ]]");
699     return 2;
700     }
701     }
702     argv[argc] = NULL;
703     }
704    
705     /* We must do DEINIT_S() prior to returning */
706     INIT_S();
707    
708     res = setjmp(leaving);
709     if (res)
710     goto ret;
711    
712     /* resetting ngroups is probably unnecessary.  it will
713     * force a new call to getgroups(), which prevents using
714     * group data fetched during a previous call.  but the
715     * only way the group data could be stale is if there's
716     * been an intervening call to setgroups(), and this
717     * isn't likely in the case of a shell.  paranoia
718     * prevails...
719     */
720     ngroups = 0;
721    
722     //argc--;
723     argv++;
724    
725     /* Implement special cases from POSIX.2, section 4.62.4 */
726     if (!argv[0]) { /* "test" */
727     res = 1;
728     goto ret;
729     }
730    #if 0
731    // Now it's fixed in the parser and should not be needed
732     if (LONE_CHAR(argv[0], '!') && argv[1]) {
733     negate = 1;
734     //argc--;
735     argv++;
736     }
737     if (!argv[1]) { /* "test [!] arg" */
738     res = (*argv[0] == '\0');
739     goto ret;
740     }
741     if (argv[2] && !argv[3]) {
742     check_operator(argv[1]);
743     if (last_operator->op_type == BINOP) {
744     /* "test [!] arg1 <binary_op> arg2" */
745     args = &argv[0];
746     res = (binop() == 0);
747     goto ret;
748     }
749     }
750    
751     /* Some complex expression. Undo '!' removal */
752     if (negate) {
753     negate = 0;
754     //argc++;
755     argv--;
756     }
757    #endif
758     args = &argv[0];
759     res = !oexpr(check_operator(*args));
760    
761     if (*args != NULL && *++args != NULL) {
762     /* TODO: example when this happens? */
763     bb_error_msg("%s: unknown operand", *args);
764     res = 2;
765     }
766     ret:
767     DEINIT_S();
768    // return negate ? !res : res;
769     return res;
770    }

Legend:
Removed from v.532  
changed lines
  Added in v.816