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 */
50struct 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
90static 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 */
98static 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 */
106void
107directory_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 */
146directory_error_t
147directory_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
197nomem:;
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 */
206directory_error_t
207directory_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
240nomem:;
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 */
257bool_t
258directory_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
286nomem:
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 */
300boolean_t
301directory_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 */
327static
328int
329directory_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 */
380const char *
381directory_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 */
425const char *
426directory_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 */
441const char *
442directory_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 */
456static 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
464static 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 */
473static char directory_error_unknown_errno[40] = "0";
474static char *directory_error_unknown_params[] = {
475    directory_error_unknown_errno
476};
477static 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
486static
487directory_error_t
488directory_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