/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * special.c * * This module contains code required to remove special contents from * the contents file when a pkgrm is done on a system upgraded to use * the new database. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "pkglib.h" #include /* This specifies the maximum length of a contents file line read in. */ #define LINESZ 8192 #define SPECIAL_MALLOC "unable to maintain package contents text due to "\ "insufficient memory." #define SPECIAL_ACCESS "unable to maintain package contents text due to "\ "an access failure." #define SPECIAL_INPUT "unable to maintain package contents text: alternate "\ "root path too long" /* * strcompare * * This function is used by qsort to sort an array of special contents * rule strings. This array must be sorted to facilitate efficient * rule processing. See qsort(3c) regarding qsort compare functions. */ static int strcompare(const void *pv1, const void *pv2) { char **ppc1 = (char **) pv1; char **ppc2 = (char **) pv2; int i = strcmp(*ppc1, *ppc2); if (i < 0) return (-1); if (i > 0) return (1); return (0); } /* * match * * This function determines whether a file name (pc) matches a rule * from the special contents file (pcrule). We assume that neither * string is ever NULL. * * Return: 1 on match, 0 on no match. * Side effects: none. */ static int match(const char *pc, char *pcrule) { int n = strlen(pcrule); int wild = 0; if (pcrule[n - 1] == '*') { wild = 1; pcrule[n - 1] = '\0'; } if (!wild) { if (fnmatch(pc, pcrule, FNM_PATHNAME) == 0 || fnmatch(pc, pcrule, 0) == 0) return (1); } else { int j; j = strncmp(pc, pcrule, n - 1); pcrule[n - 1] = '*'; if (j == 0) return (1); } return (0); } /* * search_special_contents * * This function assumes that a series of calls will be made requesting * whether a given path matches the special contents rules or not. We * assume that * * a) the special_contents array is sorted * b) the calls will be made with paths in a sorted order * * Given that, we can keep track of where the last search ended and * begin the new search at that point. This reduces the cost of a * special contents matching search to O(n) from O(n^2). * * ppcSC A pointer to an array of special contents obtained via * get_special_contents(). * path A path: determine whether it matches the special * contents rules or not. * piX The position in the special_contents array we have already * arrived at through searching. This must be initialized to * zero before initiating a series of search_special_contents * operations. * * Example: * { * int i = 0, j, max; * char **ppSC = NULL; * if (get_special_contents(NULL, &ppcSC, &max) != 0) exit(1); * for (j = 0; paths != NULL && paths[j] != NULL; j++) { * if (search_special_contents(ppcSC, path[j], &i)) { * do_something_with_special_path(path[j]); * } * } * } * * Return: 1 if there is a match, 0 otherwise. * Side effects: The value of *piX will be set between calls to this * function. To make this function thread safe, use search arrays. * Also: Nonmatching entries are eliminated, set to NULL. */ static int search_special_contents(char **ppcSC, const char *pcpath, int *piX, int max) { int wild; if (ppcSC == NULL || *piX == max) return (0); while (*piX < max) { int j, k; if (ppcSC[*piX] == NULL) { (*piX)++; continue; } j = strlen(ppcSC[*piX]); k = strcmp(pcpath, ppcSC[*piX]); wild = (ppcSC[*piX][j - 1] == '*'); /* * Depending on whether the path string compared with the * rule, we take different actions. If the path is less * than the rule, we keep the rule. If the path equals * the rule, we advance the rule (as long as the rule is * not a wild card). If the path is greater than the rule, * we have to advance the rule list until we are less or equal * again. This way we only have to make one pass through the * rules, as we make one pass through the path strings. We * assume that the rules and the path strings are sorted. */ if (k < 0) { if (wild == 0) return (0); if (match(pcpath, ppcSC[*piX])) return (1); break; } else if (k == 0) { int x = match(pcpath, ppcSC[*piX]); if (wild == 0) (*piX)++; return (x); } else { /* One last try. */ if (match(pcpath, ppcSC[*piX])) return (1); /* * As pcpath > ppcSC[*piX] we have passed up this * rule - it cannot apply. Therefore, we do not * need to retain it. Removing the rule will make * subsequent searching more efficient. */ free(ppcSC[*piX]); ppcSC[*piX] = NULL; (*piX)++; } } return (0); } /* * get_special_contents * * Retrieves the special contents file entries, if they exist. These * are sorted. We do not assume the special_contents file is in sorted * order. * * pcroot The root of the install database. If NULL assume '/'. * pppcSC A pointer to a char **. This pointer will be set to * point at NULL if there is no special_contents file or * to a sorted array of strings, NULL terminated, otherwise. * piMax The # of entries in the special contents result. * * Returns: 0 on no error, nonzero on error. * Side effects: the pppcSC pointer is set to point at a newly * allocated array of pointers to strings.. The caller must * free this buffer. The value of *piMax is set to the # of * entries in ppcSC. */ static int get_special_contents(const char *pcroot, char ***pppcSC, int *piMax) { int e, i; FILE *fp; char line[2048]; char **ppc; char *pc = "var/sadm/install/special_contents"; char path[PATH_MAX]; struct stat s; /* Initialize the return values. */ *piMax = 0; *pppcSC = NULL; if (pcroot == NULL) { pcroot = "/"; } if (pcroot[strlen(pcroot) - 1] == '/') { if (snprintf(path, PATH_MAX, "%s%s", pcroot, pc) >= PATH_MAX) { progerr(gettext(SPECIAL_INPUT)); return (1); } } else { if (snprintf(path, PATH_MAX, "%s/%s", pcroot, pc) >= PATH_MAX) { progerr(gettext(SPECIAL_INPUT)); return (1); } } errno = 0; e = stat(path, &s); if (e != 0 && errno == ENOENT) return (0); /* No special contents file. Do nothing. */ if (access(path, R_OK) != 0 || (fp = fopen(path, "r")) == NULL) { /* Could not open special contents which exists */ progerr(gettext(SPECIAL_ACCESS)); return (1); } for (i = 0; fgets(line, 2048, fp) != NULL; i++); rewind(fp); if ((ppc = (char **) calloc(i + 1, sizeof (char *))) == NULL) { progerr(gettext(SPECIAL_MALLOC)); return (1); } for (i = 0; fgets(line, 2048, fp) != NULL; ) { int n; if (line[0] == '#' || line[0] == ' ' || line[0] == '\n' || line[0] == '\t' || line[0] == '\r') continue; n = strlen(line); if (line[n - 1] == '\n') line[n - 1] = '\0'; ppc[i++] = strdup(line); } qsort(ppc, i, sizeof (char *), strcompare); *pppcSC = ppc; *piMax = i; return (0); } /* * free_special_contents * * This function frees special_contents which have been allocated using * get_special_contents. * * pppcSC A pointer to a buffer allocated using get_special_contents. * max The number of entries allocated. * * Result: None. * Side effects: Frees memory allocated using get_special_contents and * sets the pointer passed in to NULL. */ static void free_special_contents(char ***pppcSC, int max) { int i; char **ppc = NULL; if (*pppcSC == NULL) return; ppc = *pppcSC; for (i = 0; ppc != NULL && i < max; i++) if (ppc[i] == NULL) free(ppc[i]); if (ppc != NULL) free(ppc); *pppcSC = NULL; } /* * get_path * * Return the first field of a string delimited by a space. * * pcline A line from the contents file. * * Return: NULL if an error. Otherwise a string allocated by this * function. The caller must free the string. * Side effects: none. */ static char * get_path(const char *pcline) { int i = strcspn(pcline, " "); char *pc = NULL; if (i <= 1 || (pc = (char *) calloc(i + 1, 1)) == NULL) return (NULL); (void) memcpy(pc, pcline, i); return (pc); } /* * generate_special_contents_rules * * This procedure will generate an array of integers which will be a mask * to apply to the ppcfextra array. If set to 1, then the content must be * added to the contents file. Otherwise it will not be: The old contents * file will be used for this path value, if one even exists. * * ient The number of ppcfextra contents installed. * ppcfent The contents installed. * ppcSC The rules (special contents) * max The number of special contents rules. * ppiIndex The array of integer values, determining whether * individual ppcfextra items match special contents rules. * This array will be created and set in this function and * returned. * * Return: 0 success, nonzero failure * Side effects: allocates an array of integers that the caller must free. */ static int generate_special_contents_rules(int ient, struct cfent **ppcfent, char **ppcSC, int max, int **ppiIndex) { int i, j; int *pi = (int *) calloc(ient, sizeof (int)); if (pi == NULL) { progerr(gettext(SPECIAL_MALLOC)); return (1); } /* * For each entry in ppcfextra, check if it matches a rule. * If it does not, set the entry in the index to -1. */ for (i = 0, j = 0; i < ient && j < max; i++) { if (search_special_contents(ppcSC, ppcfent[i]->path, &j, max) == 1) { pi[i] = 1; } else { pi[i] = 0; } } /* * In case we ran out of rules before contents, we will not use * those contents. Make sure these contents are set to 0 and * will not be copied from the ppcfent array into the contents * file. */ for (i = i; i < ient; i++) pi[i] = 0; *ppiIndex = pi; return (0); } /* * pathcmp * * Compare a path to a cfent. It will match either if the path is * equal to the cfent path, or if the cfent is a symbolic or link * and *that* matches. * * path a path * pent a contents entry * * Returns: as per strcmp * Side effects: none. */ static int pathcmp(const char *pc, const struct cfent *pent) { int i; if ((pent->ftype == 's' || pent->ftype == 'l') && pent->ainfo.local) { char *p, *q; if ((p = strstr(pc, "=")) == NULL) { i = strcmp(pc, pent->path); /* A path without additional chars strcmp's to less */ if (i == 0) i = -1; } else { /* Break the link path into two pieces. */ *p = '\0'; /* Compare the first piece. */ i = strcmp(pc, pent->path); /* If equal we must compare the second piece. */ if (i == 0) { q = p + 1; i = strcmp(q, pent->ainfo.local); } /* Restore the link path. */ *p = '='; } } else { i = strcmp(pc, pent->path); } return (i); } /* * ----------------------------------------------------------------------- * Externally visible function. */ /* * special_contents_remove * * Given a set of entries to remove and an alternate root, this function * will do everything required to ensure that the entries are removed * from the contents file if they are listed in the special_contents * file. The contents file will get changed only in the case that the * entire operation has succeeded. * * ient The number of entries. * ppcfent The entries to remove. * pcroot The alternate install root. Could be NULL. In this * case, assume root is '/' * * Result: 0 on success, nonzero on failure. If an error occurs, an * error string will get output to standard error alerting the user. * Side effects: The contents file may change as a result of this call, * such that lines in the in the file will be changed or removed. * If the call fails, a t.contents file may be left behind. This * temporary file should be removed subsequently. */ int special_contents_remove(int ient, struct cfent **ppcfent, const char *pcroot) { int result = 0; /* Assume we will succeed. Return result. */ char **ppcSC = NULL; /* The special contents rules, sorted. */ int i, j; /* Indexes into contents & special contents */ FILE *fpi = NULL, /* Input of contents file */ *fpo = NULL; /* Output to temp contents file */ char cpath[PATH_MAX], /* Contents file path */ tcpath[PATH_MAX]; /* Temp contents file path */ const char *pccontents = "var/sadm/install/contents"; const char *pctcontents = "var/sadm/install/t.contents"; char line[LINESZ]; /* Reads in and writes out contents lines. */ time_t t; /* Used to create a timestamp comment. */ int max; /* Max number of special contents entries. */ int *piIndex; /* An index to ppcfents to remove from cfile */ cpath[0] = tcpath[0] = '\0'; if (ient == 0 || ppcfent == NULL || ppcfent[0] == NULL) { goto remove_done; } if ((get_special_contents(pcroot, &ppcSC, &max)) != 0) { result = 1; goto remove_done; } /* Check if there are no special contents actions to take. */ if (ppcSC == NULL) { goto remove_done; } if (pcroot == NULL) pcroot = "/"; if (pcroot[strlen(pcroot) - 1] == '/') { if (snprintf(cpath, PATH_MAX, "%s%s", pcroot, pccontents) >= PATH_MAX || snprintf(tcpath, PATH_MAX, "%s%s", pcroot, pctcontents) >= PATH_MAX) { progerr(gettext(SPECIAL_INPUT)); result = -1; goto remove_done; } } else { if (snprintf(cpath, PATH_MAX, "%s/%s", pcroot, pccontents) >= PATH_MAX || snprintf(tcpath, PATH_MAX, "%s/%s", pcroot, pctcontents) >= PATH_MAX) { progerr(gettext(SPECIAL_INPUT)); result = -1; goto remove_done; } } /* Open the temporary contents file to write, contents to read. */ if (access(cpath, F_OK | R_OK) != 0) { /* * This is not a problem since no contents means nothing * to remove due to special contents rules. */ result = 0; cpath[0] = '\0'; /* This signals omission of 'rename cleanup' */ goto remove_done; } if (access(cpath, W_OK) != 0) { /* can't write contents file, something is wrong. */ progerr(gettext(SPECIAL_ACCESS)); result = 1; goto remove_done; } if ((fpi = fopen(cpath, "r")) == NULL) { /* Given the access test above, this should not happen. */ progerr(gettext(SPECIAL_ACCESS)); result = 1; goto remove_done; } if ((fpo = fopen(tcpath, "w")) == NULL) { /* open t.contents failed */ progerr(gettext(SPECIAL_ACCESS)); result = 1; goto remove_done; } if (generate_special_contents_rules(ient, ppcfent, ppcSC, max, &piIndex) != 0) { result = 1; goto remove_done; } /* * Copy contents to t.contents unless there is an entry in * the ppcfent array which corresponds to an index set to 1. * * These items are the removed package contents which matche an * entry in ppcSC (the special_contents rules). * * Since both the contents and rules are sorted, we can * make a single efficient pass. */ (void) memset(line, 0, LINESZ); for (i = 0, j = 0; fgets(line, LINESZ, fpi) != NULL; ) { char *pcpath = NULL; /* * Note: This could be done better: We should figure out * which are the last 2 lines and only trim those off. * This will suffice to do this and will only be done as * part of special_contents handling. */ if (line[0] == '#') continue; /* Do not copy the final 2 comment lines */ pcpath = get_path(line); if (pcpath != NULL && i < ient) { int k; while (piIndex[i] == 0) i++; if (i < ient) k = pathcmp(pcpath, ppcfent[i]); if (k < 0 || i >= ient) { /* Just copy contents -> t.contents */ /*EMPTY*/ } else if (k == 0) { /* We have a match. Do not copy the content. */ i++; free(pcpath); (void) memset(line, 0, LINESZ); continue; } else while (i < ient) { /* * This is a complex case: The content * entry is further along alphabetically * than the rule. Skip over all rules which * apply until we come to a rule which is * greater than the current entry, or equal * to it. If equal, do not copy, otherwise * do copy the entry. */ if (piIndex[i] == 0) { i++; continue; } else if ((k = pathcmp(pcpath, ppcfent[i])) >= 0) { i++; if (k == 0) { free(pcpath); (void) memset(line, 0, LINESZ); break; } } else { /* path < rule, end special case */ break; } } /* * Avoid copying the old content when path == rule * This occurs when the complex case ends on a match. */ if (k == 0) continue; } if (fprintf(fpo, "%s", line) < 0) { /* Failing to write output would be catastrophic. */ progerr(gettext(SPECIAL_ACCESS)); result = 1; break; } (void) memset(line, 0, LINESZ); } t = time(NULL); (void) fprintf(fpo, "# Last modified by pkgremove\n"); (void) fprintf(fpo, "# %s", ctime(&t)); remove_done: free_special_contents(&ppcSC, max); if (fpi != NULL) (void) fclose(fpi); if (fpo != NULL) (void) fclose(fpo); if (result == 0) { if (tcpath[0] != '\0' && cpath[0] != '\0' && rename(tcpath, cpath) != 0) { progerr(gettext(SPECIAL_ACCESS)); result = 1; } } else { if (tcpath[0] != '\0' && remove(tcpath) != 0) { /* * Do not output a diagnostic message. This condition * occurs only when we are unable to clean up after * a failure. A temporary file will linger. */ result = 1; } } return (result); }