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