xref: /titanic_50/usr/src/cmd/bart/compare.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <unistd.h>
29 #include "bart.h"
30 
31 static int compare_manifests(FILE *rulesfile, char *control, char *test,
32     boolean_t prog_fmt, uint_t flags);
33 static void extract_fname_ftype(char *line, char *fname, char *type);
34 static int report_add(char *fname, char *type);
35 static int report_delete(char *fname, char *type);
36 static int evaluate_differences(char *control_line, char *test_line,
37     boolean_t prog_fmt, int flags);
38 static void report_error(char *fname, char *type, char *ctrl_val,
39     char *test_val, boolean_t prog_fmt);
40 static int read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos,
41     char **line, char *fname);
42 static void parse_line(char *line, char *fname, char *type, char *size,
43     char *mode, char *acl, char *mtime, char *uid, char *gid, char *contents,
44     char *devnode, char *dest);
45 static void init_default_flags(uint_t *flags);
46 static void get_token(char *line, int *curr_pos, int line_len, char *buf,
47     int buf_size);
48 
49 int
50 bart_compare(int argc, char **argv)
51 {
52 	char			*control_fname, *test_fname;
53 	int			c;
54 	FILE			*rules_fd = NULL;
55 	uint_t 			glob_flags;
56 	boolean_t		prog_fmt = B_FALSE;
57 
58 	init_default_flags(&glob_flags);
59 
60 	while ((c = getopt(argc, argv, "pr:i:")) != EOF) {
61 		switch (c) {
62 		case 'p':
63 			prog_fmt = B_TRUE;
64 			break;
65 
66 		case 'r':
67 			if (optarg == NULL)
68 				usage();
69 
70 			if (strcmp(optarg, "-") == 0)
71 				rules_fd = stdin;
72 			else
73 				rules_fd = fopen(optarg, "r");
74 			if (rules_fd == NULL) {
75 				perror(optarg);
76 				usage();
77 			}
78 			break;
79 
80 		case 'i':
81 			process_glob_ignores(optarg, &glob_flags);
82 			break;
83 
84 		case '?':
85 		default:
86 			usage();
87 		}
88 	}
89 
90 	/* Make sure we have the right number of args */
91 	if ((optind + 2) != argc)
92 		usage();
93 	argv += optind;
94 	control_fname = argv[0];
95 	test_fname = argv[1];
96 	/* At this point, the filenames are sane, so do the comparison */
97 	return (compare_manifests(rules_fd, control_fname, test_fname,
98 	    prog_fmt, glob_flags));
99 }
100 
101 static int
102 compare_manifests(FILE *rulesfile, char *control, char *test,
103 boolean_t prog_fmt, uint_t flags)
104 {
105 	FILE	*control_fd, *test_fd;
106 	char	*control_line, *test_line, control_buf[BUF_SIZE],
107 		test_buf[BUF_SIZE], control_fname[PATH_MAX],
108 		control_type[TYPE_SIZE], test_fname[PATH_MAX],
109 		test_type[TYPE_SIZE];
110 	int	control_pos, test_pos, ret, fname_cmp, return_status;
111 
112 	return_status = EXIT;
113 
114 	return_status = read_rules(rulesfile, "", flags, 0);
115 
116 	control_fd = fopen(control, "r");
117 	if (control_fd == NULL) {
118 		perror(control);
119 		return (FATAL_EXIT);
120 	}
121 
122 	test_fd = fopen(test, "r");
123 	if (test_fd == NULL) {
124 		perror(test);
125 		return (FATAL_EXIT);
126 	}
127 
128 	control_pos = read_manifest_line(control_fd, control_buf,
129 	    BUF_SIZE, 0, &control_line, control);
130 	test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, 0,
131 	    &test_line, test);
132 
133 	while ((control_pos != -1) && (test_pos != -1)) {
134 		ret = strcmp(control_line, test_line);
135 		if (ret == 0) {
136 			/* Lines compare OK, just read the next lines.... */
137 			control_pos = read_manifest_line(control_fd,
138 			    control_buf, BUF_SIZE, control_pos, &control_line,
139 			    control);
140 			test_pos = read_manifest_line(test_fd, test_buf,
141 			    BUF_SIZE, test_pos, &test_line, test);
142 			continue;
143 		}
144 
145 		/*
146 		 * Something didn't compare properly.
147 		 */
148 		extract_fname_ftype(control_line, control_fname, control_type);
149 		extract_fname_ftype(test_line, test_fname, test_type);
150 		fname_cmp = strcmp(control_fname, test_fname);
151 
152 		if (fname_cmp == 0) {
153 			/*
154 			 * Filenames were the same, see what was
155 			 * different and continue.
156 			 */
157 			if (evaluate_differences(control_line, test_line,
158 			    prog_fmt, flags) != 0)
159 				return_status = WARNING_EXIT;
160 
161 			control_pos = read_manifest_line(control_fd,
162 			    control_buf, BUF_SIZE, control_pos, &control_line,
163 			    control);
164 			test_pos = read_manifest_line(test_fd, test_buf,
165 			    BUF_SIZE, test_pos, &test_line, test);
166 		} else if (fname_cmp > 0) {
167 			/* Filenames were different, a files was ADDED */
168 			if (report_add(test_fname, test_type)) {
169 				report_error(test_fname, ADD_KEYWORD, NULL,
170 				    NULL, prog_fmt);
171 				return_status = WARNING_EXIT;
172 			}
173 			test_pos = read_manifest_line(test_fd, test_buf,
174 			    BUF_SIZE, test_pos, &test_line, test);
175 		} else if (fname_cmp < 0) {
176 			/* Filenames were different, a files was DELETED */
177 			if (report_delete(control_fname, control_type)) {
178 				report_error(control_fname, DELETE_KEYWORD,
179 				    NULL, NULL, prog_fmt);
180 				return_status = WARNING_EXIT;
181 			}
182 			control_pos = read_manifest_line(control_fd,
183 			    control_buf, BUF_SIZE, control_pos, &control_line,
184 			    control);
185 		}
186 	}
187 
188 	/*
189 	 * Entering this while loop means files were DELETED from the test
190 	 * manifest.
191 	 */
192 	while (control_pos != -1) {
193 		(void) sscanf(control_line, "%1023s", control_fname);
194 		if (report_delete(control_fname, control_type)) {
195 			report_error(control_fname, DELETE_KEYWORD, NULL,
196 			    NULL, prog_fmt);
197 			return_status = WARNING_EXIT;
198 		}
199 		control_pos = read_manifest_line(control_fd, control_buf,
200 		    BUF_SIZE, control_pos, &control_line, control);
201 	}
202 
203 	/*
204 	 * Entering this while loop means files were ADDED to the test
205 	 * manifest.
206 	 */
207 	while (test_pos != -1) {
208 		(void) sscanf(test_line, "%1023s", test_fname);
209 		if (report_add(test_fname, test_type)) {
210 			report_error(test_fname, ADD_KEYWORD, NULL,
211 			    NULL, prog_fmt);
212 			return_status = WARNING_EXIT;
213 		}
214 		test_pos = read_manifest_line(test_fd, test_buf,
215 		    BUF_SIZE, test_pos, &test_line, test);
216 	}
217 
218 	(void) fclose(control_fd);
219 	(void) fclose(test_fd);
220 
221 	/* For programmatic mode, add a newline for cosmetic reasons */
222 	if (prog_fmt && (return_status != 0))
223 		(void) printf("\n");
224 
225 	return (return_status);
226 }
227 
228 static void
229 parse_line(char *line, char *fname, char *type, char *size, char *mode,
230 char *acl, char *mtime, char *uid, char *gid, char *contents, char *devnode,
231 char *dest)
232 {
233 	int		pos, line_len;
234 
235 	line_len = strlen(line);
236 	pos = 0;
237 
238 	get_token(line, &pos, line_len, fname, PATH_MAX);
239 	get_token(line, &pos, line_len, type, TYPE_SIZE);
240 	get_token(line, &pos, line_len, size, MISC_SIZE);
241 	get_token(line, &pos, line_len, mode, MISC_SIZE);
242 	get_token(line, &pos, line_len, acl, ACL_SIZE);
243 	get_token(line, &pos, line_len, mtime, MISC_SIZE);
244 	get_token(line, &pos, line_len, uid, MISC_SIZE);
245 	get_token(line, &pos, line_len, gid, MISC_SIZE);
246 
247 	/* Reset these fields... */
248 
249 	*contents = NULL;
250 	*devnode = '\0';
251 	*dest = '\0';
252 
253 	/* Handle filetypes which have a last field..... */
254 	if (type[0] == 'F')
255 		get_token(line, &pos, line_len, contents, PATH_MAX);
256 	else if ((type[0] == 'B') || (type[0] == 'C'))
257 		get_token(line, &pos, line_len, devnode, PATH_MAX);
258 	else if (type[0] == 'L')
259 		get_token(line, &pos, line_len, dest, PATH_MAX);
260 }
261 
262 static void
263 get_token(char *line, int *curr_pos, int line_len, char *buf, int buf_size)
264 {
265 	int	cnt = 0;
266 
267 	while (isspace(line[*curr_pos]) && (*curr_pos < line_len))
268 		(*curr_pos)++;
269 
270 	while (!isspace(line[*curr_pos]) &&
271 	    (*curr_pos < line_len) && (cnt < (buf_size-1))) {
272 		buf[cnt] = line[*curr_pos];
273 		(*curr_pos)++;
274 		cnt++;
275 	}
276 	buf[cnt] = '\0';
277 }
278 
279 /*
280  * Utility function: extract fname and type from this line
281  */
282 static void
283 extract_fname_ftype(char *line, char *fname, char *type)
284 {
285 	int		line_len, pos;
286 
287 	pos = 0;
288 	line_len = strlen(line);
289 
290 	get_token(line, &pos, line_len, fname, PATH_MAX);
291 	get_token(line, &pos, line_len, type, TYPE_SIZE);
292 }
293 
294 /*
295  * Utility function: tells us whether or not this addition should be reported
296  *
297  * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is
298  * reported.
299  */
300 static int
301 report_add(char *fname, char *type)
302 {
303 	struct rule	*rule_ptr;
304 
305 	rule_ptr = check_rules(fname, type[0]);
306 	if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_ADD))
307 		return (1);
308 	else
309 		return (0);
310 }
311 
312 /*
313  * Utility function: tells us whether or not this deletion should be reported
314  *
315  * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is
316  * reported.
317  */
318 static int
319 report_delete(char *fname, char *type)
320 {
321 	struct rule	*rule_ptr;
322 
323 	rule_ptr = check_rules(fname, type[0]);
324 
325 	if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_DELETE))
326 		return (1);
327 	else
328 		return (0);
329 }
330 
331 /*
332  * This function takes in the two entries, which have been flagged as
333  * different, breaks them up and reports discrepancies.  Note, discrepancies
334  * are affected by the 'CHECK' and 'IGNORE' stanzas which may apply to
335  * these entries.
336  *
337  * Returns the number of discrepancies reported.
338  */
339 static int
340 evaluate_differences(char *control_line, char *test_line,
341     boolean_t prog_fmt, int flags)
342 {
343 	char		ctrl_fname[PATH_MAX], test_fname[PATH_MAX],
344 			ctrl_type[TYPE_SIZE], test_type[TYPE_SIZE],
345 			ctrl_size[MISC_SIZE], ctrl_mode[MISC_SIZE],
346 			ctrl_acl[ACL_SIZE], ctrl_mtime[MISC_SIZE],
347 			ctrl_uid[MISC_SIZE], ctrl_gid[MISC_SIZE],
348 			ctrl_dest[PATH_MAX], ctrl_contents[PATH_MAX],
349 			ctrl_devnode[PATH_MAX], test_size[MISC_SIZE],
350 			test_mode[MISC_SIZE], test_acl[ACL_SIZE],
351 			test_mtime[MISC_SIZE], test_uid[MISC_SIZE],
352 			test_gid[MISC_SIZE], test_dest[PATH_MAX],
353 			test_contents[PATH_MAX], test_devnode[PATH_MAX],
354 			*tag;
355 	int		ret_val;
356 	struct rule	*rule_ptr;
357 
358 	ret_val = 0;
359 
360 	parse_line(control_line, ctrl_fname, ctrl_type, ctrl_size, ctrl_mode,
361 	    ctrl_acl, ctrl_mtime, ctrl_uid, ctrl_gid, ctrl_contents,
362 	    ctrl_devnode, ctrl_dest);
363 
364 	/*
365 	 * Now we know the fname and type, let's get the rule that matches this
366 	 * manifest entry.  If there is a match, make sure to setup the
367 	 * correct reporting flags.
368 	 */
369 	rule_ptr = check_rules(ctrl_fname, ctrl_type[0]);
370 	if (rule_ptr != NULL)
371 		flags = rule_ptr->attr_list;
372 
373 	parse_line(test_line, test_fname, test_type, test_size, test_mode,
374 	    test_acl, test_mtime, test_uid, test_gid, test_contents,
375 	    test_devnode, test_dest);
376 
377 	/*
378 	 * Report the errors based upon which keywords have been set by
379 	 * the user.
380 	 */
381 	if ((flags & ATTR_TYPE) && (ctrl_type[0] != test_type[0])) {
382 		report_error(ctrl_fname, TYPE_KEYWORD, ctrl_type,
383 		    test_type, prog_fmt);
384 		ret_val++;
385 	}
386 
387 	if ((flags & ATTR_SIZE) && (strcmp(ctrl_size, test_size) != 0)) {
388 		report_error(ctrl_fname, SIZE_KEYWORD, ctrl_size,
389 		    test_size, prog_fmt);
390 		ret_val++;
391 	}
392 
393 	if ((flags & ATTR_MODE) && (strcmp(ctrl_mode, test_mode) != 0)) {
394 		report_error(ctrl_fname, MODE_KEYWORD, ctrl_mode,
395 		    test_mode, prog_fmt);
396 		ret_val++;
397 	}
398 
399 	if ((flags & ATTR_ACL) && (strcmp(ctrl_acl, test_acl) != 0)) {
400 		report_error(ctrl_fname, ACL_KEYWORD, ctrl_acl,
401 		    test_acl, prog_fmt);
402 		ret_val++;
403 	}
404 
405 	if ((flags & ATTR_MTIME) && (ctrl_type[0] == test_type[0])) {
406 		if (strcmp(ctrl_mtime, test_mtime) != 0) {
407 			switch (ctrl_type[0]) {
408 			case 'D':
409 				tag = "dirmtime";
410 				break;
411 			case 'L':
412 				tag = "lnmtime";
413 				break;
414 			default:
415 				tag = "mtime";
416 				break;
417 			}
418 			if (flags == 0) {
419 				report_error(ctrl_fname, tag, ctrl_mtime,
420 				    test_mtime, prog_fmt);
421 			ret_val++;
422 		}
423 	}
424 
425 	if ((ctrl_type[0] == 'F') && (flags & ATTR_MTIME) &&
426 	    (strcmp(ctrl_mtime, test_mtime) != 0)) {
427 		report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime, test_mtime,
428 		    prog_fmt);
429 		ret_val++;
430 	}
431 
432 	if ((ctrl_type[0] == 'D') && (flags & ATTR_DIRMTIME) &&
433 	    (strcmp(ctrl_mtime, test_mtime) != 0)) {
434 		report_error(ctrl_fname, DIRMTIME_KEYWORD, ctrl_mtime,
435 		    test_mtime, prog_fmt);
436 		ret_val++;
437 	}
438 
439 	if ((ctrl_type[0] == 'L') && (flags & ATTR_LNMTIME) &&
440 	    (strcmp(ctrl_mtime, test_mtime) != 0)) {
441 		report_error(ctrl_fname, LNMTIME_KEYWORD, ctrl_mtime,
442 		    test_mtime, prog_fmt);
443 		ret_val++;
444 	}
445 	} else if ((flags & ATTR_MTIME) &&
446 	    (strcmp(ctrl_mtime, test_mtime) != 0)) {
447 		report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime,
448 		    test_mtime, prog_fmt);
449 		ret_val++;
450 	}
451 
452 	if ((flags & ATTR_UID) && (strcmp(ctrl_uid, test_uid) != 0)) {
453 		report_error(ctrl_fname, UID_KEYWORD, ctrl_uid,
454 		    test_uid, prog_fmt);
455 		ret_val++;
456 	}
457 
458 	if ((flags & ATTR_GID) && (strcmp(ctrl_gid, test_gid) != 0)) {
459 		report_error(ctrl_fname, GID_KEYWORD, ctrl_gid,
460 		    test_gid, prog_fmt);
461 		ret_val++;
462 	}
463 
464 	if ((flags & ATTR_DEVNODE) &&
465 	    (strcmp(ctrl_devnode, test_devnode) != 0)) {
466 		report_error(ctrl_fname, DEVNODE_KEYWORD, ctrl_devnode,
467 		    test_devnode, prog_fmt);
468 		ret_val++;
469 	}
470 
471 	if ((flags & ATTR_DEST) && (strcmp(ctrl_dest, test_dest) != 0)) {
472 		report_error(ctrl_fname, DEST_KEYWORD, ctrl_dest,
473 		    test_dest, prog_fmt);
474 		ret_val++;
475 	}
476 
477 	if ((flags & ATTR_CONTENTS) &&
478 	    (strcmp(ctrl_contents, test_contents)) != 0) {
479 		report_error(ctrl_fname, CONTENTS_KEYWORD, ctrl_contents,
480 		    test_contents, prog_fmt);
481 		ret_val++;
482 	}
483 
484 	return (ret_val);
485 }
486 
487 /*
488  * Function responsible for reporting errors.
489  */
490 static void
491 report_error(char *fname, char *type, char *ctrl_val, char *test_val,
492     boolean_t prog_fmt)
493 {
494 	static char	last_fname[PATH_MAX] = "";
495 
496 	if (!prog_fmt) {
497 		/* Verbose mode */
498 		if (strcmp(fname, last_fname) != 0) {
499 			(void) printf("%s:\n", fname);
500 			(void) strlcpy(last_fname, fname, sizeof (last_fname));
501 		}
502 
503 		if (strcmp(type, ADD_KEYWORD) == 0 ||
504 		    strcmp(type, DELETE_KEYWORD) == 0)
505 			(void) printf("  %s\n", type);
506 		else
507 			(void) printf("  %s  control:%s  test:%s\n", type,
508 			    ctrl_val, test_val);
509 	} else {
510 		/* Programmatic mode */
511 		if (strcmp(fname, last_fname) != 0) {
512 			/* Ensure a line is not printed for the initial case */
513 			if (strlen(last_fname) != 0)
514 				(void) printf("\n");
515 			(void) strlcpy(last_fname, fname, sizeof (last_fname));
516 			(void) printf("%s ", fname);
517 		}
518 
519 		(void) printf("%s ", type);
520 		if (strcmp(type, ADD_KEYWORD) != 0 &&
521 		    strcmp(type, DELETE_KEYWORD) != 0) {
522 			(void) printf("%s ", ctrl_val);
523 			(void) printf("%s ", test_val);
524 		}
525 	}
526 }
527 
528 /*
529  * Function responsible for reading in a line from the manifest.
530  * Takes in the file ptr and a buffer, parses the buffer  and sets the 'line'
531  * ptr correctly.  In the case when the buffer is fully parsed, this function
532  * reads more data from the file ptr and refills the buffer.
533  */
534 static int
535 read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos,
536     char **line, char *fname)
537 {
538 	int	end_pos, len, iscomment = 0, filepos;
539 
540 	/*
541 	 * Initialization case: make sure the manifest version is OK
542 	 */
543 	if (start_pos == 0) {
544 		end_pos = 0;
545 		buf[0] = '\0';
546 		filepos = ftell(fd);
547 		(void) fread((void *) buf, (size_t)buf_size, (size_t)1, fd);
548 
549 		*line = buf;
550 
551 		if (filepos == 0) {
552 			if (strncmp(buf, MANIFEST_VER,
553 			    strlen(MANIFEST_VER)) != 0)
554 				(void) fprintf(stderr, MISSING_VER, fname);
555 			if ((*line[0] == '!') || (*line[0] == '#'))
556 				iscomment++;
557 
558 			while (iscomment) {
559 				while ((buf[end_pos] != '\n') &&
560 				    (buf[end_pos] != '\0') &&
561 				    (end_pos < buf_size))
562 					end_pos++;
563 
564 				if (end_pos >= buf_size)
565 					return (-1);
566 
567 				end_pos++;
568 				*line = &(buf[end_pos]);
569 				iscomment = 0;
570 				if ((*line[0] == '!') || (*line[0] == '#'))
571 					iscomment++;
572 			}
573 		}
574 
575 		while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') &&
576 		    (end_pos < buf_size))
577 			end_pos++;
578 
579 		if (end_pos < buf_size) {
580 			if (buf[end_pos] == '\n') {
581 				buf[end_pos] = '\0';
582 				return (end_pos);
583 			}
584 
585 			if (buf[end_pos] == '\0')
586 				return (-1);
587 		}
588 
589 		(void) fprintf(stderr, MANIFEST_ERR);
590 		exit(FATAL_EXIT);
591 	}
592 
593 	end_pos = (start_pos+1);
594 	*line = &(buf[end_pos]);
595 
596 	/* Read the buffer until EOL or the buffer is empty */
597 	while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') &&
598 	    (end_pos < buf_size))
599 		end_pos++;
600 
601 	if (end_pos < buf_size) {
602 		/* Found the end of the line, normal exit */
603 		if (buf[end_pos] == '\n') {
604 			buf[end_pos] = '\0';
605 			return (end_pos);
606 		}
607 
608 		/* No more input to read */
609 		if (buf[end_pos] == '\0')
610 			return (-1);
611 	}
612 
613 	/*
614 	 * The following code takes the remainder of the buffer and
615 	 * puts it at the beginning.  The space after the remainder, which
616 	 * is now at the beginning, is blanked.
617 	 * At this point, read in more data and continue to find the EOL....
618 	 */
619 	len = end_pos - (start_pos + 1);
620 	(void) memcpy(buf, &(buf[start_pos+1]), (size_t)len);
621 	(void) memset(&buf[len], '\0', (buf_size - len));
622 	(void) fread((void *) &buf[len], (size_t)(buf_size-len), (size_t)1, fd);
623 	*line = buf;
624 	end_pos = len;
625 
626 	/* Read the buffer until EOL or the buffer is empty */
627 	while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') &&
628 	    (end_pos < buf_size))
629 		end_pos++;
630 
631 	if (end_pos < buf_size) {
632 		/* Found the end of the line, normal exit */
633 		if (buf[end_pos] == '\n') {
634 			buf[end_pos] = '\0';
635 			return (end_pos);
636 		}
637 
638 		/* No more input to read */
639 		if (buf[end_pos] == '\0')
640 			return (-1);
641 	}
642 
643 	(void) fprintf(stderr, MANIFEST_ERR);
644 	exit(FATAL_EXIT);
645 
646 	/* NOTREACHED */
647 }
648 
649 static void
650 init_default_flags(uint_t *flags)
651 {
652 	/* Default behavior: everything is checked *except* dirmtime */
653 	*flags = ATTR_ALL & ~(ATTR_DIRMTIME);
654 }
655