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
directory_error_free(directory_error_t de)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
directory_error(const char * code,const char * fmt,...)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
directory_error_from_rpc(directory_error_rpc * de_rpc)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
directory_error_to_rpc(directory_error_rpc * de_rpc,directory_error_t de)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
directory_error_is_instance_of(directory_error_t de,char * code)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
directory_error_expand(char * buf,directory_error_t de)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 *
directory_error_printable(directory_error_t de)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 *
directory_error_code(directory_error_t de)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 *
directory_error_param(directory_error_t de,int param)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
directory_error_internal_error(int err)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