Annotation of /trunk/mkinitrd-magellan/klibc/usr/dash/bltin/test.c
Parent Directory | Revision Log
Revision 1122 -
(hide 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 | niro | 532 | /* |
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 | niro | 1122 | #include <fcntl.h> |
15 | #include <stdint.h> | ||
16 | niro | 532 | #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 | niro | 1122 | static enum token t_lex(char **); |
147 | static int isoperand(char **); | ||
148 | niro | 532 | static int newerf(const char *, const char *); |
149 | static int olderf(const char *, const char *); | ||
150 | static int equalf(const char *, const char *); | ||
151 | niro | 1122 | #ifdef HAVE_FACCESSAT |
152 | static int test_file_access(const char *, int); | ||
153 | #else | ||
154 | niro | 532 | static int test_st_mode(const struct stat64 *, int); |
155 | static int bash_group_member(gid_t); | ||
156 | niro | 1122 | #endif |
157 | niro | 532 | |
158 | niro | 1122 | 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 | niro | 532 | int |
176 | testcmd(int argc, char **argv) | ||
177 | { | ||
178 | niro | 1122 | const struct t_op *op; |
179 | enum token n; | ||
180 | niro | 532 | int res; |
181 | |||
182 | niro | 1122 | if (*argv[0] == '[') { |
183 | if (*argv[--argc] != ']') | ||
184 | niro | 532 | error("missing ]"); |
185 | argv[argc] = NULL; | ||
186 | } | ||
187 | |||
188 | niro | 1122 | argv++; |
189 | argc--; | ||
190 | |||
191 | if (argc < 1) | ||
192 | niro | 532 | return 1; |
193 | |||
194 | niro | 1122 | /* |
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 | niro | 532 | |
207 | niro | 1122 | case 4: |
208 | if (!strcmp(argv[0], "(") && !strcmp(argv[argc - 1], ")")) { | ||
209 | argv[--argc] = NULL; | ||
210 | argv++; | ||
211 | argc--; | ||
212 | } | ||
213 | } | ||
214 | niro | 532 | |
215 | niro | 1122 | 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 | niro | 532 | 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 | niro | 1122 | int res = 0; |
241 | niro | 532 | |
242 | niro | 1122 | 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 | niro | 532 | return res; |
250 | } | ||
251 | |||
252 | static int | ||
253 | aexpr(enum token n) | ||
254 | { | ||
255 | niro | 1122 | int res = 1; |
256 | niro | 532 | |
257 | niro | 1122 | 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 | niro | 532 | return res; |
266 | } | ||
267 | |||
268 | static int | ||
269 | nexpr(enum token n) | ||
270 | { | ||
271 | if (n == UNOT) | ||
272 | niro | 1122 | return !nexpr(t_lex(++t_wp)); |
273 | niro | 532 | 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 | niro | 1122 | if ((nn = t_lex(++t_wp)) == RPAREN) |
286 | niro | 532 | return 0; /* missing expression */ |
287 | res = oexpr(nn); | ||
288 | niro | 1122 | if (t_lex(++t_wp) != RPAREN) |
289 | niro | 532 | 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 | niro | 1122 | #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 | niro | 532 | default: |
312 | return filstat(*t_wp, n); | ||
313 | } | ||
314 | } | ||
315 | |||
316 | niro | 1122 | if (t_lex(t_wp + 1), t_wp_op && t_wp_op->op_type == BINOP) { |
317 | niro | 532 | 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 | niro | 1122 | (void) t_lex(++t_wp); |
331 | niro | 532 | 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 | niro | 1122 | #ifndef HAVE_FACCESSAT |
381 | niro | 532 | 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 | niro | 1122 | #endif |
388 | niro | 532 | 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 | niro | 1122 | static enum token t_lex(char **tp) |
422 | niro | 532 | { |
423 | struct t_op const *op; | ||
424 | niro | 1122 | char *s = *tp; |
425 | niro | 532 | |
426 | if (s == 0) { | ||
427 | t_wp_op = (struct t_op *)0; | ||
428 | return EOI; | ||
429 | } | ||
430 | niro | 1122 | |
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 | niro | 532 | } |
437 | niro | 1122 | |
438 | niro | 532 | t_wp_op = (struct t_op *)0; |
439 | return OPERAND; | ||
440 | } | ||
441 | |||
442 | niro | 1122 | static int isoperand(char **tp) |
443 | niro | 532 | { |
444 | struct t_op const *op; | ||
445 | niro | 1122 | char *s; |
446 | niro | 532 | |
447 | niro | 1122 | if (!(s = tp[1])) |
448 | niro | 532 | return 1; |
449 | niro | 1122 | if (!tp[2]) |
450 | niro | 532 | return 0; |
451 | |||
452 | niro | 1122 | op = getop(s); |
453 | return op && op->op_type == BINOP; | ||
454 | niro | 532 | } |
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 | niro | 1122 | #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 | niro | 532 | /* |
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 | niro | 815 | if ((getgroups(ngroups, group_array)) != ngroups) |
534 | return (0); | ||
535 | niro | 532 | |
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 | niro | 1122 | #endif /* HAVE_FACCESSAT */ |