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