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 */
68static int
69strcompare(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 */
91static int
92match(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 */
155static int
156search_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 */
239static int
240get_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 */
320static void
321free_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 */
350static char *
351get_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 */
381static int
382generate_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 */
433static int
434pathcmp(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 */
497int
498special_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
683remove_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