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