/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Error handling support for directory lookup. * Actually, this is intended to be a very generic and extensible error * reporting mechanism. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "directory.h" /* * This is the actual implementation of the opaque directory_error_t structure. */ struct directory_error { /* * True if this directory_error_t is statically allocated. Used to * handle out of memory errors during error reporting. */ boolean_t is_static; /* * The error code. This is a locale-independent string that * represents the precise error (to some level of granularity) * that occurred. Internationalization processing could map it * to an message. Errors may be subclassed by appending a dot * and a name for the subclass. * * Note that this code plus the parameters allows for structured * processing of error results. */ char *code; /* * The default (in the absence of internationalization) format for * the error message. %n interposes params[n - 1]. */ char *fmt; /* * Parameters to the error message. Note that subclasses are * required to have the same initial parameters as their superclasses, * so that code that processes the superclass can work on the subclass. */ int nparams; char **params; /* * Cached printable form (that is, with params[] interpolated into * fmt) of the error message. Created when requested. */ char *printable; }; static directory_error_t directory_error_internal_error(int err); /* * For debugging, reference count of directory_error instances still in * existence. When the system is idle, this should be zero. * Note that no attempt is made to make this MT safe, so it is not reliable * in an MT environment. */ static int directory_errors_outstanding = 0; /* * Free the specified directory_error_t. Note that this invalidates all strings * returned based on it. * * Does nothing when de==NULL. */ void directory_error_free(directory_error_t de) { int i; if (de == NULL) return; /* Don't free our internal static directory_error_ts! */ if (de->is_static) return; free(de->code); de->code = NULL; free(de->fmt); de->fmt = NULL; /* Free parameters, if any */ if (de->params != NULL) { for (i = 0; i < de->nparams; i++) { free(de->params[i]); de->params[i] = NULL; } free(de->params); de->params = NULL; } /* Free cached printable */ free(de->printable); de->printable = NULL; free(de); directory_errors_outstanding--; } /* * de = directory_error(code, fmt [, arg1 ... ]); * Code, fmt, and arguments must be strings and will be copied. */ directory_error_t directory_error(const char *code, const char *fmt, ...) { directory_error_t de = NULL; va_list va; int i; de = calloc(1, sizeof (*de)); if (de == NULL) goto nomem; directory_errors_outstanding++; de->is_static = B_FALSE; de->code = strdup(code); if (de->code == NULL) goto nomem; de->fmt = strdup(fmt); if (de->fmt == NULL) goto nomem; /* Count our parameters */ va_start(va, fmt); for (i = 0; va_arg(va, char *) != NULL; i++) /* LOOP */; va_end(va); de->nparams = i; /* * Note that we do not copy the terminating NULL because we have * a count. */ de->params = calloc(de->nparams, sizeof (char *)); if (de->params == NULL) goto nomem; va_start(va, fmt); for (i = 0; i < de->nparams; i++) { de->params[i] = strdup((char *)va_arg(va, char *)); if (de->params[i] == NULL) { va_end(va); goto nomem; } } va_end(va); return (de); nomem:; int err = errno; directory_error_free(de); return (directory_error_internal_error(err)); } /* * Transform a directory_error returned by RPC into a directory_error_t. */ directory_error_t directory_error_from_rpc(directory_error_rpc *de_rpc) { directory_error_t de; int i; de = calloc(1, sizeof (*de)); if (de == NULL) goto nomem; directory_errors_outstanding++; de->is_static = B_FALSE; de->code = strdup(de_rpc->code); if (de->code == NULL) goto nomem; de->fmt = strdup(de_rpc->fmt); if (de->fmt == NULL) goto nomem; de->nparams = de_rpc->params.params_len; de->params = calloc(de->nparams, sizeof (char *)); if (de->params == NULL) goto nomem; for (i = 0; i < de->nparams; i++) { de->params[i] = strdup(de_rpc->params.params_val[i]); if (de->params[i] == NULL) goto nomem; } return (de); nomem:; int err = errno; directory_error_free(de); return (directory_error_internal_error(err)); } /* * Convert a directory_error_t into a directory_error to send over RPC. * * Returns TRUE on successful conversion, FALSE on failure. * * Frees the directory_error_t. * * Note that most functions in this suite return boolean_t, as defined * by types.h. This function is intended to be used directly as the * return value from an RPC service function, and so it returns bool_t. */ bool_t directory_error_to_rpc(directory_error_rpc *de_rpc, directory_error_t de) { int i; idmap_utf8str *params; de_rpc->code = strdup(de->code); if (de_rpc->code == NULL) goto nomem; de_rpc->fmt = strdup(de->fmt); if (de_rpc->fmt == NULL) goto nomem; params = calloc(de->nparams, sizeof (idmap_utf8str)); if (params == NULL) goto nomem; de_rpc->params.params_val = params; de_rpc->params.params_len = de->nparams; for (i = 0; i < de->nparams; i++) { params[i] = strdup(de->params[i]); if (params[i] == NULL) goto nomem; } directory_error_free(de); return (TRUE); nomem: logger(LOG_ERR, "Warning: failed to convert error for RPC\n" "Original error: %s\n" "Conversion error: %s\n", strerror(errno), directory_error_printable(de)); directory_error_free(de); return (FALSE); } /* * Determines whether this directory_error_t is an instance of the * particular error, or a subclass of that error. */ boolean_t directory_error_is_instance_of(directory_error_t de, char *code) { int len; if (de == NULL || de->code == NULL) return (B_FALSE); len = strlen(code); if (strncasecmp(de->code, code, len) != 0) return (B_FALSE); if (de->code[len] == '\0' || de->code[len] == '.') return (B_TRUE); return (B_FALSE); } /* * Expand the directory_error_t in de into buf, returning the size of the * resulting string including terminating \0. If buf is NULL, just * return the size. * * Return -1 if there are no substitutions, so that the caller can * avoid memory allocation. */ static int directory_error_expand(char *buf, directory_error_t de) { int bufsiz; boolean_t has_subst; const char *p; char c; long n; const char *s; char *newp; bufsiz = 0; has_subst = B_FALSE; for (p = dgettext(TEXT_DOMAIN, de->fmt); *p != '\0'; ) { c = *p++; if (c == '%') { has_subst = B_TRUE; if (isdigit(*p)) { n = strtol(p, &newp, 10); p = newp; if (de->params == NULL || n < 1 || n > de->nparams) s = dgettext(TEXT_DOMAIN, "(missing)"); else s = de->params[n - 1]; if (buf != NULL) (void) strcpy(buf + bufsiz, s); bufsiz += strlen(s); continue; } } if (buf != NULL) buf[bufsiz] = c; bufsiz++; } if (buf != NULL) buf[bufsiz] = '\0'; bufsiz++; return (has_subst ? bufsiz : -1); } /* * Returns a printable version of this directory_error_t, suitable for * human consumption. * * The value returned is valid as long as the directory_error_t is valid, * and is freed when the directory_error_t is freed. */ const char * directory_error_printable(directory_error_t de) { char *s; int bufsiz; if (de->printable != NULL) return (de->printable); bufsiz = directory_error_expand(NULL, de); /* * Short circuit case to avoid memory allocation when there is * no parameter substitution. */ if (bufsiz < 0) return (dgettext(TEXT_DOMAIN, de->fmt)); s = malloc(bufsiz); if (s == NULL) { return (dgettext(TEXT_DOMAIN, "Out of memory while expanding directory_error_t")); } (void) directory_error_expand(s, de); /* * Stash the expansion away for later free, and to short-circuit * repeated expansions. */ de->printable = s; return (de->printable); } /* * Returns the error code for the particular error, as a string. * Note that this function should not normally be used to answer * the question "did error X happen", since the value returned * could be a subclass of X. directory_error_is_instance_of is intended * to answer that question. * * The value returned is valid as long as the directory_error_t is valid, * and is freed when the directory_error_t is freed. */ const char * directory_error_code(directory_error_t de) { return (de->code); } /* * Returns one of the parameters of the directory_error_t, or NULL if * the parameter does not exist. * * Note that it is required that error subclasses have initial parameters * the same as their superclasses. * * The value returned is valid as long as the directory_error_t is valid, * and is freed when the directory_error_t is freed. */ const char * directory_error_param(directory_error_t de, int param) { if (param >= de->nparams) return (NULL); return (de->params[param]); } /* * Here are some (almost) constant directory_error_t structures * for use in reporting errors encountered while creating a * directory_error_t structure. Unfortunately, the original error * report is lost. */ #define gettext(x) x /* let xgettext see these messages */ static struct directory_error directory_error_ENOMEM = { B_TRUE, "ENOMEM.directory_error_t", gettext("Out of memory while creating a directory_error_t"), 0, NULL, NULL, }; static struct directory_error directory_error_EAGAIN = { B_TRUE, "EAGAIN.directory_error_t", gettext("Out of resources while creating a directory_error_t"), 0, NULL, NULL, }; /* 40 is big enough for even 128 bits */ static char directory_error_unknown_errno[40] = "0"; static char *directory_error_unknown_params[] = { directory_error_unknown_errno }; static struct directory_error directory_error_unknown = { B_TRUE, "Unknown.directory_error_t", gettext("Unknown error (%1) while creating a directory_error_t"), 1, directory_error_unknown_params, NULL, }; #undef gettext static directory_error_t directory_error_internal_error(int err) { switch (err) { case ENOMEM: return (&directory_error_ENOMEM); case EAGAIN: return (&directory_error_EAGAIN); default: /* Pray that we don't have a reentrancy problem ... */ (void) sprintf(directory_error_unknown_errno, "%u", err); return (&directory_error_unknown); } }