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