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