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 }