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