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