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