xref: /onnv-gate/usr/src/cmd/svr4pkg/pkgremove/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 
89 struct dstrp {
90 	char **ppc;
91 	int    len;
92 	int    max;
93 };
94 
95 static int append_dstrp(struct dstrp *pd, const char *str);
96 static int in_list(const char *pcList, const char *pcItem);
97 static void get_all_dependents_r(struct dstrp *, struct dstrp *,
98     Wsreg_component *, int *, const char *);
99 static 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  */
151 int
wsreg_pkgrm_check(const char * pcRoot,const char * pcPKG,char *** pppcID,char *** pppcName)152 wsreg_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  */
232 static int
in_list(const char * pcList,const char * pcItem)233 in_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  */
298 static int
append_dstrp(struct dstrp * pd,const char * str)299 append_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  */
368 static void
get_all_dependents_r(struct dstrp * id,struct dstrp * nm,Wsreg_component * pws,int * pdepth,const char * locale)369 get_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  */
432 static char *
get_locale()433 get_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