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