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