xref: /freebsd/bin/test/test.c (revision d184218c)
1 /*	$NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $	*/
2 
3 /*-
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  */
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  */
16 
17 #include <sys/cdefs.h>
18 __FBSDID("$FreeBSD$");
19 
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 
23 #include <ctype.h>
24 #include <err.h>
25 #include <errno.h>
26 #include <inttypes.h>
27 #include <limits.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #ifdef SHELL
35 #define main testcmd
36 #include "bltin/bltin.h"
37 #else
38 #include <locale.h>
39 
40 static void error(const char *, ...) __dead2 __printf0like(1, 2);
41 
42 static void
43 error(const char *msg, ...)
44 {
45 	va_list ap;
46 	va_start(ap, msg);
47 	verrx(2, msg, ap);
48 	/*NOTREACHED*/
49 	va_end(ap);
50 }
51 #endif
52 
53 /* test(1) accepts the following grammar:
54 	oexpr	::= aexpr | aexpr "-o" oexpr ;
55 	aexpr	::= nexpr | nexpr "-a" aexpr ;
56 	nexpr	::= primary | "!" primary
57 	primary	::= unary-operator operand
58 		| operand binary-operator operand
59 		| operand
60 		| "(" oexpr ")"
61 		;
62 	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
63 		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
64 
65 	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
66 			"-nt"|"-nt[abcm][abcm]"|"-ot"|"-ot[abcm][abcm])"|"-ef";
67 	operand ::= <any legal UNIX file name>
68 */
69 
70 enum token {
71 	EOI,
72 	FILRD,
73 	FILWR,
74 	FILEX,
75 	FILEXIST,
76 	FILREG,
77 	FILDIR,
78 	FILCDEV,
79 	FILBDEV,
80 	FILFIFO,
81 	FILSOCK,
82 	FILSYM,
83 	FILGZ,
84 	FILTT,
85 	FILSUID,
86 	FILSGID,
87 	FILSTCK,
88 	FILNTAA,
89 	FILNTAB,
90 	FILNTAC,
91 	FILNTAM,
92 	FILNTBA,
93 	FILNTBB,
94 	FILNTBC,
95 	FILNTBM,
96 	FILNTCA,
97 	FILNTCB,
98 	FILNTCC,
99 	FILNTCM,
100 	FILNTMA,
101 	FILNTMB,
102 	FILNTMC,
103 	FILNTMM,
104 	FILOTAA,
105 	FILOTAB,
106 	FILOTAC,
107 	FILOTAM,
108 	FILOTBA,
109 	FILOTBB,
110 	FILOTBC,
111 	FILOTBM,
112 	FILOTCA,
113 	FILOTCB,
114 	FILOTCC,
115 	FILOTCM,
116 	FILOTMA,
117 	FILOTMB,
118 	FILOTMC,
119 	FILOTMM,
120 	FILEQ,
121 	FILUID,
122 	FILGID,
123 	STREZ,
124 	STRNZ,
125 	STREQ,
126 	STRNE,
127 	STRLT,
128 	STRGT,
129 	INTEQ,
130 	INTNE,
131 	INTGE,
132 	INTGT,
133 	INTLE,
134 	INTLT,
135 	UNOT,
136 	BAND,
137 	BOR,
138 	LPAREN,
139 	RPAREN,
140 	OPERAND
141 };
142 
143 enum token_types {
144 	UNOP,
145 	BINOP,
146 	BUNOP,
147 	BBINOP,
148 	PAREN
149 };
150 
151 enum time_types {
152 	ATIME,
153 	BTIME,
154 	CTIME,
155 	MTIME
156 };
157 
158 static struct t_op {
159 	char op_text[6];
160 	char op_num, op_type;
161 } const ops [] = {
162 	{"-r",	FILRD,	UNOP},
163 	{"-w",	FILWR,	UNOP},
164 	{"-x",	FILEX,	UNOP},
165 	{"-e",	FILEXIST,UNOP},
166 	{"-f",	FILREG,	UNOP},
167 	{"-d",	FILDIR,	UNOP},
168 	{"-c",	FILCDEV,UNOP},
169 	{"-b",	FILBDEV,UNOP},
170 	{"-p",	FILFIFO,UNOP},
171 	{"-u",	FILSUID,UNOP},
172 	{"-g",	FILSGID,UNOP},
173 	{"-k",	FILSTCK,UNOP},
174 	{"-s",	FILGZ,	UNOP},
175 	{"-t",	FILTT,	UNOP},
176 	{"-z",	STREZ,	UNOP},
177 	{"-n",	STRNZ,	UNOP},
178 	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
179 	{"-O",	FILUID,	UNOP},
180 	{"-G",	FILGID,	UNOP},
181 	{"-L",	FILSYM,	UNOP},
182 	{"-S",	FILSOCK,UNOP},
183 	{"=",	STREQ,	BINOP},
184 	{"==",	STREQ,	BINOP},
185 	{"!=",	STRNE,	BINOP},
186 	{"<",	STRLT,	BINOP},
187 	{">",	STRGT,	BINOP},
188 	{"-eq",	INTEQ,	BINOP},
189 	{"-ne",	INTNE,	BINOP},
190 	{"-ge",	INTGE,	BINOP},
191 	{"-gt",	INTGT,	BINOP},
192 	{"-le",	INTLE,	BINOP},
193 	{"-lt",	INTLT,	BINOP},
194 	{"-nt",	FILNTMM,	BINOP},
195 	{"-ntaa",	FILNTAA,	BINOP},
196 	{"-ntab",	FILNTAB,	BINOP},
197 	{"-ntac",	FILNTAC,	BINOP},
198 	{"-ntam",	FILNTAM,	BINOP},
199 	{"-ntba",	FILNTBA,	BINOP},
200 	{"-ntbb",	FILNTBB,	BINOP},
201 	{"-ntbc",	FILNTBC,	BINOP},
202 	{"-ntbm",	FILNTBM,	BINOP},
203 	{"-ntca",	FILNTCA,	BINOP},
204 	{"-ntcb",	FILNTCB,	BINOP},
205 	{"-ntcc",	FILNTCC,	BINOP},
206 	{"-ntcm",	FILNTCM,	BINOP},
207 	{"-ntma",	FILNTMA,	BINOP},
208 	{"-ntmb",	FILNTMB,	BINOP},
209 	{"-ntmc",	FILNTMC,	BINOP},
210 	{"-ntmm",	FILNTMM,	BINOP},
211 	{"-ot",	FILOTMM,	BINOP},
212 	{"-otaa",	FILOTAA,	BINOP},
213 	{"-otab",	FILOTBB,	BINOP},
214 	{"-otac",	FILOTAC,	BINOP},
215 	{"-otam",	FILOTAM,	BINOP},
216 	{"-otba",	FILOTBA,	BINOP},
217 	{"-otbb",	FILOTBB,	BINOP},
218 	{"-otbc",	FILOTBC,	BINOP},
219 	{"-otbm",	FILOTBM,	BINOP},
220 	{"-otca",	FILOTCA,	BINOP},
221 	{"-otcb",	FILOTCB,	BINOP},
222 	{"-otcc",	FILOTCC,	BINOP},
223 	{"-otcm",	FILOTCM,	BINOP},
224 	{"-otma",	FILOTMA,	BINOP},
225 	{"-otmb",	FILOTMB,	BINOP},
226 	{"-otmc",	FILOTMC,	BINOP},
227 	{"-otmm",	FILOTMM,	BINOP},
228 	{"-ef",	FILEQ,	BINOP},
229 	{"!",	UNOT,	BUNOP},
230 	{"-a",	BAND,	BBINOP},
231 	{"-o",	BOR,	BBINOP},
232 	{"(",	LPAREN,	PAREN},
233 	{")",	RPAREN,	PAREN},
234 	{"",	0,	0}
235 };
236 
237 static struct t_op const *t_wp_op;
238 static int nargc;
239 static char **t_wp;
240 static int parenlevel;
241 
242 static int	aexpr(enum token);
243 static int	binop(void);
244 static int	equalf(const char *, const char *);
245 static int	filstat(char *, enum token);
246 static int	getn(const char *);
247 static intmax_t	getq(const char *);
248 static int	intcmp(const char *, const char *);
249 static int	isunopoperand(void);
250 static int	islparenoperand(void);
251 static int	isrparenoperand(void);
252 static int	newerf(const char *, const char *, enum time_types,
253 		       enum time_types);
254 static int	nexpr(enum token);
255 static int	oexpr(enum token);
256 static int	primary(enum token);
257 static void	syntax(const char *, const char *);
258 static enum	token t_lex(char *);
259 
260 int
261 main(int argc, char **argv)
262 {
263 	int	res;
264 	char	*p;
265 
266 	if ((p = strrchr(argv[0], '/')) == NULL)
267 		p = argv[0];
268 	else
269 		p++;
270 	if (strcmp(p, "[") == 0) {
271 		if (strcmp(argv[--argc], "]") != 0)
272 			error("missing ]");
273 		argv[argc] = NULL;
274 	}
275 
276 	/* no expression => false */
277 	if (--argc <= 0)
278 		return 1;
279 
280 #ifndef SHELL
281 	(void)setlocale(LC_CTYPE, "");
282 #endif
283 	nargc = argc;
284 	t_wp = &argv[1];
285 	parenlevel = 0;
286 	if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
287 		/* Things like ! "" -o x do not fit in the normal grammar. */
288 		--nargc;
289 		++t_wp;
290 		res = oexpr(t_lex(*t_wp));
291 	} else
292 		res = !oexpr(t_lex(*t_wp));
293 
294 	if (--nargc > 0)
295 		syntax(*t_wp, "unexpected operator");
296 
297 	return res;
298 }
299 
300 static void
301 syntax(const char *op, const char *msg)
302 {
303 
304 	if (op && *op)
305 		error("%s: %s", op, msg);
306 	else
307 		error("%s", msg);
308 }
309 
310 static int
311 oexpr(enum token n)
312 {
313 	int res;
314 
315 	res = aexpr(n);
316 	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
317 		return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
318 		    res;
319 	t_wp--;
320 	nargc++;
321 	return res;
322 }
323 
324 static int
325 aexpr(enum token n)
326 {
327 	int res;
328 
329 	res = nexpr(n);
330 	if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
331 		return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
332 		    res;
333 	t_wp--;
334 	nargc++;
335 	return res;
336 }
337 
338 static int
339 nexpr(enum token n)
340 {
341 	if (n == UNOT)
342 		return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
343 	return primary(n);
344 }
345 
346 static int
347 primary(enum token n)
348 {
349 	enum token nn;
350 	int res;
351 
352 	if (n == EOI)
353 		return 0;		/* missing expression */
354 	if (n == LPAREN) {
355 		parenlevel++;
356 		if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
357 		    RPAREN) {
358 			parenlevel--;
359 			return 0;	/* missing expression */
360 		}
361 		res = oexpr(nn);
362 		if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
363 			syntax(NULL, "closing paren expected");
364 		parenlevel--;
365 		return res;
366 	}
367 	if (t_wp_op && t_wp_op->op_type == UNOP) {
368 		/* unary expression */
369 		if (--nargc == 0)
370 			syntax(t_wp_op->op_text, "argument expected");
371 		switch (n) {
372 		case STREZ:
373 			return strlen(*++t_wp) == 0;
374 		case STRNZ:
375 			return strlen(*++t_wp) != 0;
376 		case FILTT:
377 			return isatty(getn(*++t_wp));
378 		default:
379 			return filstat(*++t_wp, n);
380 		}
381 	}
382 
383 	if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type ==
384 	    BINOP) {
385 		return binop();
386 	}
387 
388 	return strlen(*t_wp) > 0;
389 }
390 
391 static int
392 binop(void)
393 {
394 	const char *opnd1, *opnd2;
395 	struct t_op const *op;
396 
397 	opnd1 = *t_wp;
398 	(void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL);
399 	op = t_wp_op;
400 
401 	if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
402 		syntax(op->op_text, "argument expected");
403 
404 	switch (op->op_num) {
405 	case STREQ:
406 		return strcmp(opnd1, opnd2) == 0;
407 	case STRNE:
408 		return strcmp(opnd1, opnd2) != 0;
409 	case STRLT:
410 		return strcmp(opnd1, opnd2) < 0;
411 	case STRGT:
412 		return strcmp(opnd1, opnd2) > 0;
413 	case INTEQ:
414 		return intcmp(opnd1, opnd2) == 0;
415 	case INTNE:
416 		return intcmp(opnd1, opnd2) != 0;
417 	case INTGE:
418 		return intcmp(opnd1, opnd2) >= 0;
419 	case INTGT:
420 		return intcmp(opnd1, opnd2) > 0;
421 	case INTLE:
422 		return intcmp(opnd1, opnd2) <= 0;
423 	case INTLT:
424 		return intcmp(opnd1, opnd2) < 0;
425 	case FILNTAA:
426 		return newerf(opnd1, opnd2, ATIME, ATIME);
427 	case FILNTAB:
428 		return newerf(opnd1, opnd2, ATIME, BTIME);
429 	case FILNTAC:
430 		return newerf(opnd1, opnd2, ATIME, CTIME);
431 	case FILNTAM:
432 		return newerf(opnd1, opnd2, ATIME, MTIME);
433 	case FILNTBA:
434 		return newerf(opnd1, opnd2, BTIME, ATIME);
435 	case FILNTBB:
436 		return newerf(opnd1, opnd2, BTIME, BTIME);
437 	case FILNTBC:
438 		return newerf(opnd1, opnd2, BTIME, CTIME);
439 	case FILNTBM:
440 		return newerf(opnd1, opnd2, BTIME, MTIME);
441 	case FILNTCA:
442 		return newerf(opnd1, opnd2, CTIME, ATIME);
443 	case FILNTCB:
444 		return newerf(opnd1, opnd2, CTIME, BTIME);
445 	case FILNTCC:
446 		return newerf(opnd1, opnd2, CTIME, CTIME);
447 	case FILNTCM:
448 		return newerf(opnd1, opnd2, CTIME, MTIME);
449 	case FILNTMA:
450 		return newerf(opnd1, opnd2, MTIME, ATIME);
451 	case FILNTMB:
452 		return newerf(opnd1, opnd2, MTIME, BTIME);
453 	case FILNTMC:
454 		return newerf(opnd1, opnd2, MTIME, CTIME);
455 	case FILNTMM:
456 		return newerf(opnd1, opnd2, MTIME, MTIME);
457 	case FILOTAA:
458 		return newerf(opnd2, opnd1, ATIME, ATIME);
459 	case FILOTAB:
460 		return newerf(opnd2, opnd1, BTIME, ATIME);
461 	case FILOTAC:
462 		return newerf(opnd2, opnd1, CTIME, ATIME);
463 	case FILOTAM:
464 		return newerf(opnd2, opnd1, MTIME, ATIME);
465 	case FILOTBA:
466 		return newerf(opnd2, opnd1, ATIME, BTIME);
467 	case FILOTBB:
468 		return newerf(opnd2, opnd1, BTIME, BTIME);
469 	case FILOTBC:
470 		return newerf(opnd2, opnd1, CTIME, BTIME);
471 	case FILOTBM:
472 		return newerf(opnd2, opnd1, MTIME, BTIME);
473 	case FILOTCA:
474 		return newerf(opnd2, opnd1, ATIME, CTIME);
475 	case FILOTCB:
476 		return newerf(opnd2, opnd1, BTIME, CTIME);
477 	case FILOTCC:
478 		return newerf(opnd2, opnd1, CTIME, CTIME);
479 	case FILOTCM:
480 		return newerf(opnd2, opnd1, MTIME, CTIME);
481 	case FILOTMA:
482 		return newerf(opnd2, opnd1, ATIME, MTIME);
483 	case FILOTMB:
484 		return newerf(opnd2, opnd1, BTIME, MTIME);
485 	case FILOTMC:
486 		return newerf(opnd2, opnd1, CTIME, MTIME);
487 	case FILOTMM:
488 		return newerf(opnd2, opnd1, MTIME, MTIME);
489 	case FILEQ:
490 		return equalf (opnd1, opnd2);
491 	default:
492 		abort();
493 		/* NOTREACHED */
494 	}
495 }
496 
497 static int
498 filstat(char *nm, enum token mode)
499 {
500 	struct stat s;
501 
502 	if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
503 		return 0;
504 
505 	switch (mode) {
506 	case FILRD:
507 		return (eaccess(nm, R_OK) == 0);
508 	case FILWR:
509 		return (eaccess(nm, W_OK) == 0);
510 	case FILEX:
511 		/* XXX work around eaccess(2) false positives for superuser */
512 		if (eaccess(nm, X_OK) != 0)
513 			return 0;
514 		if (S_ISDIR(s.st_mode) || geteuid() != 0)
515 			return 1;
516 		return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
517 	case FILEXIST:
518 		return (eaccess(nm, F_OK) == 0);
519 	case FILREG:
520 		return S_ISREG(s.st_mode);
521 	case FILDIR:
522 		return S_ISDIR(s.st_mode);
523 	case FILCDEV:
524 		return S_ISCHR(s.st_mode);
525 	case FILBDEV:
526 		return S_ISBLK(s.st_mode);
527 	case FILFIFO:
528 		return S_ISFIFO(s.st_mode);
529 	case FILSOCK:
530 		return S_ISSOCK(s.st_mode);
531 	case FILSYM:
532 		return S_ISLNK(s.st_mode);
533 	case FILSUID:
534 		return (s.st_mode & S_ISUID) != 0;
535 	case FILSGID:
536 		return (s.st_mode & S_ISGID) != 0;
537 	case FILSTCK:
538 		return (s.st_mode & S_ISVTX) != 0;
539 	case FILGZ:
540 		return s.st_size > (off_t)0;
541 	case FILUID:
542 		return s.st_uid == geteuid();
543 	case FILGID:
544 		return s.st_gid == getegid();
545 	default:
546 		return 1;
547 	}
548 }
549 
550 static enum token
551 t_lex(char *s)
552 {
553 	struct t_op const *op = ops;
554 
555 	if (s == 0) {
556 		t_wp_op = NULL;
557 		return EOI;
558 	}
559 	while (*op->op_text) {
560 		if (strcmp(s, op->op_text) == 0) {
561 			if (((op->op_type == UNOP || op->op_type == BUNOP)
562 						&& isunopoperand()) ||
563 			    (op->op_num == LPAREN && islparenoperand()) ||
564 			    (op->op_num == RPAREN && isrparenoperand()))
565 				break;
566 			t_wp_op = op;
567 			return op->op_num;
568 		}
569 		op++;
570 	}
571 	t_wp_op = NULL;
572 	return OPERAND;
573 }
574 
575 static int
576 isunopoperand(void)
577 {
578 	struct t_op const *op = ops;
579 	char *s;
580 	char *t;
581 
582 	if (nargc == 1)
583 		return 1;
584 	s = *(t_wp + 1);
585 	if (nargc == 2)
586 		return parenlevel == 1 && strcmp(s, ")") == 0;
587 	t = *(t_wp + 2);
588 	while (*op->op_text) {
589 		if (strcmp(s, op->op_text) == 0)
590 			return op->op_type == BINOP &&
591 			    (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
592 		op++;
593 	}
594 	return 0;
595 }
596 
597 static int
598 islparenoperand(void)
599 {
600 	struct t_op const *op = ops;
601 	char *s;
602 
603 	if (nargc == 1)
604 		return 1;
605 	s = *(t_wp + 1);
606 	if (nargc == 2)
607 		return parenlevel == 1 && strcmp(s, ")") == 0;
608 	if (nargc != 3)
609 		return 0;
610 	while (*op->op_text) {
611 		if (strcmp(s, op->op_text) == 0)
612 			return op->op_type == BINOP;
613 		op++;
614 	}
615 	return 0;
616 }
617 
618 static int
619 isrparenoperand(void)
620 {
621 	char *s;
622 
623 	if (nargc == 1)
624 		return 0;
625 	s = *(t_wp + 1);
626 	if (nargc == 2)
627 		return parenlevel == 1 && strcmp(s, ")") == 0;
628 	return 0;
629 }
630 
631 /* atoi with error detection */
632 static int
633 getn(const char *s)
634 {
635 	char *p;
636 	long r;
637 
638 	errno = 0;
639 	r = strtol(s, &p, 10);
640 
641 	if (s == p)
642 		error("%s: bad number", s);
643 
644 	if (errno != 0)
645 		error((errno == EINVAL) ? "%s: bad number" :
646 					  "%s: out of range", s);
647 
648 	while (isspace((unsigned char)*p))
649 		p++;
650 
651 	if (*p)
652 		error("%s: bad number", s);
653 
654 	return (int) r;
655 }
656 
657 /* atoi with error detection and 64 bit range */
658 static intmax_t
659 getq(const char *s)
660 {
661 	char *p;
662 	intmax_t r;
663 
664 	errno = 0;
665 	r = strtoimax(s, &p, 10);
666 
667 	if (s == p)
668 		error("%s: bad number", s);
669 
670 	if (errno != 0)
671 		error((errno == EINVAL) ? "%s: bad number" :
672 					  "%s: out of range", s);
673 
674 	while (isspace((unsigned char)*p))
675 		p++;
676 
677 	if (*p)
678 		error("%s: bad number", s);
679 
680 	return r;
681 }
682 
683 static int
684 intcmp (const char *s1, const char *s2)
685 {
686 	intmax_t q1, q2;
687 
688 
689 	q1 = getq(s1);
690 	q2 = getq(s2);
691 
692 	if (q1 > q2)
693 		return 1;
694 
695 	if (q1 < q2)
696 		return -1;
697 
698 	return 0;
699 }
700 
701 static int
702 newerf (const char *f1, const char *f2, enum time_types t1, enum time_types t2)
703 {
704 	struct stat b1, b2;
705 	struct timespec *ts1, *ts2;
706 
707 	if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
708 		return 0;
709 
710 	switch (t1) {
711 	case ATIME:	ts1 = &b1.st_atim;	break;
712 	case BTIME:	ts1 = &b1.st_birthtim;	break;
713 	case CTIME:	ts1 = &b1.st_ctim;	break;
714 	default:	ts1 = &b1.st_mtim;	break;
715 	}
716 
717 	switch (t2) {
718 	case ATIME:	ts2 = &b2.st_atim;	break;
719 	case BTIME:	ts2 = &b2.st_birthtim;	break;
720 	case CTIME:	ts2 = &b2.st_ctim;	break;
721 	default:	ts2 = &b2.st_mtim;	break;
722 	}
723 
724 	if (ts1->tv_sec > ts2->tv_sec)
725 		return 1;
726 	if (ts1->tv_sec < ts2->tv_sec)
727 		return 0;
728 
729        return (ts1->tv_nsec > ts2->tv_nsec);
730 }
731 
732 static int
733 equalf (const char *f1, const char *f2)
734 {
735 	struct stat b1, b2;
736 
737 	return (stat (f1, &b1) == 0 &&
738 		stat (f2, &b2) == 0 &&
739 		b1.st_dev == b2.st_dev &&
740 		b1.st_ino == b2.st_ino);
741 }
742