xref: /illumos-gate/usr/src/lib/libidmap/common/directory_error.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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