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