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 }