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