]>
Commit | Line | Data |
---|---|---|
71aad674 | 1 | /* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ |
44bd5ea7 | 2 | |
71aad674 | 3 | /*- |
44bd5ea7 A |
4 | * test(1); version 7-like -- author Erik Baalbergen |
5 | * modified by Eric Gisin to be used as built-in. | |
6 | * modified by Arnold Robbins to add SVR3 compatibility | |
7 | * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). | |
8 | * modified by J.T. Conklin for NetBSD. | |
9 | * | |
10 | * This program is in the Public Domain. | |
11 | */ | |
71aad674 A |
12 | /* |
13 | * Important: This file is used both as a standalone program /bin/test and | |
14 | * as a builtin for /bin/sh (#define SHELL). | |
15 | */ | |
44bd5ea7 A |
16 | |
17 | #include <sys/cdefs.h> | |
71aad674 | 18 | __FBSDID("$FreeBSD$"); |
44bd5ea7 A |
19 | |
20 | #include <sys/types.h> | |
21 | #include <sys/stat.h> | |
71aad674 | 22 | |
44bd5ea7 | 23 | #include <ctype.h> |
71aad674 | 24 | #include <err.h> |
44bd5ea7 | 25 | #include <errno.h> |
71aad674 A |
26 | #ifdef __APPLE__ |
27 | #include <fcntl.h> | |
28 | #endif /* __APPLE__ */ | |
29 | #include <inttypes.h> | |
30 | #include <limits.h> | |
31 | #include <stdarg.h> | |
44bd5ea7 A |
32 | #include <stdio.h> |
33 | #include <stdlib.h> | |
34 | #include <string.h> | |
71aad674 A |
35 | #include <unistd.h> |
36 | ||
37 | #ifdef __APPLE__ | |
38 | #define eaccess(path, mode) faccessat(AT_FDCWD, path, mode, AT_EACCESS) | |
39 | #define st_mtim st_mtimespec | |
40 | #endif /* __APPLE__ */ | |
41 | ||
42 | #ifdef SHELL | |
43 | #define main testcmd | |
44 | #include "bltin/bltin.h" | |
45 | #else | |
46 | #include <locale.h> | |
47 | ||
48 | static void error(const char *, ...) __dead2 __printf0like(1, 2); | |
49 | ||
50 | static void | |
51 | error(const char *msg, ...) | |
52 | { | |
53 | va_list ap; | |
54 | va_start(ap, msg); | |
55 | verrx(2, msg, ap); | |
56 | /*NOTREACHED*/ | |
57 | va_end(ap); | |
58 | } | |
59 | #endif | |
44bd5ea7 A |
60 | |
61 | /* test(1) accepts the following grammar: | |
62 | oexpr ::= aexpr | aexpr "-o" oexpr ; | |
63 | aexpr ::= nexpr | nexpr "-a" aexpr ; | |
64 | nexpr ::= primary | "!" primary | |
65 | primary ::= unary-operator operand | |
66 | | operand binary-operator operand | |
67 | | operand | |
68 | | "(" oexpr ")" | |
69 | ; | |
70 | unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| | |
71 | "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; | |
72 | ||
73 | binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| | |
74 | "-nt"|"-ot"|"-ef"; | |
75 | operand ::= <any legal UNIX file name> | |
76 | */ | |
77 | ||
71aad674 A |
78 | enum token_types { |
79 | UNOP = 0x100, | |
80 | BINOP = 0x200, | |
81 | BUNOP = 0x300, | |
82 | BBINOP = 0x400, | |
83 | PAREN = 0x500 | |
84 | }; | |
85 | ||
44bd5ea7 A |
86 | enum token { |
87 | EOI, | |
71aad674 A |
88 | OPERAND, |
89 | FILRD = UNOP + 1, | |
44bd5ea7 A |
90 | FILWR, |
91 | FILEX, | |
92 | FILEXIST, | |
93 | FILREG, | |
94 | FILDIR, | |
95 | FILCDEV, | |
96 | FILBDEV, | |
97 | FILFIFO, | |
98 | FILSOCK, | |
99 | FILSYM, | |
100 | FILGZ, | |
101 | FILTT, | |
102 | FILSUID, | |
103 | FILSGID, | |
104 | FILSTCK, | |
44bd5ea7 A |
105 | STREZ, |
106 | STRNZ, | |
71aad674 A |
107 | FILUID, |
108 | FILGID, | |
109 | FILNT = BINOP + 1, | |
110 | FILOT, | |
111 | FILEQ, | |
44bd5ea7 A |
112 | STREQ, |
113 | STRNE, | |
114 | STRLT, | |
115 | STRGT, | |
116 | INTEQ, | |
117 | INTNE, | |
118 | INTGE, | |
119 | INTGT, | |
120 | INTLE, | |
121 | INTLT, | |
71aad674 A |
122 | UNOT = BUNOP + 1, |
123 | BAND = BBINOP + 1, | |
44bd5ea7 | 124 | BOR, |
71aad674 A |
125 | LPAREN = PAREN + 1, |
126 | RPAREN | |
44bd5ea7 A |
127 | }; |
128 | ||
71aad674 | 129 | #define TOKEN_TYPE(token) ((token) & 0xff00) |
44bd5ea7 | 130 | |
71aad674 A |
131 | static struct t_op { |
132 | char op_text[4]; | |
133 | short op_num; | |
44bd5ea7 | 134 | } const ops [] = { |
71aad674 A |
135 | {"-r", FILRD}, |
136 | {"-w", FILWR}, | |
137 | {"-x", FILEX}, | |
138 | {"-e", FILEXIST}, | |
139 | {"-f", FILREG}, | |
140 | {"-d", FILDIR}, | |
141 | {"-c", FILCDEV}, | |
142 | {"-b", FILBDEV}, | |
143 | {"-p", FILFIFO}, | |
144 | {"-u", FILSUID}, | |
145 | {"-g", FILSGID}, | |
146 | {"-k", FILSTCK}, | |
147 | {"-s", FILGZ}, | |
148 | {"-t", FILTT}, | |
149 | {"-z", STREZ}, | |
150 | {"-n", STRNZ}, | |
151 | {"-h", FILSYM}, /* for backwards compat */ | |
152 | {"-O", FILUID}, | |
153 | {"-G", FILGID}, | |
154 | {"-L", FILSYM}, | |
155 | {"-S", FILSOCK}, | |
156 | {"=", STREQ}, | |
157 | {"==", STREQ}, | |
158 | {"!=", STRNE}, | |
159 | {"<", STRLT}, | |
160 | {">", STRGT}, | |
161 | {"-eq", INTEQ}, | |
162 | {"-ne", INTNE}, | |
163 | {"-ge", INTGE}, | |
164 | {"-gt", INTGT}, | |
165 | {"-le", INTLE}, | |
166 | {"-lt", INTLT}, | |
167 | {"-nt", FILNT}, | |
168 | {"-ot", FILOT}, | |
169 | {"-ef", FILEQ}, | |
170 | {"!", UNOT}, | |
171 | {"-a", BAND}, | |
172 | {"-o", BOR}, | |
173 | {"(", LPAREN}, | |
174 | {")", RPAREN}, | |
175 | {"", 0} | |
44bd5ea7 A |
176 | }; |
177 | ||
71aad674 A |
178 | static int nargc; |
179 | static char **t_wp; | |
180 | static int parenlevel; | |
181 | ||
182 | static int aexpr(enum token); | |
183 | static int binop(enum token); | |
184 | static int equalf(const char *, const char *); | |
185 | static int filstat(char *, enum token); | |
186 | static int getn(const char *); | |
187 | static intmax_t getq(const char *); | |
188 | static int intcmp(const char *, const char *); | |
189 | static int isunopoperand(void); | |
190 | static int islparenoperand(void); | |
191 | static int isrparenoperand(void); | |
192 | static int newerf(const char *, const char *); | |
193 | static int nexpr(enum token); | |
194 | static int oexpr(enum token); | |
195 | static int olderf(const char *, const char *); | |
196 | static int primary(enum token); | |
197 | static void syntax(const char *, const char *); | |
198 | static enum token t_lex(char *); | |
44bd5ea7 A |
199 | |
200 | int | |
71aad674 | 201 | main(int argc, char **argv) |
44bd5ea7 A |
202 | { |
203 | int res; | |
71aad674 A |
204 | char *p; |
205 | ||
206 | /* radar:4689479 */ | |
207 | if (argc == 0) | |
208 | return 1; | |
44bd5ea7 | 209 | |
71aad674 A |
210 | if ((p = strrchr(argv[0], '/')) == NULL) |
211 | p = argv[0]; | |
212 | else | |
213 | p++; | |
214 | if (strcmp(p, "[") == 0) { | |
215 | if (strcmp(argv[--argc], "]") != 0) | |
216 | error("missing ]"); | |
44bd5ea7 A |
217 | argv[argc] = NULL; |
218 | } | |
219 | ||
71aad674 A |
220 | /* no expression => false */ |
221 | if (--argc <= 0) | |
44bd5ea7 | 222 | return 1; |
44bd5ea7 | 223 | |
71aad674 A |
224 | #ifndef SHELL |
225 | (void)setlocale(LC_CTYPE, ""); | |
226 | #endif | |
227 | nargc = argc; | |
44bd5ea7 | 228 | t_wp = &argv[1]; |
71aad674 A |
229 | parenlevel = 0; |
230 | if (nargc == 4 && strcmp(*t_wp, "!") == 0) { | |
231 | /* Things like ! "" -o x do not fit in the normal grammar. */ | |
232 | --nargc; | |
233 | ++t_wp; | |
234 | res = oexpr(t_lex(*t_wp)); | |
235 | } else | |
236 | res = !oexpr(t_lex(*t_wp)); | |
237 | ||
238 | if (--nargc > 0) | |
239 | syntax(*t_wp, "unexpected operator"); | |
44bd5ea7 A |
240 | |
241 | return res; | |
242 | } | |
243 | ||
244 | static void | |
71aad674 | 245 | syntax(const char *op, const char *msg) |
44bd5ea7 | 246 | { |
71aad674 | 247 | |
44bd5ea7 | 248 | if (op && *op) |
71aad674 | 249 | error("%s: %s", op, msg); |
44bd5ea7 | 250 | else |
71aad674 | 251 | error("%s", msg); |
44bd5ea7 A |
252 | } |
253 | ||
254 | static int | |
71aad674 | 255 | oexpr(enum token n) |
44bd5ea7 A |
256 | { |
257 | int res; | |
258 | ||
259 | res = aexpr(n); | |
71aad674 A |
260 | if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) |
261 | return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || | |
262 | res; | |
44bd5ea7 | 263 | t_wp--; |
71aad674 | 264 | nargc++; |
44bd5ea7 A |
265 | return res; |
266 | } | |
267 | ||
268 | static int | |
71aad674 | 269 | aexpr(enum token n) |
44bd5ea7 A |
270 | { |
271 | int res; | |
272 | ||
273 | res = nexpr(n); | |
71aad674 A |
274 | if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) |
275 | return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && | |
276 | res; | |
44bd5ea7 | 277 | t_wp--; |
71aad674 | 278 | nargc++; |
44bd5ea7 A |
279 | return res; |
280 | } | |
281 | ||
282 | static int | |
71aad674 | 283 | nexpr(enum token n) |
44bd5ea7 A |
284 | { |
285 | if (n == UNOT) | |
71aad674 | 286 | return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); |
44bd5ea7 A |
287 | return primary(n); |
288 | } | |
289 | ||
290 | static int | |
71aad674 | 291 | primary(enum token n) |
44bd5ea7 | 292 | { |
71aad674 | 293 | enum token nn; |
44bd5ea7 A |
294 | int res; |
295 | ||
296 | if (n == EOI) | |
71aad674 | 297 | return 0; /* missing expression */ |
44bd5ea7 | 298 | if (n == LPAREN) { |
71aad674 A |
299 | parenlevel++; |
300 | if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == | |
301 | RPAREN) { | |
302 | parenlevel--; | |
303 | return 0; /* missing expression */ | |
304 | } | |
305 | res = oexpr(nn); | |
306 | if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) | |
44bd5ea7 | 307 | syntax(NULL, "closing paren expected"); |
71aad674 | 308 | parenlevel--; |
44bd5ea7 A |
309 | return res; |
310 | } | |
71aad674 | 311 | if (TOKEN_TYPE(n) == UNOP) { |
44bd5ea7 | 312 | /* unary expression */ |
71aad674 A |
313 | if (--nargc == 0) |
314 | syntax(NULL, "argument expected"); /* impossible */ | |
44bd5ea7 A |
315 | switch (n) { |
316 | case STREZ: | |
71aad674 | 317 | return strlen(*++t_wp) == 0; |
44bd5ea7 | 318 | case STRNZ: |
71aad674 | 319 | return strlen(*++t_wp) != 0; |
44bd5ea7 | 320 | case FILTT: |
71aad674 | 321 | return isatty(getn(*++t_wp)); |
44bd5ea7 | 322 | default: |
71aad674 | 323 | return filstat(*++t_wp, n); |
44bd5ea7 A |
324 | } |
325 | } | |
326 | ||
71aad674 A |
327 | nn = t_lex(nargc > 0 ? t_wp[1] : NULL); |
328 | if (TOKEN_TYPE(nn) == BINOP) | |
329 | return binop(nn); | |
44bd5ea7 A |
330 | |
331 | return strlen(*t_wp) > 0; | |
332 | } | |
333 | ||
334 | static int | |
71aad674 | 335 | binop(enum token n) |
44bd5ea7 | 336 | { |
71aad674 | 337 | const char *opnd1, *op, *opnd2; |
44bd5ea7 A |
338 | |
339 | opnd1 = *t_wp; | |
71aad674 | 340 | op = nargc > 0 ? (--nargc, *++t_wp) : NULL; |
44bd5ea7 | 341 | |
71aad674 A |
342 | if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) |
343 | syntax(op, "argument expected"); | |
344 | ||
345 | switch (n) { | |
44bd5ea7 A |
346 | case STREQ: |
347 | return strcmp(opnd1, opnd2) == 0; | |
348 | case STRNE: | |
349 | return strcmp(opnd1, opnd2) != 0; | |
350 | case STRLT: | |
351 | return strcmp(opnd1, opnd2) < 0; | |
352 | case STRGT: | |
353 | return strcmp(opnd1, opnd2) > 0; | |
354 | case INTEQ: | |
71aad674 | 355 | return intcmp(opnd1, opnd2) == 0; |
44bd5ea7 | 356 | case INTNE: |
71aad674 | 357 | return intcmp(opnd1, opnd2) != 0; |
44bd5ea7 | 358 | case INTGE: |
71aad674 | 359 | return intcmp(opnd1, opnd2) >= 0; |
44bd5ea7 | 360 | case INTGT: |
71aad674 | 361 | return intcmp(opnd1, opnd2) > 0; |
44bd5ea7 | 362 | case INTLE: |
71aad674 | 363 | return intcmp(opnd1, opnd2) <= 0; |
44bd5ea7 | 364 | case INTLT: |
71aad674 | 365 | return intcmp(opnd1, opnd2) < 0; |
44bd5ea7 A |
366 | case FILNT: |
367 | return newerf (opnd1, opnd2); | |
368 | case FILOT: | |
369 | return olderf (opnd1, opnd2); | |
370 | case FILEQ: | |
371 | return equalf (opnd1, opnd2); | |
372 | default: | |
373 | abort(); | |
374 | /* NOTREACHED */ | |
375 | } | |
376 | } | |
377 | ||
378 | static int | |
71aad674 | 379 | filstat(char *nm, enum token mode) |
44bd5ea7 A |
380 | { |
381 | struct stat s; | |
382 | ||
383 | if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) | |
384 | return 0; | |
385 | ||
386 | switch (mode) { | |
387 | case FILRD: | |
71aad674 | 388 | return (eaccess(nm, R_OK) == 0); |
44bd5ea7 | 389 | case FILWR: |
71aad674 | 390 | return (eaccess(nm, W_OK) == 0); |
44bd5ea7 | 391 | case FILEX: |
71aad674 A |
392 | /* XXX work around eaccess(2) false positives for superuser */ |
393 | if (eaccess(nm, X_OK) != 0) | |
394 | return 0; | |
395 | if (S_ISDIR(s.st_mode) || geteuid() != 0) | |
396 | return 1; | |
397 | return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; | |
44bd5ea7 | 398 | case FILEXIST: |
71aad674 | 399 | return (eaccess(nm, F_OK) == 0); |
44bd5ea7 A |
400 | case FILREG: |
401 | return S_ISREG(s.st_mode); | |
402 | case FILDIR: | |
403 | return S_ISDIR(s.st_mode); | |
404 | case FILCDEV: | |
405 | return S_ISCHR(s.st_mode); | |
406 | case FILBDEV: | |
407 | return S_ISBLK(s.st_mode); | |
408 | case FILFIFO: | |
409 | return S_ISFIFO(s.st_mode); | |
410 | case FILSOCK: | |
411 | return S_ISSOCK(s.st_mode); | |
412 | case FILSYM: | |
413 | return S_ISLNK(s.st_mode); | |
414 | case FILSUID: | |
415 | return (s.st_mode & S_ISUID) != 0; | |
416 | case FILSGID: | |
417 | return (s.st_mode & S_ISGID) != 0; | |
418 | case FILSTCK: | |
419 | return (s.st_mode & S_ISVTX) != 0; | |
420 | case FILGZ: | |
421 | return s.st_size > (off_t)0; | |
422 | case FILUID: | |
423 | return s.st_uid == geteuid(); | |
424 | case FILGID: | |
425 | return s.st_gid == getegid(); | |
426 | default: | |
427 | return 1; | |
428 | } | |
429 | } | |
430 | ||
431 | static enum token | |
71aad674 | 432 | t_lex(char *s) |
44bd5ea7 A |
433 | { |
434 | struct t_op const *op = ops; | |
435 | ||
436 | if (s == 0) { | |
44bd5ea7 A |
437 | return EOI; |
438 | } | |
71aad674 | 439 | while (*op->op_text) { |
44bd5ea7 | 440 | if (strcmp(s, op->op_text) == 0) { |
71aad674 A |
441 | if (((TOKEN_TYPE(op->op_num) == UNOP || |
442 | TOKEN_TYPE(op->op_num) == BUNOP) | |
443 | && isunopoperand()) || | |
444 | (op->op_num == LPAREN && islparenoperand()) || | |
445 | (op->op_num == RPAREN && isrparenoperand())) | |
446 | break; | |
44bd5ea7 A |
447 | return op->op_num; |
448 | } | |
449 | op++; | |
450 | } | |
44bd5ea7 A |
451 | return OPERAND; |
452 | } | |
453 | ||
71aad674 A |
454 | static int |
455 | isunopoperand(void) | |
456 | { | |
457 | struct t_op const *op = ops; | |
458 | char *s; | |
459 | char *t; | |
460 | ||
461 | if (nargc == 1) | |
462 | return 1; | |
463 | s = *(t_wp + 1); | |
464 | if (nargc == 2) | |
465 | return parenlevel == 1 && strcmp(s, ")") == 0; | |
466 | t = *(t_wp + 2); | |
467 | while (*op->op_text) { | |
468 | if (strcmp(s, op->op_text) == 0) | |
469 | return TOKEN_TYPE(op->op_num) == BINOP && | |
470 | (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); | |
471 | op++; | |
472 | } | |
473 | return 0; | |
474 | } | |
475 | ||
476 | static int | |
477 | islparenoperand(void) | |
478 | { | |
479 | struct t_op const *op = ops; | |
480 | char *s; | |
481 | ||
482 | if (nargc == 1) | |
483 | return 1; | |
484 | s = *(t_wp + 1); | |
485 | if (nargc == 2) | |
486 | return parenlevel == 1 && strcmp(s, ")") == 0; | |
487 | if (nargc != 3) | |
488 | return 0; | |
489 | while (*op->op_text) { | |
490 | if (strcmp(s, op->op_text) == 0) | |
491 | return TOKEN_TYPE(op->op_num) == BINOP; | |
492 | op++; | |
493 | } | |
494 | return 0; | |
495 | } | |
496 | ||
497 | static int | |
498 | isrparenoperand(void) | |
499 | { | |
500 | char *s; | |
501 | ||
502 | if (nargc == 1) | |
503 | return 0; | |
504 | s = *(t_wp + 1); | |
505 | if (nargc == 2) | |
506 | return parenlevel == 1 && strcmp(s, ")") == 0; | |
507 | return 0; | |
508 | } | |
509 | ||
44bd5ea7 A |
510 | /* atoi with error detection */ |
511 | static int | |
71aad674 | 512 | getn(const char *s) |
44bd5ea7 A |
513 | { |
514 | char *p; | |
515 | long r; | |
516 | ||
517 | errno = 0; | |
518 | r = strtol(s, &p, 10); | |
519 | ||
71aad674 A |
520 | if (s == p) |
521 | error("%s: bad number", s); | |
522 | ||
44bd5ea7 | 523 | if (errno != 0) |
71aad674 A |
524 | error((errno == EINVAL) ? "%s: bad number" : |
525 | "%s: out of range", s); | |
526 | ||
527 | while (isspace((unsigned char)*p)) | |
528 | p++; | |
44bd5ea7 | 529 | |
44bd5ea7 | 530 | if (*p) |
71aad674 | 531 | error("%s: bad number", s); |
44bd5ea7 A |
532 | |
533 | return (int) r; | |
534 | } | |
535 | ||
71aad674 A |
536 | /* atoi with error detection and 64 bit range */ |
537 | static intmax_t | |
538 | getq(const char *s) | |
539 | { | |
540 | char *p; | |
541 | intmax_t r; | |
542 | ||
543 | errno = 0; | |
544 | r = strtoimax(s, &p, 10); | |
545 | ||
546 | if (s == p) | |
547 | error("%s: bad number", s); | |
548 | ||
549 | if (errno != 0) | |
550 | error((errno == EINVAL) ? "%s: bad number" : | |
551 | "%s: out of range", s); | |
552 | ||
553 | while (isspace((unsigned char)*p)) | |
554 | p++; | |
555 | ||
556 | if (*p) | |
557 | error("%s: bad number", s); | |
558 | ||
559 | return r; | |
560 | } | |
561 | ||
44bd5ea7 | 562 | static int |
71aad674 | 563 | intcmp (const char *s1, const char *s2) |
44bd5ea7 | 564 | { |
71aad674 | 565 | intmax_t q1, q2; |
44bd5ea7 | 566 | |
71aad674 A |
567 | |
568 | q1 = getq(s1); | |
569 | q2 = getq(s2); | |
570 | ||
571 | if (q1 > q2) | |
572 | return 1; | |
573 | ||
574 | if (q1 < q2) | |
575 | return -1; | |
576 | ||
577 | return 0; | |
44bd5ea7 A |
578 | } |
579 | ||
580 | static int | |
71aad674 | 581 | newerf (const char *f1, const char *f2) |
44bd5ea7 A |
582 | { |
583 | struct stat b1, b2; | |
584 | ||
71aad674 A |
585 | if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) |
586 | return 0; | |
587 | ||
588 | if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec) | |
589 | return 1; | |
590 | if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec) | |
591 | return 0; | |
592 | ||
593 | return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec); | |
594 | } | |
595 | ||
596 | static int | |
597 | olderf (const char *f1, const char *f2) | |
598 | { | |
599 | return (newerf(f2, f1)); | |
44bd5ea7 A |
600 | } |
601 | ||
602 | static int | |
71aad674 | 603 | equalf (const char *f1, const char *f2) |
44bd5ea7 A |
604 | { |
605 | struct stat b1, b2; | |
606 | ||
607 | return (stat (f1, &b1) == 0 && | |
608 | stat (f2, &b2) == 0 && | |
609 | b1.st_dev == b2.st_dev && | |
610 | b1.st_ino == b2.st_ino); | |
611 | } |