xref: /titanic_50/usr/src/tools/protocmp/protodir.c (revision c77a61a72b5ecdc507d6cf104142edd371a16c84)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2006 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 
30 #include <stdio.h>
31 #include <sys/param.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <strings.h>
35 #include <errno.h>
36 #include <dirent.h>
37 #include <sys/stat.h>
38 
39 #include "list.h"
40 #include "protodir.h"
41 #include "arch.h"
42 #include "exception_list.h"
43 
44 #define	FS	" \t\n"
45 
46 static char *
47 resolve_relative(const char *source, const char *link)
48 {
49 	char	*p;
50 	char	*l_next;
51 	char	*l_pos;
52 	static char	curpath[MAXPATHLEN];
53 
54 	/* if absolute path - no relocation required */
55 	if (link[0] == '/')
56 		return (strcpy(curpath, link));
57 
58 	(void) strcpy(curpath, source);
59 	p = rindex(curpath, '/');
60 	*p = '\0';
61 	l_pos = (char *)link;
62 	do {
63 		l_next = index(l_pos, '/');
64 		if (strncmp(l_pos, "../", 3) == 0) {
65 			if ((p = rindex(curpath, '/')) != NULL)
66 				*p = '\0';
67 			else
68 				curpath[0] = '\0';
69 		} else if (strncmp(l_pos, "./", 2)) {
70 			/* if not . then we process */
71 			if (curpath[0])
72 				(void) strcat(curpath, "/");
73 			if (l_next) {
74 				(void) strncat(curpath, l_pos,
75 				    (l_next - l_pos));
76 			} else
77 				(void) strcat(curpath, l_pos);
78 		}
79 		l_pos = l_next + 1;
80 	} while (l_next);
81 
82 	return (curpath);
83 }
84 
85 
86 static int
87 parse_proto_line(const char *basedir, char *line, elem_list *list, short arch,
88     const char *pkgname)
89 {
90 	char	*type, *class, *file, *src, *maj, *min, *perm, *owner, *group;
91 	char	p_line[BUFSIZ];
92 	elem	*dup;
93 	static elem *e = NULL;
94 
95 	(void) strcpy(p_line, line);
96 	if (!e)
97 		e = (elem *)calloc(1, sizeof (elem));
98 
99 	e->flag = 0;
100 
101 	if (!(type  = strtok(p_line, FS))) {
102 		(void) fprintf(stderr, "error: bad line(type) : %s\n", line);
103 		return (-1);
104 	}
105 
106 	e->file_type = type[0];
107 
108 	if ((class = strtok(NULL, FS)) == NULL) {
109 		(void) fprintf(stderr, "error: bad line(class) : %s\n", line);
110 		return (-1);
111 	}
112 
113 	/*
114 	 * Just ignore 'legacy' entries.  These are not present in the proto
115 	 * area at all.  They're phantoms.
116 	 */
117 	if (strcmp(class, "legacy") == 0)
118 		return (0);
119 
120 	if (!(file  = strtok(NULL, FS))) {
121 		(void) fprintf(stderr, "error: bad line(file_name) : %s\n",
122 		    line);
123 		return (-1);
124 	}
125 
126 	e->symsrc = NULL;
127 	if ((src = index(file, '=')) != NULL) {
128 		/*
129 		 * The '=' operator is subtly different for link and non-link
130 		 * entries.  For the hard or soft link case, the left hand side
131 		 * exists in the proto area and is created by the package.
132 		 *
133 		 * When the file is an editable file, it's very likely that the
134 		 * right hand side is only a fragment of that file, which is
135 		 * delivered by multiple packages in the consolidation.  Thus it
136 		 * can't exist in the proto area, and because we can't really
137 		 * know where the file's root directory is, we should skip the
138 		 * file.
139 		 *
140 		 * For all other filetypes, assume the right hand side is in the
141 		 * proto area.
142 		 */
143 		if (e->file_type == SYM_LINK_T || e->file_type == LINK_T) {
144 			*src++ = '\0';
145 			e->symsrc = strdup(src);
146 		} else if (e->file_type == EDIT_T) {
147 			return (0);
148 		} else {
149 			file = src + 1;
150 		}
151 	}
152 
153 	/*
154 	 * if a basedir has a value, prepend it to the filename
155 	 */
156 	if (basedir[0])
157 		(void) strcat(strcat(strcpy(e->name, basedir), "/"), file);
158 	else
159 		(void) strcpy(e->name, file);
160 
161 	if (e->file_type != SYM_LINK_T) {
162 		if ((e->file_type == CHAR_DEV_T) ||
163 		    (e->file_type == BLOCK_DEV_T)) {
164 			if (!(maj = strtok(NULL, FS))) {
165 				(void) fprintf(stderr,
166 				    "error: bad line(major number) : %s\n",
167 				    line);
168 				return (-1);
169 			}
170 			e->major = atoi(maj);
171 
172 			if (!(min = strtok(NULL, FS))) {
173 				(void) fprintf(stderr,
174 				    "error: bad line(minor number) : %s\n",
175 				    line);
176 				return (-1);
177 			}
178 			e->minor = atoi(min);
179 		} else {
180 			e->major = -1;
181 			e->minor = -1;
182 		}
183 
184 		if (!(perm = strtok(NULL, FS))) {
185 			(void) fprintf(stderr,
186 			    "error: bad line(permission) : %s\n", line);
187 			return (-1);
188 		}
189 		e->perm = strtol(perm, NULL, 8);
190 
191 		if (!(owner = strtok(NULL, FS))) {
192 			(void) fprintf(stderr,
193 			    "error: bad line(owner) : %s\n", line);
194 			return (-1);
195 		}
196 		(void) strcpy(e->owner, owner);
197 
198 		if (!(group = strtok(NULL, FS))) {
199 			(void) fprintf(stderr,
200 			    "error: bad line(group) : %s\n", line);
201 			return (-1);
202 		}
203 		(void) strcpy(e->group, group);
204 	}
205 
206 	e->inode = 0;
207 	e->ref_cnt = 1;
208 	e->arch = arch;
209 	e->link_parent = NULL;
210 
211 	if (!(dup = find_elem(list, e, FOLLOW_LINK))) {
212 		e->pkgs = add_pkg(NULL, pkgname); /* init pkgs list */
213 		add_elem(list, e);
214 		e = NULL;
215 		return (1);
216 	} else if (dup->file_type == DIR_T) {
217 		if (!(dup->pkgs = add_pkg(dup->pkgs, pkgname))) {
218 			/* add entry to pkgs */
219 			(void) fprintf(stderr,
220 			    "warning: %s: Duplicate entry for %s\n",
221 			    pkgname, dup->name);
222 			return (-1);
223 		}
224 		if (e->perm != dup->perm) {
225 			(void) fprintf(stderr,
226 			    "warning: %s: permissions %#o of %s do not match "
227 			    "previous permissions %#o\n",
228 			    pkgname, e->perm, dup->name, dup->perm);
229 		}
230 		if (strcmp(e->owner, dup->owner) != 0) {
231 			(void) fprintf(stderr,
232 			    "warning: %s: owner \"%s\" of %s does not match "
233 			    "previous owner \"%s\"\n",
234 			    pkgname, e->owner, dup->name, dup->owner);
235 		}
236 		if (strcmp(e->group, dup->group) != 0) {
237 			(void) fprintf(stderr,
238 			    "warning: %s: group \"%s\" of %s does not match "
239 			    "previous group \"%s\"\n",
240 			    pkgname, e->group, dup->name, dup->group);
241 		}
242 	} else {
243 		/*
244 		 * Signal an error only if this is something that's not on the
245 		 * exception list.
246 		 */
247 		(void) strcpy(e->name, file);
248 		if (find_elem(&exception_list, e, FOLLOW_LINK) == NULL) {
249 			(void) fprintf(stderr,
250 			    "warning: %s: duplicate entry for %s - ignored\n",
251 			    pkgname, e->name);
252 			return (-1);
253 		}
254 	}
255 
256 	return (0);
257 }
258 
259 static int
260 parse_proto_link(const char *basedir, char *line, elem_list *list, short arch,
261     const char *pkgname)
262 {
263 	char	*type, *file, *src;
264 	char	p_line[BUFSIZ];
265 	elem	*p, *dup;
266 	elem	key;
267 	static elem	*e = NULL;
268 
269 
270 	(void) strcpy(p_line, line);
271 	if (!e)
272 		e = (elem *)calloc(1, sizeof (elem));
273 
274 	e->flag = 0;
275 	type = strtok(p_line, FS);
276 	e->arch = arch;
277 
278 	e->file_type = type[0];
279 	(void) strtok(NULL, FS);   /* burn class */
280 
281 	file = strtok(NULL, FS);
282 	if ((src = index(file, '=')) != NULL) {
283 		*src++ = '\0';
284 		e->symsrc = strdup(src);
285 	} else {
286 		(void) fprintf(stderr,
287 		    "error: %s: hard link does not have destination (%s)\n",
288 		    pkgname, file);
289 		return (0);
290 	}
291 
292 	/*
293 	 * if a basedir has a value, prepend it to the filename
294 	 */
295 	if (basedir[0])
296 		(void) strcat(strcat(strcpy(e->name, basedir), "/"), file);
297 	else
298 		(void) strcpy(e->name, file);
299 
300 	/*
301 	 * now we need to find the file that we link to - to do this
302 	 * we build a key.
303 	 */
304 
305 	src = resolve_relative(e->name, e->symsrc);
306 	(void) strcpy(key.name, src);
307 	key.arch = e->arch;
308 	if ((p = find_elem(list, &key, NO_FOLLOW_LINK)) == NULL) {
309 		(void) fprintf(stderr,
310 		    "error: %s: hardlink to non-existent file: %s=%s\n",
311 		    pkgname, e->name, e->symsrc);
312 		return (0);
313 	}
314 	if ((p->file_type == SYM_LINK_T) || (p->file_type == LINK_T)) {
315 		(void) fprintf(stderr,
316 		    "error: %s: hardlink must link to a file or directory "
317 		    "(not other links): %s=%s\n", pkgname, e->name, p->name);
318 		return (0);
319 	}
320 	e->link_parent = p;
321 	e->link_sib = p->link_sib;
322 	p->link_sib = e;
323 	p->ref_cnt++;
324 	e->inode = p->inode;
325 	e->perm = p->perm;
326 	e->ref_cnt = p->ref_cnt;
327 	e->major = p->major;
328 	e->minor = p->minor;
329 	(void) strcpy(e->owner, p->owner);
330 	(void) strcpy(e->group, p->owner);
331 
332 	if (!(dup = find_elem(list, e, NO_FOLLOW_LINK))) {
333 		e->pkgs = add_pkg(NULL, pkgname); /* init pkgs list */
334 		e->link_sib = NULL;
335 		add_elem(list, e);
336 		e = NULL;
337 		return (1);
338 	} else {
339 		/*
340 		 * Signal an error only if this is something that's not on the
341 		 * exception list.
342 		 */
343 		(void) strcpy(e->name, file);
344 		if (find_elem(&exception_list, e, FOLLOW_LINK) == NULL) {
345 			(void) fprintf(stderr,
346 			    "warning: %s: duplicate entry for %s - ignored\n",
347 			    pkgname, e->name);
348 			return (-1);
349 		}
350 	}
351 
352 	return (0);
353 }
354 
355 
356 /*
357  * open up the pkginfo file and find the ARCH= and the BASEDIR= macros.
358  * I will set the arch and basedir variables based on these fields.
359  */
360 static void
361 read_pkginfo(const char *protodir, short *arch, char *basedir)
362 {
363 	char	pkginfofile[MAXPATHLEN];
364 	char	architecture[MAXPATHLEN];
365 	char	buf[BUFSIZ];
366 	FILE	*pkginfo_fp;
367 	int	hits = 0;
368 	int	i;
369 	int	index;
370 
371 
372 	architecture[0] = '\0';
373 	basedir[0] = '\0';
374 	*arch = P_ISA;
375 
376 	/*
377 	 * determine whether the pkginfo file is a pkginfo.tmpl or
378 	 * a pkginfo file
379 	 */
380 	(void) strcat(strcat(strcpy(pkginfofile, protodir), "/"),
381 	    "pkginfo.tmpl");
382 
383 	if ((pkginfo_fp = fopen(pkginfofile, "r")) == NULL) {
384 		(void) strcat(strcat(strcpy(pkginfofile, protodir), "/"),
385 		    "pkginfo");
386 		if ((pkginfo_fp = fopen(pkginfofile, "r")) == NULL) {
387 			perror(pkginfofile);
388 			return;
389 		}
390 	}
391 
392 
393 	while (fgets(buf, BUFSIZ, pkginfo_fp) && (hits != 3)) {
394 		if (strncmp(buf, "ARCH=", 5) == 0) {
395 			index = 0;
396 			/*
397 			 * remove any '"' in the ARCH field.
398 			 */
399 			for (i = 5; buf[i]; i++) {
400 				if (buf[i] != '"')
401 					architecture[index++] = buf[i];
402 			}
403 			/* -1 because above copy included '\n' */
404 			architecture[index-1] = '\0';
405 			hits += 1;
406 		} else if (strncmp(buf, "BASEDIR=", 8) == 0) {
407 			index = 0;
408 			/*
409 			 * remove any '"' in the BASEDIR field, and
410 			 * strip off a leading '/' if present.
411 			 */
412 			for (i = 8; buf[i]; i++) {
413 				if (buf[i] != '"' &&
414 				    (buf[i] != '/' || index != 0)) {
415 					buf[index++] = buf[i];
416 				}
417 			}
418 			/* -1 because above copy included '\n' */
419 			buf[index-1] = '\0';
420 			(void) strcpy(basedir, &buf[0]);
421 			hits += 2;
422 		}
423 	}
424 	(void) fclose(pkginfo_fp);
425 
426 	if (architecture[0])
427 		if ((*arch = assign_arch(architecture)) == NULL) {
428 			(void) fprintf(stderr,
429 			    "warning: Unknown architecture %s found in %s\n",
430 			    architecture, pkginfofile);
431 		}
432 }
433 
434 /*
435  * The first pass through the prototype file goes through and reads
436  * in all the entries except 'hard links'.  Those must be processed
437  * in a second pass.
438  *
439  * If any !includes are found in the prototype file this routine
440  * will be called recursively.
441  *
442  * Args:
443  *   protofile - full pathname to prototype file to be processed.
444  *   protodir  - directory in which prototype file resides.
445  *   list      - list to which elements will be added
446  *   arch      - architecture of current prototype
447  *   basedir   - basedir for package
448  *   pkgname   - name of package
449  *
450  * Returns:
451  *   returns number of items added to list.
452  *
453  */
454 static int
455 first_pass_prototype(const char *protofile, const char *protodir,
456     elem_list *list, short arch, const char *basedir, const char *pkgname)
457 {
458 	int	elem_count = 0;
459 	FILE	*proto_fp;
460 	char	include_file[MAXPATHLEN];
461 	char	buf[BUFSIZ];
462 
463 	if ((proto_fp = fopen(protofile, "r")) == NULL) {
464 		perror(protofile);
465 		return (0);
466 	}
467 
468 	/*
469 	 * first pass through file - process everything but
470 	 * hard links.
471 	 */
472 	while (fgets(buf, BUFSIZ, proto_fp)) {
473 		int	rc;
474 
475 		switch (buf[0]) {
476 		case FILE_T:
477 		case EDIT_T:
478 		case VOLATILE_T:
479 		case DIR_T:
480 		case SYM_LINK_T:
481 		case CHAR_DEV_T:
482 		case BLOCK_DEV_T:
483 			if ((rc = parse_proto_line(basedir, buf, list, arch,
484 			    pkgname)) >= 0) {
485 				elem_count += rc;
486 			} else {
487 				(void) fprintf(stderr,
488 				    "error: Errors found in %s\n", protofile);
489 			}
490 			break;
491 		case LINK_T:
492 		case 'i':
493 		case '#':
494 		case '\n':
495 			break;
496 		case '!':
497 			/* Is this an include statement - if so process */
498 			if (strncmp(buf, "!include", 8) == 0) {
499 				char *inc_file = (char *)(buf + 9);
500 
501 				/* burn white space */
502 				while ((*inc_file == ' ') ||
503 				    (*inc_file == '\t'))
504 					inc_file++;
505 				if (*inc_file) {
506 					/* remove trailing \n */
507 					inc_file[strlen(inc_file) - 1] = '\0';
508 					(void) strcat(strcat(strcpy(
509 					    include_file, protodir), "/"),
510 					    inc_file);
511 					elem_count +=
512 					    first_pass_prototype(include_file,
513 					    protodir, list, arch, basedir,
514 					    pkgname);
515 				} else {
516 					(void) fprintf(stderr,
517 					    "warning: bad !include statement "
518 					    "in prototype %s : %s\n",
519 					    protofile, buf);
520 				}
521 			} else {
522 				(void) fprintf(stderr,
523 				    "warning: unexpected ! notation in "
524 				    "prototype %s : %s\n", protofile, buf);
525 
526 			}
527 			break;
528 		default:
529 			(void) fprintf(stderr,
530 			    "warning: unexpected line in prototype %s : %s\n",
531 			    protofile, buf);
532 			break;
533 		}
534 	}
535 
536 	(void) fclose(proto_fp);
537 
538 	return (elem_count);
539 }
540 
541 /*
542  * The second pass through the prototype file goes through and reads
543  * and processes only the 'hard links' in the prototype file.  These
544  * are resolved and added accordingly to the elements list(list).
545  *
546  * If any !includes are found in the prototype file this routine
547  * will be called recursively.
548  *
549  * Args:
550  *   protofile - full pathname to prototype file to be processed.
551  *   protodir  - directory in which prototype file resides.
552  *   list      - list to which elements will be added
553  *   arch      - architecture of current prototype
554  *   basedir   - basedir for package
555  *   pkgname   - package name
556  *
557  * Returns:
558  *   returns number of items added to list.
559  *
560  */
561 static int
562 second_pass_prototype(const char *protofile, const char *protodir,
563     elem_list *list, short arch, const char *basedir, const char *pkgname)
564 {
565 	FILE	*proto_fp;
566 	int	elem_count = 0;
567 	char	include_file[MAXPATHLEN];
568 	char	buf[BUFSIZ];
569 
570 	if ((proto_fp = fopen(protofile, "r")) == NULL) {
571 		perror(protofile);
572 		return (0);
573 	}
574 
575 	/*
576 	 * second pass through prototype file - process the hard links
577 	 * now.
578 	 */
579 	while (fgets(buf, BUFSIZ, proto_fp))
580 		if (buf[0] == LINK_T) {
581 			int	rc;
582 
583 			if ((rc = parse_proto_link(basedir, buf, list, arch,
584 			    pkgname)) >= 0) {
585 				elem_count += rc;
586 			} else {
587 				(void) fprintf(stderr,
588 				    "error: Errors found in %s\n", protofile);
589 			}
590 		} else if (strncmp(buf, "!include", 8) == 0) {
591 			/*
592 			 * This is a file to include
593 			 */
594 			char *inc_file = (char *)(buf + 9);
595 
596 			/* burn white space */
597 			while ((*inc_file == ' ') || (*inc_file == '\t'))
598 				inc_file++;
599 
600 			if (*inc_file) {
601 				/* remove trailing \n */
602 				inc_file[strlen(inc_file) - 1] = '\0';
603 				/* build up include file name to be opened. */
604 				(void) strcat(strcat(strcpy(include_file,
605 				    protodir), "/"), inc_file);
606 				/*
607 				 * recursively call this routine to process the
608 				 * !include file.
609 				 */
610 				elem_count +=
611 				    second_pass_prototype(include_file,
612 				    protodir, list, arch, basedir, pkgname);
613 			} else {
614 				(void) fprintf(stderr,
615 				    "warning: Bad !include statement in "
616 				    "prototype %s : %s\n", protofile, buf);
617 			}
618 		}
619 
620 		(void) fclose(proto_fp);
621 
622 		return (elem_count);
623 }
624 
625 /*
626  * Args:
627  *    pkgname  - name of package being processed
628  *    protodir - pathname to package defs directory
629  *    list     - List of elements read in, elements are added to this
630  *		 as they are read in.
631  *    verbose  - verbose output
632  *
633  * Returns:
634  *    number of elements added to list
635  */
636 int
637 process_package_dir(const char *pkgname, const char *protodir,
638     elem_list *list, int verbose)
639 {
640 	struct stat	st_buf;
641 	char		protofile[MAXPATHLEN];
642 	char		basedir[MAXPATHLEN];
643 	short		arch;
644 	int		count = 0;
645 
646 
647 	/*
648 	 * skip any packages we've already handled (because of
649 	 * dependencies)
650 	 */
651 	if (processed_package(pkgname)) {
652 		return (0);
653 	}
654 
655 	/*
656 	 * find the prototype file.  Legal forms of the name are:
657 	 *		prototype
658 	 *		prototype_<mach> (where mach == (sparc || i386 || ppc)
659 	 */
660 	(void) strcat(strcat(strcpy(protofile, protodir), "/"), "prototype");
661 	if (stat(protofile, &st_buf) < 0) {
662 		if (errno == ENOENT) {
663 			(void) strcat(strcat(strcat(strcpy(protofile,
664 			    protodir), "/"), "prototype"), PROTO_EXT);
665 			if (stat(protofile, &st_buf) < 0) {
666 				if (errno == ENOENT) {
667 					if (verbose) {
668 						(void) fprintf(stderr,
669 						    "warning: no prototype "
670 						    "file found in %s, "
671 						    "skipping...\n",
672 						    protodir);
673 					}
674 				} else
675 					perror(protofile);
676 				return (0);
677 			}
678 		} else {
679 			perror(protofile);
680 			return (0);
681 		}
682 	}
683 
684 	mark_processed(pkgname);
685 
686 	read_pkginfo(protodir, &arch, basedir);
687 
688 	count += first_pass_prototype(protofile, protodir, list, arch,
689 	    basedir, pkgname);
690 	count += second_pass_prototype(protofile, protodir, list, arch,
691 	    basedir, pkgname);
692 
693 	/* print_list(list); */
694 	return (count);
695 }
696 
697 int
698 read_in_protodir(const char *dir_name, elem_list *list, int verbose)
699 {
700 	DIR		*p_dir;
701 	struct dirent	*dp;
702 	char		protodir[MAXPATHLEN];
703 	struct stat	st_buf;
704 	int		count = 0;
705 
706 	if ((p_dir = opendir(dir_name)) == NULL) {
707 		perror(dir_name);
708 		exit(1);
709 	}
710 
711 	list->type = PROTODIR_LIST;
712 
713 	while ((dp = readdir(p_dir)) != NULL) {
714 		/*
715 		 * let's not check "." and ".." - I don't really like this
716 		 * but I wasn't really sure you could be sure that they
717 		 * are always the first two entries in the directory
718 		 * structure  - so I put it in the loop.
719 		 *
720 		 * Also - we skip all directories that are names .del-*.
721 		 * and any SCCS directories too.
722 		 */
723 		if ((strcmp(dp->d_name, ".") == 0) ||
724 		    (strcmp(dp->d_name, "..") == 0) ||
725 		    (strncmp(dp->d_name, ".del-", 5) == 0) ||
726 		    (strcmp(dp->d_name, "SCCS") == 0))
727 			continue;
728 
729 		(void) strcat(strcat(strcpy(protodir, dir_name), "/"),
730 		    dp->d_name);
731 		if (stat(protodir, &st_buf) < 0) {
732 			perror(protodir);
733 			continue;
734 		}
735 		if (!S_ISDIR(st_buf.st_mode)) {
736 			if (verbose) {
737 				(void) fprintf(stderr,
738 				    "warning: %s not a directory\n", protodir);
739 			}
740 			continue;
741 		}
742 
743 		count += process_dependencies(dp->d_name, dir_name, list,
744 		    verbose);
745 
746 		count += process_package_dir(dp->d_name, protodir, list,
747 		    verbose);
748 	}
749 
750 	if (verbose)
751 		(void) printf("read in %d lines\n", count);
752 
753 	(void) closedir(p_dir);
754 
755 	return (count);
756 }
757