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
  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
  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
 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
 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
 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 *
 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
 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
 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
 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 }