]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
Merge pull request #14901 from w-simon/fix-tests
[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(Table *t, size_t first_column, ...) {
1014 size_t allocated, column;
1015 va_list ap;
1016
1017 assert(t);
1018
1019 allocated = t->n_display_map;
1020 column = first_column;
1021
1022 va_start(ap, first_column);
1023 for (;;) {
1024 assert(column < t->n_columns);
1025
1026 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
1027 va_end(ap);
1028 return -ENOMEM;
1029 }
1030
1031 t->display_map[t->n_display_map++] = column;
1032
1033 column = va_arg(ap, size_t);
1034 if (column == (size_t) -1)
1035 break;
1036
1037 }
1038 va_end(ap);
1039
1040 return 0;
1041 }
1042
1043 int table_set_sort(Table *t, size_t first_column, ...) {
1044 size_t allocated, column;
1045 va_list ap;
1046
1047 assert(t);
1048
1049 allocated = t->n_sort_map;
1050 column = first_column;
1051
1052 va_start(ap, first_column);
1053 for (;;) {
1054 assert(column < t->n_columns);
1055
1056 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
1057 va_end(ap);
1058 return -ENOMEM;
1059 }
1060
1061 t->sort_map[t->n_sort_map++] = column;
1062
1063 column = va_arg(ap, size_t);
1064 if (column == (size_t) -1)
1065 break;
1066 }
1067 va_end(ap);
1068
1069 return 0;
1070 }
1071
1072 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1073 assert(a);
1074 assert(b);
1075
1076 if (a->type == b->type) {
1077
1078 /* We only define ordering for cells of the same data type. If cells with different data types are
1079 * compared we follow the order the cells were originally added in */
1080
1081 switch (a->type) {
1082
1083 case TABLE_STRING:
1084 return strcmp(a->string, b->string);
1085
1086 case TABLE_PATH:
1087 return path_compare(a->string, b->string);
1088
1089 case TABLE_STRV:
1090 return strv_compare(a->strv, b->strv);
1091
1092 case TABLE_BOOLEAN:
1093 if (!a->boolean && b->boolean)
1094 return -1;
1095 if (a->boolean && !b->boolean)
1096 return 1;
1097 return 0;
1098
1099 case TABLE_TIMESTAMP:
1100 case TABLE_TIMESTAMP_UTC:
1101 case TABLE_TIMESTAMP_RELATIVE:
1102 return CMP(a->timestamp, b->timestamp);
1103
1104 case TABLE_TIMESPAN:
1105 case TABLE_TIMESPAN_MSEC:
1106 return CMP(a->timespan, b->timespan);
1107
1108 case TABLE_SIZE:
1109 case TABLE_BPS:
1110 return CMP(a->size, b->size);
1111
1112 case TABLE_INT:
1113 return CMP(a->int_val, b->int_val);
1114
1115 case TABLE_INT8:
1116 return CMP(a->int8, b->int8);
1117
1118 case TABLE_INT16:
1119 return CMP(a->int16, b->int16);
1120
1121 case TABLE_INT32:
1122 return CMP(a->int32, b->int32);
1123
1124 case TABLE_INT64:
1125 return CMP(a->int64, b->int64);
1126
1127 case TABLE_UINT:
1128 return CMP(a->uint_val, b->uint_val);
1129
1130 case TABLE_UINT8:
1131 return CMP(a->uint8, b->uint8);
1132
1133 case TABLE_UINT16:
1134 return CMP(a->uint16, b->uint16);
1135
1136 case TABLE_UINT32:
1137 return CMP(a->uint32, b->uint32);
1138
1139 case TABLE_UINT64:
1140 return CMP(a->uint64, b->uint64);
1141
1142 case TABLE_PERCENT:
1143 return CMP(a->percent, b->percent);
1144
1145 case TABLE_IFINDEX:
1146 return CMP(a->ifindex, b->ifindex);
1147
1148 case TABLE_IN_ADDR:
1149 return CMP(a->address.in.s_addr, b->address.in.s_addr);
1150
1151 case TABLE_IN6_ADDR:
1152 return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
1153
1154 case TABLE_UUID:
1155 case TABLE_ID128:
1156 return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
1157
1158 default:
1159 ;
1160 }
1161 }
1162
1163 /* Generic fallback using the original order in which the cells where added. */
1164 return CMP(index_a, index_b);
1165 }
1166
1167 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1168 size_t i;
1169 int r;
1170
1171 assert(t);
1172 assert(t->sort_map);
1173
1174 /* Make sure the header stays at the beginning */
1175 if (*a < t->n_columns && *b < t->n_columns)
1176 return 0;
1177 if (*a < t->n_columns)
1178 return -1;
1179 if (*b < t->n_columns)
1180 return 1;
1181
1182 /* Order other lines by the sorting map */
1183 for (i = 0; i < t->n_sort_map; i++) {
1184 TableData *d, *dd;
1185
1186 d = t->data[*a + t->sort_map[i]];
1187 dd = t->data[*b + t->sort_map[i]];
1188
1189 r = cell_data_compare(d, *a, dd, *b);
1190 if (r != 0)
1191 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1192 }
1193
1194 /* Order identical lines by the order there were originally added in */
1195 return CMP(*a, *b);
1196 }
1197
1198 static const char *table_data_format(Table *t, TableData *d) {
1199 assert(d);
1200
1201 if (d->formatted)
1202 return d->formatted;
1203
1204 switch (d->type) {
1205 case TABLE_EMPTY:
1206 return strempty(t->empty_string);
1207
1208 case TABLE_STRING:
1209 case TABLE_PATH:
1210 if (d->uppercase) {
1211 char *p, *q;
1212
1213 d->formatted = new(char, strlen(d->string) + 1);
1214 if (!d->formatted)
1215 return NULL;
1216
1217 for (p = d->string, q = d->formatted; *p; p++, q++)
1218 *q = (char) toupper((unsigned char) *p);
1219 *q = 0;
1220
1221 return d->formatted;
1222 }
1223
1224 return d->string;
1225
1226 case TABLE_STRV: {
1227 char *p;
1228
1229 p = strv_join(d->strv, "\n");
1230 if (!p)
1231 return NULL;
1232
1233 d->formatted = p;
1234 break;
1235 }
1236
1237 case TABLE_BOOLEAN:
1238 return yes_no(d->boolean);
1239
1240 case TABLE_TIMESTAMP:
1241 case TABLE_TIMESTAMP_UTC:
1242 case TABLE_TIMESTAMP_RELATIVE: {
1243 _cleanup_free_ char *p;
1244 char *ret;
1245
1246 p = new(char, FORMAT_TIMESTAMP_MAX);
1247 if (!p)
1248 return NULL;
1249
1250 if (d->type == TABLE_TIMESTAMP)
1251 ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1252 else if (d->type == TABLE_TIMESTAMP_UTC)
1253 ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1254 else
1255 ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1256 if (!ret)
1257 return "n/a";
1258
1259 d->formatted = TAKE_PTR(p);
1260 break;
1261 }
1262
1263 case TABLE_TIMESPAN:
1264 case TABLE_TIMESPAN_MSEC: {
1265 _cleanup_free_ char *p;
1266
1267 p = new(char, FORMAT_TIMESPAN_MAX);
1268 if (!p)
1269 return NULL;
1270
1271 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1272 d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1273 return "n/a";
1274
1275 d->formatted = TAKE_PTR(p);
1276 break;
1277 }
1278
1279 case TABLE_SIZE: {
1280 _cleanup_free_ char *p;
1281
1282 p = new(char, FORMAT_BYTES_MAX);
1283 if (!p)
1284 return NULL;
1285
1286 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1287 return "n/a";
1288
1289 d->formatted = TAKE_PTR(p);
1290 break;
1291 }
1292
1293 case TABLE_BPS: {
1294 _cleanup_free_ char *p;
1295 size_t n;
1296
1297 p = new(char, FORMAT_BYTES_MAX+2);
1298 if (!p)
1299 return NULL;
1300
1301 if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
1302 return "n/a";
1303
1304 n = strlen(p);
1305 strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1306
1307 d->formatted = TAKE_PTR(p);
1308 break;
1309 }
1310
1311 case TABLE_INT: {
1312 _cleanup_free_ char *p;
1313
1314 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1315 if (!p)
1316 return NULL;
1317
1318 sprintf(p, "%i", d->int_val);
1319 d->formatted = TAKE_PTR(p);
1320 break;
1321 }
1322
1323 case TABLE_INT8: {
1324 _cleanup_free_ char *p;
1325
1326 p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
1327 if (!p)
1328 return NULL;
1329
1330 sprintf(p, "%" PRIi8, d->int8);
1331 d->formatted = TAKE_PTR(p);
1332 break;
1333 }
1334
1335 case TABLE_INT16: {
1336 _cleanup_free_ char *p;
1337
1338 p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
1339 if (!p)
1340 return NULL;
1341
1342 sprintf(p, "%" PRIi16, d->int16);
1343 d->formatted = TAKE_PTR(p);
1344 break;
1345 }
1346
1347 case TABLE_INT32: {
1348 _cleanup_free_ char *p;
1349
1350 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1351 if (!p)
1352 return NULL;
1353
1354 sprintf(p, "%" PRIi32, d->int32);
1355 d->formatted = TAKE_PTR(p);
1356 break;
1357 }
1358
1359 case TABLE_INT64: {
1360 _cleanup_free_ char *p;
1361
1362 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1363 if (!p)
1364 return NULL;
1365
1366 sprintf(p, "%" PRIi64, d->int64);
1367 d->formatted = TAKE_PTR(p);
1368 break;
1369 }
1370
1371 case TABLE_UINT: {
1372 _cleanup_free_ char *p;
1373
1374 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1375 if (!p)
1376 return NULL;
1377
1378 sprintf(p, "%u", d->uint_val);
1379 d->formatted = TAKE_PTR(p);
1380 break;
1381 }
1382
1383 case TABLE_UINT8: {
1384 _cleanup_free_ char *p;
1385
1386 p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
1387 if (!p)
1388 return NULL;
1389
1390 sprintf(p, "%" PRIu8, d->uint8);
1391 d->formatted = TAKE_PTR(p);
1392 break;
1393 }
1394
1395 case TABLE_UINT16: {
1396 _cleanup_free_ char *p;
1397
1398 p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
1399 if (!p)
1400 return NULL;
1401
1402 sprintf(p, "%" PRIu16, d->uint16);
1403 d->formatted = TAKE_PTR(p);
1404 break;
1405 }
1406
1407 case TABLE_UINT32: {
1408 _cleanup_free_ char *p;
1409
1410 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1411 if (!p)
1412 return NULL;
1413
1414 sprintf(p, "%" PRIu32, d->uint32);
1415 d->formatted = TAKE_PTR(p);
1416 break;
1417 }
1418
1419 case TABLE_UINT64: {
1420 _cleanup_free_ char *p;
1421
1422 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1423 if (!p)
1424 return NULL;
1425
1426 sprintf(p, "%" PRIu64, d->uint64);
1427 d->formatted = TAKE_PTR(p);
1428 break;
1429 }
1430
1431 case TABLE_PERCENT: {
1432 _cleanup_free_ char *p;
1433
1434 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1435 if (!p)
1436 return NULL;
1437
1438 sprintf(p, "%i%%" , d->percent);
1439 d->formatted = TAKE_PTR(p);
1440 break;
1441 }
1442
1443 case TABLE_IFINDEX: {
1444 _cleanup_free_ char *p = NULL;
1445 char name[IF_NAMESIZE + 1];
1446
1447 if (format_ifname(d->ifindex, name)) {
1448 p = strdup(name);
1449 if (!p)
1450 return NULL;
1451 } else {
1452 if (asprintf(&p, "%i" , d->ifindex) < 0)
1453 return NULL;
1454 }
1455
1456 d->formatted = TAKE_PTR(p);
1457 break;
1458 }
1459
1460 case TABLE_IN_ADDR:
1461 case TABLE_IN6_ADDR: {
1462 _cleanup_free_ char *p = NULL;
1463
1464 if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
1465 &d->address, &p) < 0)
1466 return NULL;
1467
1468 d->formatted = TAKE_PTR(p);
1469 break;
1470 }
1471
1472 case TABLE_ID128: {
1473 char *p;
1474
1475 p = new(char, SD_ID128_STRING_MAX);
1476 if (!p)
1477 return NULL;
1478
1479 d->formatted = sd_id128_to_string(d->id128, p);
1480 break;
1481 }
1482
1483 case TABLE_UUID: {
1484 char *p;
1485
1486 p = new(char, ID128_UUID_STRING_MAX);
1487 if (!p)
1488 return NULL;
1489
1490 d->formatted = id128_to_uuid_string(d->id128, p);
1491 break;
1492 }
1493
1494 default:
1495 assert_not_reached("Unexpected type?");
1496 }
1497
1498 return d->formatted;
1499 }
1500
1501 static int console_width_height(
1502 const char *s,
1503 size_t *ret_width,
1504 size_t *ret_height) {
1505
1506 size_t max_width = 0, height = 0;
1507 const char *p;
1508
1509 assert(s);
1510
1511 /* Determine the width and height in console character cells the specified string needs. */
1512
1513 do {
1514 size_t k;
1515
1516 p = strchr(s, '\n');
1517 if (p) {
1518 _cleanup_free_ char *c = NULL;
1519
1520 c = strndup(s, p - s);
1521 if (!c)
1522 return -ENOMEM;
1523
1524 k = utf8_console_width(c);
1525 s = p + 1;
1526 } else {
1527 k = utf8_console_width(s);
1528 s = NULL;
1529 }
1530 if (k == (size_t) -1)
1531 return -EINVAL;
1532 if (k > max_width)
1533 max_width = k;
1534
1535 height++;
1536 } while (!isempty(s));
1537
1538 if (ret_width)
1539 *ret_width = max_width;
1540
1541 if (ret_height)
1542 *ret_height = height;
1543
1544 return 0;
1545 }
1546
1547 static int table_data_requested_width_height(
1548 Table *table,
1549 TableData *d,
1550 size_t *ret_width,
1551 size_t *ret_height) {
1552
1553 _cleanup_free_ char *truncated = NULL;
1554 bool truncation_applied = false;
1555 size_t width, height;
1556 const char *t;
1557 int r;
1558
1559 t = table_data_format(table, d);
1560 if (!t)
1561 return -ENOMEM;
1562
1563 if (table->cell_height_max != (size_t) -1) {
1564 r = string_truncate_lines(t, table->cell_height_max, &truncated);
1565 if (r < 0)
1566 return r;
1567 if (r > 0)
1568 truncation_applied = true;
1569
1570 t = truncated;
1571 }
1572
1573 r = console_width_height(t, &width, &height);
1574 if (r < 0)
1575 return r;
1576
1577 if (d->maximum_width != (size_t) -1 && width > d->maximum_width)
1578 width = d->maximum_width;
1579
1580 if (width < d->minimum_width)
1581 width = d->minimum_width;
1582
1583 if (ret_width)
1584 *ret_width = width;
1585 if (ret_height)
1586 *ret_height = height;
1587
1588 return truncation_applied;
1589 }
1590
1591 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1592 size_t w = 0, space, lspace, old_length, clickable_length;
1593 _cleanup_free_ char *clickable = NULL;
1594 const char *p;
1595 char *ret;
1596 size_t i;
1597 int r;
1598
1599 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1600
1601 assert(str);
1602 assert(percent <= 100);
1603
1604 old_length = strlen(str);
1605
1606 if (url) {
1607 r = terminal_urlify(url, str, &clickable);
1608 if (r < 0)
1609 return NULL;
1610
1611 clickable_length = strlen(clickable);
1612 } else
1613 clickable_length = old_length;
1614
1615 /* Determine current width on screen */
1616 p = str;
1617 while (p < str + old_length) {
1618 char32_t c;
1619
1620 if (utf8_encoded_to_unichar(p, &c) < 0) {
1621 p++, w++; /* count invalid chars as 1 */
1622 continue;
1623 }
1624
1625 p = utf8_next_char(p);
1626 w += unichar_iswide(c) ? 2 : 1;
1627 }
1628
1629 /* Already wider than the target, if so, don't do anything */
1630 if (w >= new_length)
1631 return clickable ? TAKE_PTR(clickable) : strdup(str);
1632
1633 /* How much spaces shall we add? An how much on the left side? */
1634 space = new_length - w;
1635 lspace = space * percent / 100U;
1636
1637 ret = new(char, space + clickable_length + 1);
1638 if (!ret)
1639 return NULL;
1640
1641 for (i = 0; i < lspace; i++)
1642 ret[i] = ' ';
1643 memcpy(ret + lspace, clickable ?: str, clickable_length);
1644 for (i = lspace + clickable_length; i < space + clickable_length; i++)
1645 ret[i] = ' ';
1646
1647 ret[space + clickable_length] = 0;
1648 return ret;
1649 }
1650
1651 static const char* table_data_color(TableData *d) {
1652 assert(d);
1653
1654 if (d->color)
1655 return d->color;
1656
1657 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1658 if (d->type == TABLE_EMPTY)
1659 return ansi_grey();
1660
1661 return NULL;
1662 }
1663
1664 int table_print(Table *t, FILE *f) {
1665 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1666 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1667 *width;
1668 _cleanup_free_ size_t *sorted = NULL;
1669 uint64_t *column_weight, weight_sum;
1670 int r;
1671
1672 assert(t);
1673
1674 if (!f)
1675 f = stdout;
1676
1677 /* Ensure we have no incomplete rows */
1678 assert(t->n_cells % t->n_columns == 0);
1679
1680 n_rows = t->n_cells / t->n_columns;
1681 assert(n_rows > 0); /* at least the header row must be complete */
1682
1683 if (t->sort_map) {
1684 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1685
1686 sorted = new(size_t, n_rows);
1687 if (!sorted)
1688 return -ENOMEM;
1689
1690 for (i = 0; i < n_rows; i++)
1691 sorted[i] = i * t->n_columns;
1692
1693 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1694 }
1695
1696 if (t->display_map)
1697 display_columns = t->n_display_map;
1698 else
1699 display_columns = t->n_columns;
1700
1701 assert(display_columns > 0);
1702
1703 minimum_width = newa(size_t, display_columns);
1704 maximum_width = newa(size_t, display_columns);
1705 requested_width = newa(size_t, display_columns);
1706 width = newa(size_t, display_columns);
1707 column_weight = newa0(uint64_t, display_columns);
1708
1709 for (j = 0; j < display_columns; j++) {
1710 minimum_width[j] = 1;
1711 maximum_width[j] = (size_t) -1;
1712 requested_width[j] = (size_t) -1;
1713 }
1714
1715 /* First pass: determine column sizes */
1716 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1717 TableData **row;
1718
1719 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1720 * hence we don't care for sorted[] during the first pass. */
1721 row = t->data + i * t->n_columns;
1722
1723 for (j = 0; j < display_columns; j++) {
1724 TableData *d;
1725 size_t req_width, req_height;
1726
1727 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1728
1729 r = table_data_requested_width_height(t, d, &req_width, &req_height);
1730 if (r < 0)
1731 return r;
1732 if (r > 0) { /* Truncated because too many lines? */
1733 _cleanup_free_ char *last = NULL;
1734 const char *field;
1735
1736 /* If we are going to show only the first few lines of a cell that has
1737 * multiple make sure that we have enough space horizontally to show an
1738 * ellipsis. Hence, let's figure out the last line, and account for its
1739 * length plus ellipsis. */
1740
1741 field = table_data_format(t, d);
1742 if (!field)
1743 return -ENOMEM;
1744
1745 assert_se(t->cell_height_max > 0);
1746 r = string_extract_line(field, t->cell_height_max-1, &last);
1747 if (r < 0)
1748 return r;
1749
1750 req_width = MAX(req_width,
1751 utf8_console_width(last) +
1752 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
1753 }
1754
1755 /* Determine the biggest width that any cell in this column would like to have */
1756 if (requested_width[j] == (size_t) -1 ||
1757 requested_width[j] < req_width)
1758 requested_width[j] = req_width;
1759
1760 /* Determine the minimum width any cell in this column needs */
1761 if (minimum_width[j] < d->minimum_width)
1762 minimum_width[j] = d->minimum_width;
1763
1764 /* Determine the maximum width any cell in this column needs */
1765 if (d->maximum_width != (size_t) -1 &&
1766 (maximum_width[j] == (size_t) -1 ||
1767 maximum_width[j] > d->maximum_width))
1768 maximum_width[j] = d->maximum_width;
1769
1770 /* Determine the full columns weight */
1771 column_weight[j] += d->weight;
1772 }
1773 }
1774
1775 /* One space between each column */
1776 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1777
1778 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1779 weight_sum = 0;
1780 for (j = 0; j < display_columns; j++) {
1781 weight_sum += column_weight[j];
1782
1783 table_minimum_width += minimum_width[j];
1784
1785 if (maximum_width[j] == (size_t) -1)
1786 table_maximum_width = (size_t) -1;
1787 else
1788 table_maximum_width += maximum_width[j];
1789
1790 table_requested_width += requested_width[j];
1791 }
1792
1793 /* Calculate effective table width */
1794 if (t->width != 0 && t->width != (size_t) -1)
1795 table_effective_width = t->width;
1796 else if (t->width == 0 || pager_have() || !isatty(STDOUT_FILENO))
1797 table_effective_width = table_requested_width;
1798 else
1799 table_effective_width = MIN(table_requested_width, columns());
1800
1801 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1802 table_effective_width = table_maximum_width;
1803
1804 if (table_effective_width < table_minimum_width)
1805 table_effective_width = table_minimum_width;
1806
1807 if (table_effective_width >= table_requested_width) {
1808 size_t extra;
1809
1810 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1811 * each column with what it asked for and the distribute the rest. */
1812
1813 extra = table_effective_width - table_requested_width;
1814
1815 for (j = 0; j < display_columns; j++) {
1816 size_t delta;
1817
1818 if (weight_sum == 0)
1819 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1820 else
1821 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1822
1823 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1824 width[j] = maximum_width[j];
1825
1826 if (width[j] < minimum_width[j])
1827 width[j] = minimum_width[j];
1828
1829 assert(width[j] >= requested_width[j]);
1830 delta = width[j] - requested_width[j];
1831
1832 /* Subtract what we just added from the rest */
1833 if (extra > delta)
1834 extra -= delta;
1835 else
1836 extra = 0;
1837
1838 assert(weight_sum >= column_weight[j]);
1839 weight_sum -= column_weight[j];
1840 }
1841
1842 } else {
1843 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1844 * with the minimum they need, and then distribute anything left. */
1845 bool finalize = false;
1846 size_t extra;
1847
1848 extra = table_effective_width - table_minimum_width;
1849
1850 for (j = 0; j < display_columns; j++)
1851 width[j] = (size_t) -1;
1852
1853 for (;;) {
1854 bool restart = false;
1855
1856 for (j = 0; j < display_columns; j++) {
1857 size_t delta, w;
1858
1859 /* Did this column already get something assigned? If so, let's skip to the next */
1860 if (width[j] != (size_t) -1)
1861 continue;
1862
1863 if (weight_sum == 0)
1864 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1865 else
1866 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1867
1868 if (w >= requested_width[j]) {
1869 /* Never give more than requested. If we hit a column like this, there's more
1870 * space to allocate to other columns which means we need to restart the
1871 * iteration. However, if we hit a column like this, let's assign it the space
1872 * it wanted for good early.*/
1873
1874 w = requested_width[j];
1875 restart = true;
1876
1877 } else if (!finalize)
1878 continue;
1879
1880 width[j] = w;
1881
1882 assert(w >= minimum_width[j]);
1883 delta = w - minimum_width[j];
1884
1885 assert(delta <= extra);
1886 extra -= delta;
1887
1888 assert(weight_sum >= column_weight[j]);
1889 weight_sum -= column_weight[j];
1890
1891 if (restart && !finalize)
1892 break;
1893 }
1894
1895 if (finalize)
1896 break;
1897
1898 if (!restart)
1899 finalize = true;
1900 }
1901 }
1902
1903 /* Second pass: show output */
1904 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1905 size_t n_subline = 0;
1906 bool more_sublines;
1907 TableData **row;
1908
1909 if (sorted)
1910 row = t->data + sorted[i];
1911 else
1912 row = t->data + i * t->n_columns;
1913
1914 do {
1915 more_sublines = false;
1916
1917 for (j = 0; j < display_columns; j++) {
1918 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
1919 bool lines_truncated = false;
1920 const char *field;
1921 TableData *d;
1922 size_t l;
1923
1924 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1925
1926 field = table_data_format(t, d);
1927 if (!field)
1928 return -ENOMEM;
1929
1930 r = string_extract_line(field, n_subline, &extracted);
1931 if (r < 0)
1932 return r;
1933 if (r > 0) {
1934 /* There are more lines to come */
1935 if ((t->cell_height_max == (size_t) -1 || n_subline + 1 < t->cell_height_max))
1936 more_sublines = true; /* There are more lines to come */
1937 else
1938 lines_truncated = true;
1939 }
1940 if (extracted)
1941 field = extracted;
1942
1943 l = utf8_console_width(field);
1944 if (l > width[j]) {
1945 /* Field is wider than allocated space. Let's ellipsize */
1946
1947 buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
1948 lines_truncated ? 100 : d->ellipsize_percent);
1949 if (!buffer)
1950 return -ENOMEM;
1951
1952 field = buffer;
1953 } else {
1954 if (lines_truncated) {
1955 _cleanup_free_ char *padded = NULL;
1956
1957 /* We truncated more lines of this cell, let's add an
1958 * ellipsis. We first append it, but thta might make our
1959 * string grow above what we have space for, hence ellipsize
1960 * right after. This will truncate the ellipsis and add a new
1961 * one. */
1962
1963 padded = strjoin(field, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
1964 if (!padded)
1965 return -ENOMEM;
1966
1967 buffer = ellipsize(padded, width[j], 100);
1968 if (!buffer)
1969 return -ENOMEM;
1970
1971 field = buffer;
1972 l = utf8_console_width(field);
1973 }
1974
1975 if (l < width[j]) {
1976 _cleanup_free_ char *aligned = NULL;
1977 /* Field is shorter than allocated space. Let's align with spaces */
1978
1979 aligned = align_string_mem(field, d->url, width[j], d->align_percent);
1980 if (!aligned)
1981 return -ENOMEM;
1982
1983 free_and_replace(buffer, aligned);
1984 field = buffer;
1985 }
1986 }
1987
1988 if (l >= width[j] && d->url) {
1989 _cleanup_free_ char *clickable = NULL;
1990
1991 r = terminal_urlify(d->url, field, &clickable);
1992 if (r < 0)
1993 return r;
1994
1995 free_and_replace(buffer, clickable);
1996 field = buffer;
1997 }
1998
1999 if (row == t->data) /* underline header line fully, including the column separator */
2000 fputs(ansi_underline(), f);
2001
2002 if (j > 0)
2003 fputc(' ', f); /* column separator */
2004
2005 if (table_data_color(d) && colors_enabled()) {
2006 if (row == t->data) /* first undo header underliner */
2007 fputs(ANSI_NORMAL, f);
2008
2009 fputs(table_data_color(d), f);
2010 }
2011
2012 fputs(field, f);
2013
2014 if (colors_enabled() && (table_data_color(d) || row == t->data))
2015 fputs(ANSI_NORMAL, f);
2016 }
2017
2018 fputc('\n', f);
2019 n_subline ++;
2020 } while (more_sublines);
2021 }
2022
2023 return fflush_and_check(f);
2024 }
2025
2026 int table_format(Table *t, char **ret) {
2027 _cleanup_fclose_ FILE *f = NULL;
2028 char *buf = NULL;
2029 size_t sz = 0;
2030 int r;
2031
2032 f = open_memstream_unlocked(&buf, &sz);
2033 if (!f)
2034 return -ENOMEM;
2035
2036 r = table_print(t, f);
2037 if (r < 0)
2038 return r;
2039
2040 f = safe_fclose(f);
2041
2042 *ret = buf;
2043
2044 return 0;
2045 }
2046
2047 size_t table_get_rows(Table *t) {
2048 if (!t)
2049 return 0;
2050
2051 assert(t->n_columns > 0);
2052 return t->n_cells / t->n_columns;
2053 }
2054
2055 size_t table_get_columns(Table *t) {
2056 if (!t)
2057 return 0;
2058
2059 assert(t->n_columns > 0);
2060 return t->n_columns;
2061 }
2062
2063 int table_set_reverse(Table *t, size_t column, bool b) {
2064 assert(t);
2065 assert(column < t->n_columns);
2066
2067 if (!t->reverse_map) {
2068 if (!b)
2069 return 0;
2070
2071 t->reverse_map = new0(bool, t->n_columns);
2072 if (!t->reverse_map)
2073 return -ENOMEM;
2074 }
2075
2076 t->reverse_map[column] = b;
2077 return 0;
2078 }
2079
2080 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
2081 size_t i;
2082
2083 assert(t);
2084
2085 if (column >= t->n_columns)
2086 return NULL;
2087
2088 i = row * t->n_columns + column;
2089 if (i >= t->n_cells)
2090 return NULL;
2091
2092 return TABLE_INDEX_TO_CELL(i);
2093 }
2094
2095 const void *table_get(Table *t, TableCell *cell) {
2096 TableData *d;
2097
2098 assert(t);
2099
2100 d = table_get_data(t, cell);
2101 if (!d)
2102 return NULL;
2103
2104 return d->data;
2105 }
2106
2107 const void* table_get_at(Table *t, size_t row, size_t column) {
2108 TableCell *cell;
2109
2110 cell = table_get_cell(t, row, column);
2111 if (!cell)
2112 return NULL;
2113
2114 return table_get(t, cell);
2115 }
2116
2117 static int table_data_to_json(TableData *d, JsonVariant **ret) {
2118
2119 switch (d->type) {
2120
2121 case TABLE_EMPTY:
2122 return json_variant_new_null(ret);
2123
2124 case TABLE_STRING:
2125 case TABLE_PATH:
2126 return json_variant_new_string(ret, d->string);
2127
2128 case TABLE_STRV:
2129 return json_variant_new_array_strv(ret, d->strv);
2130
2131 case TABLE_BOOLEAN:
2132 return json_variant_new_boolean(ret, d->boolean);
2133
2134 case TABLE_TIMESTAMP:
2135 case TABLE_TIMESTAMP_UTC:
2136 case TABLE_TIMESTAMP_RELATIVE:
2137 if (d->timestamp == USEC_INFINITY)
2138 return json_variant_new_null(ret);
2139
2140 return json_variant_new_unsigned(ret, d->timestamp);
2141
2142 case TABLE_TIMESPAN:
2143 case TABLE_TIMESPAN_MSEC:
2144 if (d->timespan == USEC_INFINITY)
2145 return json_variant_new_null(ret);
2146
2147 return json_variant_new_unsigned(ret, d->timespan);
2148
2149 case TABLE_SIZE:
2150 case TABLE_BPS:
2151 if (d->size == (size_t) -1)
2152 return json_variant_new_null(ret);
2153
2154 return json_variant_new_unsigned(ret, d->size);
2155
2156 case TABLE_INT:
2157 return json_variant_new_integer(ret, d->int_val);
2158
2159 case TABLE_INT8:
2160 return json_variant_new_integer(ret, d->int8);
2161
2162 case TABLE_INT16:
2163 return json_variant_new_integer(ret, d->int16);
2164
2165 case TABLE_INT32:
2166 return json_variant_new_integer(ret, d->int32);
2167
2168 case TABLE_INT64:
2169 return json_variant_new_integer(ret, d->int64);
2170
2171 case TABLE_UINT:
2172 return json_variant_new_unsigned(ret, d->uint_val);
2173
2174 case TABLE_UINT8:
2175 return json_variant_new_unsigned(ret, d->uint8);
2176
2177 case TABLE_UINT16:
2178 return json_variant_new_unsigned(ret, d->uint16);
2179
2180 case TABLE_UINT32:
2181 return json_variant_new_unsigned(ret, d->uint32);
2182
2183 case TABLE_UINT64:
2184 return json_variant_new_unsigned(ret, d->uint64);
2185
2186 case TABLE_PERCENT:
2187 return json_variant_new_integer(ret, d->percent);
2188
2189 case TABLE_IFINDEX:
2190 return json_variant_new_integer(ret, d->ifindex);
2191
2192 case TABLE_IN_ADDR:
2193 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
2194
2195 case TABLE_IN6_ADDR:
2196 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
2197
2198 case TABLE_ID128: {
2199 char buf[SD_ID128_STRING_MAX];
2200 return json_variant_new_string(ret, sd_id128_to_string(d->id128, buf));
2201 }
2202
2203 case TABLE_UUID: {
2204 char buf[ID128_UUID_STRING_MAX];
2205 return json_variant_new_string(ret, id128_to_uuid_string(d->id128, buf));
2206 }
2207
2208 default:
2209 return -EINVAL;
2210 }
2211 }
2212
2213 int table_to_json(Table *t, JsonVariant **ret) {
2214 JsonVariant **rows = NULL, **elements = NULL;
2215 _cleanup_free_ size_t *sorted = NULL;
2216 size_t n_rows, i, j, display_columns;
2217 int r;
2218
2219 assert(t);
2220
2221 /* Ensure we have no incomplete rows */
2222 assert(t->n_cells % t->n_columns == 0);
2223
2224 n_rows = t->n_cells / t->n_columns;
2225 assert(n_rows > 0); /* at least the header row must be complete */
2226
2227 if (t->sort_map) {
2228 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2229
2230 sorted = new(size_t, n_rows);
2231 if (!sorted) {
2232 r = -ENOMEM;
2233 goto finish;
2234 }
2235
2236 for (i = 0; i < n_rows; i++)
2237 sorted[i] = i * t->n_columns;
2238
2239 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2240 }
2241
2242 if (t->display_map)
2243 display_columns = t->n_display_map;
2244 else
2245 display_columns = t->n_columns;
2246 assert(display_columns > 0);
2247
2248 elements = new0(JsonVariant*, display_columns * 2);
2249 if (!elements) {
2250 r = -ENOMEM;
2251 goto finish;
2252 }
2253
2254 for (j = 0; j < display_columns; j++) {
2255 TableData *d;
2256
2257 assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
2258
2259 r = table_data_to_json(d, elements + j*2);
2260 if (r < 0)
2261 goto finish;
2262 }
2263
2264 rows = new0(JsonVariant*, n_rows-1);
2265 if (!rows) {
2266 r = -ENOMEM;
2267 goto finish;
2268 }
2269
2270 for (i = 1; i < n_rows; i++) {
2271 TableData **row;
2272
2273 if (sorted)
2274 row = t->data + sorted[i];
2275 else
2276 row = t->data + i * t->n_columns;
2277
2278 for (j = 0; j < display_columns; j++) {
2279 TableData *d;
2280 size_t k;
2281
2282 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2283
2284 k = j*2+1;
2285 elements[k] = json_variant_unref(elements[k]);
2286
2287 r = table_data_to_json(d, elements + k);
2288 if (r < 0)
2289 goto finish;
2290 }
2291
2292 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2293 if (r < 0)
2294 goto finish;
2295 }
2296
2297 r = json_variant_new_array(ret, rows, n_rows - 1);
2298
2299 finish:
2300 if (rows) {
2301 json_variant_unref_many(rows, n_rows-1);
2302 free(rows);
2303 }
2304
2305 if (elements) {
2306 json_variant_unref_many(elements, display_columns*2);
2307 free(elements);
2308 }
2309
2310 return r;
2311 }
2312
2313 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2314 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2315 int r;
2316
2317 assert(t);
2318
2319 if (!f)
2320 f = stdout;
2321
2322 r = table_to_json(t, &v);
2323 if (r < 0)
2324 return r;
2325
2326 json_variant_dump(v, flags, f, NULL);
2327
2328 return fflush_and_check(f);
2329 }