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