xref: /titanic_50/usr/src/cmd/svr4pkg/pkgremove/special.c (revision 5c51f1241dbbdf2656d0e10011981411ed0c9673)
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 2003 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 
28 /*
29  * special.c
30  *
31  * This module contains code required to remove special contents from
32  * the contents file when a pkgrm is done on a system upgraded to use
33  * the new database.
34  */
35 
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <assert.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <time.h>
43 #include <limits.h>
44 #include <fnmatch.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <pkgstrct.h>
48 #include "pkglib.h"
49 #include <libintl.h>
50 
51 /* This specifies the maximum length of a contents file line read in. */
52 #define	LINESZ	8192
53 
54 #define	SPECIAL_MALLOC	"unable to maintain package contents text due to "\
55 			"insufficient memory."
56 #define	SPECIAL_ACCESS	"unable to maintain package contents text due to "\
57 			"an access failure."
58 #define	SPECIAL_INPUT	"unable to maintain package contents text: alternate "\
59 			"root path too long"
60 
61 /*
62  * strcompare
63  *
64  * This function is used by qsort to sort an array of special contents
65  * rule strings.  This array must be sorted to facilitate efficient
66  * rule processing.  See qsort(3c) regarding qsort compare functions.
67  */
68 static int
strcompare(const void * pv1,const void * pv2)69 strcompare(const void *pv1, const void *pv2)
70 {
71 	char **ppc1 = (char **) pv1;
72 	char **ppc2 = (char **) pv2;
73 	int i = strcmp(*ppc1, *ppc2);
74 	if (i < 0)
75 		return (-1);
76 	if (i > 0)
77 		return (1);
78 	return (0);
79 }
80 
81 /*
82  * match
83  *
84  * This function determines whether a file name (pc) matches a rule
85  * from the special contents file (pcrule).  We assume that neither
86  * string is ever NULL.
87  *
88  * Return: 1 on match, 0 on no match.
89  * Side effects: none.
90  */
91 static int
match(const char * pc,char * pcrule)92 match(const char *pc, char *pcrule)
93 {
94 	int n = strlen(pcrule);
95 	int wild = 0;
96 	if (pcrule[n - 1] == '*') {
97 		wild = 1;
98 		pcrule[n - 1] = '\0';
99 	}
100 
101 	if (!wild) {
102 		if (fnmatch(pc, pcrule, FNM_PATHNAME) == 0 ||
103 		    fnmatch(pc, pcrule, 0) == 0)
104 		return (1);
105 	} else {
106 		int j;
107 		j = strncmp(pc, pcrule, n - 1);
108 		pcrule[n - 1] = '*';
109 		if (j == 0)
110 			return (1);
111 	}
112 	return (0);
113 }
114 
115 /*
116  * search_special_contents
117  *
118  * This function assumes that a series of calls will be made requesting
119  * whether a given path matches the special contents rules or not.  We
120  * assume that
121  *
122  *   a) the special_contents array is sorted
123  *   b) the calls will be made with paths in a sorted order
124  *
125  * Given that, we can keep track of where the last search ended and
126  * begin the new search at that point.  This reduces the cost of a
127  * special contents matching search to O(n) from O(n^2).
128  *
129  *   ppcSC  A pointer to an array of special contents obtained via
130  *	  get_special_contents().
131  *   path   A path: determine whether it matches the special
132  *	  contents rules or not.
133  *   piX    The position in the special_contents array we have already
134  *	  arrived at through searching.  This must be initialized to
135  *	  zero before initiating a series of search_special_contents
136  *	  operations.
137  *
138  * Example:
139  * {
140  *	int i = 0, j, max;
141  *	char **ppSC = NULL;
142  *	if (get_special_contents(NULL, &ppcSC, &max) != 0) exit(1);
143  *	for (j = 0; paths != NULL && paths[j] != NULL; j++) {
144  *		if (search_special_contents(ppcSC, path[j], &i)) {
145  *			do_something_with_special_path(path[j]);
146  *		}
147  *	}
148  * }
149  *
150  * Return: 1 if there is a match, 0 otherwise.
151  * Side effects: The value of *piX will be set between calls to this
152  *    function.  To make this function thread safe, use search arrays.
153  *    Also:  Nonmatching entries are eliminated, set to NULL.
154  */
155 static int
search_special_contents(char ** ppcSC,const char * pcpath,int * piX,int max)156 search_special_contents(char **ppcSC, const char *pcpath, int *piX, int max)
157 {
158 	int wild;
159 	if (ppcSC == NULL || *piX == max)
160 		return (0);
161 
162 	while (*piX < max) {
163 
164 		int j, k;
165 		if (ppcSC[*piX] == NULL) {
166 			(*piX)++;
167 			continue;
168 		}
169 
170 		j = strlen(ppcSC[*piX]);
171 		k = strcmp(pcpath, ppcSC[*piX]);
172 		wild = (ppcSC[*piX][j - 1] == '*');
173 
174 		/*
175 		 * Depending on whether the path string compared with the
176 		 * rule, we take different actions.  If the path is less
177 		 * than the rule, we keep the rule.  If the path equals
178 		 * the rule, we advance the rule (as long as the rule is
179 		 * not a wild card).  If the path is greater than the rule,
180 		 * we have to advance the rule list until we are less or equal
181 		 * again.  This way we only have to make one pass through the
182 		 * rules, as we make one pass through the path strings.  We
183 		 * assume that the rules and the path strings are sorted.
184 		 */
185 		if (k < 0) {
186 
187 			if (wild == 0)
188 				return (0);
189 
190 			if (match(pcpath, ppcSC[*piX]))
191 				return (1);
192 			break;
193 
194 		} else if (k == 0) {
195 
196 			int x = match(pcpath, ppcSC[*piX]);
197 			if (wild == 0) (*piX)++;
198 			return (x);
199 
200 		} else {
201 			/* One last try. */
202 			if (match(pcpath, ppcSC[*piX]))
203 				return (1);
204 
205 			/*
206 			 * As pcpath > ppcSC[*piX] we have passed up this
207 			 * rule - it cannot apply.  Therefore, we do not
208 			 * need to retain it.  Removing the rule will make
209 			 * subsequent searching more efficient.
210 			 */
211 			free(ppcSC[*piX]);
212 			ppcSC[*piX] = NULL;
213 
214 			(*piX)++;
215 		}
216 	}
217 	return (0);
218 }
219 
220 /*
221  * get_special_contents
222  *
223  * Retrieves the special contents file entries, if they exist.  These
224  * are sorted.  We do not assume the special_contents file is in sorted
225  * order.
226  *
227  *   pcroot   The root of the install database.  If NULL assume '/'.
228  *   pppcSC   A pointer to a char **.  This pointer will be set to
229  *		point at NULL if there is no special_contents file or
230  *		to a sorted array of strings, NULL terminated, otherwise.
231  *   piMax    The # of entries in the special contents result.
232  *
233  * Returns:  0 on no error, nonzero on error.
234  * Side effects:  the pppcSC pointer is set to point at a newly
235  *   allocated array of pointers to strings..  The caller must
236  *   free this buffer.  The value of *piMax is set to the # of
237  *   entries in ppcSC.
238  */
239 static int
get_special_contents(const char * pcroot,char *** pppcSC,int * piMax)240 get_special_contents(const char *pcroot, char ***pppcSC, int *piMax)
241 {
242 	int e, i;
243 	FILE *fp;
244 	char line[2048];
245 	char **ppc;
246 	char *pc = "var/sadm/install/special_contents";
247 	char path[PATH_MAX];
248 	struct stat s;
249 
250 	/* Initialize the return values. */
251 	*piMax = 0;
252 	*pppcSC = NULL;
253 
254 	if (pcroot == NULL) {
255 		pcroot = "/";
256 	}
257 
258 	if (pcroot[strlen(pcroot) - 1] == '/') {
259 		if (snprintf(path, PATH_MAX, "%s%s", pcroot, pc) >= PATH_MAX) {
260 			progerr(gettext(SPECIAL_INPUT));
261 			return (1);
262 		}
263 	} else {
264 		if (snprintf(path, PATH_MAX, "%s/%s", pcroot, pc)
265 		    >= PATH_MAX) {
266 			progerr(gettext(SPECIAL_INPUT));
267 			return (1);
268 		}
269 	}
270 
271 	errno = 0;
272 	e = stat(path, &s);
273 	if (e != 0 && errno == ENOENT)
274 		return (0); /* No special contents file.  Do nothing. */
275 
276 	if (access(path, R_OK) != 0 || (fp = fopen(path, "r")) == NULL) {
277 		/* Could not open special contents which exists */
278 		progerr(gettext(SPECIAL_ACCESS));
279 		return (1);
280 	}
281 
282 	for (i = 0; fgets(line, 2048, fp) != NULL; i++);
283 	rewind(fp);
284 	if ((ppc = (char **) calloc(i + 1, sizeof (char *))) == NULL) {
285 		progerr(gettext(SPECIAL_MALLOC));
286 		return (1);
287 	}
288 
289 	for (i = 0; fgets(line, 2048, fp) != NULL; ) {
290 		int n;
291 		if (line[0] == '#' || line[0] == ' ' || line[0] == '\n' ||
292 		    line[0] == '\t' || line[0] == '\r')
293 			continue;
294 		n = strlen(line);
295 		if (line[n - 1] == '\n')
296 			line[n - 1] = '\0';
297 		ppc[i++] = strdup(line);
298 	}
299 
300 	qsort(ppc, i, sizeof (char *), strcompare);
301 
302 	*pppcSC = ppc;
303 	*piMax = i;
304 	return (0);
305 }
306 
307 /*
308  * free_special_contents
309  *
310  * This function frees special_contents which have been allocated using
311  * get_special_contents.
312  *
313  *   pppcSC    A pointer to a buffer allocated using get_special_contents.
314  *   max       The number of entries allocated.
315  *
316  * Result: None.
317  * Side effects: Frees memory allocated using get_special_contents and
318  *    sets the pointer passed in to NULL.
319  */
320 static void
free_special_contents(char *** pppcSC,int max)321 free_special_contents(char ***pppcSC, int max)
322 {
323 	int i;
324 	char **ppc = NULL;
325 	if (*pppcSC == NULL)
326 		return;
327 
328 	ppc = *pppcSC;
329 	for (i = 0; ppc != NULL && i < max; i++)
330 		if (ppc[i] == NULL)
331 			free(ppc[i]);
332 
333 	if (ppc != NULL)
334 		free(ppc);
335 
336 	*pppcSC = NULL;
337 }
338 
339 /*
340  * get_path
341  *
342  * Return the first field of a string delimited by a space.
343  *
344  *   pcline	A line from the contents file.
345  *
346  * Return: NULL if an error.  Otherwise a string allocated by this
347  *   function.  The caller must free the string.
348  * Side effects: none.
349  */
350 static char *
get_path(const char * pcline)351 get_path(const char *pcline)
352 {
353 	int i = strcspn(pcline, " ");
354 	char *pc = NULL;
355 	if (i <= 1 || (pc = (char *) calloc(i + 1, 1)) == NULL)
356 		return (NULL);
357 	(void) memcpy(pc, pcline, i);
358 	return (pc);
359 }
360 
361 /*
362  * generate_special_contents_rules
363  *
364  * This procedure will generate an array of integers which will be a mask
365  * to apply to the ppcfextra array.  If set to 1, then the content must be
366  * added to the contents file.  Otherwise it will not be:  The old contents
367  * file will be used for this path value, if one even exists.
368  *
369  *    ient	The number of ppcfextra contents installed.
370  *    ppcfent	The contents installed.
371  *    ppcSC	The rules (special contents)
372  *    max	The number of special contents rules.
373  *    ppiIndex	The array of integer values, determining whether
374  *		individual ppcfextra items match special contents rules.
375  *		This array will be created and set in this function and
376  *		returned.
377  *
378  * Return: 0 success, nonzero failure
379  * Side effects: allocates an array of integers that the caller must free.
380  */
381 static int
generate_special_contents_rules(int ient,struct cfent ** ppcfent,char ** ppcSC,int max,int ** ppiIndex)382 generate_special_contents_rules(int ient, struct cfent **ppcfent,
383     char **ppcSC, int max, int **ppiIndex)
384 {
385 	int i, j;
386 	int *pi = (int *) calloc(ient, sizeof (int));
387 	if (pi == NULL) {
388 		progerr(gettext(SPECIAL_MALLOC));
389 		return (1);
390 	}
391 
392 	/*
393 	 * For each entry in ppcfextra, check if it matches a rule.
394 	 * If it does not, set the entry in the index to -1.
395 	 */
396 	for (i = 0, j = 0; i < ient && j < max; i++) {
397 		if (search_special_contents(ppcSC, ppcfent[i]->path,
398 		    &j, max) == 1) {
399 			pi[i] = 1;
400 
401 		} else {
402 			pi[i] = 0;
403 		}
404 	}
405 
406 	/*
407 	 * In case we ran out of rules before contents, we will not use
408 	 * those contents.  Make sure these contents are set to 0 and
409 	 * will not be copied from the ppcfent array into the contents
410 	 * file.
411 	 */
412 	for (i = i; i < ient; i++)
413 		pi[i] = 0;
414 
415 	*ppiIndex = pi;
416 	return (0);
417 }
418 
419 
420 /*
421  * pathcmp
422  *
423  * Compare a path to a cfent.  It will match either if the path is
424  * equal to the cfent path, or if the cfent is a symbolic or link
425  * and *that* matches.
426  *
427  *    path	a path
428  *    pent      a contents entry
429  *
430  * Returns: as per strcmp
431  * Side effects: none.
432  */
433 static int
pathcmp(const char * pc,const struct cfent * pent)434 pathcmp(const char *pc, const struct cfent *pent)
435 {
436 	int i;
437 	if ((pent->ftype == 's' || pent->ftype == 'l') &&
438 	    pent->ainfo.local) {
439 		char *p, *q;
440 		if ((p = strstr(pc, "=")) == NULL) {
441 
442 			i = strcmp(pc, pent->path);
443 
444 			/* A path without additional chars strcmp's to less */
445 			if (i == 0)
446 				i = -1;
447 
448 		} else {
449 			/* Break the link path into two pieces. */
450 			*p = '\0';
451 
452 			/* Compare the first piece. */
453 			i = strcmp(pc, pent->path);
454 
455 			/* If equal we must compare the second piece. */
456 			if (i == 0) {
457 				q = p + 1;
458 				i = strcmp(q, pent->ainfo.local);
459 			}
460 
461 			/* Restore the link path. */
462 			*p = '=';
463 		}
464 	} else {
465 		i = strcmp(pc, pent->path);
466 	}
467 
468 	return (i);
469 }
470 
471 /*
472  * -----------------------------------------------------------------------
473  * Externally visible function.
474  */
475 
476 /*
477  * special_contents_remove
478  *
479  * Given a set of entries to remove and an alternate root, this function
480  * will do everything required to ensure that the entries are removed
481  * from the contents file if they are listed in the special_contents
482  * file.  The contents file will get changed only in the case that the
483  * entire operation has succeeded.
484  *
485  *  ient	The number of entries.
486  *  ppcfent	The entries to remove.
487  *  pcroot	The alternate install root.  Could be NULL.  In this
488  *		case, assume root is '/'
489  *
490  * Result: 0 on success, nonzero on failure.  If an error occurs, an
491  *    error string will get output to standard error alerting the user.
492  * Side effects: The contents file may change as a result of this call,
493  *    such that lines in the in the file will be changed or removed.
494  *    If the call fails, a t.contents file may be left behind.  This
495  *    temporary file should be removed subsequently.
496  */
497 int
special_contents_remove(int ient,struct cfent ** ppcfent,const char * pcroot)498 special_contents_remove(int ient, struct cfent **ppcfent, const char *pcroot)
499 {
500 	int result = 0;		/* Assume we will succeed.  Return result. */
501 	char **ppcSC = NULL;	/* The special contents rules, sorted. */
502 	int i, j;		/* Indexes into contents & special contents */
503 	FILE *fpi = NULL,	/* Input of contents file */
504 	    *fpo = NULL;	/* Output to temp contents file */
505 	char cpath[PATH_MAX],	/* Contents file path */
506 	    tcpath[PATH_MAX];	/* Temp contents file path */
507 	const char *pccontents = "var/sadm/install/contents";
508 	const char *pctcontents = "var/sadm/install/t.contents";
509 	char line[LINESZ];	/* Reads in and writes out contents lines. */
510 	time_t t;		/* Used to create a timestamp comment. */
511 	int max;		/* Max number of special contents entries. */
512 	int *piIndex;		/* An index to ppcfents to remove from cfile */
513 
514 	cpath[0] = tcpath[0] = '\0';
515 
516 	if (ient == 0 || ppcfent == NULL || ppcfent[0] == NULL) {
517 		goto remove_done;
518 	}
519 
520 	if ((get_special_contents(pcroot, &ppcSC, &max)) != 0) {
521 		result = 1;
522 		goto remove_done;
523 	}
524 
525 	/* Check if there are no special contents actions to take. */
526 	if (ppcSC == NULL) {
527 		goto remove_done;
528 	}
529 
530 	if (pcroot == NULL) pcroot = "/";
531 	if (pcroot[strlen(pcroot) - 1] == '/') {
532 		if (snprintf(cpath, PATH_MAX, "%s%s", pcroot, pccontents)
533 		    >= PATH_MAX ||
534 		    snprintf(tcpath, PATH_MAX, "%s%s", pcroot, pctcontents)
535 		    >= PATH_MAX) {
536 			progerr(gettext(SPECIAL_INPUT));
537 			result = -1;
538 			goto remove_done;
539 		}
540 	} else {
541 		if (snprintf(cpath, PATH_MAX, "%s/%s", pcroot, pccontents)
542 		    >= PATH_MAX ||
543 		    snprintf(tcpath, PATH_MAX, "%s/%s", pcroot, pctcontents)
544 		    >= PATH_MAX) {
545 			progerr(gettext(SPECIAL_INPUT));
546 			result = -1;
547 			goto remove_done;
548 		}
549 	}
550 
551 	/* Open the temporary contents file to write, contents to read. */
552 	if (access(cpath, F_OK | R_OK) != 0) {
553 		/*
554 		 * This is not a problem since no contents means nothing
555 		 * to remove due to special contents rules.
556 		 */
557 		result = 0;
558 		cpath[0] = '\0'; /* This signals omission of 'rename cleanup' */
559 		goto remove_done;
560 	}
561 
562 	if (access(cpath, W_OK) != 0) {
563 		/* can't write contents file, something is wrong. */
564 		progerr(gettext(SPECIAL_ACCESS));
565 		result = 1;
566 		goto remove_done;
567 
568 	}
569 
570 	if ((fpi = fopen(cpath, "r")) == NULL) {
571 		/* Given the access test above, this should not happen. */
572 		progerr(gettext(SPECIAL_ACCESS));
573 		result = 1;
574 		goto remove_done;
575 	}
576 
577 	if ((fpo = fopen(tcpath, "w")) == NULL) {
578 		/* open t.contents failed */
579 		progerr(gettext(SPECIAL_ACCESS));
580 		result = 1;
581 		goto remove_done;
582 	}
583 
584 	if (generate_special_contents_rules(ient, ppcfent, ppcSC, max, &piIndex)
585 	    != 0) {
586 		result = 1;
587 		goto remove_done;
588 	}
589 
590 	/*
591 	 * Copy contents to t.contents unless there is an entry in
592 	 * the ppcfent array which corresponds to an index set to 1.
593 	 *
594 	 * These items are the removed package contents which matche an
595 	 * entry in ppcSC (the special_contents rules).
596 	 *
597 	 * Since both the contents and rules are sorted, we can
598 	 * make a single efficient pass.
599 	 */
600 	(void) memset(line, 0, LINESZ);
601 
602 	for (i = 0, j = 0; fgets(line, LINESZ, fpi) != NULL; ) {
603 
604 		char *pcpath = NULL;
605 
606 		/*
607 		 * Note:  This could be done better:  We should figure out
608 		 * which are the last 2 lines and only trim those off.
609 		 * This will suffice to do this and will only be done as
610 		 * part of special_contents handling.
611 		 */
612 		if (line[0] == '#')
613 			continue; /* Do not copy the final 2 comment lines */
614 
615 		pcpath = get_path(line);
616 
617 		if (pcpath != NULL && i < ient) {
618 			int k;
619 			while (piIndex[i] == 0)
620 				i++;
621 
622 			if (i < ient)
623 				k = pathcmp(pcpath, ppcfent[i]);
624 
625 			if (k < 0 || i >= ient) {
626 				/* Just copy contents -> t.contents */
627 				/*EMPTY*/
628 			} else if (k == 0) {
629 				/* We have a match.  Do not copy the content. */
630 				i++;
631 				free(pcpath);
632 				(void) memset(line, 0, LINESZ);
633 				continue;
634 			} else while (i < ient) {
635 
636 				/*
637 				 * This is a complex case:  The content
638 				 * entry is further along alphabetically
639 				 * than the rule.  Skip over all rules which
640 				 * apply until we come to a rule which is
641 				 * greater than the current entry, or equal
642 				 * to it.  If equal, do not copy, otherwise
643 				 * do copy the entry.
644 				 */
645 				if (piIndex[i] == 0) {
646 					i++;
647 					continue;
648 				} else if ((k = pathcmp(pcpath, ppcfent[i]))
649 				    >= 0) {
650 					i++;
651 					if (k == 0) {
652 						free(pcpath);
653 						(void) memset(line, 0, LINESZ);
654 						break;
655 					}
656 				} else {
657 					/* path < rule, end special case */
658 					break;
659 				}
660 			}
661 
662 			/*
663 			 * Avoid copying the old content when path == rule
664 			 * This occurs when the complex case ends on a match.
665 			 */
666 			if (k == 0)
667 				continue;
668 		}
669 
670 		if (fprintf(fpo, "%s", line) < 0) {
671 			/* Failing to write output would be catastrophic. */
672 			progerr(gettext(SPECIAL_ACCESS));
673 			result = 1;
674 			break;
675 		}
676 		(void) memset(line, 0, LINESZ);
677 	}
678 
679 	t = time(NULL);
680 	(void) fprintf(fpo, "# Last modified by pkgremove\n");
681 	(void) fprintf(fpo, "# %s", ctime(&t));
682 
683 remove_done:
684 	free_special_contents(&ppcSC, max);
685 
686 	if (fpi != NULL)
687 		(void) fclose(fpi);
688 
689 	if (fpo != NULL)
690 		(void) fclose(fpo);
691 
692 	if (result == 0) {
693 		if (tcpath[0] != '\0' && cpath[0] != '\0' &&
694 		    rename(tcpath, cpath) != 0) {
695 			progerr(gettext(SPECIAL_ACCESS));
696 			result = 1;
697 		}
698 	} else {
699 		if (tcpath[0] != '\0' && remove(tcpath) != 0) {
700 			/*
701 			 * Do not output a diagnostic message.  This condition
702 			 * occurs only when we are unable to clean up after
703 			 * a failure.  A temporary file will linger.
704 			 */
705 			result = 1;
706 		}
707 	}
708 
709 	return (result);
710 }
711