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 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Error handling support for directory lookup. 29 * Actually, this is intended to be a very generic and extensible error 30 * reporting mechanism. 31 */ 32 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <thread.h> 36 #include <errno.h> 37 #include <stdarg.h> 38 #include <malloc.h> 39 #include <string.h> 40 #include <ctype.h> 41 #include <syslog.h> 42 #include <idmap_impl.h> 43 #include <rpcsvc/idmap_prot.h> 44 #include <libintl.h> 45 #include "directory.h" 46 47 /* 48 * This is the actual implementation of the opaque directory_error_t structure. 49 */ 50 struct directory_error { 51 /* 52 * True if this directory_error_t is statically allocated. Used to 53 * handle out of memory errors during error reporting. 54 */ 55 boolean_t is_static; 56 57 /* 58 * The error code. This is a locale-independent string that 59 * represents the precise error (to some level of granularity) 60 * that occurred. Internationalization processing could map it 61 * to an message. Errors may be subclassed by appending a dot 62 * and a name for the subclass. 63 * 64 * Note that this code plus the parameters allows for structured 65 * processing of error results. 66 */ 67 char *code; 68 69 /* 70 * The default (in the absence of internationalization) format for 71 * the error message. %n interposes params[n - 1]. 72 */ 73 char *fmt; 74 75 /* 76 * Parameters to the error message. Note that subclasses are 77 * required to have the same initial parameters as their superclasses, 78 * so that code that processes the superclass can work on the subclass. 79 */ 80 int nparams; 81 char **params; 82 83 /* 84 * Cached printable form (that is, with params[] interpolated into 85 * fmt) of the error message. Created when requested. 86 */ 87 char *printable; 88 }; 89 90 static directory_error_t directory_error_internal_error(int err); 91 92 /* 93 * For debugging, reference count of directory_error instances still in 94 * existence. When the system is idle, this should be zero. 95 * Note that no attempt is made to make this MT safe, so it is not reliable 96 * in an MT environment. 97 */ 98 static int directory_errors_outstanding = 0; 99 100 /* 101 * Free the specified directory_error_t. Note that this invalidates all strings 102 * returned based on it. 103 * 104 * Does nothing when de==NULL. 105 */ 106 void 107 directory_error_free(directory_error_t de) 108 { 109 int i; 110 111 if (de == NULL) 112 return; 113 114 /* Don't free our internal static directory_error_ts! */ 115 if (de->is_static) 116 return; 117 118 free(de->code); 119 de->code = NULL; 120 free(de->fmt); 121 de->fmt = NULL; 122 123 /* Free parameters, if any */ 124 if (de->params != NULL) { 125 for (i = 0; i < de->nparams; i++) { 126 free(de->params[i]); 127 de->params[i] = NULL; 128 } 129 free(de->params); 130 de->params = NULL; 131 } 132 133 /* Free cached printable */ 134 free(de->printable); 135 de->printable = NULL; 136 137 free(de); 138 139 directory_errors_outstanding--; 140 } 141 142 /* 143 * de = directory_error(code, fmt [, arg1 ... ]); 144 * Code, fmt, and arguments must be strings and will be copied. 145 */ 146 directory_error_t 147 directory_error(const char *code, const char *fmt, ...) 148 { 149 directory_error_t de = NULL; 150 va_list va; 151 int i; 152 153 de = calloc(1, sizeof (*de)); 154 if (de == NULL) 155 goto nomem; 156 157 directory_errors_outstanding++; 158 159 de->is_static = B_FALSE; 160 161 de->code = strdup(code); 162 if (de->code == NULL) 163 goto nomem; 164 165 de->fmt = strdup(fmt); 166 if (de->fmt == NULL) 167 goto nomem; 168 169 /* Count our parameters */ 170 va_start(va, fmt); 171 for (i = 0; va_arg(va, char *) != NULL; i++) 172 /* LOOP */; 173 va_end(va); 174 175 de->nparams = i; 176 177 /* 178 * Note that we do not copy the terminating NULL because we have 179 * a count. 180 */ 181 de->params = calloc(de->nparams, sizeof (char *)); 182 if (de->params == NULL) 183 goto nomem; 184 185 va_start(va, fmt); 186 for (i = 0; i < de->nparams; i++) { 187 de->params[i] = strdup((char *)va_arg(va, char *)); 188 if (de->params[i] == NULL) { 189 va_end(va); 190 goto nomem; 191 } 192 } 193 va_end(va); 194 195 return (de); 196 197 nomem:; 198 int err = errno; 199 directory_error_free(de); 200 return (directory_error_internal_error(err)); 201 } 202 203 /* 204 * Transform a directory_error returned by RPC into a directory_error_t. 205 */ 206 directory_error_t 207 directory_error_from_rpc(directory_error_rpc *de_rpc) 208 { 209 directory_error_t de; 210 int i; 211 212 de = calloc(1, sizeof (*de)); 213 if (de == NULL) 214 goto nomem; 215 216 directory_errors_outstanding++; 217 218 de->is_static = B_FALSE; 219 de->code = strdup(de_rpc->code); 220 if (de->code == NULL) 221 goto nomem; 222 de->fmt = strdup(de_rpc->fmt); 223 if (de->fmt == NULL) 224 goto nomem; 225 226 de->nparams = de_rpc->params.params_len; 227 228 de->params = calloc(de->nparams, sizeof (char *)); 229 if (de->params == NULL) 230 goto nomem; 231 232 for (i = 0; i < de->nparams; i++) { 233 de->params[i] = strdup(de_rpc->params.params_val[i]); 234 if (de->params[i] == NULL) 235 goto nomem; 236 } 237 238 return (de); 239 240 nomem:; 241 int err = errno; 242 directory_error_free(de); 243 return (directory_error_internal_error(err)); 244 } 245 246 /* 247 * Convert a directory_error_t into a directory_error to send over RPC. 248 * 249 * Returns TRUE on successful conversion, FALSE on failure. 250 * 251 * Frees the directory_error_t. 252 * 253 * Note that most functions in this suite return boolean_t, as defined 254 * by types.h. This function is intended to be used directly as the 255 * return value from an RPC service function, and so it returns bool_t. 256 */ 257 bool_t 258 directory_error_to_rpc(directory_error_rpc *de_rpc, directory_error_t de) 259 { 260 int i; 261 idmap_utf8str *params; 262 263 de_rpc->code = strdup(de->code); 264 if (de_rpc->code == NULL) 265 goto nomem; 266 267 de_rpc->fmt = strdup(de->fmt); 268 if (de_rpc->fmt == NULL) 269 goto nomem; 270 271 params = calloc(de->nparams, sizeof (idmap_utf8str)); 272 if (params == NULL) 273 goto nomem; 274 de_rpc->params.params_val = params; 275 de_rpc->params.params_len = de->nparams; 276 277 for (i = 0; i < de->nparams; i++) { 278 params[i] = strdup(de->params[i]); 279 if (params[i] == NULL) 280 goto nomem; 281 } 282 283 directory_error_free(de); 284 return (TRUE); 285 286 nomem: 287 logger(LOG_ERR, "Warning: failed to convert error for RPC\n" 288 "Original error: %s\n" 289 "Conversion error: %s\n", 290 strerror(errno), 291 directory_error_printable(de)); 292 directory_error_free(de); 293 return (FALSE); 294 } 295 296 /* 297 * Determines whether this directory_error_t is an instance of the 298 * particular error, or a subclass of that error. 299 */ 300 boolean_t 301 directory_error_is_instance_of(directory_error_t de, char *code) 302 { 303 int len; 304 305 if (de == NULL || de->code == NULL) 306 return (B_FALSE); 307 308 len = strlen(code); 309 310 if (strncasecmp(de->code, code, len) != 0) 311 return (B_FALSE); 312 313 if (de->code[len] == '\0' || de->code[len] == '.') 314 return (B_TRUE); 315 316 return (B_FALSE); 317 } 318 319 /* 320 * Expand the directory_error_t in de into buf, returning the size of the 321 * resulting string including terminating \0. If buf is NULL, just 322 * return the size. 323 * 324 * Return -1 if there are no substitutions, so that the caller can 325 * avoid memory allocation. 326 */ 327 static 328 int 329 directory_error_expand(char *buf, directory_error_t de) 330 { 331 int bufsiz; 332 boolean_t has_subst; 333 const char *p; 334 char c; 335 long n; 336 const char *s; 337 char *newp; 338 339 bufsiz = 0; 340 has_subst = B_FALSE; 341 342 for (p = dgettext(TEXT_DOMAIN, de->fmt); *p != '\0'; ) { 343 c = *p++; 344 if (c == '%') { 345 has_subst = B_TRUE; 346 if (isdigit(*p)) { 347 n = strtol(p, &newp, 10); 348 p = newp; 349 if (de->params == NULL || 350 n < 1 || 351 n > de->nparams) 352 s = dgettext(TEXT_DOMAIN, "(missing)"); 353 else 354 s = de->params[n - 1]; 355 if (buf != NULL) 356 (void) strcpy(buf + bufsiz, s); 357 bufsiz += strlen(s); 358 continue; 359 } 360 } 361 if (buf != NULL) 362 buf[bufsiz] = c; 363 bufsiz++; 364 } 365 366 if (buf != NULL) 367 buf[bufsiz] = '\0'; 368 bufsiz++; 369 370 return (has_subst ? bufsiz : -1); 371 } 372 373 /* 374 * Returns a printable version of this directory_error_t, suitable for 375 * human consumption. 376 * 377 * The value returned is valid as long as the directory_error_t is valid, 378 * and is freed when the directory_error_t is freed. 379 */ 380 const char * 381 directory_error_printable(directory_error_t de) 382 { 383 char *s; 384 int bufsiz; 385 386 if (de->printable != NULL) 387 return (de->printable); 388 389 bufsiz = directory_error_expand(NULL, de); 390 391 /* 392 * Short circuit case to avoid memory allocation when there is 393 * no parameter substitution. 394 */ 395 if (bufsiz < 0) 396 return (dgettext(TEXT_DOMAIN, de->fmt)); 397 398 s = malloc(bufsiz); 399 if (s == NULL) { 400 return (dgettext(TEXT_DOMAIN, 401 "Out of memory while expanding directory_error_t")); 402 } 403 404 (void) directory_error_expand(s, de); 405 406 /* 407 * Stash the expansion away for later free, and to short-circuit 408 * repeated expansions. 409 */ 410 de->printable = s; 411 412 return (de->printable); 413 } 414 415 /* 416 * Returns the error code for the particular error, as a string. 417 * Note that this function should not normally be used to answer 418 * the question "did error X happen", since the value returned 419 * could be a subclass of X. directory_error_is_instance_of is intended 420 * to answer that question. 421 * 422 * The value returned is valid as long as the directory_error_t is valid, 423 * and is freed when the directory_error_t is freed. 424 */ 425 const char * 426 directory_error_code(directory_error_t de) 427 { 428 return (de->code); 429 } 430 431 /* 432 * Returns one of the parameters of the directory_error_t, or NULL if 433 * the parameter does not exist. 434 * 435 * Note that it is required that error subclasses have initial parameters 436 * the same as their superclasses. 437 * 438 * The value returned is valid as long as the directory_error_t is valid, 439 * and is freed when the directory_error_t is freed. 440 */ 441 const char * 442 directory_error_param(directory_error_t de, int param) 443 { 444 if (param >= de->nparams) 445 return (NULL); 446 return (de->params[param]); 447 } 448 449 /* 450 * Here are some (almost) constant directory_error_t structures 451 * for use in reporting errors encountered while creating a 452 * directory_error_t structure. Unfortunately, the original error 453 * report is lost. 454 */ 455 #define gettext(x) x /* let xgettext see these messages */ 456 static struct directory_error directory_error_ENOMEM = { 457 B_TRUE, 458 "ENOMEM.directory_error_t", 459 gettext("Out of memory while creating a directory_error_t"), 460 0, NULL, 461 NULL, 462 }; 463 464 static struct directory_error directory_error_EAGAIN = { 465 B_TRUE, 466 "EAGAIN.directory_error_t", 467 gettext("Out of resources while creating a directory_error_t"), 468 0, NULL, 469 NULL, 470 }; 471 472 /* 40 is big enough for even 128 bits */ 473 static char directory_error_unknown_errno[40] = "0"; 474 static char *directory_error_unknown_params[] = { 475 directory_error_unknown_errno 476 }; 477 static struct directory_error directory_error_unknown = { 478 B_TRUE, 479 "Unknown.directory_error_t", 480 gettext("Unknown error (%1) while creating a directory_error_t"), 481 1, directory_error_unknown_params, 482 NULL, 483 }; 484 #undef gettext 485 486 static 487 directory_error_t 488 directory_error_internal_error(int err) 489 { 490 switch (err) { 491 case ENOMEM: return (&directory_error_ENOMEM); 492 case EAGAIN: return (&directory_error_EAGAIN); 493 default: 494 /* Pray that we don't have a reentrancy problem ... */ 495 (void) sprintf(directory_error_unknown_errno, "%u", err); 496 return (&directory_error_unknown); 497 } 498 } 499