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