Contents of /trunk/mkinitrd-magellan/klibc/usr/dash/bltin/test.c
Parent Directory | Revision Log
Revision 1122 -
(show annotations)
(download)
Wed Aug 18 21:11:40 2010 UTC (13 years, 8 months ago) by niro
File MIME type: text/plain
File size: 10037 byte(s)
Wed Aug 18 21:11:40 2010 UTC (13 years, 8 months ago) by niro
File MIME type: text/plain
File size: 10037 byte(s)
-updated to klibc-1.5.19
1 | /* |
2 | * test(1); version 7-like -- author Erik Baalbergen |
3 | * modified by Eric Gisin to be used as built-in. |
4 | * modified by Arnold Robbins to add SVR3 compatibility |
5 | * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). |
6 | * modified by J.T. Conklin for NetBSD. |
7 | * |
8 | * This program is in the Public Domain. |
9 | */ |
10 | |
11 | #include <sys/stat.h> |
12 | #include <sys/types.h> |
13 | |
14 | #include <fcntl.h> |
15 | #include <stdint.h> |
16 | #include <stdlib.h> |
17 | #include <string.h> |
18 | #include <unistd.h> |
19 | #include <stdarg.h> |
20 | #include "bltin.h" |
21 | |
22 | /* test(1) accepts the following grammar: |
23 | oexpr ::= aexpr | aexpr "-o" oexpr ; |
24 | aexpr ::= nexpr | nexpr "-a" aexpr ; |
25 | nexpr ::= primary | "!" primary |
26 | primary ::= unary-operator operand |
27 | | operand binary-operator operand |
28 | | operand |
29 | | "(" oexpr ")" |
30 | ; |
31 | unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| |
32 | "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; |
33 | |
34 | binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| |
35 | "-nt"|"-ot"|"-ef"; |
36 | operand ::= <any legal UNIX file name> |
37 | */ |
38 | |
39 | enum token { |
40 | EOI, |
41 | FILRD, |
42 | FILWR, |
43 | FILEX, |
44 | FILEXIST, |
45 | FILREG, |
46 | FILDIR, |
47 | FILCDEV, |
48 | FILBDEV, |
49 | FILFIFO, |
50 | FILSOCK, |
51 | FILSYM, |
52 | FILGZ, |
53 | FILTT, |
54 | FILSUID, |
55 | FILSGID, |
56 | FILSTCK, |
57 | FILNT, |
58 | FILOT, |
59 | FILEQ, |
60 | FILUID, |
61 | FILGID, |
62 | STREZ, |
63 | STRNZ, |
64 | STREQ, |
65 | STRNE, |
66 | STRLT, |
67 | STRGT, |
68 | INTEQ, |
69 | INTNE, |
70 | INTGE, |
71 | INTGT, |
72 | INTLE, |
73 | INTLT, |
74 | UNOT, |
75 | BAND, |
76 | BOR, |
77 | LPAREN, |
78 | RPAREN, |
79 | OPERAND |
80 | }; |
81 | |
82 | enum token_types { |
83 | UNOP, |
84 | BINOP, |
85 | BUNOP, |
86 | BBINOP, |
87 | PAREN |
88 | }; |
89 | |
90 | static struct t_op { |
91 | const char *op_text; |
92 | short op_num, op_type; |
93 | } const ops [] = { |
94 | {"-r", FILRD, UNOP}, |
95 | {"-w", FILWR, UNOP}, |
96 | {"-x", FILEX, UNOP}, |
97 | {"-e", FILEXIST,UNOP}, |
98 | {"-f", FILREG, UNOP}, |
99 | {"-d", FILDIR, UNOP}, |
100 | {"-c", FILCDEV,UNOP}, |
101 | {"-b", FILBDEV,UNOP}, |
102 | {"-p", FILFIFO,UNOP}, |
103 | {"-u", FILSUID,UNOP}, |
104 | {"-g", FILSGID,UNOP}, |
105 | {"-k", FILSTCK,UNOP}, |
106 | {"-s", FILGZ, UNOP}, |
107 | {"-t", FILTT, UNOP}, |
108 | {"-z", STREZ, UNOP}, |
109 | {"-n", STRNZ, UNOP}, |
110 | {"-h", FILSYM, UNOP}, /* for backwards compat */ |
111 | {"-O", FILUID, UNOP}, |
112 | {"-G", FILGID, UNOP}, |
113 | {"-L", FILSYM, UNOP}, |
114 | {"-S", FILSOCK,UNOP}, |
115 | {"=", STREQ, BINOP}, |
116 | {"!=", STRNE, BINOP}, |
117 | {"<", STRLT, BINOP}, |
118 | {">", STRGT, BINOP}, |
119 | {"-eq", INTEQ, BINOP}, |
120 | {"-ne", INTNE, BINOP}, |
121 | {"-ge", INTGE, BINOP}, |
122 | {"-gt", INTGT, BINOP}, |
123 | {"-le", INTLE, BINOP}, |
124 | {"-lt", INTLT, BINOP}, |
125 | {"-nt", FILNT, BINOP}, |
126 | {"-ot", FILOT, BINOP}, |
127 | {"-ef", FILEQ, BINOP}, |
128 | {"!", UNOT, BUNOP}, |
129 | {"-a", BAND, BBINOP}, |
130 | {"-o", BOR, BBINOP}, |
131 | {"(", LPAREN, PAREN}, |
132 | {")", RPAREN, PAREN}, |
133 | {0, 0, 0} |
134 | }; |
135 | |
136 | static char **t_wp; |
137 | static struct t_op const *t_wp_op; |
138 | |
139 | static void syntax(const char *, const char *); |
140 | static int oexpr(enum token); |
141 | static int aexpr(enum token); |
142 | static int nexpr(enum token); |
143 | static int primary(enum token); |
144 | static int binop(void); |
145 | static int filstat(char *, enum token); |
146 | static enum token t_lex(char **); |
147 | static int isoperand(char **); |
148 | static int newerf(const char *, const char *); |
149 | static int olderf(const char *, const char *); |
150 | static int equalf(const char *, const char *); |
151 | #ifdef HAVE_FACCESSAT |
152 | static int test_file_access(const char *, int); |
153 | #else |
154 | static int test_st_mode(const struct stat64 *, int); |
155 | static int bash_group_member(gid_t); |
156 | #endif |
157 | |
158 | static inline intmax_t getn(const char *s) |
159 | { |
160 | return atomax10(s); |
161 | } |
162 | |
163 | static const struct t_op *getop(const char *s) |
164 | { |
165 | const struct t_op *op; |
166 | |
167 | for (op = ops; op->op_text; op++) { |
168 | if (strcmp(s, op->op_text) == 0) |
169 | return op; |
170 | } |
171 | |
172 | return NULL; |
173 | } |
174 | |
175 | int |
176 | testcmd(int argc, char **argv) |
177 | { |
178 | const struct t_op *op; |
179 | enum token n; |
180 | int res; |
181 | |
182 | if (*argv[0] == '[') { |
183 | if (*argv[--argc] != ']') |
184 | error("missing ]"); |
185 | argv[argc] = NULL; |
186 | } |
187 | |
188 | argv++; |
189 | argc--; |
190 | |
191 | if (argc < 1) |
192 | return 1; |
193 | |
194 | /* |
195 | * POSIX prescriptions: he who wrote this deserves the Nobel |
196 | * peace prize. |
197 | */ |
198 | switch (argc) { |
199 | case 3: |
200 | op = getop(argv[1]); |
201 | if (op && op->op_type == BINOP) { |
202 | n = OPERAND; |
203 | goto eval; |
204 | } |
205 | /* fall through */ |
206 | |
207 | case 4: |
208 | if (!strcmp(argv[0], "(") && !strcmp(argv[argc - 1], ")")) { |
209 | argv[--argc] = NULL; |
210 | argv++; |
211 | argc--; |
212 | } |
213 | } |
214 | |
215 | n = t_lex(argv); |
216 | |
217 | eval: |
218 | t_wp = argv; |
219 | res = !oexpr(n); |
220 | argv = t_wp; |
221 | |
222 | if (argv[0] != NULL && argv[1] != NULL) |
223 | syntax(argv[0], "unexpected operator"); |
224 | |
225 | return res; |
226 | } |
227 | |
228 | static void |
229 | syntax(const char *op, const char *msg) |
230 | { |
231 | if (op && *op) |
232 | error("%s: %s", op, msg); |
233 | else |
234 | error("%s", msg); |
235 | } |
236 | |
237 | static int |
238 | oexpr(enum token n) |
239 | { |
240 | int res = 0; |
241 | |
242 | for (;;) { |
243 | res |= aexpr(n); |
244 | n = t_lex(t_wp + 1); |
245 | if (n != BOR) |
246 | break; |
247 | n = t_lex(t_wp += 2); |
248 | } |
249 | return res; |
250 | } |
251 | |
252 | static int |
253 | aexpr(enum token n) |
254 | { |
255 | int res = 1; |
256 | |
257 | for (;;) { |
258 | if (!nexpr(n)) |
259 | res = 0; |
260 | n = t_lex(t_wp + 1); |
261 | if (n != BAND) |
262 | break; |
263 | n = t_lex(t_wp += 2); |
264 | } |
265 | return res; |
266 | } |
267 | |
268 | static int |
269 | nexpr(enum token n) |
270 | { |
271 | if (n == UNOT) |
272 | return !nexpr(t_lex(++t_wp)); |
273 | return primary(n); |
274 | } |
275 | |
276 | static int |
277 | primary(enum token n) |
278 | { |
279 | enum token nn; |
280 | int res; |
281 | |
282 | if (n == EOI) |
283 | return 0; /* missing expression */ |
284 | if (n == LPAREN) { |
285 | if ((nn = t_lex(++t_wp)) == RPAREN) |
286 | return 0; /* missing expression */ |
287 | res = oexpr(nn); |
288 | if (t_lex(++t_wp) != RPAREN) |
289 | syntax(NULL, "closing paren expected"); |
290 | return res; |
291 | } |
292 | if (t_wp_op && t_wp_op->op_type == UNOP) { |
293 | /* unary expression */ |
294 | if (*++t_wp == NULL) |
295 | syntax(t_wp_op->op_text, "argument expected"); |
296 | switch (n) { |
297 | case STREZ: |
298 | return strlen(*t_wp) == 0; |
299 | case STRNZ: |
300 | return strlen(*t_wp) != 0; |
301 | case FILTT: |
302 | return isatty(getn(*t_wp)); |
303 | #ifdef HAVE_FACCESSAT |
304 | case FILRD: |
305 | return test_file_access(*t_wp, R_OK); |
306 | case FILWR: |
307 | return test_file_access(*t_wp, W_OK); |
308 | case FILEX: |
309 | return test_file_access(*t_wp, X_OK); |
310 | #endif |
311 | default: |
312 | return filstat(*t_wp, n); |
313 | } |
314 | } |
315 | |
316 | if (t_lex(t_wp + 1), t_wp_op && t_wp_op->op_type == BINOP) { |
317 | return binop(); |
318 | } |
319 | |
320 | return strlen(*t_wp) > 0; |
321 | } |
322 | |
323 | static int |
324 | binop(void) |
325 | { |
326 | const char *opnd1, *opnd2; |
327 | struct t_op const *op; |
328 | |
329 | opnd1 = *t_wp; |
330 | (void) t_lex(++t_wp); |
331 | op = t_wp_op; |
332 | |
333 | if ((opnd2 = *++t_wp) == (char *)0) |
334 | syntax(op->op_text, "argument expected"); |
335 | |
336 | switch (op->op_num) { |
337 | default: |
338 | #ifdef DEBUG |
339 | abort(); |
340 | /* NOTREACHED */ |
341 | #endif |
342 | case STREQ: |
343 | return strcmp(opnd1, opnd2) == 0; |
344 | case STRNE: |
345 | return strcmp(opnd1, opnd2) != 0; |
346 | case STRLT: |
347 | return strcmp(opnd1, opnd2) < 0; |
348 | case STRGT: |
349 | return strcmp(opnd1, opnd2) > 0; |
350 | case INTEQ: |
351 | return getn(opnd1) == getn(opnd2); |
352 | case INTNE: |
353 | return getn(opnd1) != getn(opnd2); |
354 | case INTGE: |
355 | return getn(opnd1) >= getn(opnd2); |
356 | case INTGT: |
357 | return getn(opnd1) > getn(opnd2); |
358 | case INTLE: |
359 | return getn(opnd1) <= getn(opnd2); |
360 | case INTLT: |
361 | return getn(opnd1) < getn(opnd2); |
362 | case FILNT: |
363 | return newerf (opnd1, opnd2); |
364 | case FILOT: |
365 | return olderf (opnd1, opnd2); |
366 | case FILEQ: |
367 | return equalf (opnd1, opnd2); |
368 | } |
369 | } |
370 | |
371 | static int |
372 | filstat(char *nm, enum token mode) |
373 | { |
374 | struct stat64 s; |
375 | |
376 | if (mode == FILSYM ? lstat64(nm, &s) : stat64(nm, &s)) |
377 | return 0; |
378 | |
379 | switch (mode) { |
380 | #ifndef HAVE_FACCESSAT |
381 | case FILRD: |
382 | return test_st_mode(&s, R_OK); |
383 | case FILWR: |
384 | return test_st_mode(&s, W_OK); |
385 | case FILEX: |
386 | return test_st_mode(&s, X_OK); |
387 | #endif |
388 | case FILEXIST: |
389 | return 1; |
390 | case FILREG: |
391 | return S_ISREG(s.st_mode); |
392 | case FILDIR: |
393 | return S_ISDIR(s.st_mode); |
394 | case FILCDEV: |
395 | return S_ISCHR(s.st_mode); |
396 | case FILBDEV: |
397 | return S_ISBLK(s.st_mode); |
398 | case FILFIFO: |
399 | return S_ISFIFO(s.st_mode); |
400 | case FILSOCK: |
401 | return S_ISSOCK(s.st_mode); |
402 | case FILSYM: |
403 | return S_ISLNK(s.st_mode); |
404 | case FILSUID: |
405 | return (s.st_mode & S_ISUID) != 0; |
406 | case FILSGID: |
407 | return (s.st_mode & S_ISGID) != 0; |
408 | case FILSTCK: |
409 | return (s.st_mode & S_ISVTX) != 0; |
410 | case FILGZ: |
411 | return !!s.st_size; |
412 | case FILUID: |
413 | return s.st_uid == geteuid(); |
414 | case FILGID: |
415 | return s.st_gid == getegid(); |
416 | default: |
417 | return 1; |
418 | } |
419 | } |
420 | |
421 | static enum token t_lex(char **tp) |
422 | { |
423 | struct t_op const *op; |
424 | char *s = *tp; |
425 | |
426 | if (s == 0) { |
427 | t_wp_op = (struct t_op *)0; |
428 | return EOI; |
429 | } |
430 | |
431 | op = getop(s); |
432 | if (op && !(op->op_type == UNOP && isoperand(tp)) && |
433 | !(op->op_num == LPAREN && !tp[1])) { |
434 | t_wp_op = op; |
435 | return op->op_num; |
436 | } |
437 | |
438 | t_wp_op = (struct t_op *)0; |
439 | return OPERAND; |
440 | } |
441 | |
442 | static int isoperand(char **tp) |
443 | { |
444 | struct t_op const *op; |
445 | char *s; |
446 | |
447 | if (!(s = tp[1])) |
448 | return 1; |
449 | if (!tp[2]) |
450 | return 0; |
451 | |
452 | op = getop(s); |
453 | return op && op->op_type == BINOP; |
454 | } |
455 | |
456 | static int |
457 | newerf (const char *f1, const char *f2) |
458 | { |
459 | struct stat b1, b2; |
460 | |
461 | return (stat (f1, &b1) == 0 && |
462 | stat (f2, &b2) == 0 && |
463 | b1.st_mtime > b2.st_mtime); |
464 | } |
465 | |
466 | static int |
467 | olderf (const char *f1, const char *f2) |
468 | { |
469 | struct stat b1, b2; |
470 | |
471 | return (stat (f1, &b1) == 0 && |
472 | stat (f2, &b2) == 0 && |
473 | b1.st_mtime < b2.st_mtime); |
474 | } |
475 | |
476 | static int |
477 | equalf (const char *f1, const char *f2) |
478 | { |
479 | struct stat b1, b2; |
480 | |
481 | return (stat (f1, &b1) == 0 && |
482 | stat (f2, &b2) == 0 && |
483 | b1.st_dev == b2.st_dev && |
484 | b1.st_ino == b2.st_ino); |
485 | } |
486 | |
487 | #ifdef HAVE_FACCESSAT |
488 | static int test_file_access(const char *path, int mode) |
489 | { |
490 | return !faccessat(AT_FDCWD, path, mode, AT_EACCESS); |
491 | } |
492 | #else /* HAVE_FACCESSAT */ |
493 | /* |
494 | * Similar to what access(2) does, but uses the effective uid and gid. |
495 | * Doesn't make the mistake of telling root that any file is executable. |
496 | * Returns non-zero if the file is accessible. |
497 | */ |
498 | static int |
499 | test_st_mode(const struct stat64 *st, int mode) |
500 | { |
501 | int euid = geteuid(); |
502 | |
503 | if (euid == 0) { |
504 | /* Root can read or write any file. */ |
505 | if (mode != X_OK) |
506 | return 1; |
507 | |
508 | /* Root can execute any file that has any one of the execute |
509 | bits set. */ |
510 | mode = S_IXUSR | S_IXGRP | S_IXOTH; |
511 | } else if (st->st_uid == euid) |
512 | mode <<= 6; |
513 | else if (bash_group_member(st->st_gid)) |
514 | mode <<= 3; |
515 | |
516 | return st->st_mode & mode; |
517 | } |
518 | |
519 | /* Return non-zero if GID is one that we have in our groups list. */ |
520 | static int |
521 | bash_group_member(gid_t gid) |
522 | { |
523 | register int i; |
524 | gid_t *group_array; |
525 | int ngroups; |
526 | |
527 | /* Short-circuit if possible, maybe saving a call to getgroups(). */ |
528 | if (gid == getgid() || gid == getegid()) |
529 | return (1); |
530 | |
531 | ngroups = getgroups(0, NULL); |
532 | group_array = stalloc(ngroups * sizeof(gid_t)); |
533 | if ((getgroups(ngroups, group_array)) != ngroups) |
534 | return (0); |
535 | |
536 | /* Search through the list looking for GID. */ |
537 | for (i = 0; i < ngroups; i++) |
538 | if (gid == group_array[i]) |
539 | return (1); |
540 | |
541 | return (0); |
542 | } |
543 | #endif /* HAVE_FACCESSAT */ |