]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
tree-wide: use memstream-util
[thirdparty/systemd.git] / src / shared / format-table.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <ctype.h>
4 #include <net/if.h>
5 #include <unistd.h>
6
7 #include "sd-id128.h"
8
9 #include "alloc-util.h"
10 #include "devnum-util.h"
11 #include "fd-util.h"
12 #include "fileio.h"
13 #include "format-table.h"
14 #include "format-util.h"
15 #include "fs-util.h"
16 #include "glyph-util.h"
17 #include "gunicode.h"
18 #include "id128-util.h"
19 #include "in-addr-util.h"
20 #include "memory-util.h"
21 #include "memstream-util.h"
22 #include "pager.h"
23 #include "parse-util.h"
24 #include "path-util.h"
25 #include "pretty-print.h"
26 #include "process-util.h"
27 #include "signal-util.h"
28 #include "sort-util.h"
29 #include "stat-util.h"
30 #include "string-util.h"
31 #include "strxcpyx.h"
32 #include "terminal-util.h"
33 #include "time-util.h"
34 #include "user-util.h"
35 #include "utf8.h"
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
58 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
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
69 typedef 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 */
75 size_t formatted_for_width; /* the width we tried to format for */
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
80 bool uppercase; /* Uppercase string on display */
81
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 */
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 */
84 char *url; /* A URL to use for a clickable hyperlink */
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];
94 char **strv;
95 int int_val;
96 int8_t int8;
97 int16_t int16;
98 int32_t int32;
99 int64_t int64;
100 unsigned uint_val;
101 uint8_t uint8;
102 uint16_t uint16;
103 uint32_t uint32;
104 uint64_t uint64;
105 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
106 int ifindex;
107 union in_addr_union address;
108 sd_id128_t id128;
109 uid_t uid;
110 gid_t gid;
111 pid_t pid;
112 mode_t mode;
113 dev_t devnum;
114 /* … add more here as we start supporting more cell data types … */
115 };
116 } TableData;
117
118 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
119 size_t i;
120
121 assert(cell);
122
123 i = PTR_TO_SIZE(cell);
124 assert(i > 0);
125
126 return i-1;
127 }
128
129 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
130 assert(index != SIZE_MAX);
131 return SIZE_TO_PTR(index + 1);
132 }
133
134 struct Table {
135 size_t n_columns;
136 size_t n_cells;
137
138 bool header; /* Whether to show the header row? */
139 bool vertical; /* Whether to field names are on the left rather than the first line */
140
141 TableErsatz ersatz; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */
142
143 size_t width; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
144 * width or less wide, but not wider. Otherwise the width to format this table in. */
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.) */
146
147 TableData **data;
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;
154
155 char **json_fields;
156 size_t n_json_fields;
157
158 bool *reverse_map;
159 };
160
161 Table *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,
173 .width = SIZE_MAX,
174 .cell_height_max = SIZE_MAX,
175 .ersatz = TABLE_ERSATZ_EMPTY,
176 };
177
178 return TAKE_PTR(t);
179 }
180
181 Table *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 (;;) {
191 if (!va_arg(ap, const char*))
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
202 va_start(ap, first_header);
203 for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
204 TableCell *cell;
205
206 r = table_add_cell(t, &cell, TABLE_HEADER, h);
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
218 Table *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
229 if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
230 return NULL;
231
232 if (table_set_align_percent(t, cell, 100) < 0)
233 return NULL;
234
235 if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
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
244 static TableData *table_data_free(TableData *d) {
245 assert(d);
246
247 free(d->formatted);
248 free(d->url);
249
250 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
251 strv_free(d->strv);
252
253 return mfree(d);
254 }
255
256 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
257 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
258
259 Table *table_unref(Table *t) {
260 if (!t)
261 return NULL;
262
263 for (size_t i = 0; i < t->n_cells; i++)
264 table_data_unref(t->data[i]);
265
266 free(t->data);
267 free(t->display_map);
268 free(t->sort_map);
269 free(t->reverse_map);
270
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
276 return mfree(t);
277 }
278
279 static 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:
287 case TABLE_PATH:
288 case TABLE_PATH_BASENAME:
289 case TABLE_FIELD:
290 case TABLE_HEADER:
291 return strlen(data) + 1;
292
293 case TABLE_STRV:
294 case TABLE_STRV_WRAPPED:
295 return sizeof(char **);
296
297 case TABLE_BOOLEAN_CHECKMARK:
298 case TABLE_BOOLEAN:
299 return sizeof(bool);
300
301 case TABLE_TIMESTAMP:
302 case TABLE_TIMESTAMP_UTC:
303 case TABLE_TIMESTAMP_RELATIVE:
304 case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
305 case TABLE_TIMESTAMP_LEFT:
306 case TABLE_TIMESTAMP_DATE:
307 case TABLE_TIMESPAN:
308 case TABLE_TIMESPAN_MSEC:
309 case TABLE_TIMESPAN_DAY:
310 return sizeof(usec_t);
311
312 case TABLE_SIZE:
313 case TABLE_INT64:
314 case TABLE_UINT64:
315 case TABLE_UINT64_HEX:
316 case TABLE_BPS:
317 return sizeof(uint64_t);
318
319 case TABLE_INT32:
320 case TABLE_UINT32:
321 return sizeof(uint32_t);
322
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
331 case TABLE_INT:
332 case TABLE_UINT:
333 case TABLE_PERCENT:
334 case TABLE_IFINDEX:
335 case TABLE_SIGNAL:
336 return sizeof(int);
337
338 case TABLE_IN_ADDR:
339 return sizeof(struct in_addr);
340
341 case TABLE_IN6_ADDR:
342 return sizeof(struct in6_addr);
343
344 case TABLE_UUID:
345 case TABLE_ID128:
346 return sizeof(sd_id128_t);
347
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
355 case TABLE_MODE:
356 case TABLE_MODE_INODE_TYPE:
357 return sizeof(mode_t);
358
359 case TABLE_DEVNUM:
360 return sizeof(dev_t);
361
362 default:
363 assert_not_reached();
364 }
365 }
366
367 static 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,
375 unsigned ellipsize_percent,
376 bool uppercase) {
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;
398
399 if (d->uppercase != uppercase)
400 return false;
401
402 /* If a color/url is set, refuse to merge */
403 if (d->color || d->rgap_color)
404 return false;
405 if (d->url)
406 return false;
407
408 k = table_data_size(type, data);
409 l = table_data_size(d->type, d->data);
410 if (k != l)
411 return false;
412
413 return memcmp_safe(data, d->data, l) == 0;
414 }
415
416 static 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,
423 unsigned ellipsize_percent,
424 bool uppercase) {
425
426 _cleanup_free_ TableData *d = NULL;
427 size_t data_size;
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;
442 d->uppercase = uppercase;
443
444 if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
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);
452 }
453
454 int 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;
466 bool uppercase;
467 TableData *p;
468
469 assert(t);
470 assert(type >= 0);
471 assert(type < _TABLE_DATA_TYPE_MAX);
472
473 /* Special rule: patch NULL data fields to the empty field */
474 if (!data)
475 type = TABLE_EMPTY;
476
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 */
484 if (minimum_width == SIZE_MAX)
485 minimum_width = p ? p->minimum_width : 1;
486
487 if (weight == UINT_MAX)
488 weight = p ? p->weight : DEFAULT_WEIGHT;
489
490 if (align_percent == UINT_MAX)
491 align_percent = p ? p->align_percent : 0;
492
493 if (ellipsize_percent == UINT_MAX)
494 ellipsize_percent = p ? p->ellipsize_percent : 100;
495
496 assert(align_percent <= 100);
497 assert(ellipsize_percent <= 100);
498
499 uppercase = type == TABLE_HEADER;
500
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
504 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
505 d = table_data_ref(p);
506 else {
507 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
508 if (!d)
509 return -ENOMEM;
510 }
511
512 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
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
523 int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
524 _cleanup_free_ char *buffer = NULL;
525 va_list ap;
526 int r;
527
528 assert(t);
529 assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER));
530
531 va_start(ap, format);
532 r = vasprintf(&buffer, format, ap);
533 va_end(ap);
534 if (r < 0)
535 return -ENOMEM;
536
537 return table_add_cell(t, ret_cell, dt, buffer);
538 }
539
540 int 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
561 int 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
572 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
573 return -ENOMEM;
574
575 t->data[t->n_cells++] = table_data_ref(t->data[i]);
576 return 0;
577 }
578
579 static int table_dedup_cell(Table *t, TableCell *cell) {
580 _cleanup_free_ char *curl = NULL;
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
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,
612 od->ellipsize_percent,
613 od->uppercase);
614 if (!nd)
615 return -ENOMEM;
616
617 nd->color = od->color;
618 nd->rgap_color = od->rgap_color;
619 nd->url = TAKE_PTR(curl);
620
621 table_data_unref(od);
622 t->data[i] = nd;
623
624 assert(nd->n_ref == 1);
625
626 return 1;
627 }
628
629 static 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
647 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
648 int r;
649
650 assert(t);
651 assert(cell);
652
653 if (minimum_width == SIZE_MAX)
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
664 int 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
678 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
679 int r;
680
681 assert(t);
682 assert(cell);
683
684 if (weight == UINT_MAX)
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
695 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
696 int r;
697
698 assert(t);
699 assert(cell);
700
701 if (percent == UINT_MAX)
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
714 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
715 int r;
716
717 assert(t);
718 assert(cell);
719
720 if (percent == UINT_MAX)
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
733 int 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
747 int 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
761 int 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
781 int 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
802 int 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,
829 od->ellipsize_percent,
830 od->uppercase);
831 if (!nd)
832 return -ENOMEM;
833
834 nd->color = od->color;
835 nd->rgap_color = od->rgap_color;
836 nd->url = TAKE_PTR(curl);
837
838 table_data_unref(od);
839 t->data[i] = nd;
840
841 return 0;
842 }
843
844 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
845 TableCell *last_cell = NULL;
846 va_list ap;
847 int r;
848
849 assert(t);
850 assert(first_type >= 0);
851 assert(first_type < _TABLE_DATA_TYPE_MAX);
852
853 va_start(ap, first_type);
854
855 for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
856 const void *data;
857 union {
858 uint64_t size;
859 usec_t usec;
860 int int_val;
861 int8_t int8;
862 int16_t int16;
863 int32_t int32;
864 int64_t int64;
865 unsigned uint_val;
866 uint8_t uint8;
867 uint16_t uint16;
868 uint32_t uint32;
869 uint64_t uint64;
870 int percent;
871 int ifindex;
872 bool b;
873 union in_addr_union address;
874 sd_id128_t id128;
875 uid_t uid;
876 gid_t gid;
877 pid_t pid;
878 mode_t mode;
879 dev_t devnum;
880 } buffer;
881
882 switch (type) {
883
884 case TABLE_EMPTY:
885 data = NULL;
886 break;
887
888 case TABLE_STRING:
889 case TABLE_PATH:
890 case TABLE_PATH_BASENAME:
891 case TABLE_FIELD:
892 case TABLE_HEADER:
893 data = va_arg(ap, const char *);
894 break;
895
896 case TABLE_STRV:
897 case TABLE_STRV_WRAPPED:
898 data = va_arg(ap, char * const *);
899 break;
900
901 case TABLE_BOOLEAN_CHECKMARK:
902 case TABLE_BOOLEAN:
903 buffer.b = va_arg(ap, int);
904 data = &buffer.b;
905 break;
906
907 case TABLE_TIMESTAMP:
908 case TABLE_TIMESTAMP_UTC:
909 case TABLE_TIMESTAMP_RELATIVE:
910 case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
911 case TABLE_TIMESTAMP_LEFT:
912 case TABLE_TIMESTAMP_DATE:
913 case TABLE_TIMESPAN:
914 case TABLE_TIMESPAN_MSEC:
915 case TABLE_TIMESPAN_DAY:
916 buffer.usec = va_arg(ap, usec_t);
917 data = &buffer.usec;
918 break;
919
920 case TABLE_SIZE:
921 case TABLE_BPS:
922 buffer.size = va_arg(ap, uint64_t);
923 data = &buffer.size;
924 break;
925
926 case TABLE_INT:
927 case TABLE_SIGNAL:
928 buffer.int_val = va_arg(ap, int);
929 data = &buffer.int_val;
930 break;
931
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
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
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
983 case TABLE_UINT32:
984 buffer.uint32 = va_arg(ap, uint32_t);
985 data = &buffer.uint32;
986 break;
987
988 case TABLE_UINT64:
989 case TABLE_UINT64_HEX:
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
999 case TABLE_IFINDEX:
1000 buffer.ifindex = va_arg(ap, int);
1001 data = &buffer.ifindex;
1002 break;
1003
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
1014 case TABLE_UUID:
1015 case TABLE_ID128:
1016 buffer.id128 = va_arg(ap, sd_id128_t);
1017 data = &buffer.id128;
1018 break;
1019
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
1035 case TABLE_MODE:
1036 case TABLE_MODE_INODE_TYPE:
1037 buffer.mode = va_arg(ap, mode_t);
1038 data = &buffer.mode;
1039 break;
1040
1041 case TABLE_DEVNUM:
1042 buffer.devnum = va_arg(ap, dev_t);
1043 data = &buffer.devnum;
1044 break;
1045
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);
1050 goto check;
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);
1056 goto check;
1057 }
1058
1059 case TABLE_SET_WEIGHT: {
1060 unsigned w = va_arg(ap, unsigned);
1061 r = table_set_weight(t, last_cell, w);
1062 goto check;
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);
1068 goto check;
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);
1074 goto check;
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);
1080 goto check;
1081 }
1082
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);
1086 goto check;
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);
1099 goto check;
1100 }
1101
1102 case TABLE_SET_URL: {
1103 const char *u = va_arg(ap, const char*);
1104 r = table_set_url(t, last_cell, u);
1105 goto check;
1106 }
1107
1108 case TABLE_SET_UPPERCASE: {
1109 int u = va_arg(ap, int);
1110 r = table_set_uppercase(t, last_cell, u);
1111 goto check;
1112 }
1113
1114 case _TABLE_DATA_TYPE_MAX:
1115 /* Used as end marker */
1116 va_end(ap);
1117 return 0;
1118
1119 default:
1120 assert_not_reached();
1121 }
1122
1123 r = table_add_cell(t, &last_cell, type, data);
1124 check:
1125 if (r < 0) {
1126 va_end(ap);
1127 return r;
1128 }
1129 }
1130 }
1131
1132 void table_set_header(Table *t, bool b) {
1133 assert(t);
1134
1135 t->header = b;
1136 }
1137
1138 void table_set_width(Table *t, size_t width) {
1139 assert(t);
1140
1141 t->width = width;
1142 }
1143
1144 void table_set_cell_height_max(Table *t, size_t height) {
1145 assert(t);
1146 assert(height >= 1 || height == SIZE_MAX);
1147
1148 t->cell_height_max = height;
1149 }
1150
1151 void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
1152 assert(t);
1153 assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
1154
1155 t->ersatz = ersatz;
1156 }
1157
1158 static 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 }
1171 }
1172
1173 static int table_set_display_all(Table *t) {
1174 size_t *d;
1175
1176 assert(t);
1177
1178 /* Initialize the display map to the identity */
1179
1180 d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
1181 if (!d)
1182 return -ENOMEM;
1183
1184 for (size_t i = 0; i < t->n_columns; i++)
1185 d[i] = i;
1186
1187 t->display_map = d;
1188 t->n_display_map = t->n_columns;
1189
1190 return 0;
1191 }
1192
1193 int table_set_display_internal(Table *t, size_t first_column, ...) {
1194 size_t column;
1195 va_list ap;
1196
1197 assert(t);
1198
1199 column = first_column;
1200
1201 va_start(ap, first_column);
1202 for (;;) {
1203 assert(column < t->n_columns);
1204
1205 if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
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);
1213 if (column == SIZE_MAX)
1214 break;
1215
1216 }
1217 va_end(ap);
1218
1219 return 0;
1220 }
1221
1222 int table_set_sort_internal(Table *t, size_t first_column, ...) {
1223 size_t column;
1224 va_list ap;
1225
1226 assert(t);
1227
1228 column = first_column;
1229
1230 va_start(ap, first_column);
1231 for (;;) {
1232 assert(column < t->n_columns);
1233
1234 if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
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);
1242 if (column == SIZE_MAX)
1243 break;
1244 }
1245 va_end(ap);
1246
1247 return 0;
1248 }
1249
1250 int table_hide_column_from_display_internal(Table *t, ...) {
1251 size_t cur = 0;
1252 int r;
1253
1254 assert(t);
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
1263 for (size_t i = 0; i < t->n_display_map; i++) {
1264 bool listed = false;
1265 va_list ap;
1266
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)
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
1292 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1293 int r;
1294
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:
1306 case TABLE_FIELD:
1307 case TABLE_HEADER:
1308 return strcmp(a->string, b->string);
1309
1310 case TABLE_PATH:
1311 case TABLE_PATH_BASENAME:
1312 return path_compare(a->string, b->string);
1313
1314 case TABLE_STRV:
1315 case TABLE_STRV_WRAPPED:
1316 return strv_compare(a->strv, b->strv);
1317
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:
1326 case TABLE_TIMESTAMP_UTC:
1327 case TABLE_TIMESTAMP_RELATIVE:
1328 case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1329 case TABLE_TIMESTAMP_LEFT:
1330 case TABLE_TIMESTAMP_DATE:
1331 return CMP(a->timestamp, b->timestamp);
1332
1333 case TABLE_TIMESPAN:
1334 case TABLE_TIMESPAN_MSEC:
1335 case TABLE_TIMESPAN_DAY:
1336 return CMP(a->timespan, b->timespan);
1337
1338 case TABLE_SIZE:
1339 case TABLE_BPS:
1340 return CMP(a->size, b->size);
1341
1342 case TABLE_INT:
1343 case TABLE_SIGNAL:
1344 return CMP(a->int_val, b->int_val);
1345
1346 case TABLE_INT8:
1347 return CMP(a->int8, b->int8);
1348
1349 case TABLE_INT16:
1350 return CMP(a->int16, b->int16);
1351
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
1361 case TABLE_UINT8:
1362 return CMP(a->uint8, b->uint8);
1363
1364 case TABLE_UINT16:
1365 return CMP(a->uint16, b->uint16);
1366
1367 case TABLE_UINT32:
1368 return CMP(a->uint32, b->uint32);
1369
1370 case TABLE_UINT64:
1371 case TABLE_UINT64_HEX:
1372 return CMP(a->uint64, b->uint64);
1373
1374 case TABLE_PERCENT:
1375 return CMP(a->percent, b->percent);
1376
1377 case TABLE_IFINDEX:
1378 return CMP(a->ifindex, b->ifindex);
1379
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
1386 case TABLE_UUID:
1387 case TABLE_ID128:
1388 return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
1389
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
1399 case TABLE_MODE:
1400 case TABLE_MODE_INODE_TYPE:
1401 return CMP(a->mode, b->mode);
1402
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
1410 default:
1411 ;
1412 }
1413 }
1414
1415 /* Generic fallback using the original order in which the cells where added. */
1416 return CMP(index_a, index_b);
1417 }
1418
1419 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
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 */
1434 for (size_t i = 0; i < t->n_sort_map; i++) {
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)
1442 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1443 }
1444
1445 /* Order identical lines by the order there were originally added in */
1446 return CMP(*a, *b);
1447 }
1448
1449 static char* format_strv_width(char **strv, size_t column_width) {
1450 _cleanup_(memstream_done) MemStream m = {};
1451 FILE *f;
1452
1453 f = memstream_init(&m);
1454 if (!f)
1455 return NULL;
1456
1457 size_t position = 0;
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
1474 char *buf;
1475 if (memstream_finalize(&m, &buf, NULL) < 0)
1476 return NULL;
1477
1478 return buf;
1479 }
1480
1481 static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
1482 assert(d);
1483
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))
1487 return d->formatted;
1488
1489 switch (d->type) {
1490 case TABLE_EMPTY:
1491 return table_ersatz_string(t);
1492
1493 case TABLE_STRING:
1494 case TABLE_PATH:
1495 case TABLE_PATH_BASENAME:
1496 case TABLE_FIELD:
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
1506 if (d->uppercase && !avoid_uppercasing) {
1507 d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
1508 if (!d->formatted)
1509 return NULL;
1510
1511 char *q = d->formatted;
1512 for (const char *p = s; *p; p++)
1513 *(q++) = (char) toupper((unsigned char) *p);
1514
1515 if (d->type == TABLE_FIELD)
1516 *(q++) = ':';
1517
1518 *q = 0;
1519 return d->formatted;
1520 } else if (d->type == TABLE_FIELD) {
1521 d->formatted = strjoin(s, ":");
1522 if (!d->formatted)
1523 return NULL;
1524
1525 return d->formatted;
1526 }
1527
1528 if (bn) {
1529 d->formatted = TAKE_PTR(bn);
1530 return d->formatted;
1531 }
1532
1533 return d->string;
1534 }
1535
1536 case TABLE_STRV:
1537 if (strv_isempty(d->strv))
1538 return table_ersatz_string(t);
1539
1540 d->formatted = strv_join(d->strv, "\n");
1541 if (!d->formatted)
1542 return NULL;
1543 break;
1544
1545 case TABLE_STRV_WRAPPED: {
1546 if (strv_isempty(d->strv))
1547 return table_ersatz_string(t);
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
1561 case TABLE_BOOLEAN:
1562 return yes_no(d->boolean);
1563
1564 case TABLE_BOOLEAN_CHECKMARK:
1565 return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
1566
1567 case TABLE_TIMESTAMP:
1568 case TABLE_TIMESTAMP_UTC:
1569 case TABLE_TIMESTAMP_RELATIVE:
1570 case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1571 case TABLE_TIMESTAMP_LEFT:
1572 case TABLE_TIMESTAMP_DATE: {
1573 _cleanup_free_ char *p = NULL;
1574 char *ret;
1575
1576 p = new(char,
1577 IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1578 FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1579 if (!p)
1580 return NULL;
1581
1582 if (d->type == TABLE_TIMESTAMP)
1583 ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1584 else if (d->type == TABLE_TIMESTAMP_UTC)
1585 ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
1586 else if (d->type == TABLE_TIMESTAMP_DATE)
1587 ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
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);
1592 else
1593 ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
1594 d->timestamp, CLOCK_REALTIME,
1595 /* implicit_left = */ d->type == TABLE_TIMESTAMP_LEFT);
1596 if (!ret)
1597 return "-";
1598
1599 d->formatted = TAKE_PTR(p);
1600 break;
1601 }
1602
1603 case TABLE_TIMESPAN:
1604 case TABLE_TIMESPAN_MSEC:
1605 case TABLE_TIMESPAN_DAY: {
1606 _cleanup_free_ char *p = NULL;
1607
1608 p = new(char, FORMAT_TIMESPAN_MAX);
1609 if (!p)
1610 return NULL;
1611
1612 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1613 d->type == TABLE_TIMESPAN ? 0 :
1614 d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1615 return "-";
1616
1617 d->formatted = TAKE_PTR(p);
1618 break;
1619 }
1620
1621 case TABLE_SIZE: {
1622 _cleanup_free_ char *p = NULL;
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))
1629 return table_ersatz_string(t);
1630
1631 d->formatted = TAKE_PTR(p);
1632 break;
1633 }
1634
1635 case TABLE_BPS: {
1636 _cleanup_free_ char *p = NULL;
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))
1644 return table_ersatz_string(t);
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
1653 case TABLE_INT: {
1654 _cleanup_free_ char *p = NULL;
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
1665 case TABLE_INT8: {
1666 _cleanup_free_ char *p = NULL;
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: {
1678 _cleanup_free_ char *p = NULL;
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
1689 case TABLE_INT32: {
1690 _cleanup_free_ char *p = NULL;
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: {
1702 _cleanup_free_ char *p = NULL;
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: {
1714 _cleanup_free_ char *p = NULL;
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
1725 case TABLE_UINT8: {
1726 _cleanup_free_ char *p = NULL;
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: {
1738 _cleanup_free_ char *p = NULL;
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
1749 case TABLE_UINT32: {
1750 _cleanup_free_ char *p = NULL;
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
1761 case TABLE_UINT64: {
1762 _cleanup_free_ char *p = NULL;
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
1773 case TABLE_UINT64_HEX: {
1774 _cleanup_free_ char *p = NULL;
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
1785 case TABLE_PERCENT: {
1786 _cleanup_free_ char *p = NULL;
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
1797 case TABLE_IFINDEX: {
1798 _cleanup_free_ char *p = NULL;
1799
1800 if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
1801 return NULL;
1802
1803 d->formatted = TAKE_PTR(p);
1804 break;
1805 }
1806
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
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
1833 p = new(char, SD_ID128_UUID_STRING_MAX);
1834 if (!p)
1835 return NULL;
1836
1837 d->formatted = sd_id128_to_uuid_string(d->id128, p);
1838 break;
1839 }
1840
1841 case TABLE_UID: {
1842 char *p;
1843
1844 if (!uid_is_valid(d->uid))
1845 return table_ersatz_string(t);
1846
1847 p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1848 if (!p)
1849 return NULL;
1850 sprintf(p, UID_FMT, d->uid);
1851
1852 d->formatted = p;
1853 break;
1854 }
1855
1856 case TABLE_GID: {
1857 char *p;
1858
1859 if (!gid_is_valid(d->gid))
1860 return table_ersatz_string(t);
1861
1862 p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1863 if (!p)
1864 return NULL;
1865 sprintf(p, GID_FMT, d->gid);
1866
1867 d->formatted = p;
1868 break;
1869 }
1870
1871 case TABLE_PID: {
1872 char *p;
1873
1874 if (!pid_is_valid(d->pid))
1875 return table_ersatz_string(t);
1876
1877 p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
1878 if (!p)
1879 return NULL;
1880 sprintf(p, PID_FMT, d->pid);
1881
1882 d->formatted = p;
1883 break;
1884 }
1885
1886 case TABLE_SIGNAL: {
1887 const char *suffix;
1888 char *p;
1889
1890 suffix = signal_to_string(d->int_val);
1891 if (!suffix)
1892 return table_ersatz_string(t);
1893
1894 p = strjoin("SIG", suffix);
1895 if (!p)
1896 return NULL;
1897
1898 d->formatted = p;
1899 break;
1900 }
1901
1902 case TABLE_MODE: {
1903 char *p;
1904
1905 if (d->mode == MODE_INVALID)
1906 return table_ersatz_string(t);
1907
1908 p = new(char, 4 + 1);
1909 if (!p)
1910 return NULL;
1911
1912 sprintf(p, "%04o", d->mode & 07777);
1913 d->formatted = p;
1914 break;
1915 }
1916
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
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
1933 default:
1934 assert_not_reached();
1935 }
1936
1937 return d->formatted;
1938 }
1939
1940 static 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 }
1969 if (k == SIZE_MAX)
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
1986 static int table_data_requested_width_height(
1987 Table *table,
1988 TableData *d,
1989 size_t available_width,
1990 size_t *ret_width,
1991 size_t *ret_height,
1992 bool *have_soft) {
1993
1994 _cleanup_free_ char *truncated = NULL;
1995 bool truncation_applied = false;
1996 size_t width, height;
1997 const char *t;
1998 int r;
1999 bool soft = false;
2000
2001 t = table_data_format(table, d, false, available_width, &soft);
2002 if (!t)
2003 return -ENOMEM;
2004
2005 if (table->cell_height_max != SIZE_MAX) {
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;
2011
2012 t = truncated;
2013 }
2014
2015 r = console_width_height(t, &width, &height);
2016 if (r < 0)
2017 return r;
2018
2019 if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
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;
2029 if (have_soft && soft)
2030 *have_soft = true;
2031
2032 return truncation_applied;
2033 }
2034
2035 static 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;
2038 const char *p;
2039 char *ret;
2040 int r;
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
2047 old_length = strlen(str);
2048
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
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)
2074 return clickable ? TAKE_PTR(clickable) : strdup(str);
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
2080 ret = new(char, space + clickable_length + 1);
2081 if (!ret)
2082 return NULL;
2083
2084 for (size_t i = 0; i < lspace; i++)
2085 ret[i] = ' ';
2086 memcpy(ret + lspace, clickable ?: str, clickable_length);
2087 for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
2088 ret[i] = ' ';
2089
2090 ret[space + clickable_length] = 0;
2091 return ret;
2092 }
2093
2094 static 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. */
2101 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
2102 return strv_isempty(d->strv);
2103
2104 /* Note that an empty string we do not consider empty here! */
2105 return false;
2106 }
2107
2108 static 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 */
2115 if (table_data_isempty(d))
2116 return ansi_grey();
2117
2118 if (d->type == TABLE_FIELD)
2119 return ansi_bright_blue();
2120 if (d->type == TABLE_HEADER)
2121 return ansi_underline();
2122
2123 return NULL;
2124 }
2125
2126 static const char* table_data_rgap_color(TableData *d) {
2127 assert(d);
2128
2129 if (d->rgap_color)
2130 return d->rgap_color;
2131
2132 if (d->type == TABLE_HEADER)
2133 return ansi_underline();
2134
2135 return NULL;
2136 }
2137
2138 int table_print(Table *t, FILE *f) {
2139 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
2140 table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2141 *width = NULL;
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
2164 for (size_t i = 0; i < n_rows; i++)
2165 sorted[i] = i * t->n_columns;
2166
2167 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
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);
2180 column_weight = newa0(uint64_t, display_columns);
2181
2182 for (size_t j = 0; j < display_columns; j++) {
2183 minimum_width[j] = 1;
2184 maximum_width[j] = SIZE_MAX;
2185 }
2186
2187 for (unsigned pass = 0; pass < 2; pass++) {
2188 /* First pass: determine column sizes */
2189
2190 for (size_t j = 0; j < display_columns; j++)
2191 requested_width[j] = SIZE_MAX;
2192
2193 bool any_soft = false;
2194
2195 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2196 TableData **row;
2197
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;
2201
2202 for (size_t j = 0; j < display_columns; j++) {
2203 TableData *d;
2204 size_t req_width, req_height;
2205
2206 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2207
2208 r = table_data_requested_width_height(t, d,
2209 width ? width[j] : SIZE_MAX,
2210 &req_width, &req_height, &any_soft);
2211 if (r < 0)
2212 return r;
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;
2227
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 }
2237
2238 /* Determine the biggest width that any cell in this column would like to have */
2239 if (requested_width[j] == SIZE_MAX ||
2240 requested_width[j] < req_width)
2241 requested_width[j] = req_width;
2242
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;
2246
2247 /* Determine the maximum width any cell in this column needs */
2248 if (d->maximum_width != SIZE_MAX &&
2249 (maximum_width[j] == SIZE_MAX ||
2250 maximum_width[j] > d->maximum_width))
2251 maximum_width[j] = d->maximum_width;
2252
2253 /* Determine the full columns weight */
2254 column_weight[j] += d->weight;
2255 }
2256 }
2257
2258 /* One space between each column */
2259 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
2260
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];
2265
2266 table_minimum_width += minimum_width[j];
2267
2268 if (maximum_width[j] == SIZE_MAX)
2269 table_maximum_width = SIZE_MAX;
2270 else
2271 table_maximum_width += maximum_width[j];
2272
2273 table_requested_width += requested_width[j];
2274 }
2275
2276 /* Calculate effective table width */
2277 if (t->width != 0 && t->width != SIZE_MAX)
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;
2282 else
2283 table_effective_width = MIN(table_requested_width, columns());
2284
2285 if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
2286 table_effective_width = table_maximum_width;
2287
2288 if (table_effective_width < table_minimum_width)
2289 table_effective_width = table_minimum_width;
2290
2291 if (!width)
2292 width = newa(size_t, display_columns);
2293
2294 if (table_effective_width >= table_requested_width) {
2295 size_t extra;
2296
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. */
2299
2300 extra = table_effective_width - table_requested_width;
2301
2302 for (size_t j = 0; j < display_columns; j++) {
2303 size_t delta;
2304
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;
2309
2310 if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
2311 width[j] = maximum_width[j];
2312
2313 if (width[j] < minimum_width[j])
2314 width[j] = minimum_width[j];
2315
2316 delta = LESS_BY(width[j], requested_width[j]);
2317
2318 /* Subtract what we just added from the rest */
2319 if (extra > delta)
2320 extra -= delta;
2321 else
2322 extra = 0;
2323
2324 assert(weight_sum >= column_weight[j]);
2325 weight_sum -= column_weight[j];
2326 }
2327
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;
2334
2335 extra = table_effective_width - table_minimum_width;
2336
2337 for (size_t j = 0; j < display_columns; j++)
2338 width[j] = SIZE_MAX;
2339
2340 for (;;) {
2341 bool restart = false;
2342
2343 for (size_t j = 0; j < display_columns; j++) {
2344 size_t delta, w;
2345
2346 /* Did this column already get something assigned? If so, let's skip to the next */
2347 if (width[j] != SIZE_MAX)
2348 continue;
2349
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;
2354
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
2359 * it wanted for good early. */
2360
2361 w = requested_width[j];
2362 restart = true;
2363
2364 } else if (!finalize)
2365 continue;
2366
2367 width[j] = w;
2368
2369 assert(w >= minimum_width[j]);
2370 delta = w - minimum_width[j];
2371
2372 assert(delta <= extra);
2373 extra -= delta;
2374
2375 assert(weight_sum >= column_weight[j]);
2376 weight_sum -= column_weight[j];
2377
2378 if (restart && !finalize)
2379 break;
2380 }
2381
2382 if (finalize)
2383 break;
2384
2385 if (!restart)
2386 finalize = true;
2387 }
2388
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. */
2392 break;
2393 }
2394 }
2395
2396 /* Second pass: show output */
2397 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2398 size_t n_subline = 0;
2399 bool more_sublines;
2400 TableData **row;
2401
2402 if (sorted)
2403 row = t->data + sorted[i];
2404 else
2405 row = t->data + i * t->n_columns;
2406
2407 do {
2408 const char *gap_color = NULL;
2409 more_sublines = false;
2410
2411 for (size_t j = 0; j < display_columns; j++) {
2412 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
2413 bool lines_truncated = false;
2414 const char *field, *color = NULL;
2415 TableData *d;
2416 size_t l;
2417
2418 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2419
2420 field = table_data_format(t, d, false, width[j], NULL);
2421 if (!field)
2422 return -ENOMEM;
2423
2424 r = string_extract_line(field, n_subline, &extracted);
2425 if (r < 0)
2426 return r;
2427 if (r > 0) {
2428 /* There are more lines to come */
2429 if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
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
2452 * ellipsis. We first append it, but that might make our
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
2477 /* Drop trailing white spaces of last column when no cosmetics is set. */
2478 if (j == display_columns - 1 &&
2479 (!colors_enabled() || !table_data_color(d)) &&
2480 (!urlify_enabled() || !d->url))
2481 delete_trailing_chars(aligned, NULL);
2482
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
2499 if (colors_enabled() && gap_color)
2500 fputs(gap_color, f);
2501
2502 if (j > 0)
2503 fputc(' ', f); /* column separator left of cell */
2504
2505 if (colors_enabled()) {
2506 color = table_data_color(d);
2507
2508 /* Undo gap color */
2509 if (gap_color)
2510 fputs(ANSI_NORMAL, f);
2511
2512 if (color)
2513 fputs(color, f);
2514 }
2515
2516 fputs(field, f);
2517
2518 if (colors_enabled() && color)
2519 fputs(ANSI_NORMAL, f);
2520
2521 gap_color = table_data_rgap_color(d);
2522 }
2523
2524 fputc('\n', f);
2525 n_subline ++;
2526 } while (more_sublines);
2527 }
2528
2529 return fflush_and_check(f);
2530 }
2531
2532 int table_format(Table *t, char **ret) {
2533 _cleanup_(memstream_done) MemStream m = {};
2534 FILE *f;
2535 int r;
2536
2537 assert(t);
2538 assert(ret);
2539
2540 f = memstream_init(&m);
2541 if (!f)
2542 return -ENOMEM;
2543
2544 r = table_print(t, f);
2545 if (r < 0)
2546 return r;
2547
2548 return memstream_finalize(&m, ret, NULL);
2549 }
2550
2551 size_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
2559 size_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 }
2566
2567 int 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 }
2583
2584 TableCell *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 }
2598
2599 const 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
2611 const 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 }
2620
2621 static 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:
2629 case TABLE_PATH:
2630 case TABLE_PATH_BASENAME:
2631 case TABLE_FIELD:
2632 case TABLE_HEADER:
2633 return json_variant_new_string(ret, d->string);
2634
2635 case TABLE_STRV:
2636 case TABLE_STRV_WRAPPED:
2637 return json_variant_new_array_strv(ret, d->strv);
2638
2639 case TABLE_BOOLEAN_CHECKMARK:
2640 case TABLE_BOOLEAN:
2641 return json_variant_new_boolean(ret, d->boolean);
2642
2643 case TABLE_TIMESTAMP:
2644 case TABLE_TIMESTAMP_UTC:
2645 case TABLE_TIMESTAMP_RELATIVE:
2646 case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2647 case TABLE_TIMESTAMP_LEFT:
2648 case TABLE_TIMESTAMP_DATE:
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:
2655 case TABLE_TIMESPAN_MSEC:
2656 case TABLE_TIMESPAN_DAY:
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:
2663 case TABLE_BPS:
2664 if (d->size == UINT64_MAX)
2665 return json_variant_new_null(ret);
2666
2667 return json_variant_new_unsigned(ret, d->size);
2668
2669 case TABLE_INT:
2670 return json_variant_new_integer(ret, d->int_val);
2671
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
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
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
2693 case TABLE_UINT32:
2694 return json_variant_new_unsigned(ret, d->uint32);
2695
2696 case TABLE_UINT64:
2697 case TABLE_UINT64_HEX:
2698 return json_variant_new_unsigned(ret, d->uint64);
2699
2700 case TABLE_PERCENT:
2701 return json_variant_new_integer(ret, d->percent);
2702
2703 case TABLE_IFINDEX:
2704 if (d->ifindex <= 0)
2705 return json_variant_new_null(ret);
2706
2707 return json_variant_new_integer(ret, d->ifindex);
2708
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
2715 case TABLE_ID128:
2716 return json_variant_new_id128(ret, d->id128);
2717
2718 case TABLE_UUID:
2719 return json_variant_new_uuid(ret, d->id128);
2720
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
2745 case TABLE_MODE:
2746 case TABLE_MODE_INODE_TYPE:
2747 if (d->mode == MODE_INVALID)
2748 return json_variant_new_null(ret);
2749
2750 return json_variant_new_unsigned(ret, d->mode);
2751
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
2760 default:
2761 return -EINVAL;
2762 }
2763 }
2764
2765 static char* string_to_json_field_name(const char *f) {
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
2770 char *c = strdup(f);
2771 if (!c)
2772 return NULL;
2773
2774 for (char *x = c; *x; x++)
2775 if (isspace(*x))
2776 *x = '_';
2777
2778 return c;
2779 }
2780
2781 static 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
2805 static const char *table_get_json_field_name(Table *t, size_t idx) {
2806 assert(t);
2807
2808 return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
2809 }
2810
2811 static int table_to_json_regular(Table *t, JsonVariant **ret) {
2812 JsonVariant **rows = NULL, **elements = NULL;
2813 _cleanup_free_ size_t *sorted = NULL;
2814 size_t n_rows, display_columns;
2815 int r;
2816
2817 assert(t);
2818 assert(!t->vertical);
2819
2820 /* Ensure we have no incomplete rows */
2821 assert(t->n_columns > 0);
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
2836 for (size_t i = 0; i < n_rows; i++)
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
2854 for (size_t j = 0; j < display_columns; j++) {
2855 _cleanup_free_ char *mangled = NULL;
2856 const char *n;
2857 size_t c;
2858
2859 c = t->display_map ? t->display_map[j] : j;
2860
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) {
2864 r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
2865 if (r < 0)
2866 goto finish;
2867
2868 n = mangled;
2869 }
2870
2871 r = json_variant_new_string(elements + j*2, n);
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
2882 for (size_t i = 1; i < n_rows; i++) {
2883 TableData **row;
2884
2885 if (sorted)
2886 row = t->data + sorted[i];
2887 else
2888 row = t->data + i * t->n_columns;
2889
2890 for (size_t j = 0; j < display_columns; j++) {
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
2911 finish:
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
2925 static 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) {
2953 r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2954 if (r < 0)
2955 goto finish;
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
2971 finish:
2972 if (elements) {
2973 json_variant_unref_many(elements, n_elements);
2974 free(elements);
2975 }
2976
2977 return r;
2978 }
2979
2980 int 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
2989 int 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
2995 if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */
2996 return table_print(t, f);
2997
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 }
3009
3010 int 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
3021 /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
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))
3025 pager_open(pager_flags);
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 }
3036
3037 int table_set_json_field_name(Table *t, size_t idx, const char *name) {
3038 int r;
3039
3040 assert(t);
3041
3042 if (name) {
3043 size_t m;
3044
3045 m = MAX(idx + 1, t->n_json_fields);
3046 if (!GREEDY_REALLOC0(t->json_fields, m))
3047 return -ENOMEM;
3048
3049 r = free_and_strdup(t->json_fields + idx, name);
3050 if (r < 0)
3051 return r;
3052
3053 t->n_json_fields = m;
3054 return r;
3055 } else {
3056 if (idx >= t->n_json_fields)
3057 return 0;
3058
3059 t->json_fields[idx] = mfree(t->json_fields[idx]);
3060 return 1;
3061 }
3062 }