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