wsreg_pkgrm.c revision 9781:ccf49524d5dc
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 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27
28/*
29 * wsreg_pkgrm.c
30 *
31 * Background information:
32 *
33 * In the past, pkgrm did not check whether a package was needed by
34 * products in the product registry.  The only check that pkgrm does
35 * is whether any packages depend on the package to be removed.  This
36 * meant that it was trivial to use pkgrm correctly and damage products
37 * (installed by webstart wizards) - without even receiving a warning.
38 *
39 * This enhancement to pkgrm will determine if the package to remove is
40 * needed by any registered products.  If not, a '0' is returned and the
41 * pkgrm can proceed.  If there is a conflict, nonzero is returned and
42 * a list of all products which will be effected.  Note that removing
43 * one package may damage several products.  This is because some
44 * packages are used by several products, and some components are shared
45 * by several products.
46 *
47 * The list returned is a string, which the caller must free by calling
48 * free().
49 *
50 * The purpose of the list is to inform the user, exactly as is done with
51 * the 'depends' information.  The user must be presented with the list
52 * as a warning and be able to either abort the operation or proceed -
53 * well advised of the consequences.
54 *
55 * How this works
56 *
57 * Installed products are associated with 'components' in a product
58 * registry database.  Components in the product registry are often
59 * associated with packages.  Packages are the mechanism in which
60 * software is actually installed, on Solaris.  For example, when a
61 * webstart wizard install occurs, one or more packages are added.
62 * These are associated with 'components' (install metadata containers)
63 * in the product registry.  The product registry interface acts as
64 * though these packages *really are* installed.
65 *
66 * In order to ensure that this remains the case, the product registry
67 * is examined for instances of a package before that package is removed.
68 *
69 * See libwsreg(3LIB) for general information about the product
70 * registry library used to determine if removing a package is OK.
71 *
72 * See prodreg(1M) for information about a tool which can be used
73 * to inspect the product registry.  Any component which has an
74 * attribute 'pkgs' will list those packages which cannot be removed
75 * safely.  For example: 'pkgs= SUNWfoo SUNWbar' would imply that
76 * neither SUNWfoo or SUNWbar can be removed.
77 */
78
79#include <assert.h>
80#include <string.h>
81#include <stdio.h>
82#include <stdlib.h>
83#include <ctype.h>
84#include <errno.h>
85#include <locale.h>
86
87#include "wsreg_pkgrm.h"
88
89struct dstrp {
90	char **ppc;
91	int    len;
92	int    max;
93};
94
95static int append_dstrp(struct dstrp *pd, const char *str);
96static int in_list(const char *pcList, const char *pcItem);
97static void get_all_dependents_r(struct dstrp *, struct dstrp *,
98    Wsreg_component *, int *, const char *);
99static char *get_locale();
100
101/*
102 * wsreg_pkgrm_check
103 *
104 * This routine determines if removing a particular package will
105 * 'damage' a product.
106 *
107 *    pcRoot      IN:  The alternate root directory.  If this parameter
108 *                     is NULL - then the root "/" is assumed.
109 *
110 *    pcPKG       IN:  The name of the package to remove (a normal NULL-
111 *                     terminated string.)
112 *                     This parameter must not be NULL.
113 *
114 *    pppcID     OUT:  The location of a char ** pointer is passed in.
115 *                     This parameter must not be NULL.  The result
116 *                     will be a NULL terminated array of ID strings.
117 *                     The caller must free both the array of strings
118 *                     and each individual string.  Example:
119 *
120 *                     char ** ppcID;
121 *                     int i;
122 *
123 *                     if (wsreg_pkgrm_check(NULL, "SUNWblah", &ppcID, ..)
124 *                         > 0) {
125 *
126 *                         for (i = 0; ppcID[i]; i++) {
127 *                             do_something(ppcID[i]);
128 *                             free(ppcID[i]);
129 *                         }
130 *                         free(ppcID);
131 *                     }
132 *
133 *    pppcName   OUT:  As pppcID, except this contains the human readable
134 *                     localized name of the component.  The index of the
135 *                     name array coincides with that of the ID array, so
136 *                     there will be the same number of items in both and
137 *                     the component whose name is *pppcName[0] has the
138 *                     id *pppcID[0].
139 *
140 * Returns: 0 if there is no problem.  pkgrm my proceed.
141 *          positive - there is a conflict.  pppcID & pppcName return strings.
142 *          negative - there was a problem running this function.
143 *                     Error conditions include: (errno will be set)
144 *                      ENOENT	The pcRoot directory was not valid.
145 *			ENOMEM	The string to return could not be allocated.
146 *			EACCES	The registry database could not be read.
147 *
148 * Side effects: The pppcID and pppcName parameters may be changed and set
149 *     to the value of arrays of strings which the caller must free.
150 */
151int
152wsreg_pkgrm_check(const char *pcRoot, const char *pcPKG,
153    char ***pppcID, char ***pppcName)
154{
155	Wsreg_component **ppws;
156	struct dstrp id = { NULL, 0, 0}, nm = {NULL, 0, 0};
157	int i, r;
158	char *locale = get_locale();
159	if (locale == NULL)
160		locale = "en";
161
162	if (locale == NULL) {
163		errno = ENOMEM;
164		return (-1);
165	}
166
167	assert(pcPKG != NULL && pppcName != NULL && pppcID != NULL);
168
169	*pppcID = NULL;
170	*pppcName = NULL;
171
172	errno = 0;
173	r = 0; /* A return value 0 indicates nothing was found. */
174
175	if (pcRoot == NULL)
176		pcRoot = "/";
177
178	if (wsreg_initialize(WSREG_INIT_NORMAL, pcRoot) != WSREG_SUCCESS ||
179		wsreg_can_access_registry(O_RDONLY) == 0) {
180		errno = EACCES;
181		return (-1);
182	}
183
184	ppws = wsreg_get_all();
185
186	for (i = 0; ((ppws != NULL) && (ppws[i] != NULL)); i++) {
187		char *pcpkgs = wsreg_get_data(ppws[i], "pkgs");
188		if (pcpkgs != NULL && in_list(pcpkgs, pcPKG)) {
189			char *pcID = wsreg_get_id(ppws[i]);
190			char *pcName = wsreg_get_display_name(ppws[i],
191			    locale);
192			int depth;
193
194			depth = 0;
195			r = 1;
196
197			if (append_dstrp(&id, pcID) ||
198			    append_dstrp(&nm, pcName)) {
199				errno = ENOMEM;
200				r = -1;
201				break;
202			}
203
204			if (pcID) free(pcID);
205			if (pcName) free(pcName);
206			get_all_dependents_r(&id, &nm, ppws[i], &depth, locale);
207		}
208	}
209
210	if (r > 0) {
211		*pppcID = id.ppc;
212		*pppcName = nm.ppc;
213	}
214
215	free(locale);
216
217	if (ppws != NULL)
218		wsreg_free_component_array(ppws);
219
220	return (r);
221}
222
223/*
224 * in_list
225 *
226 *   pcList   A white space delimited list of words (non-white characters)
227 *   pcItem   A word (not NULL, an empty string or containing white space)
228 *
229 * Returns 0 if pcItem is not in pcList.  nonzero if pcItem is in pcList
230 * Side effects: None
231 */
232static int
233in_list(const char *pcList, const char *pcItem)
234{
235
236	int i = 0, j = 0, k = 0;
237
238	assert(pcItem);
239	k = strlen(pcItem);
240
241	if (pcList == NULL || k == 0)
242		return (0);
243
244	while (pcList[i] != '\0') {
245
246		if (isspace(pcList[i])) {
247			if (i == j) {
248				i++;
249				j++;
250			} else {
251
252				if ((i - j) == k &&
253				    strncmp(&pcList[j], pcItem, i - j) == 0) {
254					return (1);
255				} else {
256					j = i;
257				}
258
259			}
260		} else {
261			i++;
262		}
263
264		/* last element in the list case */
265		if (pcList[i] == '\0' && j < i &&
266		    strncmp(&pcList[j], pcItem, i - j) == 0)
267			return (1);
268	}
269
270	return (0);
271}
272
273#define	APPEND_INCR	20
274
275/*
276 * append_dstrp
277 *
278 * This routine manages a dynamic array of strings in a very minimal way.
279 * It assumes it has been passed a cleared struct dstrp = { NULL, 0, 0 }
280 * It will add the appended string to the end of the array.  When needed,
281 * the array of strings is grown to the next APPEND_INCR in size.
282 *
283 * Note this routine is different than append_dstr since that accumulates
284 * char, this accumulates char *.
285 *
286 *   pd  The dynamic string.  Must be initialized to {NULL,0,0}.  Must not
287 *       be NULL.
288 *
289 *   str The string to add.  May be of 0 length.  If NULL, a string of 0
290 *       length will be added (NOT a NULL).
291 *
292 * Returns: 0 if OK, -1 if malloc failed.
293 * Side effects: The value of pd->ppc[pd->len] changes, taking strdup(str)
294 *     The final entry in the array will be NULL.  There will be pd->len
295 *     entries.  To free this, free each string in the array and the array
296 *     itself.   The caller must free the allocated memory.
297 */
298static int
299append_dstrp(struct dstrp *pd, const char *str)
300{
301	if (str == NULL) str = "";
302
303	if (pd->max == 0) {
304
305		/* Initialize if necessary */
306		pd->len = 0;
307		pd->max = APPEND_INCR;
308		pd->ppc = (char **)calloc(APPEND_INCR * sizeof (char *), 1);
309		if (pd->ppc == NULL)
310			return (-1);
311
312	} else if ((pd->len + 2) == pd->max) {
313
314		/*
315		 * Grow the array.
316		 * Always leave room for a single NULL end item:  That is
317		 * why we grow when +2 equals the max, not +1.
318		 */
319		size_t s = (pd->max + APPEND_INCR) * sizeof (char *);
320		pd->ppc = realloc(pd->ppc, s);
321		if (pd->ppc == NULL) {
322			return (-1);
323		} else {
324			memset(pd->ppc + pd->max, '\0',
325				APPEND_INCR * sizeof (char *));
326		}
327
328		pd->max += APPEND_INCR;
329	}
330
331	if (str == NULL) {
332		pd->ppc[pd->len] = NULL;
333		pd->len++;
334	} else {
335		pd->ppc[pd->len] = (char *)strdup(str);
336		if (pd->ppc[pd->len] == NULL)
337			return (-1);
338		pd->len++;
339	}
340
341	return (0);
342}
343
344#define	DEPTH_MAX	100
345
346/*
347 * get_all_dependents_r
348 *
349 *   This routine accumulates the id and name of all components which
350 *   depend (directly or indirectly) on a component which has a pkg which
351 *   may be removed.  By calling this routine recursively, the entire list
352 *   of existing dependencies can be accumulated.
353 *
354 *   id        The dynamic accumulation of all ids of dependent components.
355 *   nm        The dynamic accumulation of all names of dep. components.
356 *   pws       The component to check for dependencies, record their
357 *             ids and names, then call check these components for redun-
358 *             dancy also.
359 *   pdepth    The depth of the recursion.  This must be set to 0 upon the
360 *             first call to this function.  Only DEPTH_MAX calls will be
361 *             attempted.
362 *   locale    The locale to use for querying for display names.
363 *
364 * Return value: None.
365 * Side effects.  strings will be added to id and nm.  The depth counter
366 *    will increase.
367 */
368static void
369get_all_dependents_r(struct dstrp *id, struct dstrp *nm, Wsreg_component *pws,
370    int *pdepth, const char *locale)
371{
372	int i;
373
374	/* Get the list of dependent components. */
375	Wsreg_component **ppws = wsreg_get_dependent_components(pws);
376	if (ppws == NULL)
377		return;
378
379	if (locale == NULL)
380		locale = "en";
381	if (locale == NULL)
382		return;
383
384	/*
385	 * Prevent infinite loops in the case where there is a cycle
386	 * in the dependency graph.  Such a cycle should never happen,
387	 * but a clueless user of the libwsreg API could construct such
388	 * a failure case.  This is defensive programming.
389	 */
390	if (*pdepth > DEPTH_MAX)
391		return;
392
393	(*pdepth)++;
394
395	for (i = 0; ppws[i]; i++) {
396		char *pcID = wsreg_get_id(ppws[i]);
397		char *pcName = wsreg_get_display_name(ppws[i], locale);
398		if (append_dstrp(id, pcID) ||
399		    append_dstrp(nm, pcName))
400			/*
401			 * Errors in append_dstrp happen only due to malloc
402			 * failing on small allocations.  If we fail here
403			 * this is the least of the user's problems.  We
404			 * can just stop accumulating new info at this point.
405			 */
406			return;
407		get_all_dependents_r(id, nm, ppws[i], pdepth, locale);
408	}
409
410	wsreg_free_component_array(ppws);
411}
412
413/*
414 * init_locale
415 *
416 * Set locale and textdomain for localization.  Note that the return value
417 * of setlocale is the locale string.  It is in the form
418 *
419 *   "/" LC_CTYPE "/" LC_COLLATE "/" LC_CTIME "/" LC_NUMERIC "/"
420 *      LC_MONETARY "/ LC_MESSAGES
421 *
422 *  This routine parses this result line to determine the value of
423 *  the LC_MESSAGES field.  If it is "C", the default language "en"
424 *  is selected.  If not, the string is disected to get only the
425 *  ISO 639 two letter tag:  "en_US.ISO8859-1" becomes "en".
426 *
427 * Returns: Returns a newly allocated language tag string.
428 *          Returns NULL if setlocale() returns a null pointer.
429 * Side effects:
430 * (1) setlocale changes behavior of the application.
431 */
432static char *
433get_locale()
434{
435	int i = 0, c, n;
436	char lang[32];
437	char *pc = setlocale(LC_ALL, "");
438	char *tag = NULL;
439
440	if (pc == NULL) {
441		return (NULL);
442	}
443
444	(void *) memset(lang, 0, 32);
445	if (pc[0] == '/') {
446
447		/* Skip to the 6th field, which is 'LC_MESSAGES.' */
448		c = 0;
449		for (i = 0; (pc[i] != NULL) && (c < 6); i++) {
450			if (pc[i] == '/') c++;
451		}
452
453		/* Strip off any dialect tag and character encoding. */
454		n = 0;
455		while ((pc[i] != NULL) && (pc[i] != '_') &&
456		    (n < 32) && (pc[i] != '.')) {
457			lang[n++] = pc[i++];
458		}
459	}
460
461	if (i > 2) {
462		if (strcmp(lang, "C") == 0) {
463			tag = strdup("en");
464		} else {
465			tag = strdup(lang);
466		}
467	} else {
468		tag = strdup("en");
469	}
470
471	return (tag);
472}
473