]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
aede59bf3463d38e23495bb0342d5f8cbf073ca4
[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
6 #include "alloc-util.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "format-table.h"
10 #include "format-util.h"
11 #include "gunicode.h"
12 #include "memory-util.h"
13 #include "pager.h"
14 #include "parse-util.h"
15 #include "pretty-print.h"
16 #include "sort-util.h"
17 #include "string-util.h"
18 #include "strxcpyx.h"
19 #include "terminal-util.h"
20 #include "time-util.h"
21 #include "utf8.h"
22 #include "util.h"
23
24 #define DEFAULT_WEIGHT 100
25
26 /*
27 A few notes on implementation details:
28
29 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
30 table. It can be easily converted to an index number and back.
31
32 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
33 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
34 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
35 outside only sees Table and TableCell.
36
37 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
38 previous one.
39
40 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
41 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
42 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
43 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
44
45 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
46 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
47 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
48 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
49 instead.
50
51 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
52 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
53 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
54 */
55
56 typedef struct TableData {
57 unsigned n_ref;
58 TableDataType type;
59
60 size_t minimum_width; /* minimum width for the column */
61 size_t maximum_width; /* maximum width for the column */
62 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
63 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
64 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
65
66 bool uppercase; /* Uppercase string on display */
67
68 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 */
69 char *url; /* A URL to use for a clickable hyperlink */
70 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
71
72 union {
73 uint8_t data[0]; /* data is generic array */
74 bool boolean;
75 usec_t timestamp;
76 usec_t timespan;
77 uint64_t size;
78 char string[0];
79 int int_val;
80 int32_t int32;
81 int64_t int64;
82 unsigned uint_val;
83 uint32_t uint32;
84 uint64_t uint64;
85 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
86 int ifindex;
87 /* … add more here as we start supporting more cell data types … */
88 };
89 } TableData;
90
91 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
92 size_t i;
93
94 assert(cell);
95
96 i = PTR_TO_SIZE(cell);
97 assert(i > 0);
98
99 return i-1;
100 }
101
102 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
103 assert(index != (size_t) -1);
104 return SIZE_TO_PTR(index + 1);
105 }
106
107 struct Table {
108 size_t n_columns;
109 size_t n_cells;
110
111 bool header; /* Whether to show the header row? */
112 size_t width; /* If != (size_t) -1 the width to format this table in */
113
114 TableData **data;
115 size_t n_allocated;
116
117 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 */
118 size_t n_display_map;
119
120 size_t *sort_map; /* The columns to order rows by, in order of preference. */
121 size_t n_sort_map;
122
123 bool *reverse_map;
124 };
125
126 Table *table_new_raw(size_t n_columns) {
127 _cleanup_(table_unrefp) Table *t = NULL;
128
129 assert(n_columns > 0);
130
131 t = new(Table, 1);
132 if (!t)
133 return NULL;
134
135 *t = (struct Table) {
136 .n_columns = n_columns,
137 .header = true,
138 .width = (size_t) -1,
139 };
140
141 return TAKE_PTR(t);
142 }
143
144 Table *table_new_internal(const char *first_header, ...) {
145 _cleanup_(table_unrefp) Table *t = NULL;
146 size_t n_columns = 1;
147 const char *h;
148 va_list ap;
149 int r;
150
151 assert(first_header);
152
153 va_start(ap, first_header);
154 for (;;) {
155 h = va_arg(ap, const char*);
156 if (!h)
157 break;
158
159 n_columns++;
160 }
161 va_end(ap);
162
163 t = table_new_raw(n_columns);
164 if (!t)
165 return NULL;
166
167 va_start(ap, first_header);
168 for (h = first_header; h; h = va_arg(ap, const char*)) {
169 TableCell *cell;
170
171 r = table_add_cell(t, &cell, TABLE_STRING, h);
172 if (r < 0) {
173 va_end(ap);
174 return NULL;
175 }
176
177 /* Make the table header uppercase */
178 r = table_set_uppercase(t, cell, true);
179 if (r < 0) {
180 va_end(ap);
181 return NULL;
182 }
183 }
184 va_end(ap);
185
186 assert(t->n_columns == t->n_cells);
187 return TAKE_PTR(t);
188 }
189
190 static TableData *table_data_free(TableData *d) {
191 assert(d);
192
193 free(d->formatted);
194 free(d->url);
195
196 return mfree(d);
197 }
198
199 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
200 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
201
202 Table *table_unref(Table *t) {
203 size_t i;
204
205 if (!t)
206 return NULL;
207
208 for (i = 0; i < t->n_cells; i++)
209 table_data_unref(t->data[i]);
210
211 free(t->data);
212 free(t->display_map);
213 free(t->sort_map);
214 free(t->reverse_map);
215
216 return mfree(t);
217 }
218
219 static size_t table_data_size(TableDataType type, const void *data) {
220
221 switch (type) {
222
223 case TABLE_EMPTY:
224 return 0;
225
226 case TABLE_STRING:
227 return strlen(data) + 1;
228
229 case TABLE_BOOLEAN:
230 return sizeof(bool);
231
232 case TABLE_TIMESTAMP:
233 case TABLE_TIMESTAMP_UTC:
234 case TABLE_TIMESTAMP_RELATIVE:
235 case TABLE_TIMESPAN:
236 case TABLE_TIMESPAN_MSEC:
237 return sizeof(usec_t);
238
239 case TABLE_SIZE:
240 case TABLE_INT64:
241 case TABLE_UINT64:
242 case TABLE_BPS:
243 return sizeof(uint64_t);
244
245 case TABLE_INT32:
246 case TABLE_UINT32:
247 return sizeof(uint32_t);
248
249 case TABLE_INT:
250 case TABLE_UINT:
251 case TABLE_PERCENT:
252 case TABLE_IFINDEX:
253 return sizeof(int);
254
255 default:
256 assert_not_reached("Uh? Unexpected cell type");
257 }
258 }
259
260 static bool table_data_matches(
261 TableData *d,
262 TableDataType type,
263 const void *data,
264 size_t minimum_width,
265 size_t maximum_width,
266 unsigned weight,
267 unsigned align_percent,
268 unsigned ellipsize_percent) {
269
270 size_t k, l;
271 assert(d);
272
273 if (d->type != type)
274 return false;
275
276 if (d->minimum_width != minimum_width)
277 return false;
278
279 if (d->maximum_width != maximum_width)
280 return false;
281
282 if (d->weight != weight)
283 return false;
284
285 if (d->align_percent != align_percent)
286 return false;
287
288 if (d->ellipsize_percent != ellipsize_percent)
289 return false;
290
291 /* If a color/url/uppercase flag is set, refuse to merge */
292 if (d->color)
293 return false;
294 if (d->url)
295 return false;
296 if (d->uppercase)
297 return false;
298
299 k = table_data_size(type, data);
300 l = table_data_size(d->type, d->data);
301
302 if (k != l)
303 return false;
304
305 return memcmp_safe(data, d->data, l) == 0;
306 }
307
308 static TableData *table_data_new(
309 TableDataType type,
310 const void *data,
311 size_t minimum_width,
312 size_t maximum_width,
313 unsigned weight,
314 unsigned align_percent,
315 unsigned ellipsize_percent) {
316
317 size_t data_size;
318 TableData *d;
319
320 data_size = table_data_size(type, data);
321
322 d = malloc0(offsetof(TableData, data) + data_size);
323 if (!d)
324 return NULL;
325
326 d->n_ref = 1;
327 d->type = type;
328 d->minimum_width = minimum_width;
329 d->maximum_width = maximum_width;
330 d->weight = weight;
331 d->align_percent = align_percent;
332 d->ellipsize_percent = ellipsize_percent;
333 memcpy_safe(d->data, data, data_size);
334
335 return d;
336 }
337
338 int table_add_cell_full(
339 Table *t,
340 TableCell **ret_cell,
341 TableDataType type,
342 const void *data,
343 size_t minimum_width,
344 size_t maximum_width,
345 unsigned weight,
346 unsigned align_percent,
347 unsigned ellipsize_percent) {
348
349 _cleanup_(table_data_unrefp) TableData *d = NULL;
350 TableData *p;
351
352 assert(t);
353 assert(type >= 0);
354 assert(type < _TABLE_DATA_TYPE_MAX);
355
356 /* Determine the cell adjacent to the current one, but one row up */
357 if (t->n_cells >= t->n_columns)
358 assert_se(p = t->data[t->n_cells - t->n_columns]);
359 else
360 p = NULL;
361
362 /* If formatting parameters are left unspecified, copy from the previous row */
363 if (minimum_width == (size_t) -1)
364 minimum_width = p ? p->minimum_width : 1;
365
366 if (weight == (unsigned) -1)
367 weight = p ? p->weight : DEFAULT_WEIGHT;
368
369 if (align_percent == (unsigned) -1)
370 align_percent = p ? p->align_percent : 0;
371
372 if (ellipsize_percent == (unsigned) -1)
373 ellipsize_percent = p ? p->ellipsize_percent : 100;
374
375 assert(align_percent <= 100);
376 assert(ellipsize_percent <= 100);
377
378 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
379 * formatting. Let's see if we can reuse the cell data and ref it once more. */
380
381 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
382 d = table_data_ref(p);
383 else {
384 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
385 if (!d)
386 return -ENOMEM;
387 }
388
389 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
390 return -ENOMEM;
391
392 if (ret_cell)
393 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
394
395 t->data[t->n_cells++] = TAKE_PTR(d);
396
397 return 0;
398 }
399
400 int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
401 _cleanup_free_ char *buffer = NULL;
402 va_list ap;
403 int r;
404
405 va_start(ap, format);
406 r = vasprintf(&buffer, format, ap);
407 va_end(ap);
408 if (r < 0)
409 return -ENOMEM;
410
411 return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
412 }
413
414 int table_dup_cell(Table *t, TableCell *cell) {
415 size_t i;
416
417 assert(t);
418
419 /* Add the data of the specified cell a second time as a new cell to the end. */
420
421 i = TABLE_CELL_TO_INDEX(cell);
422 if (i >= t->n_cells)
423 return -ENXIO;
424
425 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
426 return -ENOMEM;
427
428 t->data[t->n_cells++] = table_data_ref(t->data[i]);
429 return 0;
430 }
431
432 static int table_dedup_cell(Table *t, TableCell *cell) {
433 _cleanup_free_ char *curl = NULL;
434 TableData *nd, *od;
435 size_t i;
436
437 assert(t);
438
439 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
440 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
441
442 i = TABLE_CELL_TO_INDEX(cell);
443 if (i >= t->n_cells)
444 return -ENXIO;
445
446 assert_se(od = t->data[i]);
447 if (od->n_ref == 1)
448 return 0;
449
450 assert(od->n_ref > 1);
451
452 if (od->url) {
453 curl = strdup(od->url);
454 if (!curl)
455 return -ENOMEM;
456 }
457
458 nd = table_data_new(
459 od->type,
460 od->data,
461 od->minimum_width,
462 od->maximum_width,
463 od->weight,
464 od->align_percent,
465 od->ellipsize_percent);
466 if (!nd)
467 return -ENOMEM;
468
469 nd->color = od->color;
470 nd->url = TAKE_PTR(curl);
471 nd->uppercase = od->uppercase;
472
473 table_data_unref(od);
474 t->data[i] = nd;
475
476 assert(nd->n_ref == 1);
477
478 return 1;
479 }
480
481 static TableData *table_get_data(Table *t, TableCell *cell) {
482 size_t i;
483
484 assert(t);
485 assert(cell);
486
487 /* Get the data object of the specified cell, or NULL if it doesn't exist */
488
489 i = TABLE_CELL_TO_INDEX(cell);
490 if (i >= t->n_cells)
491 return NULL;
492
493 assert(t->data[i]);
494 assert(t->data[i]->n_ref > 0);
495
496 return t->data[i];
497 }
498
499 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
500 int r;
501
502 assert(t);
503 assert(cell);
504
505 if (minimum_width == (size_t) -1)
506 minimum_width = 1;
507
508 r = table_dedup_cell(t, cell);
509 if (r < 0)
510 return r;
511
512 table_get_data(t, cell)->minimum_width = minimum_width;
513 return 0;
514 }
515
516 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
517 int r;
518
519 assert(t);
520 assert(cell);
521
522 r = table_dedup_cell(t, cell);
523 if (r < 0)
524 return r;
525
526 table_get_data(t, cell)->maximum_width = maximum_width;
527 return 0;
528 }
529
530 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
531 int r;
532
533 assert(t);
534 assert(cell);
535
536 if (weight == (unsigned) -1)
537 weight = DEFAULT_WEIGHT;
538
539 r = table_dedup_cell(t, cell);
540 if (r < 0)
541 return r;
542
543 table_get_data(t, cell)->weight = weight;
544 return 0;
545 }
546
547 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
548 int r;
549
550 assert(t);
551 assert(cell);
552
553 if (percent == (unsigned) -1)
554 percent = 0;
555
556 assert(percent <= 100);
557
558 r = table_dedup_cell(t, cell);
559 if (r < 0)
560 return r;
561
562 table_get_data(t, cell)->align_percent = percent;
563 return 0;
564 }
565
566 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
567 int r;
568
569 assert(t);
570 assert(cell);
571
572 if (percent == (unsigned) -1)
573 percent = 100;
574
575 assert(percent <= 100);
576
577 r = table_dedup_cell(t, cell);
578 if (r < 0)
579 return r;
580
581 table_get_data(t, cell)->ellipsize_percent = percent;
582 return 0;
583 }
584
585 int table_set_color(Table *t, TableCell *cell, const char *color) {
586 int r;
587
588 assert(t);
589 assert(cell);
590
591 r = table_dedup_cell(t, cell);
592 if (r < 0)
593 return r;
594
595 table_get_data(t, cell)->color = empty_to_null(color);
596 return 0;
597 }
598
599 int table_set_url(Table *t, TableCell *cell, const char *url) {
600 _cleanup_free_ char *copy = NULL;
601 int r;
602
603 assert(t);
604 assert(cell);
605
606 if (url) {
607 copy = strdup(url);
608 if (!copy)
609 return -ENOMEM;
610 }
611
612 r = table_dedup_cell(t, cell);
613 if (r < 0)
614 return r;
615
616 return free_and_replace(table_get_data(t, cell)->url, copy);
617 }
618
619 int table_set_uppercase(Table *t, TableCell *cell, bool b) {
620 TableData *d;
621 int r;
622
623 assert(t);
624 assert(cell);
625
626 r = table_dedup_cell(t, cell);
627 if (r < 0)
628 return r;
629
630 assert_se(d = table_get_data(t, cell));
631
632 if (d->uppercase == b)
633 return 0;
634
635 d->formatted = mfree(d->formatted);
636 d->uppercase = b;
637 return 1;
638 }
639
640 int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
641 _cleanup_free_ char *curl = NULL;
642 TableData *nd, *od;
643 size_t i;
644
645 assert(t);
646 assert(cell);
647
648 i = TABLE_CELL_TO_INDEX(cell);
649 if (i >= t->n_cells)
650 return -ENXIO;
651
652 assert_se(od = t->data[i]);
653
654 if (od->url) {
655 curl = strdup(od->url);
656 if (!curl)
657 return -ENOMEM;
658 }
659
660 nd = table_data_new(
661 type,
662 data,
663 od->minimum_width,
664 od->maximum_width,
665 od->weight,
666 od->align_percent,
667 od->ellipsize_percent);
668 if (!nd)
669 return -ENOMEM;
670
671 nd->color = od->color;
672 nd->url = TAKE_PTR(curl);
673 nd->uppercase = od->uppercase;
674
675 table_data_unref(od);
676 t->data[i] = nd;
677
678 return 0;
679 }
680
681 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
682 TableDataType type;
683 va_list ap;
684 TableCell *last_cell = NULL;
685 int r;
686
687 assert(t);
688 assert(first_type >= 0);
689 assert(first_type < _TABLE_DATA_TYPE_MAX);
690
691 type = first_type;
692
693 va_start(ap, first_type);
694 for (;;) {
695 const void *data;
696 union {
697 uint64_t size;
698 usec_t usec;
699 int int_val;
700 int32_t int32;
701 int64_t int64;
702 unsigned uint_val;
703 uint32_t uint32;
704 uint64_t uint64;
705 int percent;
706 int ifindex;
707 bool b;
708 } buffer;
709
710 switch (type) {
711
712 case TABLE_EMPTY:
713 data = NULL;
714 break;
715
716 case TABLE_STRING:
717 data = va_arg(ap, const char *);
718 break;
719
720 case TABLE_BOOLEAN:
721 buffer.b = va_arg(ap, int);
722 data = &buffer.b;
723 break;
724
725 case TABLE_TIMESTAMP:
726 case TABLE_TIMESTAMP_UTC:
727 case TABLE_TIMESTAMP_RELATIVE:
728 case TABLE_TIMESPAN:
729 case TABLE_TIMESPAN_MSEC:
730 buffer.usec = va_arg(ap, usec_t);
731 data = &buffer.usec;
732 break;
733
734 case TABLE_SIZE:
735 case TABLE_BPS:
736 buffer.size = va_arg(ap, uint64_t);
737 data = &buffer.size;
738 break;
739
740 case TABLE_INT:
741 buffer.int_val = va_arg(ap, int);
742 data = &buffer.int_val;
743 break;
744
745 case TABLE_INT32:
746 buffer.int32 = va_arg(ap, int32_t);
747 data = &buffer.int32;
748 break;
749
750 case TABLE_INT64:
751 buffer.int64 = va_arg(ap, int64_t);
752 data = &buffer.int64;
753 break;
754
755 case TABLE_UINT:
756 buffer.uint_val = va_arg(ap, unsigned);
757 data = &buffer.uint_val;
758 break;
759
760 case TABLE_UINT32:
761 buffer.uint32 = va_arg(ap, uint32_t);
762 data = &buffer.uint32;
763 break;
764
765 case TABLE_UINT64:
766 buffer.uint64 = va_arg(ap, uint64_t);
767 data = &buffer.uint64;
768 break;
769
770 case TABLE_PERCENT:
771 buffer.percent = va_arg(ap, int);
772 data = &buffer.percent;
773 break;
774
775 case TABLE_IFINDEX:
776 buffer.ifindex = va_arg(ap, int);
777 data = &buffer.ifindex;
778 break;
779
780 case TABLE_SET_MINIMUM_WIDTH: {
781 size_t w = va_arg(ap, size_t);
782
783 r = table_set_minimum_width(t, last_cell, w);
784 break;
785 }
786
787 case TABLE_SET_MAXIMUM_WIDTH: {
788 size_t w = va_arg(ap, size_t);
789 r = table_set_maximum_width(t, last_cell, w);
790 break;
791 }
792
793 case TABLE_SET_WEIGHT: {
794 unsigned w = va_arg(ap, unsigned);
795 r = table_set_weight(t, last_cell, w);
796 break;
797 }
798
799 case TABLE_SET_ALIGN_PERCENT: {
800 unsigned p = va_arg(ap, unsigned);
801 r = table_set_align_percent(t, last_cell, p);
802 break;
803 }
804
805 case TABLE_SET_ELLIPSIZE_PERCENT: {
806 unsigned p = va_arg(ap, unsigned);
807 r = table_set_ellipsize_percent(t, last_cell, p);
808 break;
809 }
810
811 case TABLE_SET_COLOR: {
812 const char *c = va_arg(ap, const char*);
813 r = table_set_color(t, last_cell, c);
814 break;
815 }
816
817 case TABLE_SET_URL: {
818 const char *u = va_arg(ap, const char*);
819 r = table_set_url(t, last_cell, u);
820 break;
821 }
822
823 case TABLE_SET_UPPERCASE: {
824 int u = va_arg(ap, int);
825 r = table_set_uppercase(t, last_cell, u);
826 break;
827 }
828
829 case _TABLE_DATA_TYPE_MAX:
830 /* Used as end marker */
831 va_end(ap);
832 return 0;
833
834 default:
835 assert_not_reached("Uh? Unexpected data type.");
836 }
837
838 if (type < _TABLE_DATA_TYPE_MAX)
839 r = table_add_cell(t, &last_cell, type, data);
840
841 if (r < 0) {
842 va_end(ap);
843 return r;
844 }
845
846 type = va_arg(ap, TableDataType);
847 }
848 }
849
850 void table_set_header(Table *t, bool b) {
851 assert(t);
852
853 t->header = b;
854 }
855
856 void table_set_width(Table *t, size_t width) {
857 assert(t);
858
859 t->width = width;
860 }
861
862 int table_set_display(Table *t, size_t first_column, ...) {
863 size_t allocated, column;
864 va_list ap;
865
866 assert(t);
867
868 allocated = t->n_display_map;
869 column = first_column;
870
871 va_start(ap, first_column);
872 for (;;) {
873 assert(column < t->n_columns);
874
875 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
876 va_end(ap);
877 return -ENOMEM;
878 }
879
880 t->display_map[t->n_display_map++] = column;
881
882 column = va_arg(ap, size_t);
883 if (column == (size_t) -1)
884 break;
885
886 }
887 va_end(ap);
888
889 return 0;
890 }
891
892 int table_set_sort(Table *t, size_t first_column, ...) {
893 size_t allocated, column;
894 va_list ap;
895
896 assert(t);
897
898 allocated = t->n_sort_map;
899 column = first_column;
900
901 va_start(ap, first_column);
902 for (;;) {
903 assert(column < t->n_columns);
904
905 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
906 va_end(ap);
907 return -ENOMEM;
908 }
909
910 t->sort_map[t->n_sort_map++] = column;
911
912 column = va_arg(ap, size_t);
913 if (column == (size_t) -1)
914 break;
915 }
916 va_end(ap);
917
918 return 0;
919 }
920
921 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
922 assert(a);
923 assert(b);
924
925 if (a->type == b->type) {
926
927 /* We only define ordering for cells of the same data type. If cells with different data types are
928 * compared we follow the order the cells were originally added in */
929
930 switch (a->type) {
931
932 case TABLE_STRING:
933 return strcmp(a->string, b->string);
934
935 case TABLE_BOOLEAN:
936 if (!a->boolean && b->boolean)
937 return -1;
938 if (a->boolean && !b->boolean)
939 return 1;
940 return 0;
941
942 case TABLE_TIMESTAMP:
943 case TABLE_TIMESTAMP_UTC:
944 case TABLE_TIMESTAMP_RELATIVE:
945 return CMP(a->timestamp, b->timestamp);
946
947 case TABLE_TIMESPAN:
948 case TABLE_TIMESPAN_MSEC:
949 return CMP(a->timespan, b->timespan);
950
951 case TABLE_SIZE:
952 case TABLE_BPS:
953 return CMP(a->size, b->size);
954
955 case TABLE_INT:
956 return CMP(a->int_val, b->int_val);
957
958 case TABLE_INT32:
959 return CMP(a->int32, b->int32);
960
961 case TABLE_INT64:
962 return CMP(a->int64, b->int64);
963
964 case TABLE_UINT:
965 return CMP(a->uint_val, b->uint_val);
966
967 case TABLE_UINT32:
968 return CMP(a->uint32, b->uint32);
969
970 case TABLE_UINT64:
971 return CMP(a->uint64, b->uint64);
972
973 case TABLE_PERCENT:
974 return CMP(a->percent, b->percent);
975
976 case TABLE_IFINDEX:
977 return CMP(a->ifindex, b->ifindex);
978
979 default:
980 ;
981 }
982 }
983
984 /* Generic fallback using the original order in which the cells where added. */
985 return CMP(index_a, index_b);
986 }
987
988 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
989 size_t i;
990 int r;
991
992 assert(t);
993 assert(t->sort_map);
994
995 /* Make sure the header stays at the beginning */
996 if (*a < t->n_columns && *b < t->n_columns)
997 return 0;
998 if (*a < t->n_columns)
999 return -1;
1000 if (*b < t->n_columns)
1001 return 1;
1002
1003 /* Order other lines by the sorting map */
1004 for (i = 0; i < t->n_sort_map; i++) {
1005 TableData *d, *dd;
1006
1007 d = t->data[*a + t->sort_map[i]];
1008 dd = t->data[*b + t->sort_map[i]];
1009
1010 r = cell_data_compare(d, *a, dd, *b);
1011 if (r != 0)
1012 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1013 }
1014
1015 /* Order identical lines by the order there were originally added in */
1016 return CMP(*a, *b);
1017 }
1018
1019 static const char *table_data_format(TableData *d) {
1020 assert(d);
1021
1022 if (d->formatted)
1023 return d->formatted;
1024
1025 switch (d->type) {
1026 case TABLE_EMPTY:
1027 return "";
1028
1029 case TABLE_STRING:
1030 if (d->uppercase) {
1031 char *p, *q;
1032
1033 d->formatted = new(char, strlen(d->string) + 1);
1034 if (!d->formatted)
1035 return NULL;
1036
1037 for (p = d->string, q = d->formatted; *p; p++, q++)
1038 *q = (char) toupper((unsigned char) *p);
1039 *q = 0;
1040
1041 return d->formatted;
1042 }
1043
1044 return d->string;
1045
1046 case TABLE_BOOLEAN:
1047 return yes_no(d->boolean);
1048
1049 case TABLE_TIMESTAMP:
1050 case TABLE_TIMESTAMP_UTC:
1051 case TABLE_TIMESTAMP_RELATIVE: {
1052 _cleanup_free_ char *p;
1053 char *ret;
1054
1055 p = new(char, FORMAT_TIMESTAMP_MAX);
1056 if (!p)
1057 return NULL;
1058
1059 if (d->type == TABLE_TIMESTAMP)
1060 ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1061 else if (d->type == TABLE_TIMESTAMP_UTC)
1062 ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1063 else
1064 ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1065 if (!ret)
1066 return "n/a";
1067
1068 d->formatted = TAKE_PTR(p);
1069 break;
1070 }
1071
1072 case TABLE_TIMESPAN:
1073 case TABLE_TIMESPAN_MSEC: {
1074 _cleanup_free_ char *p;
1075
1076 p = new(char, FORMAT_TIMESPAN_MAX);
1077 if (!p)
1078 return NULL;
1079
1080 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1081 d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1082 return "n/a";
1083
1084 d->formatted = TAKE_PTR(p);
1085 break;
1086 }
1087
1088 case TABLE_SIZE: {
1089 _cleanup_free_ char *p;
1090
1091 p = new(char, FORMAT_BYTES_MAX);
1092 if (!p)
1093 return NULL;
1094
1095 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1096 return "n/a";
1097
1098 d->formatted = TAKE_PTR(p);
1099 break;
1100 }
1101
1102 case TABLE_BPS: {
1103 _cleanup_free_ char *p;
1104 size_t n;
1105
1106 p = new(char, FORMAT_BYTES_MAX+2);
1107 if (!p)
1108 return NULL;
1109
1110 if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
1111 return "n/a";
1112
1113 n = strlen(p);
1114 strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1115
1116 d->formatted = TAKE_PTR(p);
1117 break;
1118 }
1119
1120 case TABLE_INT: {
1121 _cleanup_free_ char *p;
1122
1123 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1124 if (!p)
1125 return NULL;
1126
1127 sprintf(p, "%i", d->int_val);
1128 d->formatted = TAKE_PTR(p);
1129 break;
1130 }
1131
1132 case TABLE_INT32: {
1133 _cleanup_free_ char *p;
1134
1135 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1136 if (!p)
1137 return NULL;
1138
1139 sprintf(p, "%" PRIi32, d->int32);
1140 d->formatted = TAKE_PTR(p);
1141 break;
1142 }
1143
1144 case TABLE_INT64: {
1145 _cleanup_free_ char *p;
1146
1147 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1148 if (!p)
1149 return NULL;
1150
1151 sprintf(p, "%" PRIi64, d->int64);
1152 d->formatted = TAKE_PTR(p);
1153 break;
1154 }
1155
1156 case TABLE_UINT: {
1157 _cleanup_free_ char *p;
1158
1159 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1160 if (!p)
1161 return NULL;
1162
1163 sprintf(p, "%u", d->uint_val);
1164 d->formatted = TAKE_PTR(p);
1165 break;
1166 }
1167
1168 case TABLE_UINT32: {
1169 _cleanup_free_ char *p;
1170
1171 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1172 if (!p)
1173 return NULL;
1174
1175 sprintf(p, "%" PRIu32, d->uint32);
1176 d->formatted = TAKE_PTR(p);
1177 break;
1178 }
1179
1180 case TABLE_UINT64: {
1181 _cleanup_free_ char *p;
1182
1183 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1184 if (!p)
1185 return NULL;
1186
1187 sprintf(p, "%" PRIu64, d->uint64);
1188 d->formatted = TAKE_PTR(p);
1189 break;
1190 }
1191
1192 case TABLE_PERCENT: {
1193 _cleanup_free_ char *p;
1194
1195 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1196 if (!p)
1197 return NULL;
1198
1199 sprintf(p, "%i%%" , d->percent);
1200 d->formatted = TAKE_PTR(p);
1201 break;
1202 }
1203
1204 case TABLE_IFINDEX: {
1205 _cleanup_free_ char *p;
1206 char name[IF_NAMESIZE + 1];
1207
1208 if (format_ifname(d->ifindex, name)) {
1209 p = strdup(name);
1210 if (!p)
1211 return NULL;
1212 } else {
1213 if (asprintf(&p, "%i" , d->ifindex) < 0)
1214 return NULL;
1215 }
1216
1217 d->formatted = TAKE_PTR(p);
1218 break;
1219 }
1220
1221 default:
1222 assert_not_reached("Unexpected type?");
1223 }
1224
1225 return d->formatted;
1226 }
1227
1228 static int table_data_requested_width(TableData *d, size_t *ret) {
1229 const char *t;
1230 size_t l;
1231
1232 t = table_data_format(d);
1233 if (!t)
1234 return -ENOMEM;
1235
1236 l = utf8_console_width(t);
1237 if (l == (size_t) -1)
1238 return -EINVAL;
1239
1240 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
1241 l = d->maximum_width;
1242
1243 if (l < d->minimum_width)
1244 l = d->minimum_width;
1245
1246 *ret = l;
1247 return 0;
1248 }
1249
1250 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1251 size_t w = 0, space, lspace, old_length, clickable_length;
1252 _cleanup_free_ char *clickable = NULL;
1253 const char *p;
1254 char *ret;
1255 size_t i;
1256 int r;
1257
1258 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1259
1260 assert(str);
1261 assert(percent <= 100);
1262
1263 old_length = strlen(str);
1264
1265 if (url) {
1266 r = terminal_urlify(url, str, &clickable);
1267 if (r < 0)
1268 return NULL;
1269
1270 clickable_length = strlen(clickable);
1271 } else
1272 clickable_length = old_length;
1273
1274 /* Determine current width on screen */
1275 p = str;
1276 while (p < str + old_length) {
1277 char32_t c;
1278
1279 if (utf8_encoded_to_unichar(p, &c) < 0) {
1280 p++, w++; /* count invalid chars as 1 */
1281 continue;
1282 }
1283
1284 p = utf8_next_char(p);
1285 w += unichar_iswide(c) ? 2 : 1;
1286 }
1287
1288 /* Already wider than the target, if so, don't do anything */
1289 if (w >= new_length)
1290 return clickable ? TAKE_PTR(clickable) : strdup(str);
1291
1292 /* How much spaces shall we add? An how much on the left side? */
1293 space = new_length - w;
1294 lspace = space * percent / 100U;
1295
1296 ret = new(char, space + clickable_length + 1);
1297 if (!ret)
1298 return NULL;
1299
1300 for (i = 0; i < lspace; i++)
1301 ret[i] = ' ';
1302 memcpy(ret + lspace, clickable ?: str, clickable_length);
1303 for (i = lspace + clickable_length; i < space + clickable_length; i++)
1304 ret[i] = ' ';
1305
1306 ret[space + clickable_length] = 0;
1307 return ret;
1308 }
1309
1310 int table_print(Table *t, FILE *f) {
1311 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1312 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1313 *width;
1314 _cleanup_free_ size_t *sorted = NULL;
1315 uint64_t *column_weight, weight_sum;
1316 int r;
1317
1318 assert(t);
1319
1320 if (!f)
1321 f = stdout;
1322
1323 /* Ensure we have no incomplete rows */
1324 assert(t->n_cells % t->n_columns == 0);
1325
1326 n_rows = t->n_cells / t->n_columns;
1327 assert(n_rows > 0); /* at least the header row must be complete */
1328
1329 if (t->sort_map) {
1330 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1331
1332 sorted = new(size_t, n_rows);
1333 if (!sorted)
1334 return -ENOMEM;
1335
1336 for (i = 0; i < n_rows; i++)
1337 sorted[i] = i * t->n_columns;
1338
1339 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1340 }
1341
1342 if (t->display_map)
1343 display_columns = t->n_display_map;
1344 else
1345 display_columns = t->n_columns;
1346
1347 assert(display_columns > 0);
1348
1349 minimum_width = newa(size_t, display_columns);
1350 maximum_width = newa(size_t, display_columns);
1351 requested_width = newa(size_t, display_columns);
1352 width = newa(size_t, display_columns);
1353 column_weight = newa0(uint64_t, display_columns);
1354
1355 for (j = 0; j < display_columns; j++) {
1356 minimum_width[j] = 1;
1357 maximum_width[j] = (size_t) -1;
1358 requested_width[j] = (size_t) -1;
1359 }
1360
1361 /* First pass: determine column sizes */
1362 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1363 TableData **row;
1364
1365 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1366 * hence we don't care for sorted[] during the first pass. */
1367 row = t->data + i * t->n_columns;
1368
1369 for (j = 0; j < display_columns; j++) {
1370 TableData *d;
1371 size_t req;
1372
1373 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1374
1375 r = table_data_requested_width(d, &req);
1376 if (r < 0)
1377 return r;
1378
1379 /* Determine the biggest width that any cell in this column would like to have */
1380 if (requested_width[j] == (size_t) -1 ||
1381 requested_width[j] < req)
1382 requested_width[j] = req;
1383
1384 /* Determine the minimum width any cell in this column needs */
1385 if (minimum_width[j] < d->minimum_width)
1386 minimum_width[j] = d->minimum_width;
1387
1388 /* Determine the maximum width any cell in this column needs */
1389 if (d->maximum_width != (size_t) -1 &&
1390 (maximum_width[j] == (size_t) -1 ||
1391 maximum_width[j] > d->maximum_width))
1392 maximum_width[j] = d->maximum_width;
1393
1394 /* Determine the full columns weight */
1395 column_weight[j] += d->weight;
1396 }
1397 }
1398
1399 /* One space between each column */
1400 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1401
1402 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1403 weight_sum = 0;
1404 for (j = 0; j < display_columns; j++) {
1405 weight_sum += column_weight[j];
1406
1407 table_minimum_width += minimum_width[j];
1408
1409 if (maximum_width[j] == (size_t) -1)
1410 table_maximum_width = (size_t) -1;
1411 else
1412 table_maximum_width += maximum_width[j];
1413
1414 table_requested_width += requested_width[j];
1415 }
1416
1417 /* Calculate effective table width */
1418 if (t->width == (size_t) -1)
1419 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1420 else
1421 table_effective_width = t->width;
1422
1423 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1424 table_effective_width = table_maximum_width;
1425
1426 if (table_effective_width < table_minimum_width)
1427 table_effective_width = table_minimum_width;
1428
1429 if (table_effective_width >= table_requested_width) {
1430 size_t extra;
1431
1432 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1433 * each column with what it asked for and the distribute the rest. */
1434
1435 extra = table_effective_width - table_requested_width;
1436
1437 for (j = 0; j < display_columns; j++) {
1438 size_t delta;
1439
1440 if (weight_sum == 0)
1441 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1442 else
1443 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1444
1445 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1446 width[j] = maximum_width[j];
1447
1448 if (width[j] < minimum_width[j])
1449 width[j] = minimum_width[j];
1450
1451 assert(width[j] >= requested_width[j]);
1452 delta = width[j] - requested_width[j];
1453
1454 /* Subtract what we just added from the rest */
1455 if (extra > delta)
1456 extra -= delta;
1457 else
1458 extra = 0;
1459
1460 assert(weight_sum >= column_weight[j]);
1461 weight_sum -= column_weight[j];
1462 }
1463
1464 } else {
1465 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1466 * with the minimum they need, and then distribute anything left. */
1467 bool finalize = false;
1468 size_t extra;
1469
1470 extra = table_effective_width - table_minimum_width;
1471
1472 for (j = 0; j < display_columns; j++)
1473 width[j] = (size_t) -1;
1474
1475 for (;;) {
1476 bool restart = false;
1477
1478 for (j = 0; j < display_columns; j++) {
1479 size_t delta, w;
1480
1481 /* Did this column already get something assigned? If so, let's skip to the next */
1482 if (width[j] != (size_t) -1)
1483 continue;
1484
1485 if (weight_sum == 0)
1486 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1487 else
1488 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1489
1490 if (w >= requested_width[j]) {
1491 /* Never give more than requested. If we hit a column like this, there's more
1492 * space to allocate to other columns which means we need to restart the
1493 * iteration. However, if we hit a column like this, let's assign it the space
1494 * it wanted for good early.*/
1495
1496 w = requested_width[j];
1497 restart = true;
1498
1499 } else if (!finalize)
1500 continue;
1501
1502 width[j] = w;
1503
1504 assert(w >= minimum_width[j]);
1505 delta = w - minimum_width[j];
1506
1507 assert(delta <= extra);
1508 extra -= delta;
1509
1510 assert(weight_sum >= column_weight[j]);
1511 weight_sum -= column_weight[j];
1512
1513 if (restart && !finalize)
1514 break;
1515 }
1516
1517 if (finalize)
1518 break;
1519
1520 if (!restart)
1521 finalize = true;
1522 }
1523 }
1524
1525 /* Second pass: show output */
1526 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1527 TableData **row;
1528
1529 if (sorted)
1530 row = t->data + sorted[i];
1531 else
1532 row = t->data + i * t->n_columns;
1533
1534 for (j = 0; j < display_columns; j++) {
1535 _cleanup_free_ char *buffer = NULL;
1536 const char *field;
1537 TableData *d;
1538 size_t l;
1539
1540 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1541
1542 field = table_data_format(d);
1543 if (!field)
1544 return -ENOMEM;
1545
1546 l = utf8_console_width(field);
1547 if (l > width[j]) {
1548 /* Field is wider than allocated space. Let's ellipsize */
1549
1550 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1551 if (!buffer)
1552 return -ENOMEM;
1553
1554 field = buffer;
1555
1556 } else if (l < width[j]) {
1557 /* Field is shorter than allocated space. Let's align with spaces */
1558
1559 buffer = align_string_mem(field, d->url, width[j], d->align_percent);
1560 if (!buffer)
1561 return -ENOMEM;
1562
1563 field = buffer;
1564 }
1565
1566 if (l >= width[j] && d->url) {
1567 _cleanup_free_ char *clickable = NULL;
1568
1569 r = terminal_urlify(d->url, field, &clickable);
1570 if (r < 0)
1571 return r;
1572
1573 free_and_replace(buffer, clickable);
1574 field = buffer;
1575 }
1576
1577 if (row == t->data) /* underline header line fully, including the column separator */
1578 fputs(ansi_underline(), f);
1579
1580 if (j > 0)
1581 fputc(' ', f); /* column separator */
1582
1583 if (d->color && colors_enabled()) {
1584 if (row == t->data) /* first undo header underliner */
1585 fputs(ANSI_NORMAL, f);
1586
1587 fputs(d->color, f);
1588 }
1589
1590 fputs(field, f);
1591
1592 if (colors_enabled() && (d->color || row == t->data))
1593 fputs(ANSI_NORMAL, f);
1594 }
1595
1596 fputc('\n', f);
1597 }
1598
1599 return fflush_and_check(f);
1600 }
1601
1602 int table_format(Table *t, char **ret) {
1603 _cleanup_fclose_ FILE *f = NULL;
1604 char *buf = NULL;
1605 size_t sz = 0;
1606 int r;
1607
1608 f = open_memstream_unlocked(&buf, &sz);
1609 if (!f)
1610 return -ENOMEM;
1611
1612 r = table_print(t, f);
1613 if (r < 0)
1614 return r;
1615
1616 f = safe_fclose(f);
1617
1618 *ret = buf;
1619
1620 return 0;
1621 }
1622
1623 size_t table_get_rows(Table *t) {
1624 if (!t)
1625 return 0;
1626
1627 assert(t->n_columns > 0);
1628 return t->n_cells / t->n_columns;
1629 }
1630
1631 size_t table_get_columns(Table *t) {
1632 if (!t)
1633 return 0;
1634
1635 assert(t->n_columns > 0);
1636 return t->n_columns;
1637 }
1638
1639 int table_set_reverse(Table *t, size_t column, bool b) {
1640 assert(t);
1641 assert(column < t->n_columns);
1642
1643 if (!t->reverse_map) {
1644 if (!b)
1645 return 0;
1646
1647 t->reverse_map = new0(bool, t->n_columns);
1648 if (!t->reverse_map)
1649 return -ENOMEM;
1650 }
1651
1652 t->reverse_map[column] = b;
1653 return 0;
1654 }
1655
1656 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
1657 size_t i;
1658
1659 assert(t);
1660
1661 if (column >= t->n_columns)
1662 return NULL;
1663
1664 i = row * t->n_columns + column;
1665 if (i >= t->n_cells)
1666 return NULL;
1667
1668 return TABLE_INDEX_TO_CELL(i);
1669 }
1670
1671 const void *table_get(Table *t, TableCell *cell) {
1672 TableData *d;
1673
1674 assert(t);
1675
1676 d = table_get_data(t, cell);
1677 if (!d)
1678 return NULL;
1679
1680 return d->data;
1681 }
1682
1683 const void* table_get_at(Table *t, size_t row, size_t column) {
1684 TableCell *cell;
1685
1686 cell = table_get_cell(t, row, column);
1687 if (!cell)
1688 return NULL;
1689
1690 return table_get(t, cell);
1691 }
1692
1693 static int table_data_to_json(TableData *d, JsonVariant **ret) {
1694
1695 switch (d->type) {
1696
1697 case TABLE_EMPTY:
1698 return json_variant_new_null(ret);
1699
1700 case TABLE_STRING:
1701 return json_variant_new_string(ret, d->string);
1702
1703 case TABLE_BOOLEAN:
1704 return json_variant_new_boolean(ret, d->boolean);
1705
1706 case TABLE_TIMESTAMP:
1707 case TABLE_TIMESTAMP_UTC:
1708 case TABLE_TIMESTAMP_RELATIVE:
1709 if (d->timestamp == USEC_INFINITY)
1710 return json_variant_new_null(ret);
1711
1712 return json_variant_new_unsigned(ret, d->timestamp);
1713
1714 case TABLE_TIMESPAN:
1715 case TABLE_TIMESPAN_MSEC:
1716 if (d->timespan == USEC_INFINITY)
1717 return json_variant_new_null(ret);
1718
1719 return json_variant_new_unsigned(ret, d->timespan);
1720
1721 case TABLE_SIZE:
1722 case TABLE_BPS:
1723 if (d->size == (size_t) -1)
1724 return json_variant_new_null(ret);
1725
1726 return json_variant_new_unsigned(ret, d->size);
1727
1728 case TABLE_INT:
1729 return json_variant_new_integer(ret, d->int_val);
1730
1731 case TABLE_INT32:
1732 return json_variant_new_integer(ret, d->int32);
1733
1734 case TABLE_INT64:
1735 return json_variant_new_integer(ret, d->int64);
1736
1737 case TABLE_UINT:
1738 return json_variant_new_unsigned(ret, d->uint_val);
1739
1740 case TABLE_UINT32:
1741 return json_variant_new_unsigned(ret, d->uint32);
1742
1743 case TABLE_UINT64:
1744 return json_variant_new_unsigned(ret, d->uint64);
1745
1746 case TABLE_PERCENT:
1747 return json_variant_new_integer(ret, d->percent);
1748
1749 case TABLE_IFINDEX:
1750 return json_variant_new_integer(ret, d->ifindex);
1751
1752 default:
1753 return -EINVAL;
1754 }
1755 }
1756
1757 int table_to_json(Table *t, JsonVariant **ret) {
1758 JsonVariant **rows = NULL, **elements = NULL;
1759 _cleanup_free_ size_t *sorted = NULL;
1760 size_t n_rows, i, j, display_columns;
1761 int r;
1762
1763 assert(t);
1764
1765 /* Ensure we have no incomplete rows */
1766 assert(t->n_cells % t->n_columns == 0);
1767
1768 n_rows = t->n_cells / t->n_columns;
1769 assert(n_rows > 0); /* at least the header row must be complete */
1770
1771 if (t->sort_map) {
1772 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1773
1774 sorted = new(size_t, n_rows);
1775 if (!sorted) {
1776 r = -ENOMEM;
1777 goto finish;
1778 }
1779
1780 for (i = 0; i < n_rows; i++)
1781 sorted[i] = i * t->n_columns;
1782
1783 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1784 }
1785
1786 if (t->display_map)
1787 display_columns = t->n_display_map;
1788 else
1789 display_columns = t->n_columns;
1790 assert(display_columns > 0);
1791
1792 elements = new0(JsonVariant*, display_columns * 2);
1793 if (!elements) {
1794 r = -ENOMEM;
1795 goto finish;
1796 }
1797
1798 for (j = 0; j < display_columns; j++) {
1799 TableData *d;
1800
1801 assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
1802
1803 r = table_data_to_json(d, elements + j*2);
1804 if (r < 0)
1805 goto finish;
1806 }
1807
1808 rows = new0(JsonVariant*, n_rows-1);
1809 if (!rows) {
1810 r = -ENOMEM;
1811 goto finish;
1812 }
1813
1814 for (i = 1; i < n_rows; i++) {
1815 TableData **row;
1816
1817 if (sorted)
1818 row = t->data + sorted[i];
1819 else
1820 row = t->data + i * t->n_columns;
1821
1822 for (j = 0; j < display_columns; j++) {
1823 TableData *d;
1824 size_t k;
1825
1826 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1827
1828 k = j*2+1;
1829 elements[k] = json_variant_unref(elements[k]);
1830
1831 r = table_data_to_json(d, elements + k);
1832 if (r < 0)
1833 goto finish;
1834 }
1835
1836 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1837 if (r < 0)
1838 goto finish;
1839 }
1840
1841 r = json_variant_new_array(ret, rows, n_rows - 1);
1842
1843 finish:
1844 if (rows) {
1845 json_variant_unref_many(rows, n_rows-1);
1846 free(rows);
1847 }
1848
1849 if (elements) {
1850 json_variant_unref_many(elements, display_columns*2);
1851 free(elements);
1852 }
1853
1854 return r;
1855 }
1856
1857 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
1858 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1859 int r;
1860
1861 assert(t);
1862
1863 if (!f)
1864 f = stdout;
1865
1866 r = table_to_json(t, &v);
1867 if (r < 0)
1868 return r;
1869
1870 json_variant_dump(v, flags, f, NULL);
1871
1872 return fflush_and_check(f);
1873 }