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