wsreg_pkgrm.c revision 5c51f1241dbbdf2656d0e10011981411ed0c9673
/*
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* wsreg_pkgrm.c
*
* Background information:
*
* In the past, pkgrm did not check whether a package was needed by
* products in the product registry. The only check that pkgrm does
* is whether any packages depend on the package to be removed. This
* meant that it was trivial to use pkgrm correctly and damage products
* (installed by webstart wizards) - without even receiving a warning.
*
* This enhancement to pkgrm will determine if the package to remove is
* needed by any registered products. If not, a '0' is returned and the
* pkgrm can proceed. If there is a conflict, nonzero is returned and
* a list of all products which will be effected. Note that removing
* one package may damage several products. This is because some
* packages are used by several products, and some components are shared
* by several products.
*
* The list returned is a string, which the caller must free by calling
* free().
*
* The purpose of the list is to inform the user, exactly as is done with
* the 'depends' information. The user must be presented with the list
* as a warning and be able to either abort the operation or proceed -
* well advised of the consequences.
*
* How this works
*
* Installed products are associated with 'components' in a product
* registry database. Components in the product registry are often
* associated with packages. Packages are the mechanism in which
* software is actually installed, on Solaris. For example, when a
* webstart wizard install occurs, one or more packages are added.
* These are associated with 'components' (install metadata containers)
* in the product registry. The product registry interface acts as
* though these packages *really are* installed.
*
* In order to ensure that this remains the case, the product registry
* is examined for instances of a package before that package is removed.
*
* See libwsreg(3LIB) for general information about the product
* registry library used to determine if removing a package is OK.
*
* See prodreg(1M) for information about a tool which can be used
* to inspect the product registry. Any component which has an
* attribute 'pkgs' will list those packages which cannot be removed
* safely. For example: 'pkgs= SUNWfoo SUNWbar' would imply that
* neither SUNWfoo or SUNWbar can be removed.
*/
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include "wsreg_pkgrm.h"
struct dstrp {
char **ppc;
int len;
int max;
};
static int append_dstrp(struct dstrp *pd, const char *str);
static int in_list(const char *pcList, const char *pcItem);
static void get_all_dependents_r(struct dstrp *, struct dstrp *,
Wsreg_component *, int *, const char *);
static char *get_locale();
/*
* wsreg_pkgrm_check
*
* This routine determines if removing a particular package will
* 'damage' a product.
*
* pcRoot IN: The alternate root directory. If this parameter
* is NULL - then the root "/" is assumed.
*
* pcPKG IN: The name of the package to remove (a normal NULL-
* terminated string.)
* This parameter must not be NULL.
*
* pppcID OUT: The location of a char ** pointer is passed in.
* This parameter must not be NULL. The result
* will be a NULL terminated array of ID strings.
* The caller must free both the array of strings
* and each individual string. Example:
*
* char ** ppcID;
* int i;
*
* if (wsreg_pkgrm_check(NULL, "SUNWblah", &ppcID, ..)
* > 0) {
*
* for (i = 0; ppcID[i]; i++) {
* do_something(ppcID[i]);
* free(ppcID[i]);
* }
* free(ppcID);
* }
*
* pppcName OUT: As pppcID, except this contains the human readable
* localized name of the component. The index of the
* name array coincides with that of the ID array, so
* there will be the same number of items in both and
* the component whose name is *pppcName[0] has the
* id *pppcID[0].
*
* Returns: 0 if there is no problem. pkgrm my proceed.
* positive - there is a conflict. pppcID & pppcName return strings.
* negative - there was a problem running this function.
* Error conditions include: (errno will be set)
* ENOENT The pcRoot directory was not valid.
* ENOMEM The string to return could not be allocated.
* EACCES The registry database could not be read.
*
* Side effects: The pppcID and pppcName parameters may be changed and set
* to the value of arrays of strings which the caller must free.
*/
int
wsreg_pkgrm_check(const char *pcRoot, const char *pcPKG,
char ***pppcID, char ***pppcName)
{
Wsreg_component **ppws;
struct dstrp id = { NULL, 0, 0}, nm = {NULL, 0, 0};
int i, r;
char *locale = get_locale();
if (locale == NULL)
locale = "en";
if (locale == NULL) {
errno = ENOMEM;
return (-1);
}
assert(pcPKG != NULL && pppcName != NULL && pppcID != NULL);
*pppcID = NULL;
*pppcName = NULL;
errno = 0;
r = 0; /* A return value 0 indicates nothing was found. */
if (pcRoot == NULL)
pcRoot = "/";
if (wsreg_initialize(WSREG_INIT_NORMAL, pcRoot) != WSREG_SUCCESS ||
wsreg_can_access_registry(O_RDONLY) == 0) {
errno = EACCES;
return (-1);
}
ppws = wsreg_get_all();
for (i = 0; ((ppws != NULL) && (ppws[i] != NULL)); i++) {
char *pcpkgs = wsreg_get_data(ppws[i], "pkgs");
if (pcpkgs != NULL && in_list(pcpkgs, pcPKG)) {
char *pcID = wsreg_get_id(ppws[i]);
char *pcName = wsreg_get_display_name(ppws[i],
locale);
int depth;
depth = 0;
r = 1;
if (append_dstrp(&id, pcID) ||
append_dstrp(&nm, pcName)) {
errno = ENOMEM;
r = -1;
break;
}
if (pcID) free(pcID);
if (pcName) free(pcName);
get_all_dependents_r(&id, &nm, ppws[i], &depth, locale);
}
}
if (r > 0) {
*pppcID = id.ppc;
*pppcName = nm.ppc;
}
free(locale);
if (ppws != NULL)
wsreg_free_component_array(ppws);
return (r);
}
/*
* in_list
*
* pcList A white space delimited list of words (non-white characters)
* pcItem A word (not NULL, an empty string or containing white space)
*
* Returns 0 if pcItem is not in pcList. nonzero if pcItem is in pcList
* Side effects: None
*/
static int
in_list(const char *pcList, const char *pcItem)
{
int i = 0, j = 0, k = 0;
assert(pcItem);
k = strlen(pcItem);
if (pcList == NULL || k == 0)
return (0);
while (pcList[i] != '\0') {
if (isspace(pcList[i])) {
if (i == j) {
i++;
j++;
} else {
if ((i - j) == k &&
strncmp(&pcList[j], pcItem, i - j) == 0) {
return (1);
} else {
j = i;
}
}
} else {
i++;
}
/* last element in the list case */
if (pcList[i] == '\0' && j < i &&
strncmp(&pcList[j], pcItem, i - j) == 0)
return (1);
}
return (0);
}
#define APPEND_INCR 20
/*
* append_dstrp
*
* This routine manages a dynamic array of strings in a very minimal way.
* It assumes it has been passed a cleared struct dstrp = { NULL, 0, 0 }
* It will add the appended string to the end of the array. When needed,
* the array of strings is grown to the next APPEND_INCR in size.
*
* Note this routine is different than append_dstr since that accumulates
* char, this accumulates char *.
*
* pd The dynamic string. Must be initialized to {NULL,0,0}. Must not
* be NULL.
*
* str The string to add. May be of 0 length. If NULL, a string of 0
* length will be added (NOT a NULL).
*
* Returns: 0 if OK, -1 if malloc failed.
* Side effects: The value of pd->ppc[pd->len] changes, taking strdup(str)
* The final entry in the array will be NULL. There will be pd->len
* entries. To free this, free each string in the array and the array
* itself. The caller must free the allocated memory.
*/
static int
append_dstrp(struct dstrp *pd, const char *str)
{
if (str == NULL) str = "";
if (pd->max == 0) {
/* Initialize if necessary */
pd->len = 0;
pd->max = APPEND_INCR;
pd->ppc = (char **)calloc(APPEND_INCR * sizeof (char *), 1);
if (pd->ppc == NULL)
return (-1);
} else if ((pd->len + 2) == pd->max) {
/*
* Grow the array.
* Always leave room for a single NULL end item: That is
* why we grow when +2 equals the max, not +1.
*/
size_t s = (pd->max + APPEND_INCR) * sizeof (char *);
pd->ppc = realloc(pd->ppc, s);
if (pd->ppc == NULL) {
return (-1);
} else {
memset(pd->ppc + pd->max, '\0',
APPEND_INCR * sizeof (char *));
}
pd->max += APPEND_INCR;
}
if (str == NULL) {
pd->ppc[pd->len] = NULL;
pd->len++;
} else {
pd->ppc[pd->len] = (char *)strdup(str);
if (pd->ppc[pd->len] == NULL)
return (-1);
pd->len++;
}
return (0);
}
#define DEPTH_MAX 100
/*
* get_all_dependents_r
*
* This routine accumulates the id and name of all components which
* depend (directly or indirectly) on a component which has a pkg which
* may be removed. By calling this routine recursively, the entire list
* of existing dependencies can be accumulated.
*
* id The dynamic accumulation of all ids of dependent components.
* nm The dynamic accumulation of all names of dep. components.
* pws The component to check for dependencies, record their
* ids and names, then call check these components for redun-
* dancy also.
* pdepth The depth of the recursion. This must be set to 0 upon the
* first call to this function. Only DEPTH_MAX calls will be
* attempted.
* locale The locale to use for querying for display names.
*
* Return value: None.
* Side effects. strings will be added to id and nm. The depth counter
* will increase.
*/
static void
get_all_dependents_r(struct dstrp *id, struct dstrp *nm, Wsreg_component *pws,
int *pdepth, const char *locale)
{
int i;
/* Get the list of dependent components. */
Wsreg_component **ppws = wsreg_get_dependent_components(pws);
if (ppws == NULL)
return;
if (locale == NULL)
locale = "en";
if (locale == NULL)
return;
/*
* Prevent infinite loops in the case where there is a cycle
* in the dependency graph. Such a cycle should never happen,
* but a clueless user of the libwsreg API could construct such
* a failure case. This is defensive programming.
*/
if (*pdepth > DEPTH_MAX)
return;
(*pdepth)++;
for (i = 0; ppws[i]; i++) {
char *pcID = wsreg_get_id(ppws[i]);
char *pcName = wsreg_get_display_name(ppws[i], locale);
if (append_dstrp(id, pcID) ||
append_dstrp(nm, pcName))
/*
* Errors in append_dstrp happen only due to malloc
* failing on small allocations. If we fail here
* this is the least of the user's problems. We
* can just stop accumulating new info at this point.
*/
return;
get_all_dependents_r(id, nm, ppws[i], pdepth, locale);
}
wsreg_free_component_array(ppws);
}
/*
* init_locale
*
* Set locale and textdomain for localization. Note that the return value
* of setlocale is the locale string. It is in the form
*
* "/" LC_CTYPE "/" LC_COLLATE "/" LC_CTIME "/" LC_NUMERIC "/"
* LC_MONETARY "/ LC_MESSAGES
*
* This routine parses this result line to determine the value of
* the LC_MESSAGES field. If it is "C", the default language "en"
* is selected. If not, the string is disected to get only the
* ISO 639 two letter tag: "en_US.ISO8859-1" becomes "en".
*
* Returns: Returns a newly allocated language tag string.
* Returns NULL if setlocale() returns a null pointer.
* Side effects:
* (1) setlocale changes behavior of the application.
*/
static char *
get_locale()
{
int i = 0, c, n;
char lang[32];
char *pc = setlocale(LC_ALL, "");
char *tag = NULL;
if (pc == NULL) {
return (NULL);
}
(void *) memset(lang, 0, 32);
if (pc[0] == '/') {
/* Skip to the 6th field, which is 'LC_MESSAGES.' */
c = 0;
for (i = 0; (pc[i] != NULL) && (c < 6); i++) {
if (pc[i] == '/') c++;
}
/* Strip off any dialect tag and character encoding. */
n = 0;
while ((pc[i] != NULL) && (pc[i] != '_') &&
(n < 32) && (pc[i] != '.')) {
lang[n++] = pc[i++];
}
}
if (i > 2) {
if (strcmp(lang, "C") == 0) {
tag = strdup("en");
} else {
tag = strdup(lang);
}
} else {
tag = strdup("en");
}
return (tag);
}