1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <ctype.h>
4 #include <net/if.h>
5 #include <unistd.h>
6
7 #include "sd-id128.h"
8
9 #include "alloc-util.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "format-table.h"
13 #include "format-util.h"
14 #include "fs-util.h"
15 #include "glyph-util.h"
16 #include "gunicode.h"
17 #include "id128-util.h"
18 #include "in-addr-util.h"
19 #include "memory-util.h"
20 #include "pager.h"
21 #include "parse-util.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "process-util.h"
25 #include "signal-util.h"
26 #include "sort-util.h"
27 #include "string-util.h"
28 #include "strxcpyx.h"
29 #include "terminal-util.h"
30 #include "time-util.h"
31 #include "user-util.h"
32 #include "utf8.h"
33 #include "util.h"
34
35 #define DEFAULT_WEIGHT 100
36
37 /*
38 A few notes on implementation details:
39
40 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
41 table. It can be easily converted to an index number and back.
42
43 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
44 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
45 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
46 outside only sees Table and TableCell.
47
48 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
49 previous one.
50
51 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
52 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
53 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
54 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
55
56 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
57 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
58 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
59 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
60 instead.
61
62 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
63 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
64 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
65 */
66
67 typedef struct TableData {
68 unsigned n_ref;
69 TableDataType type;
70
71 size_t minimum_width; /* minimum width for the column */
72 size_t maximum_width; /* maximum width for the column */
73 size_t formatted_for_width; /* the width we tried to format for */
74 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
75 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
76 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
77
78 bool uppercase; /* Uppercase string on display */
79
80 const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
81 const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
82 char *url; /* A URL to use for a clickable hyperlink */
83 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
84
85 union {
86 uint8_t data[0]; /* data is generic array */
87 bool boolean;
88 usec_t timestamp;
89 usec_t timespan;
90 uint64_t size;
91 char string[0];
92 char **strv;
93 int int_val;
94 int8_t int8;
95 int16_t int16;
96 int32_t int32;
97 int64_t int64;
98 unsigned uint_val;
99 uint8_t uint8;
100 uint16_t uint16;
101 uint32_t uint32;
102 uint64_t uint64;
103 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
104 int ifindex;
105 union in_addr_union address;
106 sd_id128_t id128;
107 uid_t uid;
108 gid_t gid;
109 pid_t pid;
110 mode_t mode;
111 /* … add more here as we start supporting more cell data types … */
112 };
113 } TableData;
114
TABLE_CELL_TO_INDEX(TableCell * cell)115 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
116 size_t i;
117
118 assert(cell);
119
120 i = PTR_TO_SIZE(cell);
121 assert(i > 0);
122
123 return i-1;
124 }
125
TABLE_INDEX_TO_CELL(size_t index)126 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
127 assert(index != SIZE_MAX);
128 return SIZE_TO_PTR(index + 1);
129 }
130
131 struct Table {
132 size_t n_columns;
133 size_t n_cells;
134
135 bool header; /* Whether to show the header row? */
136 size_t width; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
137 * width or less wide, but not wider. Otherwise the width to format this table in. */
138 size_t cell_height_max; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If SIZE_MAX then no limit is set, the default. == 0 is not allowed.) */
139
140 TableData **data;
141
142 size_t *display_map; /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
143 size_t n_display_map;
144
145 size_t *sort_map; /* The columns to order rows by, in order of preference. */
146 size_t n_sort_map;
147
148 char **json_fields;
149 size_t n_json_fields;
150
151 bool *reverse_map;
152
153 char *empty_string;
154 };
155
table_new_raw(size_t n_columns)156 Table *table_new_raw(size_t n_columns) {
157 _cleanup_(table_unrefp) Table *t = NULL;
158
159 assert(n_columns > 0);
160
161 t = new(Table, 1);
162 if (!t)
163 return NULL;
164
165 *t = (struct Table) {
166 .n_columns = n_columns,
167 .header = true,
168 .width = SIZE_MAX,
169 .cell_height_max = SIZE_MAX,
170 };
171
172 return TAKE_PTR(t);
173 }
174
table_new_internal(const char * first_header,...)175 Table *table_new_internal(const char *first_header, ...) {
176 _cleanup_(table_unrefp) Table *t = NULL;
177 size_t n_columns = 1;
178 va_list ap;
179 int r;
180
181 assert(first_header);
182
183 va_start(ap, first_header);
184 for (;;) {
185 if (!va_arg(ap, const char*))
186 break;
187
188 n_columns++;
189 }
190 va_end(ap);
191
192 t = table_new_raw(n_columns);
193 if (!t)
194 return NULL;
195
196 va_start(ap, first_header);
197 for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
198 TableCell *cell;
199
200 r = table_add_cell(t, &cell, TABLE_STRING, h);
201 if (r < 0) {
202 va_end(ap);
203 return NULL;
204 }
205
206 /* Make the table header uppercase */
207 r = table_set_uppercase(t, cell, true);
208 if (r < 0) {
209 va_end(ap);
210 return NULL;
211 }
212 }
213 va_end(ap);
214
215 assert(t->n_columns == t->n_cells);
216 return TAKE_PTR(t);
217 }
218
table_data_free(TableData * d)219 static TableData *table_data_free(TableData *d) {
220 assert(d);
221
222 free(d->formatted);
223 free(d->url);
224
225 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
226 strv_free(d->strv);
227
228 return mfree(d);
229 }
230
231 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
232 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
233
table_unref(Table * t)234 Table *table_unref(Table *t) {
235 if (!t)
236 return NULL;
237
238 for (size_t i = 0; i < t->n_cells; i++)
239 table_data_unref(t->data[i]);
240
241 free(t->data);
242 free(t->display_map);
243 free(t->sort_map);
244 free(t->reverse_map);
245 free(t->empty_string);
246
247 for (size_t i = 0; i < t->n_json_fields; i++)
248 free(t->json_fields[i]);
249
250 free(t->json_fields);
251
252 return mfree(t);
253 }
254
table_data_size(TableDataType type,const void * data)255 static size_t table_data_size(TableDataType type, const void *data) {
256
257 switch (type) {
258
259 case TABLE_EMPTY:
260 return 0;
261
262 case TABLE_STRING:
263 case TABLE_PATH:
264 return strlen(data) + 1;
265
266 case TABLE_STRV:
267 case TABLE_STRV_WRAPPED:
268 return sizeof(char **);
269
270 case TABLE_BOOLEAN_CHECKMARK:
271 case TABLE_BOOLEAN:
272 return sizeof(bool);
273
274 case TABLE_TIMESTAMP:
275 case TABLE_TIMESTAMP_UTC:
276 case TABLE_TIMESTAMP_RELATIVE:
277 case TABLE_TIMESPAN:
278 case TABLE_TIMESPAN_MSEC:
279 return sizeof(usec_t);
280
281 case TABLE_SIZE:
282 case TABLE_INT64:
283 case TABLE_UINT64:
284 case TABLE_UINT64_HEX:
285 case TABLE_BPS:
286 return sizeof(uint64_t);
287
288 case TABLE_INT32:
289 case TABLE_UINT32:
290 return sizeof(uint32_t);
291
292 case TABLE_INT16:
293 case TABLE_UINT16:
294 return sizeof(uint16_t);
295
296 case TABLE_INT8:
297 case TABLE_UINT8:
298 return sizeof(uint8_t);
299
300 case TABLE_INT:
301 case TABLE_UINT:
302 case TABLE_PERCENT:
303 case TABLE_IFINDEX:
304 case TABLE_SIGNAL:
305 return sizeof(int);
306
307 case TABLE_IN_ADDR:
308 return sizeof(struct in_addr);
309
310 case TABLE_IN6_ADDR:
311 return sizeof(struct in6_addr);
312
313 case TABLE_UUID:
314 case TABLE_ID128:
315 return sizeof(sd_id128_t);
316
317 case TABLE_UID:
318 return sizeof(uid_t);
319 case TABLE_GID:
320 return sizeof(gid_t);
321 case TABLE_PID:
322 return sizeof(pid_t);
323
324 case TABLE_MODE:
325 return sizeof(mode_t);
326
327 default:
328 assert_not_reached();
329 }
330 }
331
table_data_matches(TableData * d,TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)332 static bool table_data_matches(
333 TableData *d,
334 TableDataType type,
335 const void *data,
336 size_t minimum_width,
337 size_t maximum_width,
338 unsigned weight,
339 unsigned align_percent,
340 unsigned ellipsize_percent) {
341
342 size_t k, l;
343 assert(d);
344
345 if (d->type != type)
346 return false;
347
348 if (d->minimum_width != minimum_width)
349 return false;
350
351 if (d->maximum_width != maximum_width)
352 return false;
353
354 if (d->weight != weight)
355 return false;
356
357 if (d->align_percent != align_percent)
358 return false;
359
360 if (d->ellipsize_percent != ellipsize_percent)
361 return false;
362
363 /* If a color/url/uppercase flag is set, refuse to merge */
364 if (d->color || d->rgap_color)
365 return false;
366 if (d->url)
367 return false;
368 if (d->uppercase)
369 return false;
370
371 k = table_data_size(type, data);
372 l = table_data_size(d->type, d->data);
373 if (k != l)
374 return false;
375
376 return memcmp_safe(data, d->data, l) == 0;
377 }
378
table_data_new(TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)379 static TableData *table_data_new(
380 TableDataType type,
381 const void *data,
382 size_t minimum_width,
383 size_t maximum_width,
384 unsigned weight,
385 unsigned align_percent,
386 unsigned ellipsize_percent) {
387
388 _cleanup_free_ TableData *d = NULL;
389 size_t data_size;
390
391 data_size = table_data_size(type, data);
392
393 d = malloc0(offsetof(TableData, data) + data_size);
394 if (!d)
395 return NULL;
396
397 d->n_ref = 1;
398 d->type = type;
399 d->minimum_width = minimum_width;
400 d->maximum_width = maximum_width;
401 d->weight = weight;
402 d->align_percent = align_percent;
403 d->ellipsize_percent = ellipsize_percent;
404
405 if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
406 d->strv = strv_copy(data);
407 if (!d->strv)
408 return NULL;
409 } else
410 memcpy_safe(d->data, data, data_size);
411
412 return TAKE_PTR(d);
413 }
414
table_add_cell_full(Table * t,TableCell ** ret_cell,TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)415 int table_add_cell_full(
416 Table *t,
417 TableCell **ret_cell,
418 TableDataType type,
419 const void *data,
420 size_t minimum_width,
421 size_t maximum_width,
422 unsigned weight,
423 unsigned align_percent,
424 unsigned ellipsize_percent) {
425
426 _cleanup_(table_data_unrefp) TableData *d = NULL;
427 TableData *p;
428
429 assert(t);
430 assert(type >= 0);
431 assert(type < _TABLE_DATA_TYPE_MAX);
432
433 /* Special rule: patch NULL data fields to the empty field */
434 if (!data)
435 type = TABLE_EMPTY;
436
437 /* Determine the cell adjacent to the current one, but one row up */
438 if (t->n_cells >= t->n_columns)
439 assert_se(p = t->data[t->n_cells - t->n_columns]);
440 else
441 p = NULL;
442
443 /* If formatting parameters are left unspecified, copy from the previous row */
444 if (minimum_width == SIZE_MAX)
445 minimum_width = p ? p->minimum_width : 1;
446
447 if (weight == UINT_MAX)
448 weight = p ? p->weight : DEFAULT_WEIGHT;
449
450 if (align_percent == UINT_MAX)
451 align_percent = p ? p->align_percent : 0;
452
453 if (ellipsize_percent == UINT_MAX)
454 ellipsize_percent = p ? p->ellipsize_percent : 100;
455
456 assert(align_percent <= 100);
457 assert(ellipsize_percent <= 100);
458
459 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
460 * formatting. Let's see if we can reuse the cell data and ref it once more. */
461
462 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
463 d = table_data_ref(p);
464 else {
465 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
466 if (!d)
467 return -ENOMEM;
468 }
469
470 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
471 return -ENOMEM;
472
473 if (ret_cell)
474 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
475
476 t->data[t->n_cells++] = TAKE_PTR(d);
477
478 return 0;
479 }
480
table_add_cell_stringf(Table * t,TableCell ** ret_cell,const char * format,...)481 int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
482 _cleanup_free_ char *buffer = NULL;
483 va_list ap;
484 int r;
485
486 va_start(ap, format);
487 r = vasprintf(&buffer, format, ap);
488 va_end(ap);
489 if (r < 0)
490 return -ENOMEM;
491
492 return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
493 }
494
table_fill_empty(Table * t,size_t until_column)495 int table_fill_empty(Table *t, size_t until_column) {
496 int r;
497
498 assert(t);
499
500 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
501 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
502
503 if (until_column >= t->n_columns)
504 return -EINVAL;
505
506 do {
507 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
508 if (r < 0)
509 return r;
510
511 } while ((t->n_cells % t->n_columns) != until_column);
512
513 return 0;
514 }
515
table_dup_cell(Table * t,TableCell * cell)516 int table_dup_cell(Table *t, TableCell *cell) {
517 size_t i;
518
519 assert(t);
520
521 /* Add the data of the specified cell a second time as a new cell to the end. */
522
523 i = TABLE_CELL_TO_INDEX(cell);
524 if (i >= t->n_cells)
525 return -ENXIO;
526
527 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
528 return -ENOMEM;
529
530 t->data[t->n_cells++] = table_data_ref(t->data[i]);
531 return 0;
532 }
533
table_dedup_cell(Table * t,TableCell * cell)534 static int table_dedup_cell(Table *t, TableCell *cell) {
535 _cleanup_free_ char *curl = NULL;
536 TableData *nd, *od;
537 size_t i;
538
539 assert(t);
540
541 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
542 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
543
544 i = TABLE_CELL_TO_INDEX(cell);
545 if (i >= t->n_cells)
546 return -ENXIO;
547
548 assert_se(od = t->data[i]);
549 if (od->n_ref == 1)
550 return 0;
551
552 assert(od->n_ref > 1);
553
554 if (od->url) {
555 curl = strdup(od->url);
556 if (!curl)
557 return -ENOMEM;
558 }
559
560 nd = table_data_new(
561 od->type,
562 od->data,
563 od->minimum_width,
564 od->maximum_width,
565 od->weight,
566 od->align_percent,
567 od->ellipsize_percent);
568 if (!nd)
569 return -ENOMEM;
570
571 nd->color = od->color;
572 nd->rgap_color = od->rgap_color;
573 nd->url = TAKE_PTR(curl);
574 nd->uppercase = od->uppercase;
575
576 table_data_unref(od);
577 t->data[i] = nd;
578
579 assert(nd->n_ref == 1);
580
581 return 1;
582 }
583
table_get_data(Table * t,TableCell * cell)584 static TableData *table_get_data(Table *t, TableCell *cell) {
585 size_t i;
586
587 assert(t);
588 assert(cell);
589
590 /* Get the data object of the specified cell, or NULL if it doesn't exist */
591
592 i = TABLE_CELL_TO_INDEX(cell);
593 if (i >= t->n_cells)
594 return NULL;
595
596 assert(t->data[i]);
597 assert(t->data[i]->n_ref > 0);
598
599 return t->data[i];
600 }
601
table_set_minimum_width(Table * t,TableCell * cell,size_t minimum_width)602 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
603 int r;
604
605 assert(t);
606 assert(cell);
607
608 if (minimum_width == SIZE_MAX)
609 minimum_width = 1;
610
611 r = table_dedup_cell(t, cell);
612 if (r < 0)
613 return r;
614
615 table_get_data(t, cell)->minimum_width = minimum_width;
616 return 0;
617 }
618
table_set_maximum_width(Table * t,TableCell * cell,size_t maximum_width)619 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
620 int r;
621
622 assert(t);
623 assert(cell);
624
625 r = table_dedup_cell(t, cell);
626 if (r < 0)
627 return r;
628
629 table_get_data(t, cell)->maximum_width = maximum_width;
630 return 0;
631 }
632
table_set_weight(Table * t,TableCell * cell,unsigned weight)633 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
634 int r;
635
636 assert(t);
637 assert(cell);
638
639 if (weight == UINT_MAX)
640 weight = DEFAULT_WEIGHT;
641
642 r = table_dedup_cell(t, cell);
643 if (r < 0)
644 return r;
645
646 table_get_data(t, cell)->weight = weight;
647 return 0;
648 }
649
table_set_align_percent(Table * t,TableCell * cell,unsigned percent)650 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
651 int r;
652
653 assert(t);
654 assert(cell);
655
656 if (percent == UINT_MAX)
657 percent = 0;
658
659 assert(percent <= 100);
660
661 r = table_dedup_cell(t, cell);
662 if (r < 0)
663 return r;
664
665 table_get_data(t, cell)->align_percent = percent;
666 return 0;
667 }
668
table_set_ellipsize_percent(Table * t,TableCell * cell,unsigned percent)669 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
670 int r;
671
672 assert(t);
673 assert(cell);
674
675 if (percent == UINT_MAX)
676 percent = 100;
677
678 assert(percent <= 100);
679
680 r = table_dedup_cell(t, cell);
681 if (r < 0)
682 return r;
683
684 table_get_data(t, cell)->ellipsize_percent = percent;
685 return 0;
686 }
687
table_set_color(Table * t,TableCell * cell,const char * color)688 int table_set_color(Table *t, TableCell *cell, const char *color) {
689 int r;
690
691 assert(t);
692 assert(cell);
693
694 r = table_dedup_cell(t, cell);
695 if (r < 0)
696 return r;
697
698 table_get_data(t, cell)->color = empty_to_null(color);
699 return 0;
700 }
701
table_set_rgap_color(Table * t,TableCell * cell,const char * color)702 int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
703 int r;
704
705 assert(t);
706 assert(cell);
707
708 r = table_dedup_cell(t, cell);
709 if (r < 0)
710 return r;
711
712 table_get_data(t, cell)->rgap_color = empty_to_null(color);
713 return 0;
714 }
715
table_set_url(Table * t,TableCell * cell,const char * url)716 int table_set_url(Table *t, TableCell *cell, const char *url) {
717 _cleanup_free_ char *copy = NULL;
718 int r;
719
720 assert(t);
721 assert(cell);
722
723 if (url) {
724 copy = strdup(url);
725 if (!copy)
726 return -ENOMEM;
727 }
728
729 r = table_dedup_cell(t, cell);
730 if (r < 0)
731 return r;
732
733 return free_and_replace(table_get_data(t, cell)->url, copy);
734 }
735
table_set_uppercase(Table * t,TableCell * cell,bool b)736 int table_set_uppercase(Table *t, TableCell *cell, bool b) {
737 TableData *d;
738 int r;
739
740 assert(t);
741 assert(cell);
742
743 r = table_dedup_cell(t, cell);
744 if (r < 0)
745 return r;
746
747 assert_se(d = table_get_data(t, cell));
748
749 if (d->uppercase == b)
750 return 0;
751
752 d->formatted = mfree(d->formatted);
753 d->uppercase = b;
754 return 1;
755 }
756
table_update(Table * t,TableCell * cell,TableDataType type,const void * data)757 int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
758 _cleanup_free_ char *curl = NULL;
759 TableData *nd, *od;
760 size_t i;
761
762 assert(t);
763 assert(cell);
764
765 i = TABLE_CELL_TO_INDEX(cell);
766 if (i >= t->n_cells)
767 return -ENXIO;
768
769 assert_se(od = t->data[i]);
770
771 if (od->url) {
772 curl = strdup(od->url);
773 if (!curl)
774 return -ENOMEM;
775 }
776
777 nd = table_data_new(
778 type,
779 data,
780 od->minimum_width,
781 od->maximum_width,
782 od->weight,
783 od->align_percent,
784 od->ellipsize_percent);
785 if (!nd)
786 return -ENOMEM;
787
788 nd->color = od->color;
789 nd->rgap_color = od->rgap_color;
790 nd->url = TAKE_PTR(curl);
791 nd->uppercase = od->uppercase;
792
793 table_data_unref(od);
794 t->data[i] = nd;
795
796 return 0;
797 }
798
table_add_many_internal(Table * t,TableDataType first_type,...)799 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
800 TableCell *last_cell = NULL;
801 va_list ap;
802 int r;
803
804 assert(t);
805 assert(first_type >= 0);
806 assert(first_type < _TABLE_DATA_TYPE_MAX);
807
808 va_start(ap, first_type);
809
810 for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
811 const void *data;
812 union {
813 uint64_t size;
814 usec_t usec;
815 int int_val;
816 int8_t int8;
817 int16_t int16;
818 int32_t int32;
819 int64_t int64;
820 unsigned uint_val;
821 uint8_t uint8;
822 uint16_t uint16;
823 uint32_t uint32;
824 uint64_t uint64;
825 int percent;
826 int ifindex;
827 bool b;
828 union in_addr_union address;
829 sd_id128_t id128;
830 uid_t uid;
831 gid_t gid;
832 pid_t pid;
833 mode_t mode;
834 } buffer;
835
836 switch (type) {
837
838 case TABLE_EMPTY:
839 data = NULL;
840 break;
841
842 case TABLE_STRING:
843 case TABLE_PATH:
844 data = va_arg(ap, const char *);
845 break;
846
847 case TABLE_STRV:
848 case TABLE_STRV_WRAPPED:
849 data = va_arg(ap, char * const *);
850 break;
851
852 case TABLE_BOOLEAN_CHECKMARK:
853 case TABLE_BOOLEAN:
854 buffer.b = va_arg(ap, int);
855 data = &buffer.b;
856 break;
857
858 case TABLE_TIMESTAMP:
859 case TABLE_TIMESTAMP_UTC:
860 case TABLE_TIMESTAMP_RELATIVE:
861 case TABLE_TIMESPAN:
862 case TABLE_TIMESPAN_MSEC:
863 buffer.usec = va_arg(ap, usec_t);
864 data = &buffer.usec;
865 break;
866
867 case TABLE_SIZE:
868 case TABLE_BPS:
869 buffer.size = va_arg(ap, uint64_t);
870 data = &buffer.size;
871 break;
872
873 case TABLE_INT:
874 case TABLE_SIGNAL:
875 buffer.int_val = va_arg(ap, int);
876 data = &buffer.int_val;
877 break;
878
879 case TABLE_INT8: {
880 int x = va_arg(ap, int);
881 assert(x >= INT8_MIN && x <= INT8_MAX);
882
883 buffer.int8 = x;
884 data = &buffer.int8;
885 break;
886 }
887
888 case TABLE_INT16: {
889 int x = va_arg(ap, int);
890 assert(x >= INT16_MIN && x <= INT16_MAX);
891
892 buffer.int16 = x;
893 data = &buffer.int16;
894 break;
895 }
896
897 case TABLE_INT32:
898 buffer.int32 = va_arg(ap, int32_t);
899 data = &buffer.int32;
900 break;
901
902 case TABLE_INT64:
903 buffer.int64 = va_arg(ap, int64_t);
904 data = &buffer.int64;
905 break;
906
907 case TABLE_UINT:
908 buffer.uint_val = va_arg(ap, unsigned);
909 data = &buffer.uint_val;
910 break;
911
912 case TABLE_UINT8: {
913 unsigned x = va_arg(ap, unsigned);
914 assert(x <= UINT8_MAX);
915
916 buffer.uint8 = x;
917 data = &buffer.uint8;
918 break;
919 }
920
921 case TABLE_UINT16: {
922 unsigned x = va_arg(ap, unsigned);
923 assert(x <= UINT16_MAX);
924
925 buffer.uint16 = x;
926 data = &buffer.uint16;
927 break;
928 }
929
930 case TABLE_UINT32:
931 buffer.uint32 = va_arg(ap, uint32_t);
932 data = &buffer.uint32;
933 break;
934
935 case TABLE_UINT64:
936 case TABLE_UINT64_HEX:
937 buffer.uint64 = va_arg(ap, uint64_t);
938 data = &buffer.uint64;
939 break;
940
941 case TABLE_PERCENT:
942 buffer.percent = va_arg(ap, int);
943 data = &buffer.percent;
944 break;
945
946 case TABLE_IFINDEX:
947 buffer.ifindex = va_arg(ap, int);
948 data = &buffer.ifindex;
949 break;
950
951 case TABLE_IN_ADDR:
952 buffer.address = *va_arg(ap, union in_addr_union *);
953 data = &buffer.address.in;
954 break;
955
956 case TABLE_IN6_ADDR:
957 buffer.address = *va_arg(ap, union in_addr_union *);
958 data = &buffer.address.in6;
959 break;
960
961 case TABLE_UUID:
962 case TABLE_ID128:
963 buffer.id128 = va_arg(ap, sd_id128_t);
964 data = &buffer.id128;
965 break;
966
967 case TABLE_UID:
968 buffer.uid = va_arg(ap, uid_t);
969 data = &buffer.uid;
970 break;
971
972 case TABLE_GID:
973 buffer.gid = va_arg(ap, gid_t);
974 data = &buffer.gid;
975 break;
976
977 case TABLE_PID:
978 buffer.pid = va_arg(ap, pid_t);
979 data = &buffer.pid;
980 break;
981
982 case TABLE_MODE:
983 buffer.mode = va_arg(ap, mode_t);
984 data = &buffer.mode;
985 break;
986
987 case TABLE_SET_MINIMUM_WIDTH: {
988 size_t w = va_arg(ap, size_t);
989
990 r = table_set_minimum_width(t, last_cell, w);
991 goto check;
992 }
993
994 case TABLE_SET_MAXIMUM_WIDTH: {
995 size_t w = va_arg(ap, size_t);
996 r = table_set_maximum_width(t, last_cell, w);
997 goto check;
998 }
999
1000 case TABLE_SET_WEIGHT: {
1001 unsigned w = va_arg(ap, unsigned);
1002 r = table_set_weight(t, last_cell, w);
1003 goto check;
1004 }
1005
1006 case TABLE_SET_ALIGN_PERCENT: {
1007 unsigned p = va_arg(ap, unsigned);
1008 r = table_set_align_percent(t, last_cell, p);
1009 goto check;
1010 }
1011
1012 case TABLE_SET_ELLIPSIZE_PERCENT: {
1013 unsigned p = va_arg(ap, unsigned);
1014 r = table_set_ellipsize_percent(t, last_cell, p);
1015 goto check;
1016 }
1017
1018 case TABLE_SET_COLOR: {
1019 const char *c = va_arg(ap, const char*);
1020 r = table_set_color(t, last_cell, c);
1021 goto check;
1022 }
1023
1024 case TABLE_SET_RGAP_COLOR: {
1025 const char *c = va_arg(ap, const char*);
1026 r = table_set_rgap_color(t, last_cell, c);
1027 goto check;
1028 }
1029
1030 case TABLE_SET_BOTH_COLORS: {
1031 const char *c = va_arg(ap, const char*);
1032
1033 r = table_set_color(t, last_cell, c);
1034 if (r < 0) {
1035 va_end(ap);
1036 return r;
1037 }
1038
1039 r = table_set_rgap_color(t, last_cell, c);
1040 goto check;
1041 }
1042
1043 case TABLE_SET_URL: {
1044 const char *u = va_arg(ap, const char*);
1045 r = table_set_url(t, last_cell, u);
1046 goto check;
1047 }
1048
1049 case TABLE_SET_UPPERCASE: {
1050 int u = va_arg(ap, int);
1051 r = table_set_uppercase(t, last_cell, u);
1052 goto check;
1053 }
1054
1055 case _TABLE_DATA_TYPE_MAX:
1056 /* Used as end marker */
1057 va_end(ap);
1058 return 0;
1059
1060 default:
1061 assert_not_reached();
1062 }
1063
1064 r = table_add_cell(t, &last_cell, type, data);
1065 check:
1066 if (r < 0) {
1067 va_end(ap);
1068 return r;
1069 }
1070 }
1071 }
1072
table_set_header(Table * t,bool b)1073 void table_set_header(Table *t, bool b) {
1074 assert(t);
1075
1076 t->header = b;
1077 }
1078
table_set_width(Table * t,size_t width)1079 void table_set_width(Table *t, size_t width) {
1080 assert(t);
1081
1082 t->width = width;
1083 }
1084
table_set_cell_height_max(Table * t,size_t height)1085 void table_set_cell_height_max(Table *t, size_t height) {
1086 assert(t);
1087 assert(height >= 1 || height == SIZE_MAX);
1088
1089 t->cell_height_max = height;
1090 }
1091
table_set_empty_string(Table * t,const char * empty)1092 int table_set_empty_string(Table *t, const char *empty) {
1093 assert(t);
1094
1095 return free_and_strdup(&t->empty_string, empty);
1096 }
1097
table_set_display_all(Table * t)1098 static int table_set_display_all(Table *t) {
1099 size_t *d;
1100
1101 assert(t);
1102
1103 /* Initialize the display map to the identity */
1104
1105 d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
1106 if (!d)
1107 return -ENOMEM;
1108
1109 for (size_t i = 0; i < t->n_columns; i++)
1110 d[i] = i;
1111
1112 t->display_map = d;
1113 t->n_display_map = t->n_columns;
1114
1115 return 0;
1116 }
1117
table_set_display_internal(Table * t,size_t first_column,...)1118 int table_set_display_internal(Table *t, size_t first_column, ...) {
1119 size_t column;
1120 va_list ap;
1121
1122 assert(t);
1123
1124 column = first_column;
1125
1126 va_start(ap, first_column);
1127 for (;;) {
1128 assert(column < t->n_columns);
1129
1130 if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
1131 va_end(ap);
1132 return -ENOMEM;
1133 }
1134
1135 t->display_map[t->n_display_map++] = column;
1136
1137 column = va_arg(ap, size_t);
1138 if (column == SIZE_MAX)
1139 break;
1140
1141 }
1142 va_end(ap);
1143
1144 return 0;
1145 }
1146
table_set_sort_internal(Table * t,size_t first_column,...)1147 int table_set_sort_internal(Table *t, size_t first_column, ...) {
1148 size_t column;
1149 va_list ap;
1150
1151 assert(t);
1152
1153 column = first_column;
1154
1155 va_start(ap, first_column);
1156 for (;;) {
1157 assert(column < t->n_columns);
1158
1159 if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
1160 va_end(ap);
1161 return -ENOMEM;
1162 }
1163
1164 t->sort_map[t->n_sort_map++] = column;
1165
1166 column = va_arg(ap, size_t);
1167 if (column == SIZE_MAX)
1168 break;
1169 }
1170 va_end(ap);
1171
1172 return 0;
1173 }
1174
table_hide_column_from_display_internal(Table * t,...)1175 int table_hide_column_from_display_internal(Table *t, ...) {
1176 size_t cur = 0;
1177 int r;
1178
1179 assert(t);
1180
1181 /* If the display map is empty, initialize it with all available columns */
1182 if (!t->display_map) {
1183 r = table_set_display_all(t);
1184 if (r < 0)
1185 return r;
1186 }
1187
1188 for (size_t i = 0; i < t->n_display_map; i++) {
1189 bool listed = false;
1190 va_list ap;
1191
1192 va_start(ap, t);
1193 for (;;) {
1194 size_t column;
1195
1196 column = va_arg(ap, size_t);
1197 if (column == SIZE_MAX)
1198 break;
1199 if (column == t->display_map[i]) {
1200 listed = true;
1201 break;
1202 }
1203 }
1204 va_end(ap);
1205
1206 if (listed)
1207 continue;
1208
1209 t->display_map[cur++] = t->display_map[i];
1210 }
1211
1212 t->n_display_map = cur;
1213
1214 return 0;
1215 }
1216
cell_data_compare(TableData * a,size_t index_a,TableData * b,size_t index_b)1217 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1218 assert(a);
1219 assert(b);
1220
1221 if (a->type == b->type) {
1222
1223 /* We only define ordering for cells of the same data type. If cells with different data types are
1224 * compared we follow the order the cells were originally added in */
1225
1226 switch (a->type) {
1227
1228 case TABLE_STRING:
1229 return strcmp(a->string, b->string);
1230
1231 case TABLE_PATH:
1232 return path_compare(a->string, b->string);
1233
1234 case TABLE_STRV:
1235 case TABLE_STRV_WRAPPED:
1236 return strv_compare(a->strv, b->strv);
1237
1238 case TABLE_BOOLEAN:
1239 if (!a->boolean && b->boolean)
1240 return -1;
1241 if (a->boolean && !b->boolean)
1242 return 1;
1243 return 0;
1244
1245 case TABLE_TIMESTAMP:
1246 case TABLE_TIMESTAMP_UTC:
1247 case TABLE_TIMESTAMP_RELATIVE:
1248 return CMP(a->timestamp, b->timestamp);
1249
1250 case TABLE_TIMESPAN:
1251 case TABLE_TIMESPAN_MSEC:
1252 return CMP(a->timespan, b->timespan);
1253
1254 case TABLE_SIZE:
1255 case TABLE_BPS:
1256 return CMP(a->size, b->size);
1257
1258 case TABLE_INT:
1259 case TABLE_SIGNAL:
1260 return CMP(a->int_val, b->int_val);
1261
1262 case TABLE_INT8:
1263 return CMP(a->int8, b->int8);
1264
1265 case TABLE_INT16:
1266 return CMP(a->int16, b->int16);
1267
1268 case TABLE_INT32:
1269 return CMP(a->int32, b->int32);
1270
1271 case TABLE_INT64:
1272 return CMP(a->int64, b->int64);
1273
1274 case TABLE_UINT:
1275 return CMP(a->uint_val, b->uint_val);
1276
1277 case TABLE_UINT8:
1278 return CMP(a->uint8, b->uint8);
1279
1280 case TABLE_UINT16:
1281 return CMP(a->uint16, b->uint16);
1282
1283 case TABLE_UINT32:
1284 return CMP(a->uint32, b->uint32);
1285
1286 case TABLE_UINT64:
1287 case TABLE_UINT64_HEX:
1288 return CMP(a->uint64, b->uint64);
1289
1290 case TABLE_PERCENT:
1291 return CMP(a->percent, b->percent);
1292
1293 case TABLE_IFINDEX:
1294 return CMP(a->ifindex, b->ifindex);
1295
1296 case TABLE_IN_ADDR:
1297 return CMP(a->address.in.s_addr, b->address.in.s_addr);
1298
1299 case TABLE_IN6_ADDR:
1300 return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
1301
1302 case TABLE_UUID:
1303 case TABLE_ID128:
1304 return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
1305
1306 case TABLE_UID:
1307 return CMP(a->uid, b->uid);
1308
1309 case TABLE_GID:
1310 return CMP(a->gid, b->gid);
1311
1312 case TABLE_PID:
1313 return CMP(a->pid, b->pid);
1314
1315 case TABLE_MODE:
1316 return CMP(a->mode, b->mode);
1317
1318 default:
1319 ;
1320 }
1321 }
1322
1323 /* Generic fallback using the original order in which the cells where added. */
1324 return CMP(index_a, index_b);
1325 }
1326
table_data_compare(const size_t * a,const size_t * b,Table * t)1327 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1328 int r;
1329
1330 assert(t);
1331 assert(t->sort_map);
1332
1333 /* Make sure the header stays at the beginning */
1334 if (*a < t->n_columns && *b < t->n_columns)
1335 return 0;
1336 if (*a < t->n_columns)
1337 return -1;
1338 if (*b < t->n_columns)
1339 return 1;
1340
1341 /* Order other lines by the sorting map */
1342 for (size_t i = 0; i < t->n_sort_map; i++) {
1343 TableData *d, *dd;
1344
1345 d = t->data[*a + t->sort_map[i]];
1346 dd = t->data[*b + t->sort_map[i]];
1347
1348 r = cell_data_compare(d, *a, dd, *b);
1349 if (r != 0)
1350 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1351 }
1352
1353 /* Order identical lines by the order there were originally added in */
1354 return CMP(*a, *b);
1355 }
1356
format_strv_width(char ** strv,size_t column_width)1357 static char* format_strv_width(char **strv, size_t column_width) {
1358 _cleanup_free_ char *buf = NULL; /* buf must be freed after f */
1359 _cleanup_fclose_ FILE *f = NULL;
1360 size_t sz = 0;
1361
1362 f = open_memstream_unlocked(&buf, &sz);
1363 if (!f)
1364 return NULL;
1365
1366 size_t position = 0;
1367 STRV_FOREACH(p, strv) {
1368 size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1369 * If that happens, we'll just print one item per line. */
1370
1371 if (position == 0) {
1372 fputs(*p, f);
1373 position = our_len;
1374 } else if (size_add(size_add(position, 1), our_len) <= column_width) {
1375 fprintf(f, " %s", *p);
1376 position = size_add(size_add(position, 1), our_len);
1377 } else {
1378 fprintf(f, "\n%s", *p);
1379 position = our_len;
1380 }
1381 }
1382
1383 if (fflush_and_check(f) < 0)
1384 return NULL;
1385
1386 f = safe_fclose(f);
1387 return TAKE_PTR(buf);
1388 }
1389
table_data_format(Table * t,TableData * d,bool avoid_uppercasing,size_t column_width,bool * have_soft)1390 static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
1391 assert(d);
1392
1393 if (d->formatted &&
1394 /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1395 (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
1396 return d->formatted;
1397
1398 switch (d->type) {
1399 case TABLE_EMPTY:
1400 return strempty(t->empty_string);
1401
1402 case TABLE_STRING:
1403 case TABLE_PATH:
1404 if (d->uppercase && !avoid_uppercasing) {
1405 d->formatted = new(char, strlen(d->string) + 1);
1406 if (!d->formatted)
1407 return NULL;
1408
1409 char *q = d->formatted;
1410 for (char *p = d->string; *p; p++, q++)
1411 *q = (char) toupper((unsigned char) *p);
1412 *q = 0;
1413
1414 return d->formatted;
1415 }
1416
1417 return d->string;
1418
1419 case TABLE_STRV:
1420 if (strv_isempty(d->strv))
1421 return strempty(t->empty_string);
1422
1423 d->formatted = strv_join(d->strv, "\n");
1424 if (!d->formatted)
1425 return NULL;
1426 break;
1427
1428 case TABLE_STRV_WRAPPED: {
1429 if (strv_isempty(d->strv))
1430 return strempty(t->empty_string);
1431
1432 char *buf = format_strv_width(d->strv, column_width);
1433 if (!buf)
1434 return NULL;
1435
1436 free_and_replace(d->formatted, buf);
1437 d->formatted_for_width = column_width;
1438 if (have_soft)
1439 *have_soft = true;
1440
1441 break;
1442 }
1443
1444 case TABLE_BOOLEAN:
1445 return yes_no(d->boolean);
1446
1447 case TABLE_BOOLEAN_CHECKMARK:
1448 return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
1449
1450 case TABLE_TIMESTAMP:
1451 case TABLE_TIMESTAMP_UTC:
1452 case TABLE_TIMESTAMP_RELATIVE: {
1453 _cleanup_free_ char *p = NULL;
1454 char *ret;
1455
1456 p = new(char, d->type == TABLE_TIMESTAMP_RELATIVE ? FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1457 if (!p)
1458 return NULL;
1459
1460 if (d->type == TABLE_TIMESTAMP)
1461 ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1462 else if (d->type == TABLE_TIMESTAMP_UTC)
1463 ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
1464 else
1465 ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
1466 if (!ret)
1467 return "n/a";
1468
1469 d->formatted = TAKE_PTR(p);
1470 break;
1471 }
1472
1473 case TABLE_TIMESPAN:
1474 case TABLE_TIMESPAN_MSEC: {
1475 _cleanup_free_ char *p = NULL;
1476
1477 p = new(char, FORMAT_TIMESPAN_MAX);
1478 if (!p)
1479 return NULL;
1480
1481 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1482 d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1483 return "n/a";
1484
1485 d->formatted = TAKE_PTR(p);
1486 break;
1487 }
1488
1489 case TABLE_SIZE: {
1490 _cleanup_free_ char *p = NULL;
1491
1492 p = new(char, FORMAT_BYTES_MAX);
1493 if (!p)
1494 return NULL;
1495
1496 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1497 return "n/a";
1498
1499 d->formatted = TAKE_PTR(p);
1500 break;
1501 }
1502
1503 case TABLE_BPS: {
1504 _cleanup_free_ char *p = NULL;
1505 size_t n;
1506
1507 p = new(char, FORMAT_BYTES_MAX+2);
1508 if (!p)
1509 return NULL;
1510
1511 if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
1512 return "n/a";
1513
1514 n = strlen(p);
1515 strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1516
1517 d->formatted = TAKE_PTR(p);
1518 break;
1519 }
1520
1521 case TABLE_INT: {
1522 _cleanup_free_ char *p = NULL;
1523
1524 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1525 if (!p)
1526 return NULL;
1527
1528 sprintf(p, "%i", d->int_val);
1529 d->formatted = TAKE_PTR(p);
1530 break;
1531 }
1532
1533 case TABLE_INT8: {
1534 _cleanup_free_ char *p = NULL;
1535
1536 p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
1537 if (!p)
1538 return NULL;
1539
1540 sprintf(p, "%" PRIi8, d->int8);
1541 d->formatted = TAKE_PTR(p);
1542 break;
1543 }
1544
1545 case TABLE_INT16: {
1546 _cleanup_free_ char *p = NULL;
1547
1548 p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
1549 if (!p)
1550 return NULL;
1551
1552 sprintf(p, "%" PRIi16, d->int16);
1553 d->formatted = TAKE_PTR(p);
1554 break;
1555 }
1556
1557 case TABLE_INT32: {
1558 _cleanup_free_ char *p = NULL;
1559
1560 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1561 if (!p)
1562 return NULL;
1563
1564 sprintf(p, "%" PRIi32, d->int32);
1565 d->formatted = TAKE_PTR(p);
1566 break;
1567 }
1568
1569 case TABLE_INT64: {
1570 _cleanup_free_ char *p = NULL;
1571
1572 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1573 if (!p)
1574 return NULL;
1575
1576 sprintf(p, "%" PRIi64, d->int64);
1577 d->formatted = TAKE_PTR(p);
1578 break;
1579 }
1580
1581 case TABLE_UINT: {
1582 _cleanup_free_ char *p = NULL;
1583
1584 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1585 if (!p)
1586 return NULL;
1587
1588 sprintf(p, "%u", d->uint_val);
1589 d->formatted = TAKE_PTR(p);
1590 break;
1591 }
1592
1593 case TABLE_UINT8: {
1594 _cleanup_free_ char *p = NULL;
1595
1596 p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
1597 if (!p)
1598 return NULL;
1599
1600 sprintf(p, "%" PRIu8, d->uint8);
1601 d->formatted = TAKE_PTR(p);
1602 break;
1603 }
1604
1605 case TABLE_UINT16: {
1606 _cleanup_free_ char *p = NULL;
1607
1608 p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
1609 if (!p)
1610 return NULL;
1611
1612 sprintf(p, "%" PRIu16, d->uint16);
1613 d->formatted = TAKE_PTR(p);
1614 break;
1615 }
1616
1617 case TABLE_UINT32: {
1618 _cleanup_free_ char *p = NULL;
1619
1620 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1621 if (!p)
1622 return NULL;
1623
1624 sprintf(p, "%" PRIu32, d->uint32);
1625 d->formatted = TAKE_PTR(p);
1626 break;
1627 }
1628
1629 case TABLE_UINT64: {
1630 _cleanup_free_ char *p = NULL;
1631
1632 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1633 if (!p)
1634 return NULL;
1635
1636 sprintf(p, "%" PRIu64, d->uint64);
1637 d->formatted = TAKE_PTR(p);
1638 break;
1639 }
1640
1641 case TABLE_UINT64_HEX: {
1642 _cleanup_free_ char *p = NULL;
1643
1644 p = new(char, 16 + 1);
1645 if (!p)
1646 return NULL;
1647
1648 sprintf(p, "%" PRIx64, d->uint64);
1649 d->formatted = TAKE_PTR(p);
1650 break;
1651 }
1652
1653 case TABLE_PERCENT: {
1654 _cleanup_free_ char *p = NULL;
1655
1656 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1657 if (!p)
1658 return NULL;
1659
1660 sprintf(p, "%i%%" , d->percent);
1661 d->formatted = TAKE_PTR(p);
1662 break;
1663 }
1664
1665 case TABLE_IFINDEX: {
1666 _cleanup_free_ char *p = NULL;
1667
1668 if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
1669 return NULL;
1670
1671 d->formatted = TAKE_PTR(p);
1672 break;
1673 }
1674
1675 case TABLE_IN_ADDR:
1676 case TABLE_IN6_ADDR: {
1677 _cleanup_free_ char *p = NULL;
1678
1679 if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
1680 &d->address, &p) < 0)
1681 return NULL;
1682
1683 d->formatted = TAKE_PTR(p);
1684 break;
1685 }
1686
1687 case TABLE_ID128: {
1688 char *p;
1689
1690 p = new(char, SD_ID128_STRING_MAX);
1691 if (!p)
1692 return NULL;
1693
1694 d->formatted = sd_id128_to_string(d->id128, p);
1695 break;
1696 }
1697
1698 case TABLE_UUID: {
1699 char *p;
1700
1701 p = new(char, SD_ID128_UUID_STRING_MAX);
1702 if (!p)
1703 return NULL;
1704
1705 d->formatted = sd_id128_to_uuid_string(d->id128, p);
1706 break;
1707 }
1708
1709 case TABLE_UID: {
1710 char *p;
1711
1712 if (!uid_is_valid(d->uid))
1713 return "n/a";
1714
1715 p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1716 if (!p)
1717 return NULL;
1718 sprintf(p, UID_FMT, d->uid);
1719
1720 d->formatted = p;
1721 break;
1722 }
1723
1724 case TABLE_GID: {
1725 char *p;
1726
1727 if (!gid_is_valid(d->gid))
1728 return "n/a";
1729
1730 p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1731 if (!p)
1732 return NULL;
1733 sprintf(p, GID_FMT, d->gid);
1734
1735 d->formatted = p;
1736 break;
1737 }
1738
1739 case TABLE_PID: {
1740 char *p;
1741
1742 if (!pid_is_valid(d->pid))
1743 return "n/a";
1744
1745 p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
1746 if (!p)
1747 return NULL;
1748 sprintf(p, PID_FMT, d->pid);
1749
1750 d->formatted = p;
1751 break;
1752 }
1753
1754 case TABLE_SIGNAL: {
1755 const char *suffix;
1756 char *p;
1757
1758 suffix = signal_to_string(d->int_val);
1759 if (!suffix)
1760 return "n/a";
1761
1762 p = strjoin("SIG", suffix);
1763 if (!p)
1764 return NULL;
1765
1766 d->formatted = p;
1767 break;
1768 }
1769
1770 case TABLE_MODE: {
1771 char *p;
1772
1773 if (d->mode == MODE_INVALID)
1774 return "n/a";
1775
1776 p = new(char, 4 + 1);
1777 if (!p)
1778 return NULL;
1779
1780 sprintf(p, "%04o", d->mode & 07777);
1781 d->formatted = p;
1782 break;
1783 }
1784
1785 default:
1786 assert_not_reached();
1787 }
1788
1789 return d->formatted;
1790 }
1791
console_width_height(const char * s,size_t * ret_width,size_t * ret_height)1792 static int console_width_height(
1793 const char *s,
1794 size_t *ret_width,
1795 size_t *ret_height) {
1796
1797 size_t max_width = 0, height = 0;
1798 const char *p;
1799
1800 assert(s);
1801
1802 /* Determine the width and height in console character cells the specified string needs. */
1803
1804 do {
1805 size_t k;
1806
1807 p = strchr(s, '\n');
1808 if (p) {
1809 _cleanup_free_ char *c = NULL;
1810
1811 c = strndup(s, p - s);
1812 if (!c)
1813 return -ENOMEM;
1814
1815 k = utf8_console_width(c);
1816 s = p + 1;
1817 } else {
1818 k = utf8_console_width(s);
1819 s = NULL;
1820 }
1821 if (k == SIZE_MAX)
1822 return -EINVAL;
1823 if (k > max_width)
1824 max_width = k;
1825
1826 height++;
1827 } while (!isempty(s));
1828
1829 if (ret_width)
1830 *ret_width = max_width;
1831
1832 if (ret_height)
1833 *ret_height = height;
1834
1835 return 0;
1836 }
1837
table_data_requested_width_height(Table * table,TableData * d,size_t available_width,size_t * ret_width,size_t * ret_height,bool * have_soft)1838 static int table_data_requested_width_height(
1839 Table *table,
1840 TableData *d,
1841 size_t available_width,
1842 size_t *ret_width,
1843 size_t *ret_height,
1844 bool *have_soft) {
1845
1846 _cleanup_free_ char *truncated = NULL;
1847 bool truncation_applied = false;
1848 size_t width, height;
1849 const char *t;
1850 int r;
1851 bool soft = false;
1852
1853 t = table_data_format(table, d, false, available_width, &soft);
1854 if (!t)
1855 return -ENOMEM;
1856
1857 if (table->cell_height_max != SIZE_MAX) {
1858 r = string_truncate_lines(t, table->cell_height_max, &truncated);
1859 if (r < 0)
1860 return r;
1861 if (r > 0)
1862 truncation_applied = true;
1863
1864 t = truncated;
1865 }
1866
1867 r = console_width_height(t, &width, &height);
1868 if (r < 0)
1869 return r;
1870
1871 if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
1872 width = d->maximum_width;
1873
1874 if (width < d->minimum_width)
1875 width = d->minimum_width;
1876
1877 if (ret_width)
1878 *ret_width = width;
1879 if (ret_height)
1880 *ret_height = height;
1881 if (have_soft && soft)
1882 *have_soft = true;
1883
1884 return truncation_applied;
1885 }
1886
align_string_mem(const char * str,const char * url,size_t new_length,unsigned percent)1887 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1888 size_t w = 0, space, lspace, old_length, clickable_length;
1889 _cleanup_free_ char *clickable = NULL;
1890 const char *p;
1891 char *ret;
1892 int r;
1893
1894 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1895
1896 assert(str);
1897 assert(percent <= 100);
1898
1899 old_length = strlen(str);
1900
1901 if (url) {
1902 r = terminal_urlify(url, str, &clickable);
1903 if (r < 0)
1904 return NULL;
1905
1906 clickable_length = strlen(clickable);
1907 } else
1908 clickable_length = old_length;
1909
1910 /* Determine current width on screen */
1911 p = str;
1912 while (p < str + old_length) {
1913 char32_t c;
1914
1915 if (utf8_encoded_to_unichar(p, &c) < 0) {
1916 p++, w++; /* count invalid chars as 1 */
1917 continue;
1918 }
1919
1920 p = utf8_next_char(p);
1921 w += unichar_iswide(c) ? 2 : 1;
1922 }
1923
1924 /* Already wider than the target, if so, don't do anything */
1925 if (w >= new_length)
1926 return clickable ? TAKE_PTR(clickable) : strdup(str);
1927
1928 /* How much spaces shall we add? An how much on the left side? */
1929 space = new_length - w;
1930 lspace = space * percent / 100U;
1931
1932 ret = new(char, space + clickable_length + 1);
1933 if (!ret)
1934 return NULL;
1935
1936 for (size_t i = 0; i < lspace; i++)
1937 ret[i] = ' ';
1938 memcpy(ret + lspace, clickable ?: str, clickable_length);
1939 for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
1940 ret[i] = ' ';
1941
1942 ret[space + clickable_length] = 0;
1943 return ret;
1944 }
1945
table_data_isempty(TableData * d)1946 static bool table_data_isempty(TableData *d) {
1947 assert(d);
1948
1949 if (d->type == TABLE_EMPTY)
1950 return true;
1951
1952 /* Let's also consider an empty strv as truly empty. */
1953 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
1954 return strv_isempty(d->strv);
1955
1956 /* Note that an empty string we do not consider empty here! */
1957 return false;
1958 }
1959
table_data_color(TableData * d)1960 static const char* table_data_color(TableData *d) {
1961 assert(d);
1962
1963 if (d->color)
1964 return d->color;
1965
1966 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1967 if (table_data_isempty(d))
1968 return ansi_grey();
1969
1970 return NULL;
1971 }
1972
table_data_rgap_color(TableData * d)1973 static const char* table_data_rgap_color(TableData *d) {
1974 assert(d);
1975
1976 if (d->rgap_color)
1977 return d->rgap_color;
1978
1979 return NULL;
1980 }
1981
table_print(Table * t,FILE * f)1982 int table_print(Table *t, FILE *f) {
1983 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1984 table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1985 *width = NULL;
1986 _cleanup_free_ size_t *sorted = NULL;
1987 uint64_t *column_weight, weight_sum;
1988 int r;
1989
1990 assert(t);
1991
1992 if (!f)
1993 f = stdout;
1994
1995 /* Ensure we have no incomplete rows */
1996 assert(t->n_cells % t->n_columns == 0);
1997
1998 n_rows = t->n_cells / t->n_columns;
1999 assert(n_rows > 0); /* at least the header row must be complete */
2000
2001 if (t->sort_map) {
2002 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2003
2004 sorted = new(size_t, n_rows);
2005 if (!sorted)
2006 return -ENOMEM;
2007
2008 for (size_t i = 0; i < n_rows; i++)
2009 sorted[i] = i * t->n_columns;
2010
2011 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2012 }
2013
2014 if (t->display_map)
2015 display_columns = t->n_display_map;
2016 else
2017 display_columns = t->n_columns;
2018
2019 assert(display_columns > 0);
2020
2021 minimum_width = newa(size_t, display_columns);
2022 maximum_width = newa(size_t, display_columns);
2023 requested_width = newa(size_t, display_columns);
2024 column_weight = newa0(uint64_t, display_columns);
2025
2026 for (size_t j = 0; j < display_columns; j++) {
2027 minimum_width[j] = 1;
2028 maximum_width[j] = SIZE_MAX;
2029 }
2030
2031 for (unsigned pass = 0; pass < 2; pass++) {
2032 /* First pass: determine column sizes */
2033
2034 for (size_t j = 0; j < display_columns; j++)
2035 requested_width[j] = SIZE_MAX;
2036
2037 bool any_soft = false;
2038
2039 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2040 TableData **row;
2041
2042 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2043 * hence we don't care for sorted[] during the first pass. */
2044 row = t->data + i * t->n_columns;
2045
2046 for (size_t j = 0; j < display_columns; j++) {
2047 TableData *d;
2048 size_t req_width, req_height;
2049
2050 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2051
2052 r = table_data_requested_width_height(t, d,
2053 width ? width[j] : SIZE_MAX,
2054 &req_width, &req_height, &any_soft);
2055 if (r < 0)
2056 return r;
2057 if (r > 0) { /* Truncated because too many lines? */
2058 _cleanup_free_ char *last = NULL;
2059 const char *field;
2060
2061 /* If we are going to show only the first few lines of a cell that has
2062 * multiple make sure that we have enough space horizontally to show an
2063 * ellipsis. Hence, let's figure out the last line, and account for its
2064 * length plus ellipsis. */
2065
2066 field = table_data_format(t, d, false,
2067 width ? width[j] : SIZE_MAX,
2068 &any_soft);
2069 if (!field)
2070 return -ENOMEM;
2071
2072 assert_se(t->cell_height_max > 0);
2073 r = string_extract_line(field, t->cell_height_max-1, &last);
2074 if (r < 0)
2075 return r;
2076
2077 req_width = MAX(req_width,
2078 utf8_console_width(last) +
2079 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
2080 }
2081
2082 /* Determine the biggest width that any cell in this column would like to have */
2083 if (requested_width[j] == SIZE_MAX ||
2084 requested_width[j] < req_width)
2085 requested_width[j] = req_width;
2086
2087 /* Determine the minimum width any cell in this column needs */
2088 if (minimum_width[j] < d->minimum_width)
2089 minimum_width[j] = d->minimum_width;
2090
2091 /* Determine the maximum width any cell in this column needs */
2092 if (d->maximum_width != SIZE_MAX &&
2093 (maximum_width[j] == SIZE_MAX ||
2094 maximum_width[j] > d->maximum_width))
2095 maximum_width[j] = d->maximum_width;
2096
2097 /* Determine the full columns weight */
2098 column_weight[j] += d->weight;
2099 }
2100 }
2101
2102 /* One space between each column */
2103 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
2104
2105 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2106 weight_sum = 0;
2107 for (size_t j = 0; j < display_columns; j++) {
2108 weight_sum += column_weight[j];
2109
2110 table_minimum_width += minimum_width[j];
2111
2112 if (maximum_width[j] == SIZE_MAX)
2113 table_maximum_width = SIZE_MAX;
2114 else
2115 table_maximum_width += maximum_width[j];
2116
2117 table_requested_width += requested_width[j];
2118 }
2119
2120 /* Calculate effective table width */
2121 if (t->width != 0 && t->width != SIZE_MAX)
2122 table_effective_width = t->width;
2123 else if (t->width == 0 ||
2124 ((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
2125 table_effective_width = table_requested_width;
2126 else
2127 table_effective_width = MIN(table_requested_width, columns());
2128
2129 if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
2130 table_effective_width = table_maximum_width;
2131
2132 if (table_effective_width < table_minimum_width)
2133 table_effective_width = table_minimum_width;
2134
2135 if (!width)
2136 width = newa(size_t, display_columns);
2137
2138 if (table_effective_width >= table_requested_width) {
2139 size_t extra;
2140
2141 /* We have extra room, let's distribute it among columns according to their weights. We first provide
2142 * each column with what it asked for and the distribute the rest. */
2143
2144 extra = table_effective_width - table_requested_width;
2145
2146 for (size_t j = 0; j < display_columns; j++) {
2147 size_t delta;
2148
2149 if (weight_sum == 0)
2150 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
2151 else
2152 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
2153
2154 if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
2155 width[j] = maximum_width[j];
2156
2157 if (width[j] < minimum_width[j])
2158 width[j] = minimum_width[j];
2159
2160 delta = LESS_BY(width[j], requested_width[j]);
2161
2162 /* Subtract what we just added from the rest */
2163 if (extra > delta)
2164 extra -= delta;
2165 else
2166 extra = 0;
2167
2168 assert(weight_sum >= column_weight[j]);
2169 weight_sum -= column_weight[j];
2170 }
2171
2172 break; /* Every column should be happy, no need to repeat calculations. */
2173 } else {
2174 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2175 * with the minimum they need, and then distribute anything left. */
2176 bool finalize = false;
2177 size_t extra;
2178
2179 extra = table_effective_width - table_minimum_width;
2180
2181 for (size_t j = 0; j < display_columns; j++)
2182 width[j] = SIZE_MAX;
2183
2184 for (;;) {
2185 bool restart = false;
2186
2187 for (size_t j = 0; j < display_columns; j++) {
2188 size_t delta, w;
2189
2190 /* Did this column already get something assigned? If so, let's skip to the next */
2191 if (width[j] != SIZE_MAX)
2192 continue;
2193
2194 if (weight_sum == 0)
2195 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
2196 else
2197 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
2198
2199 if (w >= requested_width[j]) {
2200 /* Never give more than requested. If we hit a column like this, there's more
2201 * space to allocate to other columns which means we need to restart the
2202 * iteration. However, if we hit a column like this, let's assign it the space
2203 * it wanted for good early. */
2204
2205 w = requested_width[j];
2206 restart = true;
2207
2208 } else if (!finalize)
2209 continue;
2210
2211 width[j] = w;
2212
2213 assert(w >= minimum_width[j]);
2214 delta = w - minimum_width[j];
2215
2216 assert(delta <= extra);
2217 extra -= delta;
2218
2219 assert(weight_sum >= column_weight[j]);
2220 weight_sum -= column_weight[j];
2221
2222 if (restart && !finalize)
2223 break;
2224 }
2225
2226 if (finalize)
2227 break;
2228
2229 if (!restart)
2230 finalize = true;
2231 }
2232
2233 if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
2234 * let's try to reformat them with the new widths. Otherwise, let's
2235 * move on. */
2236 break;
2237 }
2238 }
2239
2240 /* Second pass: show output */
2241 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2242 size_t n_subline = 0;
2243 bool more_sublines;
2244 TableData **row;
2245
2246 if (sorted)
2247 row = t->data + sorted[i];
2248 else
2249 row = t->data + i * t->n_columns;
2250
2251 do {
2252 const char *gap_color = NULL;
2253 more_sublines = false;
2254
2255 for (size_t j = 0; j < display_columns; j++) {
2256 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
2257 bool lines_truncated = false;
2258 const char *field, *color = NULL;
2259 TableData *d;
2260 size_t l;
2261
2262 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2263
2264 field = table_data_format(t, d, false, width[j], NULL);
2265 if (!field)
2266 return -ENOMEM;
2267
2268 r = string_extract_line(field, n_subline, &extracted);
2269 if (r < 0)
2270 return r;
2271 if (r > 0) {
2272 /* There are more lines to come */
2273 if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
2274 more_sublines = true; /* There are more lines to come */
2275 else
2276 lines_truncated = true;
2277 }
2278 if (extracted)
2279 field = extracted;
2280
2281 l = utf8_console_width(field);
2282 if (l > width[j]) {
2283 /* Field is wider than allocated space. Let's ellipsize */
2284
2285 buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2286 lines_truncated ? 100 : d->ellipsize_percent);
2287 if (!buffer)
2288 return -ENOMEM;
2289
2290 field = buffer;
2291 } else {
2292 if (lines_truncated) {
2293 _cleanup_free_ char *padded = NULL;
2294
2295 /* We truncated more lines of this cell, let's add an
2296 * ellipsis. We first append it, but that might make our
2297 * string grow above what we have space for, hence ellipsize
2298 * right after. This will truncate the ellipsis and add a new
2299 * one. */
2300
2301 padded = strjoin(field, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
2302 if (!padded)
2303 return -ENOMEM;
2304
2305 buffer = ellipsize(padded, width[j], 100);
2306 if (!buffer)
2307 return -ENOMEM;
2308
2309 field = buffer;
2310 l = utf8_console_width(field);
2311 }
2312
2313 if (l < width[j]) {
2314 _cleanup_free_ char *aligned = NULL;
2315 /* Field is shorter than allocated space. Let's align with spaces */
2316
2317 aligned = align_string_mem(field, d->url, width[j], d->align_percent);
2318 if (!aligned)
2319 return -ENOMEM;
2320
2321 /* Drop trailing white spaces of last column when no cosmetics is set. */
2322 if (j == display_columns - 1 &&
2323 (!colors_enabled() || (!table_data_color(d) && row != t->data)) &&
2324 (!urlify_enabled() || !d->url))
2325 delete_trailing_chars(aligned, NULL);
2326
2327 free_and_replace(buffer, aligned);
2328 field = buffer;
2329 }
2330 }
2331
2332 if (l >= width[j] && d->url) {
2333 _cleanup_free_ char *clickable = NULL;
2334
2335 r = terminal_urlify(d->url, field, &clickable);
2336 if (r < 0)
2337 return r;
2338
2339 free_and_replace(buffer, clickable);
2340 field = buffer;
2341 }
2342
2343 if (colors_enabled()) {
2344 if (gap_color)
2345 fputs(gap_color, f);
2346 else if (row == t->data) /* underline header line fully, including the column separator */
2347 fputs(ansi_underline(), f);
2348 }
2349
2350 if (j > 0)
2351 fputc(' ', f); /* column separator left of cell */
2352
2353 if (colors_enabled()) {
2354 color = table_data_color(d);
2355
2356 /* Undo gap color */
2357 if (gap_color || (color && row == t->data))
2358 fputs(ANSI_NORMAL, f);
2359
2360 if (color)
2361 fputs(color, f);
2362 else if (gap_color && row == t->data) /* underline header line cell */
2363 fputs(ansi_underline(), f);
2364 }
2365
2366 fputs(field, f);
2367
2368 if (colors_enabled() && (color || row == t->data))
2369 fputs(ANSI_NORMAL, f);
2370
2371 gap_color = table_data_rgap_color(d);
2372 }
2373
2374 fputc('\n', f);
2375 n_subline ++;
2376 } while (more_sublines);
2377 }
2378
2379 return fflush_and_check(f);
2380 }
2381
table_format(Table * t,char ** ret)2382 int table_format(Table *t, char **ret) {
2383 _cleanup_free_ char *buf = NULL;
2384 _cleanup_fclose_ FILE *f = NULL;
2385 size_t sz = 0;
2386 int r;
2387
2388 f = open_memstream_unlocked(&buf, &sz);
2389 if (!f)
2390 return -ENOMEM;
2391
2392 r = table_print(t, f);
2393 if (r < 0)
2394 return r;
2395
2396 f = safe_fclose(f);
2397
2398 *ret = TAKE_PTR(buf);
2399
2400 return 0;
2401 }
2402
table_get_rows(Table * t)2403 size_t table_get_rows(Table *t) {
2404 if (!t)
2405 return 0;
2406
2407 assert(t->n_columns > 0);
2408 return t->n_cells / t->n_columns;
2409 }
2410
table_get_columns(Table * t)2411 size_t table_get_columns(Table *t) {
2412 if (!t)
2413 return 0;
2414
2415 assert(t->n_columns > 0);
2416 return t->n_columns;
2417 }
2418
table_set_reverse(Table * t,size_t column,bool b)2419 int table_set_reverse(Table *t, size_t column, bool b) {
2420 assert(t);
2421 assert(column < t->n_columns);
2422
2423 if (!t->reverse_map) {
2424 if (!b)
2425 return 0;
2426
2427 t->reverse_map = new0(bool, t->n_columns);
2428 if (!t->reverse_map)
2429 return -ENOMEM;
2430 }
2431
2432 t->reverse_map[column] = b;
2433 return 0;
2434 }
2435
table_get_cell(Table * t,size_t row,size_t column)2436 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
2437 size_t i;
2438
2439 assert(t);
2440
2441 if (column >= t->n_columns)
2442 return NULL;
2443
2444 i = row * t->n_columns + column;
2445 if (i >= t->n_cells)
2446 return NULL;
2447
2448 return TABLE_INDEX_TO_CELL(i);
2449 }
2450
table_get(Table * t,TableCell * cell)2451 const void *table_get(Table *t, TableCell *cell) {
2452 TableData *d;
2453
2454 assert(t);
2455
2456 d = table_get_data(t, cell);
2457 if (!d)
2458 return NULL;
2459
2460 return d->data;
2461 }
2462
table_get_at(Table * t,size_t row,size_t column)2463 const void* table_get_at(Table *t, size_t row, size_t column) {
2464 TableCell *cell;
2465
2466 cell = table_get_cell(t, row, column);
2467 if (!cell)
2468 return NULL;
2469
2470 return table_get(t, cell);
2471 }
2472
table_data_to_json(TableData * d,JsonVariant ** ret)2473 static int table_data_to_json(TableData *d, JsonVariant **ret) {
2474
2475 switch (d->type) {
2476
2477 case TABLE_EMPTY:
2478 return json_variant_new_null(ret);
2479
2480 case TABLE_STRING:
2481 case TABLE_PATH:
2482 return json_variant_new_string(ret, d->string);
2483
2484 case TABLE_STRV:
2485 case TABLE_STRV_WRAPPED:
2486 return json_variant_new_array_strv(ret, d->strv);
2487
2488 case TABLE_BOOLEAN_CHECKMARK:
2489 case TABLE_BOOLEAN:
2490 return json_variant_new_boolean(ret, d->boolean);
2491
2492 case TABLE_TIMESTAMP:
2493 case TABLE_TIMESTAMP_UTC:
2494 case TABLE_TIMESTAMP_RELATIVE:
2495 if (d->timestamp == USEC_INFINITY)
2496 return json_variant_new_null(ret);
2497
2498 return json_variant_new_unsigned(ret, d->timestamp);
2499
2500 case TABLE_TIMESPAN:
2501 case TABLE_TIMESPAN_MSEC:
2502 if (d->timespan == USEC_INFINITY)
2503 return json_variant_new_null(ret);
2504
2505 return json_variant_new_unsigned(ret, d->timespan);
2506
2507 case TABLE_SIZE:
2508 case TABLE_BPS:
2509 if (d->size == UINT64_MAX)
2510 return json_variant_new_null(ret);
2511
2512 return json_variant_new_unsigned(ret, d->size);
2513
2514 case TABLE_INT:
2515 return json_variant_new_integer(ret, d->int_val);
2516
2517 case TABLE_INT8:
2518 return json_variant_new_integer(ret, d->int8);
2519
2520 case TABLE_INT16:
2521 return json_variant_new_integer(ret, d->int16);
2522
2523 case TABLE_INT32:
2524 return json_variant_new_integer(ret, d->int32);
2525
2526 case TABLE_INT64:
2527 return json_variant_new_integer(ret, d->int64);
2528
2529 case TABLE_UINT:
2530 return json_variant_new_unsigned(ret, d->uint_val);
2531
2532 case TABLE_UINT8:
2533 return json_variant_new_unsigned(ret, d->uint8);
2534
2535 case TABLE_UINT16:
2536 return json_variant_new_unsigned(ret, d->uint16);
2537
2538 case TABLE_UINT32:
2539 return json_variant_new_unsigned(ret, d->uint32);
2540
2541 case TABLE_UINT64:
2542 case TABLE_UINT64_HEX:
2543 return json_variant_new_unsigned(ret, d->uint64);
2544
2545 case TABLE_PERCENT:
2546 return json_variant_new_integer(ret, d->percent);
2547
2548 case TABLE_IFINDEX:
2549 if (d->ifindex <= 0)
2550 return json_variant_new_null(ret);
2551
2552 return json_variant_new_integer(ret, d->ifindex);
2553
2554 case TABLE_IN_ADDR:
2555 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
2556
2557 case TABLE_IN6_ADDR:
2558 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
2559
2560 case TABLE_ID128:
2561 return json_variant_new_string(ret, SD_ID128_TO_STRING(d->id128));
2562
2563 case TABLE_UUID:
2564 return json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(d->id128));
2565
2566 case TABLE_UID:
2567 if (!uid_is_valid(d->uid))
2568 return json_variant_new_null(ret);
2569
2570 return json_variant_new_integer(ret, d->uid);
2571
2572 case TABLE_GID:
2573 if (!gid_is_valid(d->gid))
2574 return json_variant_new_null(ret);
2575
2576 return json_variant_new_integer(ret, d->gid);
2577
2578 case TABLE_PID:
2579 if (!pid_is_valid(d->pid))
2580 return json_variant_new_null(ret);
2581
2582 return json_variant_new_integer(ret, d->pid);
2583
2584 case TABLE_SIGNAL:
2585 if (!SIGNAL_VALID(d->int_val))
2586 return json_variant_new_null(ret);
2587
2588 return json_variant_new_integer(ret, d->int_val);
2589
2590 case TABLE_MODE:
2591 if (d->mode == MODE_INVALID)
2592 return json_variant_new_null(ret);
2593
2594 return json_variant_new_unsigned(ret, d->mode);
2595
2596 default:
2597 return -EINVAL;
2598 }
2599 }
2600
string_to_json_field_name(const char * f)2601 static char* string_to_json_field_name(const char *f) {
2602 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2603 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2604 * underscores and leave everything as is. */
2605
2606 char *c = strdup(f);
2607 if (!c)
2608 return NULL;
2609
2610 for (char *x = c; *x; x++)
2611 if (isspace(*x))
2612 *x = '_';
2613
2614 return c;
2615 }
2616
table_get_json_field_name(Table * t,size_t column)2617 static const char *table_get_json_field_name(Table *t, size_t column) {
2618 assert(t);
2619
2620 return column < t->n_json_fields ? t->json_fields[column] : NULL;
2621 }
2622
table_to_json(Table * t,JsonVariant ** ret)2623 int table_to_json(Table *t, JsonVariant **ret) {
2624 JsonVariant **rows = NULL, **elements = NULL;
2625 _cleanup_free_ size_t *sorted = NULL;
2626 size_t n_rows, display_columns;
2627 int r;
2628
2629 assert(t);
2630
2631 /* Ensure we have no incomplete rows */
2632 assert(t->n_cells % t->n_columns == 0);
2633
2634 n_rows = t->n_cells / t->n_columns;
2635 assert(n_rows > 0); /* at least the header row must be complete */
2636
2637 if (t->sort_map) {
2638 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2639
2640 sorted = new(size_t, n_rows);
2641 if (!sorted) {
2642 r = -ENOMEM;
2643 goto finish;
2644 }
2645
2646 for (size_t i = 0; i < n_rows; i++)
2647 sorted[i] = i * t->n_columns;
2648
2649 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2650 }
2651
2652 if (t->display_map)
2653 display_columns = t->n_display_map;
2654 else
2655 display_columns = t->n_columns;
2656 assert(display_columns > 0);
2657
2658 elements = new0(JsonVariant*, display_columns * 2);
2659 if (!elements) {
2660 r = -ENOMEM;
2661 goto finish;
2662 }
2663
2664 for (size_t j = 0; j < display_columns; j++) {
2665 _cleanup_free_ char *mangled = NULL;
2666 const char *n;
2667 size_t c;
2668
2669 c = t->display_map ? t->display_map[j] : j;
2670
2671 /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
2672 n = table_get_json_field_name(t, c);
2673 if (!n) {
2674 const char *formatted;
2675 TableData *d;
2676
2677 assert_se(d = t->data[c]);
2678
2679 /* Field names must be strings, hence format whatever we got here as a string first */
2680 formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
2681 if (!formatted) {
2682 r = -ENOMEM;
2683 goto finish;
2684 }
2685
2686 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2687 mangled = string_to_json_field_name(formatted);
2688 if (!mangled) {
2689 r = -ENOMEM;
2690 goto finish;
2691 }
2692 n = mangled;
2693 }
2694
2695 r = json_variant_new_string(elements + j*2, n);
2696 if (r < 0)
2697 goto finish;
2698 }
2699
2700 rows = new0(JsonVariant*, n_rows-1);
2701 if (!rows) {
2702 r = -ENOMEM;
2703 goto finish;
2704 }
2705
2706 for (size_t i = 1; i < n_rows; i++) {
2707 TableData **row;
2708
2709 if (sorted)
2710 row = t->data + sorted[i];
2711 else
2712 row = t->data + i * t->n_columns;
2713
2714 for (size_t j = 0; j < display_columns; j++) {
2715 TableData *d;
2716 size_t k;
2717
2718 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2719
2720 k = j*2+1;
2721 elements[k] = json_variant_unref(elements[k]);
2722
2723 r = table_data_to_json(d, elements + k);
2724 if (r < 0)
2725 goto finish;
2726 }
2727
2728 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2729 if (r < 0)
2730 goto finish;
2731 }
2732
2733 r = json_variant_new_array(ret, rows, n_rows - 1);
2734
2735 finish:
2736 if (rows) {
2737 json_variant_unref_many(rows, n_rows-1);
2738 free(rows);
2739 }
2740
2741 if (elements) {
2742 json_variant_unref_many(elements, display_columns*2);
2743 free(elements);
2744 }
2745
2746 return r;
2747 }
2748
table_print_json(Table * t,FILE * f,JsonFormatFlags flags)2749 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2750 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2751 int r;
2752
2753 assert(t);
2754
2755 if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */
2756 return table_print(t, f);
2757
2758 if (!f)
2759 f = stdout;
2760
2761 r = table_to_json(t, &v);
2762 if (r < 0)
2763 return r;
2764
2765 json_variant_dump(v, flags, f, NULL);
2766
2767 return fflush_and_check(f);
2768 }
2769
table_print_with_pager(Table * t,JsonFormatFlags json_format_flags,PagerFlags pager_flags,bool show_header)2770 int table_print_with_pager(
2771 Table *t,
2772 JsonFormatFlags json_format_flags,
2773 PagerFlags pager_flags,
2774 bool show_header) {
2775
2776 bool saved_header;
2777 int r;
2778
2779 assert(t);
2780
2781 /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
2782 * the table header and logs about any error. */
2783
2784 if (json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
2785 pager_open(pager_flags);
2786
2787 saved_header = t->header;
2788 t->header = show_header;
2789 r = table_print_json(t, stdout, json_format_flags);
2790 t->header = saved_header;
2791 if (r < 0)
2792 return table_log_print_error(r);
2793
2794 return 0;
2795 }
2796
table_set_json_field_name(Table * t,size_t column,const char * name)2797 int table_set_json_field_name(Table *t, size_t column, const char *name) {
2798 int r;
2799
2800 assert(t);
2801
2802 if (name) {
2803 size_t m;
2804
2805 m = MAX(column + 1, t->n_json_fields);
2806 if (!GREEDY_REALLOC0(t->json_fields, m))
2807 return -ENOMEM;
2808
2809 r = free_and_strdup(t->json_fields + column, name);
2810 if (r < 0)
2811 return r;
2812
2813 t->n_json_fields = m;
2814 return r;
2815 } else {
2816 if (column >= t->n_json_fields)
2817 return 0;
2818
2819 t->json_fields[column] = mfree(t->json_fields[column]);
2820 return 1;
2821 }
2822 }
2823