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