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