xref: /illumos-gate/usr/src/tools/protocmp/protocmp.c (revision 7c478bd9)
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 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <fcntl.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <dirent.h>
36 #include <sys/param.h>
37 #include <errno.h>
38 #include <unistd.h>
39 #include <ftw.h>
40 
41 #include "list.h"
42 #include "protocmp.h"
43 #include "proto_list.h"
44 #include "protodir.h"
45 #include "exception_list.h"
46 #include "stdusers.h"
47 
48 #define	MAX_PROTO_REFS			5
49 #define	MAX_EXCEPTION_FILES		5
50 #define	MAX_DEPTH			50
51 
52 /*
53  * default flag values
54  */
55 static int check_group = 1;
56 static int set_group = 0;
57 static int check_user = 1;
58 static int set_user = 0;
59 static int check_perm = 1;
60 static int set_perm = 0;
61 static int check_link = 1;
62 static int check_sym = 1;
63 static int check_majmin = 1;
64 
65 static elem_list first_list;
66 static char *first_file_name;
67 
68 static elem_list second_list;
69 static char *second_file_name;
70 
71 static FILE *need_add_fp;
72 static char *need_add_file;
73 static FILE *need_rm_fp;
74 static char *need_rm_file;
75 static FILE *differ_fp;
76 static char *differ_file;
77 
78 static char *myname;
79 
80 /*
81  * default flag values
82  */
83 static int verbose = 0;
84 
85 static void
86 usage(void)
87 {
88 	(void) fputs("usage: protocmp [-gupGUPlmsLv] "
89 	    "[-e <exception-list> ...] "
90 	    "-d <protolist|pkg dir>\n\t[-d <protolist|pkg dir> ...] "
91 	    "[<protolist|pkg dir>...]|<root>]\n",
92 	    stderr);
93 	(void) fputs("   where:\n", stderr);
94 	(void) fputs("\t-g       : don't compare group\n", stderr);
95 	(void) fputs("\t-u       : don't compare owner\n", stderr);
96 	(void) fputs("\t-p       : don't compare permissions\n", stderr);
97 	(void) fputs("\t-G       : set group\n", stderr);
98 	(void) fputs("\t-U       : set owner\n", stderr);
99 	(void) fputs("\t-P       : set permissions\n", stderr);
100 	(void) fputs("\t-l       : don't compare link counts\n", stderr);
101 	(void) fputs("\t-m       : don't compare major/minor numbers\n",
102 	    stderr);
103 	(void) fputs("\t-s       : don't compare symlink values\n", stderr);
104 	(void) fputs("\t-d <protolist|pkg dir>:\n", stderr);
105 	(void) fputs("\t           proto list or packaging to check\n", stderr);
106 	(void) fputs("\t-e <file>: exceptions file\n", stderr);
107 	(void) fputs("\t-L       : list filtered exceptions\n", stderr);
108 	(void) fputs("\t-v       : verbose output\n", stderr);
109 	(void) fputs("\n"
110 "If any of the -[GUP] flags are given, then the final argument must be the\n"
111 "proto root directory itself on which to set permissions according to the\n"
112 "packaging data specified via -d options.\n", stderr);
113 }
114 
115 
116 static void
117 open_output_files(void)
118 {
119 	if ((need_add_fp =
120 	    fopen((need_add_file = tempnam(NULL, "add")), "w")) == NULL) {
121 		perror(need_add_file);
122 		exit(1);
123 	}
124 
125 	if ((need_rm_fp =
126 	    fopen((need_rm_file = tempnam(NULL, "rm")), "w")) == NULL) {
127 		perror(need_rm_file);
128 		exit(1);
129 	}
130 
131 	if ((differ_fp =
132 	    fopen((differ_file = tempnam(NULL, "diff")), "w")) == NULL) {
133 		perror(differ_file);
134 		exit(1);
135 	}
136 }
137 
138 static void
139 close_output_files(void)
140 {
141 	(void) fclose(need_add_fp);
142 	(void) fclose(need_rm_fp);
143 	(void) fclose(differ_fp);
144 }
145 
146 static void
147 print_file(char *file)
148 {
149 	FILE	*fp;
150 	int	count;
151 	char	buff[BUF_SIZE];
152 
153 	if ((fp = fopen(file, "r")) == NULL) {
154 		perror(need_add_file);
155 	}
156 
157 	while (count = fread(buff, sizeof (char), BUF_SIZE, fp))
158 		(void) fwrite(buff, sizeof (char), count, stdout);
159 	(void) fclose(fp);
160 }
161 
162 static void
163 print_header(void)
164 {
165 	(void) printf("%c %-30s %-20s %-4s %-5s %-5s %-5s %-2s %2s %2s %-9s\n",
166 	    'T', "File Name", "Reloc/Sym name", "perm", "owner", "group",
167 	    "inode", "lnk", "maj", "min", "package(s)");
168 	(void) puts("-------------------------------------------------------"
169 	    "-----------------------------------------------------");
170 }
171 
172 static void
173 print_results(void)
174 {
175 	(void) puts("*******************************************************");
176 	(void) puts("*");
177 	(void) printf("* Entries found in %s, but not found in %s\n",
178 	    first_file_name, second_file_name);
179 	(void) puts("*");
180 	(void) puts("*******************************************************");
181 	print_header();
182 	print_file(need_add_file);
183 	(void) puts("*******************************************************");
184 	(void) puts("*");
185 	(void) printf("* Entries found in %s, but not found in %s\n",
186 	    second_file_name, first_file_name);
187 	(void) puts("*");
188 	(void) puts("*******************************************************");
189 	print_header();
190 	print_file(need_rm_file);
191 	(void) puts("*******************************************************");
192 	(void) puts("*");
193 	(void) printf("* Entries that differ between %s and %s\n",
194 	    first_file_name, second_file_name);
195 	(void) puts("*");
196 	(void) printf("* filea == %s\n", first_file_name);
197 	(void) printf("* fileb == %s\n", second_file_name);
198 	(void) puts("*");
199 	(void) puts("*******************************************************");
200 	(void) fputs("Unit   ", stdout);
201 	print_header();
202 	print_file(differ_file);
203 }
204 
205 static void
206 clean_up(void)
207 {
208 	(void) unlink(need_add_file);
209 	(void) unlink(need_rm_file);
210 	(void) unlink(differ_file);
211 }
212 
213 /*
214  * elem_compare(a,b)
215  *
216  * Args:
217  *	a 		- element a
218  *	b 		- element b
219  *	different_types -
220  *		value = 0  -> comparing two elements of same
221  *			      type (eg: protodir elem vs. protodir elem).
222  *		value != 0 -> comparing two elements of different type
223  *			      (eg: protodir elem vs. protolist elem).
224  *
225  * Returns:
226  *	0   - elements are identical
227  *	>0  - elements differ
228  *	      check flags to see which fields differ.
229  */
230 static int
231 elem_compare(elem *a, elem *b, int different_types)
232 {
233 	int	res = 0;
234 	elem	*i, *j;
235 
236 	/*
237 	 * if these are hard links to other files - those are the
238 	 * files that should be compared.
239 	 */
240 	i = a->link_parent ? a->link_parent : a;
241 	j = b->link_parent ? b->link_parent : b;
242 
243 	/*
244 	 * We do not compare inodes - they always differ.
245 	 * We do not compare names because we assume that was
246 	 * checked before.
247 	 */
248 
249 	/*
250 	 * Special rules for comparison:
251 	 *
252 	 * 1) if directory - ignore ref_cnt.
253 	 * 2) if sym_link - only check file_type & symlink
254 	 * 3) elem type of FILE_T, EDIT_T, & VOLATILE_T are equivilant when
255 	 *    comparing a protodir entry to a protolist entry.
256 	 */
257 	if (i->file_type != j->file_type) {
258 		if (different_types) {
259 			/*
260 			 * Check to see if filetypes are FILE_T vs.
261 			 * EDIT_T/VOLATILE_T/LINK_T comparisons.
262 			 */
263 			if ((i->file_type == FILE_T) &&
264 			    ((j->file_type == EDIT_T) ||
265 			    (j->file_type == VOLATILE_T) ||
266 			    (j->file_type == LINK_T))) {
267 				/*EMPTY*/
268 			} else if ((j->file_type == FILE_T) &&
269 			    ((i->file_type == EDIT_T) ||
270 			    (i->file_type == VOLATILE_T) ||
271 			    (i->file_type == LINK_T))) {
272 				/*EMPTY*/
273 			} else
274 				res |= TYPE_F;
275 		} else
276 			res |= TYPE_F;
277 	}
278 
279 	/*
280 	 * if symlink - check the symlink value and then
281 	 * return.  symlink is the only field of concern
282 	 * in SYMLINKS.
283 	 */
284 	if (check_sym && ((res == 0) && (i->file_type == SYM_LINK_T))) {
285 		if ((!i->symsrc) || (!j->symsrc))
286 			res |= SYM_F;
287 		else {
288 			/*
289 			 * if either symlink starts with a './' strip it off,
290 			 * its irrelavant.
291 			 */
292 			if ((i->symsrc[0] == '.') && (i->symsrc[1] == '/'))
293 				i->symsrc += 2;
294 			if ((j->symsrc[0] == '.') && (j->symsrc[1] == '/'))
295 				j->symsrc += 2;
296 
297 			if (strncmp(i->symsrc, j->symsrc, MAXNAME) != 0)
298 				res |= SYM_F;
299 		}
300 		return (res);
301 	}
302 
303 	if ((i->file_type != DIR_T) && check_link &&
304 	    (i->ref_cnt != j->ref_cnt))
305 		res |= REF_F;
306 	if (check_user && (strncmp(i->owner, j->owner, TYPESIZE) != 0))
307 		res |= OWNER_F;
308 	if (check_group && (strncmp(i->group, j->group, TYPESIZE) != 0))
309 		res |= GROUP_F;
310 	if (check_perm && (i->perm != j->perm))
311 		res |= PERM_F;
312 	if (check_majmin && ((i->major != j->major) || (i->minor != j->minor)))
313 		res |= MAJMIN_F;
314 
315 	return (res);
316 }
317 
318 static void
319 print_elem(FILE *fp, elem *e)
320 {
321 	elem		p;
322 	pkg_list	*l;
323 	char		maj[TYPESIZE], min[TYPESIZE];
324 	char		perm[12], ref_cnt[12];
325 
326 	/*
327 	 * If this is a LINK to another file, then adopt
328 	 * the permissions of that file.
329 	 */
330 	if (e->link_parent) {
331 		p = *((elem *)e->link_parent);
332 		(void) strcpy(p.name, e->name);
333 		p.symsrc = e->symsrc;
334 		p.file_type = e->file_type;
335 		e = &p;
336 	}
337 
338 	if (!check_majmin || e->major == -1) {
339 		maj[0] = '-';
340 		maj[1] = '\0';
341 	} else {
342 		(void) sprintf(maj, "%d", e->major);
343 	}
344 
345 	if (!check_majmin || e->minor == -1) {
346 		min[0] = '-';
347 		min[1] = '\0';
348 	} else {
349 		(void) sprintf(min, "%d", e->minor);
350 	}
351 
352 	if (!check_perm) {
353 		perm[0] = '-';
354 		perm[1] = '\0';
355 	} else {
356 		(void) snprintf(perm, sizeof (perm), "%o", e->perm);
357 	}
358 
359 	if (!check_link) {
360 		ref_cnt[0] = '-';
361 		ref_cnt[1] = '\0';
362 	} else {
363 		(void) snprintf(ref_cnt, sizeof (ref_cnt), "%d", e->ref_cnt);
364 	}
365 
366 	(void) fprintf(fp, "%c %-30s %-20s %4s %-5s %-5s %6d %2s %2s %2s   ",
367 	    e->file_type, e->name,
368 	    check_sym && e->symsrc != NULL ? e->symsrc : "-", perm,
369 	    check_user ? e->owner : "-",
370 	    check_group ? e->group : "-",
371 	    e->inode, ref_cnt, maj, min);
372 	/*
373 	 * dump package list - if any.
374 	 */
375 	if (!e->pkgs)
376 		(void) fputs(" proto", fp);
377 
378 	for (l = e->pkgs; l; l = l->next) {
379 		(void) fputc(' ', fp);
380 		(void) fputs(l->pkg_name, fp);
381 	}
382 	(void) fputc('\n', fp);
383 }
384 
385 /*
386  * do_compare(a,b)
387  *
388  * Args:
389  *	different_types - see elem_compare() for explanation.
390  */
391 static void
392 do_compare(elem *a, elem *b, int different_types)
393 {
394 	int	rc;
395 
396 	if ((rc = elem_compare(a, b, different_types)) != 0) {
397 		(void) fputs("filea: ", differ_fp);
398 		print_elem(differ_fp, a);
399 		(void) fputs("fileb: ", differ_fp);
400 		print_elem(differ_fp, b);
401 		(void) fputs("    differ: ", differ_fp);
402 
403 		if (rc & SYM_F)
404 			(void) fputs("symlink", differ_fp);
405 		if (rc & PERM_F)
406 			(void) fputs("perm ", differ_fp);
407 		if (rc & REF_F)
408 			(void) fputs("ref_cnt ", differ_fp);
409 		if (rc & TYPE_F)
410 			(void) fputs("file_type ", differ_fp);
411 		if (rc & OWNER_F)
412 			(void) fputs("owner ", differ_fp);
413 		if (rc & GROUP_F)
414 			(void) fputs("group ", differ_fp);
415 		if (rc & MAJMIN_F)
416 			(void) fputs("major/minor ", differ_fp);
417 		(void) putc('\n', differ_fp);
418 		(void) putc('\n', differ_fp);
419 	}
420 }
421 
422 static void
423 check_second_vs_first(int verbose)
424 {
425 	int	i;
426 	elem	*cur;
427 
428 	for (i = 0; i < second_list.num_of_buckets; i++) {
429 		for (cur = second_list.list[i]; cur; cur = cur->next) {
430 			if (!(cur->flag & VISITED_F)) {
431 				if ((first_list.type != second_list.type) &&
432 				    find_elem(&exception_list, cur,
433 				    FOLLOW_LINK)) {
434 					/*
435 					 * this entry is filtered, we don't
436 					 * need to do any more processing.
437 					 */
438 					if (verbose) {
439 						(void) printf(
440 						    "Filtered: Need Deletion "
441 						    "of:\n\t");
442 						print_elem(stdout, cur);
443 					}
444 					continue;
445 				}
446 				/*
447 				 * It is possible for arch specific files to be
448 				 * found in a protodir but listed as arch
449 				 * independent in a protolist file.  If this is
450 				 * a protodir vs. a protolist we will make
451 				 * that check.
452 				 */
453 				if ((second_list.type == PROTODIR_LIST) &&
454 				    (cur->arch != P_ISA) &&
455 				    (first_list.type != PROTODIR_LIST)) {
456 					/*
457 					 * do a lookup for same file, but as
458 					 * type ISA.
459 					 */
460 					elem	*e;
461 
462 					e = find_elem_isa(&first_list, cur,
463 					    NO_FOLLOW_LINK);
464 					if (e) {
465 						do_compare(e, cur,
466 						    first_list.type -
467 						    second_list.type);
468 						continue;
469 					}
470 				}
471 
472 				print_elem(need_rm_fp, cur);
473 			}
474 		}
475 	}
476 }
477 
478 static void
479 check_first_vs_second(int verbose)
480 {
481 	int	i;
482 	elem	*e;
483 	elem	*cur;
484 
485 	for (i = 0; i < first_list.num_of_buckets; i++) {
486 		for (cur = first_list.list[i]; cur; cur = cur->next) {
487 			if ((first_list.type != second_list.type) &&
488 			    find_elem(&exception_list, cur, FOLLOW_LINK)) {
489 				/*
490 				 * this entry is filtered, we don't need to do
491 				 * any more processing.
492 				 */
493 				if (verbose) {
494 					(void) printf("Filtered: Need "
495 					    "Addition of:\n\t");
496 					print_elem(stdout, cur);
497 				}
498 				continue;
499 			}
500 
501 			/*
502 			 * Search package database for file.
503 			 */
504 			e = find_elem(&second_list, cur, NO_FOLLOW_LINK);
505 
506 			/*
507 			 * It is possible for arch specific files to be found
508 			 * in a protodir but listed as arch independent in a
509 			 * protolist file.  If this is a protodir vs. a
510 			 * protolist we will make that check.
511 			 */
512 			if (!e && (first_list.type == PROTODIR_LIST) &&
513 			    (cur->arch != P_ISA) &&
514 			    (second_list.type != PROTODIR_LIST)) {
515 				/*
516 				 * do a lookup for same file, but as type ISA.
517 				 */
518 				e = find_elem_isa(&second_list, cur,
519 				    NO_FOLLOW_LINK);
520 			}
521 
522 			if (!e && (first_list.type != PROTODIR_LIST) &&
523 			    (cur->arch == P_ISA) &&
524 			    (second_list.type == PROTODIR_LIST)) {
525 				/*
526 				 * do a lookup for same file, but as any
527 				 * type but ISA
528 				 */
529 				e = find_elem_mach(&second_list, cur,
530 				    NO_FOLLOW_LINK);
531 			}
532 
533 			if (e == NULL)
534 				print_elem(need_add_fp, cur);
535 			else {
536 				do_compare(cur, e,
537 				    first_list.type - second_list.type);
538 				e->flag |= VISITED_F;
539 			}
540 		}
541 	}
542 }
543 
544 static int
545 read_in_file(const char *file_name, elem_list *list)
546 {
547 	struct stat	st_buf;
548 	int		count = 0;
549 
550 	if (stat(file_name, &st_buf) == 0) {
551 		if (S_ISREG(st_buf.st_mode)) {
552 			if (verbose) {
553 				(void) printf("file(%s): trying to process "
554 				    "as protolist...\n", file_name);
555 			}
556 			count = read_in_protolist(file_name, list, verbose);
557 		} else if (S_ISDIR(st_buf.st_mode)) {
558 			if (verbose)
559 				(void) printf("directory(%s): trying to "
560 				    "process as protodir...\n", file_name);
561 			count = read_in_protodir(file_name, list, verbose);
562 		} else {
563 			(void) fprintf(stderr,
564 			    "%s not a file or a directory.\n", file_name);
565 			usage();
566 			exit(1);
567 		}
568 	} else {
569 		perror(file_name);
570 		usage();
571 		exit(1);
572 	}
573 
574 	return (count);
575 }
576 
577 /* ARGSUSED */
578 static int
579 set_values(const char *fname, const struct stat *sbp, int otype,
580     struct FTW *ftw)
581 {
582 	elem *ep;
583 	uid_t uid;
584 	gid_t gid;
585 	elem keyelem;
586 	mode_t perm;
587 
588 	if (fname[0] == '\0' || fname[1] == '\0' || fname[2] == '\0')
589 		return (0);
590 	/* skip leading "./" */
591 	fname += 2;
592 	switch (otype) {
593 	case FTW_F:
594 	case FTW_D:
595 	case FTW_DP:
596 		if (strlcpy(keyelem.name, fname, sizeof (keyelem.name)) >=
597 		    sizeof (keyelem.name)) {
598 			(void) fprintf(stderr, "%s: %s: name too long\n",
599 			    myname, fname);
600 			return (1);
601 		}
602 		keyelem.arch = P_ISA;
603 		ep = find_elem(&first_list, &keyelem, NO_FOLLOW_LINK);
604 		if (ep == NULL) {
605 			ep = find_elem_mach(&first_list, &keyelem,
606 			    NO_FOLLOW_LINK);
607 		}
608 		/*
609 		 * Do nothing if this is a hard or symbolic link,
610 		 * since links don't have this information.
611 		 *
612 		 * Assume it's a file on the exception list if it's
613 		 * not found in the packaging.  Those are root:bin 755.
614 		 */
615 		if (ep != NULL &&
616 		    (ep->file_type == SYM_LINK_T || ep->file_type == LINK_T)) {
617 			return (0);
618 		}
619 		if (!set_group) {
620 			gid = -1;
621 		} else if (ep == NULL) {
622 			gid = 0;
623 		} else if ((gid = stdfind(ep->group, groupnames)) == -1) {
624 			(void) fprintf(stderr, "%s: %s: group '%s' unknown\n",
625 			    myname, fname, ep->group);
626 			return (1);
627 		}
628 		if (!set_user) {
629 			uid = -1;
630 		} else if (ep == NULL) {
631 			uid = 2;
632 		} else if ((uid = stdfind(ep->owner, usernames)) == -1) {
633 			(void) fprintf(stderr, "%s: %s: user '%s' unknown\n",
634 			    myname, fname, ep->owner);
635 			return (1);
636 		}
637 		if ((set_group && gid != -1 && gid != sbp->st_gid) ||
638 		    (set_user && uid != -1 && uid != sbp->st_uid)) {
639 			if (verbose) {
640 				const char *owner, *group;
641 
642 				owner = ep == NULL ? "root" : ep->owner;
643 				group = ep == NULL ? "bin" : ep->group;
644 				if (set_group && set_user) {
645 					(void) printf("chown %s:%s %s\n",
646 					    owner, group, fname);
647 				} else if (set_user) {
648 					(void) printf("chown %s %s\n", owner,
649 					    fname);
650 				} else {
651 					(void) printf("chgrp %s %s\n", group,
652 					    fname);
653 				}
654 			}
655 			if (lchown(fname, uid, gid) == -1) {
656 				perror(fname);
657 				return (1);
658 			}
659 		}
660 		perm = ep == NULL ? 0755 : ep->perm;
661 		if (set_perm && ((perm ^ sbp->st_mode) & ~S_IFMT) != 0) {
662 			if (verbose)
663 				(void) printf("chmod %lo %s\n", perm, fname);
664 			if (chmod(fname, perm) == -1) {
665 				perror(fname);
666 				return (1);
667 			}
668 		}
669 		return (0);
670 	case FTW_DNR:
671 	case FTW_NS:
672 		(void) fprintf(stderr, "%s: %s: permission denied\n",
673 		    myname, fname);
674 		return (1);
675 	case FTW_SL:
676 	case FTW_SLN:
677 		return (0);
678 	default:
679 		return (1);
680 	}
681 }
682 
683 int
684 main(int argc, char **argv)
685 {
686 	int	errflg = 0;
687 	int	i, c;
688 	int	list_filtered_exceptions = NULL;
689 	int	n_proto_refs = 0;
690 	int	n_exception_files = 0;
691 	char	*proto_refs[MAX_PROTO_REFS];
692 	char	*exception_files[MAX_EXCEPTION_FILES];
693 	struct stat st_buf;
694 
695 	if ((myname = argv[0]) == NULL)
696 		myname = "protocmp";
697 
698 	while ((c = getopt(argc, argv, "gupGUPlmsLe:vd:")) != EOF) {
699 		switch (c) {
700 		case 's':
701 			check_sym = 0;
702 			break;
703 		case 'm':
704 			check_majmin = 0;
705 			break;
706 		case 'g':
707 			check_group = 0;
708 			break;
709 		case 'G':
710 			set_group = 1;
711 			break;
712 		case 'u':
713 			check_user = 0;
714 			break;
715 		case 'U':
716 			set_user = 1;
717 			break;
718 		case 'l':
719 			check_link = 0;
720 			break;
721 		case 'p':
722 			check_perm = 0;
723 			break;
724 		case 'P':
725 			set_perm = 1;
726 			break;
727 		case 'e':
728 			if (n_exception_files >= MAX_EXCEPTION_FILES) {
729 				errflg++;
730 				(void) fprintf(stderr,
731 				    "Only %d exception files supported\n",
732 					MAX_EXCEPTION_FILES);
733 			} else {
734 				exception_files[n_exception_files++] = optarg;
735 			}
736 			break;
737 		case 'L':
738 			list_filtered_exceptions++;
739 			break;
740 		case 'v':
741 			verbose++;
742 			break;
743 		case 'd':
744 			if (n_proto_refs >= MAX_PROTO_REFS) {
745 				errflg++;
746 				(void) fprintf(stderr,
747 				    "Only %d proto references supported\n",
748 					MAX_PROTO_REFS);
749 			} else {
750 				proto_refs[n_proto_refs++] = optarg;
751 			}
752 			break;
753 		case '?':
754 		default:
755 			errflg++;
756 			break;
757 		}
758 	}
759 
760 	if (argc == optind || n_proto_refs == 0) {
761 		usage();
762 		exit(1);
763 	}
764 
765 	if (set_group || set_user || set_perm) {
766 		if (optind != argc - 1) {
767 			usage();
768 			exit(1);
769 		}
770 		if (stat(argv[optind], &st_buf) == -1) {
771 			perror(argv[optind]);
772 			exit(1);
773 		}
774 		if (!S_ISDIR(st_buf.st_mode)) {
775 			(void) fprintf(stderr, "%s: %s: not a directory\n",
776 			    myname, argv[optind]);
777 			exit(1);
778 		}
779 	}
780 
781 	init_list(&first_list, HASH_SIZE);
782 	init_list(&second_list, HASH_SIZE);
783 	init_list(&exception_list, HASH_SIZE);
784 
785 	for (i = 0; i < n_exception_files; i++) {
786 		(void) read_in_exceptions(exception_files[i], verbose);
787 	}
788 
789 	for (i = 0; i < n_proto_refs; i++) {
790 		first_file_name = proto_refs[i];
791 		(void) read_in_file(first_file_name, &first_list);
792 	}
793 
794 	if (set_group || set_user || set_perm) {
795 		if (chdir(argv[optind]) == -1) {
796 			perror(argv[optind]);
797 			exit(1);
798 		}
799 		i = nftw(".", set_values, MAX_DEPTH, FTW_PHYS|FTW_DEPTH);
800 		if (i == -1) {
801 			perror("nftw");
802 			i = 1;
803 		}
804 		exit(i);
805 	}
806 
807 	for (i = optind; i < argc; i++) {
808 		second_file_name = argv[i];
809 		(void) read_in_file(second_file_name, &second_list);
810 	}
811 
812 	open_output_files();
813 
814 	if (verbose)
815 		(void) puts("comparing build to packages...");
816 
817 	check_first_vs_second(list_filtered_exceptions);
818 
819 	if (verbose)
820 		(void) puts("checking over packages...");
821 	check_second_vs_first(list_filtered_exceptions);
822 
823 	close_output_files();
824 
825 	print_results();
826 
827 	clean_up();
828 
829 	return (0);
830 }
831