xref: /illumos-gate/usr/src/lib/libofmt/common/ofmt.c (revision 6fc1cd8d)
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 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2015 by Delphix. All rights reserved.
29  * Copyright 2017 Joyent, Inc.
30  */
31 
32 #include <errno.h>
33 #include <sys/types.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <stdio.h>
38 #include <ofmt.h>
39 #include <sys/termios.h>
40 #include <unistd.h>
41 #include <sys/sysmacros.h>
42 #include <libintl.h>
43 #include <assert.h>
44 
45 /*
46  * functions and structures to internally process a comma-separated string
47  * of fields selected for output.
48  */
49 typedef struct {
50 	char	*s_buf;
51 	const char **s_fields;	/* array of pointers to the fields in s_buf */
52 	uint_t	s_nfields;	/* the number of fields in s_buf */
53 	uint_t	s_currfield;	/* the current field being processed */
54 } split_t;
55 
56 static void splitfree(split_t *);
57 static split_t *split_str(const char *, uint_t);
58 static split_t *split_fields(const ofmt_field_t *, uint_t, uint_t);
59 
60 /*
61  * The state of the output is tracked in a ofmt_state_t structure.
62  * Each os_fields[i] entry points at an ofmt_field_t array for
63  * the sub-command whose contents are provided by the caller, with
64  * os_nfields set to the number of requested fields.
65  */
66 typedef struct ofmt_state_s {
67 	ofmt_field_t	*os_fields;
68 	uint_t		os_nfields;
69 	boolean_t	os_lastfield;
70 	uint_t		os_overflow;
71 	struct winsize	os_winsize;
72 	int		os_nrow;
73 	uint_t		os_flags;
74 	uint_t		os_nbad;
75 	char		**os_badfields;
76 	int		os_maxnamelen;	/* longest name (f. multiline) */
77 	char		os_fs;		/* field seperator */
78 } ofmt_state_t;
79 /*
80  * A B_TRUE return value from the callback function will print out the contents
81  * of the output buffer, except when the buffer is returned with the empty
82  * string "", in which case the  OFMT_VAL_UNDEF will be printed.
83  *
84  * If the callback function returns B_FALSE, the "?" string will be emitted.
85  */
86 #define	OFMT_VAL_UNDEF		"--"
87 #define	OFMT_VAL_UNKNOWN	"?"
88 
89 #define	OFMT_DEFAULT_FS		':'
90 
91 /*
92  * The maximum number of rows supported by the OFMT_WRAP option.
93  */
94 #define	OFMT_MAX_ROWS		128
95 
96 static void ofmt_print_field(ofmt_state_t *, ofmt_field_t *, const char *,
97     boolean_t);
98 
99 /*
100  * Split `str' into at most `maxfields' fields, Return a pointer to a
101  * split_t containing the split fields, or NULL on failure.
102  */
103 static split_t *
split_str(const char * str,uint_t maxfields)104 split_str(const char *str, uint_t maxfields)
105 {
106 	char	*field, *token, *lasts = NULL;
107 	split_t	*sp;
108 
109 	if (*str == '\0' || maxfields == 0)
110 		return (NULL);
111 
112 	sp = calloc(sizeof (split_t), 1);
113 	if (sp == NULL)
114 		return (NULL);
115 
116 	sp->s_buf = strdup(str);
117 	sp->s_fields = malloc(sizeof (char *) * maxfields);
118 	if (sp->s_buf == NULL || sp->s_fields == NULL)
119 		goto fail;
120 
121 	token = sp->s_buf;
122 	while ((field = strtok_r(token, ",", &lasts)) != NULL) {
123 		if (sp->s_nfields == maxfields)
124 			goto fail;
125 		token = NULL;
126 		sp->s_fields[sp->s_nfields++] = field;
127 	}
128 	return (sp);
129 fail:
130 	splitfree(sp);
131 	return (NULL);
132 }
133 
134 /*
135  * Split `fields' into at most `maxfields' fields. Return a pointer to
136  * a split_t containing the split fields, or NULL on failure. Invoked
137  * when all fields are implicitly selected at handle creation by
138  * passing in a NULL fields_str
139  */
140 static split_t *
split_fields(const ofmt_field_t * template,uint_t maxfields,uint_t maxcols)141 split_fields(const ofmt_field_t *template, uint_t maxfields, uint_t maxcols)
142 {
143 	split_t	*sp;
144 	uint_t i, cols;
145 
146 	sp = calloc(sizeof (split_t), 1);
147 	if (sp == NULL)
148 		return (NULL);
149 
150 	sp->s_fields = malloc(sizeof (char *) * maxfields);
151 	if (sp->s_fields == NULL)
152 		goto fail;
153 	cols = 0;
154 	for (i = 0; i < maxfields; i++) {
155 		cols += template[i].of_width;
156 		/*
157 		 * If all fields are implied without explicitly passing
158 		 * in a fields_str, build a list of field names, stopping
159 		 * when we run out of columns.
160 		 */
161 		if (maxcols > 0 && cols > maxcols)
162 			break;
163 		sp->s_fields[sp->s_nfields++] = template[i].of_name;
164 	}
165 	return (sp);
166 fail:
167 	splitfree(sp);
168 	return (NULL);
169 }
170 
171 /*
172  * Free the split_t structure pointed to by `sp'.
173  */
174 static void
splitfree(split_t * sp)175 splitfree(split_t *sp)
176 {
177 	if (sp == NULL)
178 		return;
179 	free(sp->s_buf);
180 	free(sp->s_fields);
181 	free(sp);
182 }
183 
184 /*
185  * Open a handle to be used for printing formatted output.
186  */
187 ofmt_status_t
ofmt_open(const char * str,const ofmt_field_t * template,uint_t flags,uint_t maxcols,ofmt_handle_t * ofmt)188 ofmt_open(const char *str, const ofmt_field_t *template, uint_t flags,
189     uint_t maxcols, ofmt_handle_t *ofmt)
190 {
191 	split_t		*sp;
192 	uint_t		i, j, of_index;
193 	const ofmt_field_t *ofp;
194 	ofmt_field_t	*of;
195 	ofmt_state_t	*os = NULL;
196 	uint_t		nfields = 0;
197 	ofmt_status_t	error = OFMT_SUCCESS;
198 	boolean_t	parsable = (flags & OFMT_PARSABLE);
199 	boolean_t	wrap = (flags & OFMT_WRAP);
200 	boolean_t	multiline = (flags & OFMT_MULTILINE);
201 
202 	*ofmt = NULL;
203 	if (parsable) {
204 		if (multiline)
205 			return (OFMT_EPARSEMULTI);
206 		/*
207 		 * For parsable output mode, the caller always needs
208 		 * to specify precisely which fields are to be selected,
209 		 * since the set of fields may change over time.
210 		 */
211 		if (str == NULL || str[0] == '\0')
212 			return (OFMT_EPARSENONE);
213 		if (strcasecmp(str, "all") == 0)
214 			return (OFMT_EPARSEALL);
215 		if (wrap)
216 			return (OFMT_EPARSEWRAP);
217 	}
218 	if (template == NULL)
219 		return (OFMT_ENOTEMPLATE);
220 	for (ofp = template; ofp->of_name != NULL; ofp++)
221 		nfields++;
222 	/*
223 	 * split str into the columns selected, or construct the
224 	 * full set of columns (equivalent to -o all).
225 	 */
226 	if (str != NULL && strcasecmp(str, "all") != 0) {
227 		sp = split_str(str, nfields);
228 	} else {
229 		if (parsable || (str != NULL && strcasecmp(str, "all") == 0))
230 			maxcols = 0;
231 		sp = split_fields(template, nfields, maxcols);
232 	}
233 	if (sp == NULL)
234 		goto nomem;
235 
236 	os = calloc(sizeof (ofmt_state_t) +
237 	    sp->s_nfields * sizeof (ofmt_field_t), 1);
238 	if (os == NULL)
239 		goto nomem;
240 	*ofmt = os;
241 	os->os_fields = (ofmt_field_t *)&os[1];
242 	os->os_flags = flags;
243 	os->os_fs = OFMT_DEFAULT_FS;
244 
245 	of = os->os_fields;
246 	of_index = 0;
247 	/*
248 	 * sp->s_nfields is the number of fields requested in fields_str.
249 	 * nfields is the number of fields in template.
250 	 */
251 	for (i = 0; i < sp->s_nfields; i++) {
252 		for (j = 0; j < nfields; j++) {
253 			if (strcasecmp(sp->s_fields[i],
254 			    template[j].of_name) == 0) {
255 				break;
256 			}
257 		}
258 		if (j == nfields) {
259 			int nbad = os->os_nbad++;
260 
261 			error = OFMT_EBADFIELDS;
262 			if (os->os_badfields == NULL) {
263 				os->os_badfields = malloc(sp->s_nfields *
264 				    sizeof (char *));
265 				if (os->os_badfields == NULL)
266 					goto nomem;
267 			}
268 			os->os_badfields[nbad] = strdup(sp->s_fields[i]);
269 			if (os->os_badfields[nbad] == NULL)
270 				goto nomem;
271 			continue;
272 		}
273 		of[of_index].of_name = strdup(template[j].of_name);
274 		if (of[of_index].of_name == NULL)
275 			goto nomem;
276 		if (multiline) {
277 			int n = strlen(of[of_index].of_name);
278 
279 			os->os_maxnamelen = MAX(n, os->os_maxnamelen);
280 		}
281 		of[of_index].of_width = template[j].of_width;
282 		of[of_index].of_id = template[j].of_id;
283 		of[of_index].of_cb = template[j].of_cb;
284 		of_index++;
285 	}
286 	splitfree(sp);
287 	if (of_index == 0) /* all values in str are bogus */
288 		return (OFMT_ENOFIELDS);
289 	os->os_nfields = of_index; /* actual number of fields printed */
290 	ofmt_update_winsize(*ofmt);
291 	return (error);
292 nomem:
293 	error = OFMT_ENOMEM;
294 	if (os != NULL)
295 		ofmt_close(os);
296 	*ofmt = NULL;
297 	splitfree(sp);
298 	return (error);
299 }
300 
301 void
ofmt_set_fs(ofmt_handle_t ofmt,char fs)302 ofmt_set_fs(ofmt_handle_t ofmt, char fs)
303 {
304 	((ofmt_state_t *)ofmt)->os_fs = fs;
305 }
306 
307 /*
308  * free resources associated with the ofmt_handle_t
309  */
310 void
ofmt_close(ofmt_handle_t ofmt)311 ofmt_close(ofmt_handle_t ofmt)
312 {
313 	ofmt_state_t *os = ofmt;
314 	uint_t i;
315 
316 	if (os == NULL)
317 		return;
318 	for (i = 0; i < os->os_nfields; i++)
319 		free(os->os_fields[i].of_name);
320 	for (i = 0; i < os->os_nbad; i++)
321 		free(os->os_badfields[i]);
322 	free(os->os_badfields);
323 	free(os);
324 }
325 
326 /*
327  * Print the value for the selected field by calling the callback-function
328  * registered for the field.
329  */
330 static void
ofmt_print_field(ofmt_state_t * os,ofmt_field_t * ofp,const char * value,boolean_t escsep)331 ofmt_print_field(ofmt_state_t *os, ofmt_field_t *ofp, const char *value,
332     boolean_t escsep)
333 {
334 	uint_t	width = ofp->of_width;
335 	uint_t	valwidth;
336 	uint_t	compress;
337 	boolean_t parsable = (os->os_flags & OFMT_PARSABLE);
338 	boolean_t multiline = (os->os_flags & OFMT_MULTILINE);
339 	boolean_t rightjust = (os->os_flags & OFMT_RIGHTJUST);
340 	char	c;
341 
342 	/*
343 	 * Parsable fields are separated by os_fs. os_fs and '\' are escaped
344 	 * (prefixed by a) '\'.
345 	 */
346 	if (parsable) {
347 		if (os->os_nfields == 1) {
348 			(void) printf("%s", value);
349 			return;
350 		}
351 		while ((c = *value++) != '\0') {
352 			if (escsep && ((c == os->os_fs || c == '\\')))
353 				(void) putchar('\\');
354 			(void) putchar(c);
355 		}
356 		if (!os->os_lastfield)
357 			(void) putchar(os->os_fs);
358 	} else if (multiline) {
359 		if (value[0] == '\0')
360 			value = OFMT_VAL_UNDEF;
361 		(void) printf("%*.*s: %s", os->os_maxnamelen,
362 		    os->os_maxnamelen, ofp->of_name, value);
363 		if (!os->os_lastfield)
364 			(void) putchar('\n');
365 	} else {
366 		if (os->os_lastfield) {
367 			if (rightjust)
368 				(void) printf("%*s", width, value);
369 			else
370 				(void) printf("%s", value);
371 			os->os_overflow = 0;
372 			return;
373 		}
374 
375 		valwidth = strlen(value);
376 		if (valwidth + os->os_overflow >= width) {
377 			os->os_overflow += valwidth - width + 1;
378 			if (rightjust)
379 				(void) printf("%*s ", width, value);
380 			else
381 				(void) printf("%s ", value);
382 			return;
383 		}
384 
385 		if (os->os_overflow > 0) {
386 			compress = MIN(os->os_overflow, width - valwidth);
387 			os->os_overflow -= compress;
388 			width -= compress;
389 		}
390 		if (rightjust)
391 			(void) printf("%*s ", width, value);
392 		else
393 			(void) printf("%-*s", width, value);
394 	}
395 }
396 
397 /*
398  * Print enough to fit the field width.
399  */
400 static void
ofmt_fit_width(split_t ** spp,uint_t width,char * value,uint_t bufsize)401 ofmt_fit_width(split_t **spp, uint_t width, char *value, uint_t bufsize)
402 {
403 	split_t		*sp = *spp;
404 	char		*ptr = value, *lim = ptr + bufsize;
405 	uint_t		i, nextlen;
406 
407 	if (sp == NULL) {
408 		sp = split_str(value, OFMT_MAX_ROWS);
409 		if (sp == NULL)
410 			return;
411 
412 		*spp = sp;
413 	}
414 	for (i = sp->s_currfield; i < sp->s_nfields; i++) {
415 		ptr += snprintf(ptr, lim - ptr, "%s,", sp->s_fields[i]);
416 		if (i + 1 == sp->s_nfields) {
417 			nextlen = 0;
418 			if (ptr > value)
419 				ptr[-1] = '\0';
420 		} else {
421 			nextlen = strlen(sp->s_fields[i + 1]);
422 		}
423 
424 		if (strlen(value) + nextlen > width || ptr >= lim) {
425 			i++;
426 			break;
427 		}
428 	}
429 	sp->s_currfield = i;
430 }
431 
432 /*
433  * Print one or more rows of output values for the selected columns.
434  */
435 void
ofmt_print(ofmt_handle_t ofmt,void * arg)436 ofmt_print(ofmt_handle_t ofmt, void *arg)
437 {
438 	ofmt_state_t *os = ofmt;
439 	uint_t i;
440 	char value[1024];
441 	ofmt_field_t *of;
442 	boolean_t escsep, more_rows;
443 	ofmt_arg_t ofarg;
444 	split_t **sp = NULL;
445 	boolean_t parsable = (os->os_flags & OFMT_PARSABLE);
446 	boolean_t multiline = (os->os_flags & OFMT_MULTILINE);
447 	boolean_t wrap = (os->os_flags & OFMT_WRAP);
448 
449 	if (wrap) {
450 		sp = calloc(sizeof (split_t *), os->os_nfields);
451 		if (sp == NULL)
452 			return;
453 	}
454 
455 	if ((os->os_nrow++ % os->os_winsize.ws_row) == 0 &&
456 	    !parsable && !multiline) {
457 		if (!(os->os_flags & OFMT_NOHEADER))
458 			ofmt_print_header(os);
459 		os->os_nrow++;
460 	}
461 
462 	if (multiline && os->os_nrow > 1)
463 		(void) putchar('\n');
464 
465 	of = os->os_fields;
466 	escsep = (os->os_nfields > 1);
467 	more_rows = B_FALSE;
468 	for (i = 0; i < os->os_nfields; i++) {
469 		os->os_lastfield = (i + 1 == os->os_nfields);
470 		value[0] = '\0';
471 		ofarg.ofmt_id = of[i].of_id;
472 		ofarg.ofmt_cbarg = arg;
473 
474 		if ((*of[i].of_cb)(&ofarg, value, sizeof (value))) {
475 			if (wrap) {
476 				/*
477 				 * 'value' will be split at comma boundaries
478 				 * and stored into sp[i].
479 				 */
480 				ofmt_fit_width(&sp[i], of[i].of_width, value,
481 				    sizeof (value));
482 				if (sp[i] != NULL &&
483 				    sp[i]->s_currfield < sp[i]->s_nfields)
484 					more_rows = B_TRUE;
485 			}
486 
487 			ofmt_print_field(os, &of[i],
488 			    (*value == '\0' && !parsable) ?
489 			    OFMT_VAL_UNDEF : value, escsep);
490 		} else {
491 			ofmt_print_field(os, &of[i], OFMT_VAL_UNKNOWN, escsep);
492 		}
493 	}
494 	(void) putchar('\n');
495 
496 	while (more_rows) {
497 		more_rows = B_FALSE;
498 		for (i = 0; i < os->os_nfields; i++) {
499 			os->os_lastfield = (i + 1 == os->os_nfields);
500 			value[0] = '\0';
501 
502 			ofmt_fit_width(&sp[i], of[i].of_width,
503 			    value, sizeof (value));
504 			if (sp[i] != NULL &&
505 			    sp[i]->s_currfield < sp[i]->s_nfields)
506 				more_rows = B_TRUE;
507 
508 			ofmt_print_field(os, &of[i], value, escsep);
509 		}
510 		(void) putchar('\n');
511 	}
512 	(void) fflush(stdout);
513 
514 	if (sp != NULL) {
515 		for (i = 0; i < os->os_nfields; i++)
516 			splitfree(sp[i]);
517 		free(sp);
518 	}
519 }
520 
521 /*
522  * Print the field headers
523  */
524 void
ofmt_print_header(ofmt_handle_t ofmt)525 ofmt_print_header(ofmt_handle_t ofmt)
526 {
527 	ofmt_state_t *os = ofmt;
528 	uint_t i;
529 	ofmt_field_t *of = os->os_fields;
530 	boolean_t escsep = (os->os_nfields > 1);
531 
532 	for (i = 0; i < os->os_nfields; i++) {
533 		os->os_lastfield = (i + 1 == os->os_nfields);
534 		ofmt_print_field(os, &of[i], of[i].of_name, escsep);
535 	}
536 	(void) putchar('\n');
537 }
538 
539 /*
540  * Update the current window size.
541  */
542 void
ofmt_update_winsize(ofmt_handle_t ofmt)543 ofmt_update_winsize(ofmt_handle_t ofmt)
544 {
545 	ofmt_state_t *os = ofmt;
546 	struct winsize *winsize = &os->os_winsize;
547 
548 	if (ioctl(1, TIOCGWINSZ, winsize) == -1 ||
549 	    winsize->ws_col == 0 || winsize->ws_row == 0) {
550 		winsize->ws_col = 80;
551 		winsize->ws_row = 24;
552 	}
553 }
554 
555 /*
556  * Return error diagnostics using the information in the ofmt_handle_t
557  */
558 char *
ofmt_strerror(ofmt_handle_t ofmt,ofmt_status_t error,char * buf,uint_t bufsize)559 ofmt_strerror(ofmt_handle_t ofmt, ofmt_status_t error, char *buf,
560     uint_t bufsize)
561 {
562 	ofmt_state_t *os = ofmt;
563 	uint_t i;
564 	const char *s;
565 	char ebuf[OFMT_BUFSIZE];
566 	boolean_t parsable;
567 
568 	/*
569 	 * ebuf is intended for optional error-specific data to be appended
570 	 * after the internationalized error string for an error code.
571 	 */
572 	ebuf[0] = '\0';
573 
574 	switch (error) {
575 	case OFMT_SUCCESS:
576 		s = "success";
577 		break;
578 	case OFMT_EBADFIELDS:
579 		/*
580 		 * Enumerate the singular/plural version of the warning
581 		 * and error to simplify and improve localization.
582 		 */
583 		parsable = (os->os_flags & OFMT_PARSABLE);
584 		if (!parsable) {
585 			if (os->os_nbad > 1)
586 				s = "ignoring unknown output fields:";
587 			else
588 				s = "ignoring unknown output field:";
589 		} else {
590 			if (os->os_nbad > 1)
591 				s = "unknown output fields:";
592 			else
593 				s = "unknown output field:";
594 		}
595 		/* set up the bad fields in ebuf */
596 		for (i = 0; i < os->os_nbad; i++) {
597 			(void) strlcat(ebuf, " `", sizeof (ebuf));
598 			(void) strlcat(ebuf, os->os_badfields[i],
599 			    sizeof (ebuf));
600 			(void) strlcat(ebuf, "'", sizeof (ebuf));
601 		}
602 		break;
603 	case OFMT_ENOFIELDS:
604 		s = "no valid output fields";
605 		break;
606 	case OFMT_EPARSEMULTI:
607 		s = "multiline mode incompatible with parsable mode";
608 		break;
609 	case OFMT_EPARSEALL:
610 		s = "output field `all' invalid in parsable mode";
611 		break;
612 	case OFMT_EPARSENONE:
613 		s = "output fields must be specified in parsable mode";
614 		break;
615 	case OFMT_EPARSEWRAP:
616 		s = "parsable mode is incompatible with wrap mode";
617 		break;
618 	case OFMT_ENOTEMPLATE:
619 		s = "no template provided for fields";
620 		break;
621 	case OFMT_ENOMEM:
622 		s = strerror(ENOMEM);
623 		break;
624 	default:
625 		(void) snprintf(buf, bufsize,
626 		    dgettext(TEXT_DOMAIN, "unknown ofmt error (%d)"),
627 		    error);
628 		return (buf);
629 	}
630 	(void) snprintf(buf, bufsize, dgettext(TEXT_DOMAIN, s));
631 	(void) strlcat(buf, ebuf, bufsize);
632 	return (buf);
633 }
634 
635 void
ofmt_check(ofmt_status_t oferr,boolean_t parsable,ofmt_handle_t ofmt,void (* die)(const char *,...),void (* warn)(const char *,...))636 ofmt_check(ofmt_status_t oferr, boolean_t parsable, ofmt_handle_t ofmt,
637     void (*die)(const char *, ...), void (*warn)(const char *, ...))
638 {
639 	char buf[OFMT_BUFSIZE];
640 
641 	assert(die != NULL);
642 	assert(warn != NULL);
643 
644 	if (oferr == OFMT_SUCCESS)
645 		return;
646 
647 	(void) ofmt_strerror(ofmt, oferr, buf, sizeof (buf));
648 
649 	/*
650 	 * All errors are considered fatal in parsable mode.  OFMT_ENOMEM and
651 	 * OFMT_ENOFIELDS errors are always fatal, regardless of mode. For
652 	 * other errors, we print diagnostics in human-readable mode and
653 	 * processs what we can.
654 	 */
655 	if (parsable || oferr == OFMT_ENOFIELDS || oferr == OFMT_ENOMEM) {
656 		ofmt_close(ofmt);
657 		die(buf);
658 	} else {
659 		warn(buf);
660 	}
661 }
662