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