]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/format-table.c
analyze: slightly reword PrivatTmp= message
[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
2cd9f773 1996 delta = LESS_BY(width[j], requested_width[j]);
1960e736 1997
b0e3d799
ZJS
1998 /* Subtract what we just added from the rest */
1999 if (extra > delta)
2000 extra -= delta;
2001 else
2002 extra = 0;
1960e736 2003
b0e3d799
ZJS
2004 assert(weight_sum >= column_weight[j]);
2005 weight_sum -= column_weight[j];
2006 }
1960e736 2007
b0e3d799
ZJS
2008 break; /* Every column should be happy, no need to repeat calculations. */
2009 } else {
2010 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2011 * with the minimum they need, and then distribute anything left. */
2012 bool finalize = false;
2013 size_t extra;
1960e736 2014
b0e3d799 2015 extra = table_effective_width - table_minimum_width;
1960e736 2016
b0e3d799
ZJS
2017 for (size_t j = 0; j < display_columns; j++)
2018 width[j] = (size_t) -1;
1960e736 2019
b0e3d799
ZJS
2020 for (;;) {
2021 bool restart = false;
1960e736 2022
b0e3d799
ZJS
2023 for (size_t j = 0; j < display_columns; j++) {
2024 size_t delta, w;
1960e736 2025
b0e3d799
ZJS
2026 /* Did this column already get something assigned? If so, let's skip to the next */
2027 if (width[j] != (size_t) -1)
2028 continue;
1960e736 2029
b0e3d799
ZJS
2030 if (weight_sum == 0)
2031 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
2032 else
2033 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1960e736 2034
b0e3d799
ZJS
2035 if (w >= requested_width[j]) {
2036 /* Never give more than requested. If we hit a column like this, there's more
2037 * space to allocate to other columns which means we need to restart the
2038 * iteration. However, if we hit a column like this, let's assign it the space
2039 * it wanted for good early.*/
1960e736 2040
b0e3d799
ZJS
2041 w = requested_width[j];
2042 restart = true;
1960e736 2043
b0e3d799
ZJS
2044 } else if (!finalize)
2045 continue;
1960e736 2046
b0e3d799 2047 width[j] = w;
1960e736 2048
b0e3d799
ZJS
2049 assert(w >= minimum_width[j]);
2050 delta = w - minimum_width[j];
1960e736 2051
b0e3d799
ZJS
2052 assert(delta <= extra);
2053 extra -= delta;
1960e736 2054
b0e3d799
ZJS
2055 assert(weight_sum >= column_weight[j]);
2056 weight_sum -= column_weight[j];
1960e736 2057
b0e3d799
ZJS
2058 if (restart && !finalize)
2059 break;
2060 }
1960e736 2061
b0e3d799 2062 if (finalize)
1960e736 2063 break;
b0e3d799
ZJS
2064
2065 if (!restart)
2066 finalize = true;
1960e736
LP
2067 }
2068
b0e3d799
ZJS
2069 if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
2070 * let's try to reformat them with the new widths. Otherwise, let's
2071 * move on. */
1960e736 2072 break;
1960e736
LP
2073 }
2074 }
2075
2076 /* Second pass: show output */
6f8ca84c 2077 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
d91614e7
LP
2078 size_t n_subline = 0;
2079 bool more_sublines;
1960e736
LP
2080 TableData **row;
2081
2082 if (sorted)
2083 row = t->data + sorted[i];
2084 else
2085 row = t->data + i * t->n_columns;
2086
d91614e7 2087 do {
b0395c11 2088 const char *gap_color = NULL;
d91614e7 2089 more_sublines = false;
1960e736 2090
6f8ca84c 2091 for (size_t j = 0; j < display_columns; j++) {
d91614e7
LP
2092 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
2093 bool lines_truncated = false;
b0395c11 2094 const char *field, *color = NULL;
d91614e7
LP
2095 TableData *d;
2096 size_t l;
1960e736 2097
d91614e7 2098 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1960e736 2099
b0e3d799 2100 field = table_data_format(t, d, false, width[j], NULL);
d91614e7 2101 if (!field)
1960e736
LP
2102 return -ENOMEM;
2103
d91614e7 2104 r = string_extract_line(field, n_subline, &extracted);
165ca566
LP
2105 if (r < 0)
2106 return r;
d91614e7
LP
2107 if (r > 0) {
2108 /* There are more lines to come */
2109 if ((t->cell_height_max == (size_t) -1 || n_subline + 1 < t->cell_height_max))
2110 more_sublines = true; /* There are more lines to come */
2111 else
2112 lines_truncated = true;
2113 }
2114 if (extracted)
2115 field = extracted;
2116
2117 l = utf8_console_width(field);
2118 if (l > width[j]) {
2119 /* Field is wider than allocated space. Let's ellipsize */
2120
2121 buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2122 lines_truncated ? 100 : d->ellipsize_percent);
2123 if (!buffer)
2124 return -ENOMEM;
2125
2126 field = buffer;
2127 } else {
2128 if (lines_truncated) {
2129 _cleanup_free_ char *padded = NULL;
2130
2131 /* We truncated more lines of this cell, let's add an
162392b7 2132 * ellipsis. We first append it, but that might make our
d91614e7
LP
2133 * string grow above what we have space for, hence ellipsize
2134 * right after. This will truncate the ellipsis and add a new
2135 * one. */
2136
2137 padded = strjoin(field, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
2138 if (!padded)
2139 return -ENOMEM;
2140
2141 buffer = ellipsize(padded, width[j], 100);
2142 if (!buffer)
2143 return -ENOMEM;
2144
2145 field = buffer;
2146 l = utf8_console_width(field);
2147 }
2148
2149 if (l < width[j]) {
2150 _cleanup_free_ char *aligned = NULL;
2151 /* Field is shorter than allocated space. Let's align with spaces */
2152
2153 aligned = align_string_mem(field, d->url, width[j], d->align_percent);
2154 if (!aligned)
2155 return -ENOMEM;
2156
2157 free_and_replace(buffer, aligned);
2158 field = buffer;
2159 }
2160 }
2161
2162 if (l >= width[j] && d->url) {
2163 _cleanup_free_ char *clickable = NULL;
2164
2165 r = terminal_urlify(d->url, field, &clickable);
2166 if (r < 0)
2167 return r;
2168
2169 free_and_replace(buffer, clickable);
2170 field = buffer;
2171 }
2172
b0395c11
LP
2173 if (colors_enabled()) {
2174 if (gap_color)
2175 fputs(gap_color, f);
2176 else if (row == t->data) /* underline header line fully, including the column separator */
2177 fputs(ansi_underline(), f);
2178 }
d91614e7
LP
2179
2180 if (j > 0)
b0395c11 2181 fputc(' ', f); /* column separator left of cell */
d91614e7 2182
b0395c11
LP
2183 if (colors_enabled()) {
2184 color = table_data_color(d);
2185
2186 /* Undo gap color */
2187 if (gap_color || (color && row == t->data))
d91614e7
LP
2188 fputs(ANSI_NORMAL, f);
2189
b0395c11
LP
2190 if (color)
2191 fputs(color, f);
2192 else if (gap_color && row == t->data) /* underline header line cell */
2193 fputs(ansi_underline(), f);
d91614e7
LP
2194 }
2195
2196 fputs(field, f);
2197
b0395c11 2198 if (colors_enabled() && (color || row == t->data))
30d98de0 2199 fputs(ANSI_NORMAL, f);
b0395c11
LP
2200
2201 gap_color = table_data_rgap_color(d);
30d98de0 2202 }
1960e736 2203
d91614e7
LP
2204 fputc('\n', f);
2205 n_subline ++;
2206 } while (more_sublines);
1960e736
LP
2207 }
2208
2209 return fflush_and_check(f);
2210}
2211
2212int table_format(Table *t, char **ret) {
2213 _cleanup_fclose_ FILE *f = NULL;
2214 char *buf = NULL;
2215 size_t sz = 0;
2216 int r;
2217
2fe21124 2218 f = open_memstream_unlocked(&buf, &sz);
1960e736
LP
2219 if (!f)
2220 return -ENOMEM;
2221
1960e736
LP
2222 r = table_print(t, f);
2223 if (r < 0)
2224 return r;
2225
2226 f = safe_fclose(f);
2227
2228 *ret = buf;
2229
2230 return 0;
2231}
2232
2233size_t table_get_rows(Table *t) {
2234 if (!t)
2235 return 0;
2236
2237 assert(t->n_columns > 0);
2238 return t->n_cells / t->n_columns;
2239}
2240
2241size_t table_get_columns(Table *t) {
2242 if (!t)
2243 return 0;
2244
2245 assert(t->n_columns > 0);
2246 return t->n_columns;
2247}
a2c73e2d
LP
2248
2249int table_set_reverse(Table *t, size_t column, bool b) {
2250 assert(t);
2251 assert(column < t->n_columns);
2252
2253 if (!t->reverse_map) {
2254 if (!b)
2255 return 0;
2256
2257 t->reverse_map = new0(bool, t->n_columns);
2258 if (!t->reverse_map)
2259 return -ENOMEM;
2260 }
2261
2262 t->reverse_map[column] = b;
2263 return 0;
2264}
9314ead7
LP
2265
2266TableCell *table_get_cell(Table *t, size_t row, size_t column) {
2267 size_t i;
2268
2269 assert(t);
2270
2271 if (column >= t->n_columns)
2272 return NULL;
2273
2274 i = row * t->n_columns + column;
2275 if (i >= t->n_cells)
2276 return NULL;
2277
2278 return TABLE_INDEX_TO_CELL(i);
2279}
62d99b39
LP
2280
2281const void *table_get(Table *t, TableCell *cell) {
2282 TableData *d;
2283
2284 assert(t);
2285
2286 d = table_get_data(t, cell);
2287 if (!d)
2288 return NULL;
2289
2290 return d->data;
2291}
2292
2293const void* table_get_at(Table *t, size_t row, size_t column) {
2294 TableCell *cell;
2295
2296 cell = table_get_cell(t, row, column);
2297 if (!cell)
2298 return NULL;
2299
2300 return table_get(t, cell);
2301}
31ac2357
LP
2302
2303static int table_data_to_json(TableData *d, JsonVariant **ret) {
2304
2305 switch (d->type) {
2306
2307 case TABLE_EMPTY:
2308 return json_variant_new_null(ret);
2309
2310 case TABLE_STRING:
f93d876c 2311 case TABLE_PATH:
31ac2357
LP
2312 return json_variant_new_string(ret, d->string);
2313
4618660d 2314 case TABLE_STRV:
b0e3d799 2315 case TABLE_STRV_WRAPPED:
4618660d
YW
2316 return json_variant_new_array_strv(ret, d->strv);
2317
31ac2357
LP
2318 case TABLE_BOOLEAN:
2319 return json_variant_new_boolean(ret, d->boolean);
2320
2321 case TABLE_TIMESTAMP:
c5bbb2b5
YW
2322 case TABLE_TIMESTAMP_UTC:
2323 case TABLE_TIMESTAMP_RELATIVE:
31ac2357
LP
2324 if (d->timestamp == USEC_INFINITY)
2325 return json_variant_new_null(ret);
2326
2327 return json_variant_new_unsigned(ret, d->timestamp);
2328
2329 case TABLE_TIMESPAN:
ba99f19c 2330 case TABLE_TIMESPAN_MSEC:
31ac2357
LP
2331 if (d->timespan == USEC_INFINITY)
2332 return json_variant_new_null(ret);
2333
2334 return json_variant_new_unsigned(ret, d->timespan);
2335
2336 case TABLE_SIZE:
9ff27e64 2337 case TABLE_BPS:
2f665f24 2338 if (d->size == (uint64_t) -1)
31ac2357
LP
2339 return json_variant_new_null(ret);
2340
2341 return json_variant_new_unsigned(ret, d->size);
2342
d5538326
YW
2343 case TABLE_INT:
2344 return json_variant_new_integer(ret, d->int_val);
2345
e74294c3
YW
2346 case TABLE_INT8:
2347 return json_variant_new_integer(ret, d->int8);
2348
2349 case TABLE_INT16:
2350 return json_variant_new_integer(ret, d->int16);
2351
d5538326
YW
2352 case TABLE_INT32:
2353 return json_variant_new_integer(ret, d->int32);
2354
2355 case TABLE_INT64:
2356 return json_variant_new_integer(ret, d->int64);
2357
2358 case TABLE_UINT:
2359 return json_variant_new_unsigned(ret, d->uint_val);
2360
e74294c3
YW
2361 case TABLE_UINT8:
2362 return json_variant_new_unsigned(ret, d->uint8);
2363
2364 case TABLE_UINT16:
2365 return json_variant_new_unsigned(ret, d->uint16);
2366
31ac2357
LP
2367 case TABLE_UINT32:
2368 return json_variant_new_unsigned(ret, d->uint32);
2369
2370 case TABLE_UINT64:
2371 return json_variant_new_unsigned(ret, d->uint64);
2372
2373 case TABLE_PERCENT:
2374 return json_variant_new_integer(ret, d->percent);
2375
8390d391
YW
2376 case TABLE_IFINDEX:
2377 return json_variant_new_integer(ret, d->ifindex);
2378
a7a257cd
YW
2379 case TABLE_IN_ADDR:
2380 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
2381
2382 case TABLE_IN6_ADDR:
2383 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
2384
137688df
LP
2385 case TABLE_ID128: {
2386 char buf[SD_ID128_STRING_MAX];
2387 return json_variant_new_string(ret, sd_id128_to_string(d->id128, buf));
2388 }
2389
2390 case TABLE_UUID: {
2391 char buf[ID128_UUID_STRING_MAX];
2392 return json_variant_new_string(ret, id128_to_uuid_string(d->id128, buf));
2393 }
2394
31ac2357
LP
2395 default:
2396 return -EINVAL;
2397 }
2398}
2399
e21b76cd 2400static char* string_to_json_field_name(const char *f) {
e21b76cd
LP
2401 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2402 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2403 * underscores and leave everything as is. */
2404
6f8ca84c 2405 char *c = strdup(f);
e21b76cd
LP
2406 if (!c)
2407 return NULL;
2408
6f8ca84c 2409 for (char *x = c; *x; x++)
e21b76cd
LP
2410 if (isspace(*x))
2411 *x = '_';
2412
2413 return c;
2414}
2415
31ac2357
LP
2416int table_to_json(Table *t, JsonVariant **ret) {
2417 JsonVariant **rows = NULL, **elements = NULL;
2418 _cleanup_free_ size_t *sorted = NULL;
6f8ca84c 2419 size_t n_rows, display_columns;
31ac2357
LP
2420 int r;
2421
2422 assert(t);
2423
2424 /* Ensure we have no incomplete rows */
2425 assert(t->n_cells % t->n_columns == 0);
2426
2427 n_rows = t->n_cells / t->n_columns;
2428 assert(n_rows > 0); /* at least the header row must be complete */
2429
2430 if (t->sort_map) {
2431 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2432
2433 sorted = new(size_t, n_rows);
2434 if (!sorted) {
2435 r = -ENOMEM;
2436 goto finish;
2437 }
2438
6f8ca84c 2439 for (size_t i = 0; i < n_rows; i++)
31ac2357
LP
2440 sorted[i] = i * t->n_columns;
2441
2442 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2443 }
2444
2445 if (t->display_map)
2446 display_columns = t->n_display_map;
2447 else
2448 display_columns = t->n_columns;
2449 assert(display_columns > 0);
2450
2451 elements = new0(JsonVariant*, display_columns * 2);
2452 if (!elements) {
2453 r = -ENOMEM;
2454 goto finish;
2455 }
2456
6f8ca84c 2457 for (size_t j = 0; j < display_columns; j++) {
e21b76cd
LP
2458 _cleanup_free_ char *mangled = NULL;
2459 const char *formatted;
31ac2357
LP
2460 TableData *d;
2461
2462 assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
2463
e21b76cd 2464 /* Field names must be strings, hence format whatever we got here as a string first */
b0e3d799 2465 formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
e21b76cd
LP
2466 if (!formatted) {
2467 r = -ENOMEM;
2468 goto finish;
2469 }
2470
2471 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2472 mangled = string_to_json_field_name(formatted);
2473 if (!mangled) {
2474 r = -ENOMEM;
2475 goto finish;
2476 }
2477
2478 r = json_variant_new_string(elements + j*2, mangled);
31ac2357
LP
2479 if (r < 0)
2480 goto finish;
2481 }
2482
2483 rows = new0(JsonVariant*, n_rows-1);
2484 if (!rows) {
2485 r = -ENOMEM;
2486 goto finish;
2487 }
2488
6f8ca84c 2489 for (size_t i = 1; i < n_rows; i++) {
31ac2357
LP
2490 TableData **row;
2491
2492 if (sorted)
2493 row = t->data + sorted[i];
2494 else
2495 row = t->data + i * t->n_columns;
2496
6f8ca84c 2497 for (size_t j = 0; j < display_columns; j++) {
31ac2357
LP
2498 TableData *d;
2499 size_t k;
2500
2501 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2502
2503 k = j*2+1;
2504 elements[k] = json_variant_unref(elements[k]);
2505
2506 r = table_data_to_json(d, elements + k);
2507 if (r < 0)
2508 goto finish;
2509 }
2510
2511 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2512 if (r < 0)
2513 goto finish;
2514 }
2515
2516 r = json_variant_new_array(ret, rows, n_rows - 1);
2517
2518finish:
2519 if (rows) {
2520 json_variant_unref_many(rows, n_rows-1);
2521 free(rows);
2522 }
2523
2524 if (elements) {
2525 json_variant_unref_many(elements, display_columns*2);
2526 free(elements);
2527 }
2528
2529 return r;
2530}
2531
2532int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2533 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2534 int r;
2535
2536 assert(t);
2537
10d71263
LP
2538 if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */
2539 return table_print(t, f);
2540
31ac2357
LP
2541 if (!f)
2542 f = stdout;
2543
2544 r = table_to_json(t, &v);
2545 if (r < 0)
2546 return r;
2547
2548 json_variant_dump(v, flags, f, NULL);
2549
2550 return fflush_and_check(f);
2551}
e676b4fc
LP
2552
2553int table_print_with_pager(
2554 Table *t,
2555 JsonFormatFlags json_format_flags,
2556 PagerFlags pager_flags,
2557 bool show_header) {
2558
2559 bool saved_header;
2560 int r;
2561
2562 assert(t);
2563
2564 /* A all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
2565 * the table header and logs about any error. */
2566
2567 if (json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
2568 (void) pager_open(pager_flags);
2569
2570 saved_header = t->header;
2571 t->header = show_header;
2572 r = table_print_json(t, stdout, json_format_flags);
2573 t->header = saved_header;
2574 if (r < 0)
2575 return table_log_print_error(r);
2576
2577 return 0;
2578}