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