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