xref: /titanic_44/usr/src/lib/libinetutil/common/ofmt.c (revision 0a1ad920531b37f01f4aa8084737026621c76bdb)
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 #include <errno.h>
27 #include <sys/types.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <strings.h>
31 #include <stdio.h>
32 #include <ofmt.h>
33 #include <sys/termios.h>
34 #include <unistd.h>
35 #include <sys/sysmacros.h>
36 #include <libintl.h>
37 
38 /*
39  * functions and structures to internally process a comma-separated string
40  * of fields selected for output.
41  */
42 typedef struct {
43 	char	*s_buf;
44 	const char **s_fields;	/* array of pointers to the fields in s_buf */
45 	uint_t	s_nfields;	/* the number of fields in s_buf */
46 } split_t;
47 static void splitfree(split_t *);
48 static split_t *split_str(const char *, uint_t);
49 static split_t *split_fields(const ofmt_field_t *, uint_t, uint_t);
50 
51 /*
52  * The state of the output is tracked in a ofmt_state_t structure.
53  * Each os_fields[i] entry points at an ofmt_field_t array for
54  * the sub-command whose contents are provided by the caller, with
55  * os_nfields set to the number of requested fields.
56  */
57 typedef struct ofmt_state_s {
58 	ofmt_field_t  	*os_fields;
59 	uint_t		os_nfields;
60 	boolean_t	os_lastfield;
61 	uint_t		os_overflow;
62 	struct winsize	os_winsize;
63 	int		os_nrow;
64 	boolean_t	os_parsable;
65 	int		os_nbad;
66 	char		**os_badfields;
67 } ofmt_state_t;
68 /*
69  * A B_TRUE return value from the callback function will print out the contents
70  * of the output buffer, except when the buffer is returned with the empty
71  * string "", in which case the  OFMT_VAL_UNDEF will be printed.
72  *
73  * If the callback function returns B_FALSE, the "?" string will be emitted.
74  */
75 #define	OFMT_VAL_UNDEF		"--"
76 #define	OFMT_VAL_UNKNOWN	"?"
77 static void ofmt_print_header(ofmt_state_t *);
78 static void ofmt_print_field(ofmt_state_t *, ofmt_field_t *, const char *,
79     boolean_t);
80 
81 /*
82  * Split `str' into at most `maxfields' fields, Return a pointer to a
83  * split_t containing the split fields, or NULL on failure.
84  */
85 static split_t *
86 split_str(const char *str, uint_t maxfields)
87 {
88 	char	*field, *token, *lasts = NULL;
89 	split_t	*sp;
90 
91 	if (*str == '\0' || maxfields == 0)
92 		return (NULL);
93 
94 	sp = calloc(sizeof (split_t), 1);
95 	if (sp == NULL)
96 		return (NULL);
97 
98 	sp->s_buf = strdup(str);
99 	sp->s_fields = malloc(sizeof (char *) * maxfields);
100 	if (sp->s_buf == NULL || sp->s_fields == NULL)
101 		goto fail;
102 
103 	token = sp->s_buf;
104 	while ((field = strtok_r(token, ",", &lasts)) != NULL) {
105 		if (sp->s_nfields == maxfields)
106 			goto fail;
107 		token = NULL;
108 		sp->s_fields[sp->s_nfields++] = field;
109 	}
110 	return (sp);
111 fail:
112 	splitfree(sp);
113 	return (NULL);
114 }
115 
116 /*
117  * Split `fields' into at most `maxfields' fields. Return a pointer to
118  * a split_t containing the split fields, or NULL on failure. Invoked
119  * when all fields are implicitly selected at handle creation by
120  * passing in a NULL fields_str
121  */
122 static split_t *
123 split_fields(const ofmt_field_t *template, uint_t maxfields, uint_t maxcols)
124 {
125 	split_t	*sp;
126 	int i, cols;
127 
128 	sp = calloc(sizeof (split_t), 1);
129 	if (sp == NULL)
130 		return (NULL);
131 
132 	sp->s_fields = malloc(sizeof (char *) * maxfields);
133 	if (sp->s_fields == NULL)
134 		goto fail;
135 	cols = 0;
136 	for (i = 0; i < maxfields; i++) {
137 		cols += template[i].of_width;
138 		/*
139 		 * If all fields are implied without explicitly passing
140 		 * in a fields_str, build a list of field names, stopping
141 		 * when we run out of columns.
142 		 */
143 		if (maxcols > 0 && cols > maxcols)
144 			break;
145 		sp->s_fields[sp->s_nfields++] = template[i].of_name;
146 	}
147 	return (sp);
148 fail:
149 	splitfree(sp);
150 	return (NULL);
151 }
152 
153 /*
154  * Free the split_t structure pointed to by `sp'.
155  */
156 static void
157 splitfree(split_t *sp)
158 {
159 	if (sp == NULL)
160 		return;
161 	free(sp->s_buf);
162 	free(sp->s_fields);
163 	free(sp);
164 }
165 
166 /*
167  * Open a handle to be used for printing formatted output.
168  */
169 ofmt_status_t
170 ofmt_open(const char *str, ofmt_field_t *template, uint_t flags,
171     uint_t maxcols, ofmt_handle_t *ofmt)
172 {
173 	split_t		*sp;
174 	uint_t		i, j, of_index;
175 	ofmt_field_t	*of;
176 	ofmt_state_t	*os;
177 	int		nfields = 0;
178 	ofmt_status_t	err = OFMT_SUCCESS;
179 	boolean_t	parsable = (flags & OFMT_PARSABLE);
180 
181 	*ofmt = NULL;
182 	if (parsable) {
183 		/*
184 		 * For parsable output mode, the caller always needs
185 		 * to specify precisely which fields are to be selected,
186 		 * since the set of fields may change over time.
187 		 */
188 		if (str == NULL || str[0] == '\0')
189 			return (OFMT_EPARSENONE);
190 		if (strcmp(str, "all") == 0)
191 			return (OFMT_EPARSEALL);
192 	}
193 	if (template == NULL)
194 		return (OFMT_ENOTEMPLATE);
195 	for (of = template; of->of_name != NULL; of++)
196 		nfields++;
197 	/*
198 	 * split str into the columns selected, or construct the
199 	 * full set of columns (equivalent to -o all).
200 	 */
201 	if (str != NULL && strcmp(str, "all") != 0) {
202 		sp = split_str(str, nfields);
203 	} else {
204 		if (parsable || (str != NULL && strcmp(str, "all") == 0))
205 			maxcols = 0;
206 		sp = split_fields(template, nfields, maxcols);
207 	}
208 	if (sp == NULL)
209 		goto nomem;
210 
211 	os = calloc(sizeof (ofmt_state_t) +
212 	    sp->s_nfields * sizeof (ofmt_field_t), 1);
213 	if (os == NULL)
214 		goto nomem;
215 	*ofmt = os;
216 	os->os_fields = (ofmt_field_t *)&os[1];
217 	os->os_parsable = parsable;
218 	of = os->os_fields;
219 	of_index = 0;
220 	/*
221 	 * sp->s_nfields is the number of fields requested in fields_str.
222 	 * nfields is the number of fields in template.
223 	 */
224 	for (i = 0; i < sp->s_nfields; i++) {
225 		for (j = 0; j < nfields; j++) {
226 			if (strcasecmp(sp->s_fields[i],
227 			    template[j].of_name) == 0) {
228 				break;
229 			}
230 		}
231 		if (j == nfields) {
232 			int nbad = os->os_nbad++;
233 
234 			err = OFMT_EBADFIELDS;
235 			if (os->os_badfields == NULL) {
236 				os->os_badfields = malloc(sp->s_nfields *
237 				    sizeof (char *));
238 				if (os->os_badfields == NULL)
239 					goto nomem;
240 			}
241 			os->os_badfields[nbad] = strdup(sp->s_fields[i]);
242 			if (os->os_badfields[nbad] == NULL)
243 				goto nomem;
244 			continue;
245 		}
246 		of[of_index].of_name = strdup(template[j].of_name);
247 		if (of[of_index].of_name == NULL)
248 			goto nomem;
249 		of[of_index].of_width = template[j].of_width;
250 		of[of_index].of_id = template[j].of_id;
251 		of[of_index].of_cb = template[j].of_cb;
252 		of_index++;
253 	}
254 	splitfree(sp);
255 	if (of_index == 0) /* all values in str are bogus */
256 		return (OFMT_ENOFIELDS);
257 	os->os_nfields = of_index; /* actual number of fields printed */
258 	ofmt_update_winsize(*ofmt);
259 	return (err);
260 nomem:
261 	err = OFMT_ENOMEM;
262 	if (os != NULL)
263 		ofmt_close(os);
264 	*ofmt = NULL;
265 	splitfree(sp);
266 	return (err);
267 }
268 
269 /*
270  * free resources associated with the ofmt_handle_t
271  */
272 void
273 ofmt_close(ofmt_handle_t ofmt)
274 {
275 	ofmt_state_t *os = ofmt;
276 	int i;
277 
278 	if (os == NULL)
279 		return;
280 	for (i = 0; i < os->os_nfields; i++)
281 		free(os->os_fields[i].of_name);
282 	for (i = 0; i < os->os_nbad; i++)
283 		free(os->os_badfields[i]);
284 	free(os->os_badfields);
285 	free(os);
286 }
287 
288 /*
289  * Print the value for the selected field by calling the callback-function
290  * registered for the field.
291  */
292 static void
293 ofmt_print_field(ofmt_state_t *os, ofmt_field_t *ofp, const char *value,
294     boolean_t escsep)
295 {
296 	uint_t	width = ofp->of_width;
297 	uint_t	valwidth;
298 	uint_t	compress;
299 	boolean_t parsable = os->os_parsable;
300 	char	c;
301 
302 	/*
303 	 * Parsable fields are separated by ':'. If such a field contains
304 	 * a ':' or '\', this character is prefixed by a '\'.
305 	 */
306 	if (parsable) {
307 		if (os->os_nfields == 1) {
308 			(void) printf("%s", value);
309 			return;
310 		}
311 		while ((c = *value++) != '\0') {
312 			if (escsep && ((c == ':' || c == '\\')))
313 				(void) putchar('\\');
314 			(void) putchar(c);
315 		}
316 		if (!os->os_lastfield)
317 			(void) putchar(':');
318 		return;
319 	} else {
320 		if (value[0] == '\0')
321 			value = OFMT_VAL_UNDEF;
322 		if (os->os_lastfield) {
323 			(void) printf("%s", value);
324 			os->os_overflow = 0;
325 			return;
326 		}
327 
328 		valwidth = strlen(value);
329 		if (valwidth + os->os_overflow >= width) {
330 			os->os_overflow += valwidth - width + 1;
331 			(void) printf("%s ", value);
332 			return;
333 		}
334 
335 		if (os->os_overflow > 0) {
336 			compress = MIN(os->os_overflow, width - valwidth);
337 			os->os_overflow -= compress;
338 			width -= compress;
339 		}
340 		(void) printf("%-*s", width, value);
341 	}
342 }
343 
344 /*
345  * print one row of output values for the selected columns.
346  */
347 void
348 ofmt_print(ofmt_handle_t ofmt, void *arg)
349 {
350 	ofmt_state_t *os = ofmt;
351 	int i;
352 	char value[1024];
353 	ofmt_field_t *of;
354 	boolean_t escsep;
355 	ofmt_arg_t ofarg;
356 
357 	if ((os->os_nrow++ % os->os_winsize.ws_row) == 0 && !os->os_parsable) {
358 		ofmt_print_header(os);
359 		os->os_nrow++;
360 	}
361 
362 	of = os->os_fields;
363 	escsep = (os->os_nfields > 1);
364 	for (i = 0; i < os->os_nfields; i++) {
365 		os->os_lastfield = (i + 1 == os->os_nfields);
366 		value[0] = '\0';
367 		ofarg.ofmt_id = of[i].of_id;
368 		ofarg.ofmt_cbarg = arg;
369 		if ((*of[i].of_cb)(&ofarg, value, sizeof (value)))
370 			ofmt_print_field(os, &of[i], value, escsep);
371 		else
372 			ofmt_print_field(os, &of[i], OFMT_VAL_UNKNOWN, escsep);
373 	}
374 	(void) putchar('\n');
375 	(void) fflush(stdout);
376 }
377 
378 /*
379  * Print the field headers
380  */
381 static void
382 ofmt_print_header(ofmt_state_t *os)
383 {
384 	int i;
385 	ofmt_field_t *of = os->os_fields;
386 	boolean_t escsep = (os->os_nfields > 1);
387 
388 	for (i = 0; i < os->os_nfields; i++) {
389 		os->os_lastfield = (i + 1 == os->os_nfields);
390 		ofmt_print_field(os, &of[i], of[i].of_name, escsep);
391 	}
392 	(void) putchar('\n');
393 }
394 
395 /*
396  * Update the current window size.
397  */
398 void
399 ofmt_update_winsize(ofmt_handle_t ofmt)
400 {
401 	ofmt_state_t *os = ofmt;
402 	struct winsize *winsize = &os->os_winsize;
403 
404 	if (ioctl(1, TIOCGWINSZ, winsize) == -1 ||
405 	    winsize->ws_col == 0 || winsize->ws_row == 0) {
406 		winsize->ws_col = 80;
407 		winsize->ws_row = 24;
408 	}
409 }
410 
411 /*
412  * Return error diagnostics using the information in the ofmt_handle_t
413  */
414 char *
415 ofmt_strerror(ofmt_handle_t ofmt, ofmt_status_t err, char *buf, uint_t bufsize)
416 {
417 	ofmt_state_t *os = ofmt;
418 	int i;
419 	const char *s;
420 	char ebuf[OFMT_BUFSIZE];
421 
422 	/*
423 	 * ebuf is intended for optional error-specific data to be appended
424 	 * after the internationalized error string for an error code.
425 	 */
426 	ebuf[0] = '\0';
427 
428 	switch (err) {
429 	case OFMT_SUCCESS:
430 		s = "success";
431 		break;
432 	case OFMT_EBADFIELDS:
433 		/*
434 		 * Enumerate the singular/plural version of the warning
435 		 * and error to simplify and improve localization.
436 		 */
437 		if (!os->os_parsable) {
438 			if (os->os_nbad > 1)
439 				s = "ignoring unknown output fields:";
440 			else
441 				s = "ignoring unknown output field:";
442 		} else {
443 			if (os->os_nbad > 1)
444 				s = "unknown output fields:";
445 			else
446 				s = "unknown output field:";
447 		}
448 		/* set up the bad fields in ebuf */
449 		for (i = 0; i < os->os_nbad; i++) {
450 			(void) strlcat(ebuf, " `", sizeof (ebuf));
451 			(void) strlcat(ebuf, os->os_badfields[i],
452 			    sizeof (ebuf));
453 			(void) strlcat(ebuf, "'", sizeof (ebuf));
454 		}
455 		break;
456 	case OFMT_ENOFIELDS:
457 		s = "no valid output fields";
458 		break;
459 	case OFMT_EPARSEALL:
460 		s = "output field `all' invalid in parsable mode";
461 		break;
462 	case OFMT_EPARSENONE:
463 		s = "output fields must be specified in parsable mode";
464 		break;
465 	case OFMT_ENOTEMPLATE:
466 		s = "no template provided for fields";
467 		break;
468 	case OFMT_ENOMEM:
469 		s = strerror(ENOMEM);
470 		break;
471 	default:
472 		(void) snprintf(buf, bufsize,
473 		    dgettext(TEXT_DOMAIN, "unknown ofmt error (%d)"),
474 		    err);
475 		return (buf);
476 	}
477 	(void) snprintf(buf, bufsize, dgettext(TEXT_DOMAIN, s));
478 	(void) strlcat(buf, ebuf, bufsize);
479 	return (buf);
480 }
481