OpenSolaris_b135/lib/libidmap/common/directory_error.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 <stdio.h>
#include <stdlib.h>
#include <thread.h>
#include <errno.h>
#include <stdarg.h>
#include <malloc.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <idmap_impl.h>
#include <rpcsvc/idmap_prot.h>
#include <libintl.h>
#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);
	}
}