1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
10 #include "devnum-util.h"
13 #include "format-table.h"
14 #include "format-util.h"
16 #include "glyph-util.h"
18 #include "id128-util.h"
19 #include "in-addr-util.h"
20 #include "memory-util.h"
21 #include "memstream-util.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"
32 #include "terminal-util.h"
33 #include "time-util.h"
34 #include "user-util.h"
37 #define DEFAULT_WEIGHT 100
40 A few notes on implementation details:
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.
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.
50 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
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.
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
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.
69 typedef struct TableData
{
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 */
80 bool uppercase
; /* Uppercase string on display */
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 */
88 uint8_t data
[0]; /* data is generic array */
105 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
107 union in_addr_union address
;
114 /* … add more here as we start supporting more cell data types … */
118 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
123 i
= PTR_TO_SIZE(cell
);
129 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
130 assert(index
!= SIZE_MAX
);
131 return SIZE_TO_PTR(index
+ 1);
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 */
141 TableErsatz ersatz
; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */
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.) */
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
;
152 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
156 size_t n_json_fields
;
161 Table
*table_new_raw(size_t n_columns
) {
162 _cleanup_(table_unrefp
) Table
*t
= NULL
;
164 assert(n_columns
> 0);
170 *t
= (struct Table
) {
171 .n_columns
= n_columns
,
174 .cell_height_max
= SIZE_MAX
,
175 .ersatz
= TABLE_ERSATZ_EMPTY
,
181 Table
*table_new_internal(const char *first_header
, ...) {
182 _cleanup_(table_unrefp
) Table
*t
= NULL
;
183 size_t n_columns
= 1;
187 assert(first_header
);
189 va_start(ap
, first_header
);
191 if (!va_arg(ap
, const char*))
198 t
= table_new_raw(n_columns
);
202 va_start(ap
, first_header
);
203 for (const char *h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
206 r
= table_add_cell(t
, &cell
, TABLE_HEADER
, h
);
214 assert(t
->n_columns
== t
->n_cells
);
218 Table
*table_new_vertical(void) {
219 _cleanup_(table_unrefp
) Table
*t
= NULL
;
222 t
= table_new_raw(2);
229 if (table_add_cell(t
, &cell
, TABLE_HEADER
, "key") < 0)
232 if (table_set_align_percent(t
, cell
, 100) < 0)
235 if (table_add_cell(t
, &cell
, TABLE_HEADER
, "value") < 0)
238 if (table_set_align_percent(t
, cell
, 0) < 0)
244 static TableData
*table_data_free(TableData
*d
) {
250 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
256 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
257 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
259 Table
*table_unref(Table
*t
) {
263 for (size_t i
= 0; i
< t
->n_cells
; i
++)
264 table_data_unref(t
->data
[i
]);
267 free(t
->display_map
);
269 free(t
->reverse_map
);
271 for (size_t i
= 0; i
< t
->n_json_fields
; i
++)
272 free(t
->json_fields
[i
]);
274 free(t
->json_fields
);
279 static size_t table_data_size(TableDataType type
, const void *data
) {
288 case TABLE_PATH_BASENAME
:
291 return strlen(data
) + 1;
294 case TABLE_STRV_WRAPPED
:
295 return sizeof(char **);
297 case TABLE_BOOLEAN_CHECKMARK
:
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
:
308 case TABLE_TIMESPAN_MSEC
:
309 case TABLE_TIMESPAN_DAY
:
310 return sizeof(usec_t
);
315 case TABLE_UINT64_HEX
:
317 return sizeof(uint64_t);
321 return sizeof(uint32_t);
325 return sizeof(uint16_t);
329 return sizeof(uint8_t);
339 return sizeof(struct in_addr
);
342 return sizeof(struct in6_addr
);
346 return sizeof(sd_id128_t
);
349 return sizeof(uid_t
);
351 return sizeof(gid_t
);
353 return sizeof(pid_t
);
356 case TABLE_MODE_INODE_TYPE
:
357 return sizeof(mode_t
);
360 return sizeof(dev_t
);
363 assert_not_reached();
367 static bool table_data_matches(
371 size_t minimum_width
,
372 size_t maximum_width
,
374 unsigned align_percent
,
375 unsigned ellipsize_percent
,
384 if (d
->minimum_width
!= minimum_width
)
387 if (d
->maximum_width
!= maximum_width
)
390 if (d
->weight
!= weight
)
393 if (d
->align_percent
!= align_percent
)
396 if (d
->ellipsize_percent
!= ellipsize_percent
)
399 if (d
->uppercase
!= uppercase
)
402 /* If a color/url is set, refuse to merge */
403 if (d
->color
|| d
->rgap_color
)
408 k
= table_data_size(type
, data
);
409 l
= table_data_size(d
->type
, d
->data
);
413 return memcmp_safe(data
, d
->data
, l
) == 0;
416 static TableData
*table_data_new(
419 size_t minimum_width
,
420 size_t maximum_width
,
422 unsigned align_percent
,
423 unsigned ellipsize_percent
,
426 _cleanup_free_ TableData
*d
= NULL
;
429 data_size
= table_data_size(type
, data
);
431 d
= malloc0(offsetof(TableData
, data
) + data_size
);
437 d
->minimum_width
= minimum_width
;
438 d
->maximum_width
= maximum_width
;
440 d
->align_percent
= align_percent
;
441 d
->ellipsize_percent
= ellipsize_percent
;
442 d
->uppercase
= uppercase
;
444 if (IN_SET(type
, TABLE_STRV
, TABLE_STRV_WRAPPED
)) {
445 d
->strv
= strv_copy(data
);
449 memcpy_safe(d
->data
, data
, data_size
);
454 int table_add_cell_full(
456 TableCell
**ret_cell
,
459 size_t minimum_width
,
460 size_t maximum_width
,
462 unsigned align_percent
,
463 unsigned ellipsize_percent
) {
465 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
471 assert(type
< _TABLE_DATA_TYPE_MAX
);
473 /* Special rule: patch NULL data fields to the empty field */
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
]);
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;
487 if (weight
== UINT_MAX
)
488 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
490 if (align_percent
== UINT_MAX
)
491 align_percent
= p
? p
->align_percent
: 0;
493 if (ellipsize_percent
== UINT_MAX
)
494 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
496 assert(align_percent
<= 100);
497 assert(ellipsize_percent
<= 100);
499 uppercase
= type
== TABLE_HEADER
;
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. */
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
);
507 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
, uppercase
);
512 if (!GREEDY_REALLOC(t
->data
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
516 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
518 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
523 int table_add_cell_stringf_full(Table
*t
, TableCell
**ret_cell
, TableDataType dt
, const char *format
, ...) {
524 _cleanup_free_
char *buffer
= NULL
;
529 assert(IN_SET(dt
, TABLE_STRING
, TABLE_PATH
, TABLE_PATH_BASENAME
, TABLE_FIELD
, TABLE_HEADER
));
531 va_start(ap
, format
);
532 r
= vasprintf(&buffer
, format
, ap
);
537 return table_add_cell(t
, ret_cell
, dt
, buffer
);
540 int table_fill_empty(Table
*t
, size_t until_column
) {
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. */
548 if (until_column
>= t
->n_columns
)
552 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
556 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
561 int table_dup_cell(Table
*t
, TableCell
*cell
) {
566 /* Add the data of the specified cell a second time as a new cell to the end. */
568 i
= TABLE_CELL_TO_INDEX(cell
);
572 if (!GREEDY_REALLOC(t
->data
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
575 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
579 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
580 _cleanup_free_
char *curl
= NULL
;
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 */
589 i
= TABLE_CELL_TO_INDEX(cell
);
593 assert_se(od
= t
->data
[i
]);
597 assert(od
->n_ref
> 1);
600 curl
= strdup(od
->url
);
612 od
->ellipsize_percent
,
617 nd
->color
= od
->color
;
618 nd
->rgap_color
= od
->rgap_color
;
619 nd
->url
= TAKE_PTR(curl
);
621 table_data_unref(od
);
624 assert(nd
->n_ref
== 1);
629 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
635 /* Get the data object of the specified cell, or NULL if it doesn't exist */
637 i
= TABLE_CELL_TO_INDEX(cell
);
642 assert(t
->data
[i
]->n_ref
> 0);
647 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
653 if (minimum_width
== SIZE_MAX
)
656 r
= table_dedup_cell(t
, cell
);
660 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
664 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
670 r
= table_dedup_cell(t
, cell
);
674 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
678 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
684 if (weight
== UINT_MAX
)
685 weight
= DEFAULT_WEIGHT
;
687 r
= table_dedup_cell(t
, cell
);
691 table_get_data(t
, cell
)->weight
= weight
;
695 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
701 if (percent
== UINT_MAX
)
704 assert(percent
<= 100);
706 r
= table_dedup_cell(t
, cell
);
710 table_get_data(t
, cell
)->align_percent
= percent
;
714 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
720 if (percent
== UINT_MAX
)
723 assert(percent
<= 100);
725 r
= table_dedup_cell(t
, cell
);
729 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
733 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
739 r
= table_dedup_cell(t
, cell
);
743 table_get_data(t
, cell
)->color
= empty_to_null(color
);
747 int table_set_rgap_color(Table
*t
, TableCell
*cell
, const char *color
) {
753 r
= table_dedup_cell(t
, cell
);
757 table_get_data(t
, cell
)->rgap_color
= empty_to_null(color
);
761 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
762 _cleanup_free_
char *copy
= NULL
;
774 r
= table_dedup_cell(t
, cell
);
778 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
781 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
788 r
= table_dedup_cell(t
, cell
);
792 assert_se(d
= table_get_data(t
, cell
));
794 if (d
->uppercase
== b
)
797 d
->formatted
= mfree(d
->formatted
);
802 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
803 _cleanup_free_
char *curl
= NULL
;
810 i
= TABLE_CELL_TO_INDEX(cell
);
814 assert_se(od
= t
->data
[i
]);
817 curl
= strdup(od
->url
);
829 od
->ellipsize_percent
,
834 nd
->color
= od
->color
;
835 nd
->rgap_color
= od
->rgap_color
;
836 nd
->url
= TAKE_PTR(curl
);
838 table_data_unref(od
);
844 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
845 TableCell
*last_cell
= NULL
;
850 assert(first_type
>= 0);
851 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
853 va_start(ap
, first_type
);
855 for (TableDataType type
= first_type
;; type
= va_arg(ap
, TableDataType
)) {
873 union in_addr_union address
;
890 case TABLE_PATH_BASENAME
:
893 data
= va_arg(ap
, const char *);
897 case TABLE_STRV_WRAPPED
:
898 data
= va_arg(ap
, char * const *);
901 case TABLE_BOOLEAN_CHECKMARK
:
903 buffer
.b
= va_arg(ap
, int);
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
:
914 case TABLE_TIMESPAN_MSEC
:
915 case TABLE_TIMESPAN_DAY
:
916 buffer
.usec
= va_arg(ap
, usec_t
);
922 buffer
.size
= va_arg(ap
, uint64_t);
928 buffer
.int_val
= va_arg(ap
, int);
929 data
= &buffer
.int_val
;
933 int x
= va_arg(ap
, int);
934 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
942 int x
= va_arg(ap
, int);
943 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
946 data
= &buffer
.int16
;
951 buffer
.int32
= va_arg(ap
, int32_t);
952 data
= &buffer
.int32
;
956 buffer
.int64
= va_arg(ap
, int64_t);
957 data
= &buffer
.int64
;
961 buffer
.uint_val
= va_arg(ap
, unsigned);
962 data
= &buffer
.uint_val
;
966 unsigned x
= va_arg(ap
, unsigned);
967 assert(x
<= UINT8_MAX
);
970 data
= &buffer
.uint8
;
975 unsigned x
= va_arg(ap
, unsigned);
976 assert(x
<= UINT16_MAX
);
979 data
= &buffer
.uint16
;
984 buffer
.uint32
= va_arg(ap
, uint32_t);
985 data
= &buffer
.uint32
;
989 case TABLE_UINT64_HEX
:
990 buffer
.uint64
= va_arg(ap
, uint64_t);
991 data
= &buffer
.uint64
;
995 buffer
.percent
= va_arg(ap
, int);
996 data
= &buffer
.percent
;
1000 buffer
.ifindex
= va_arg(ap
, int);
1001 data
= &buffer
.ifindex
;
1005 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
1006 data
= &buffer
.address
.in
;
1009 case TABLE_IN6_ADDR
:
1010 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
1011 data
= &buffer
.address
.in6
;
1016 buffer
.id128
= va_arg(ap
, sd_id128_t
);
1017 data
= &buffer
.id128
;
1021 buffer
.uid
= va_arg(ap
, uid_t
);
1026 buffer
.gid
= va_arg(ap
, gid_t
);
1031 buffer
.pid
= va_arg(ap
, pid_t
);
1036 case TABLE_MODE_INODE_TYPE
:
1037 buffer
.mode
= va_arg(ap
, mode_t
);
1038 data
= &buffer
.mode
;
1042 buffer
.devnum
= va_arg(ap
, dev_t
);
1043 data
= &buffer
.devnum
;
1046 case TABLE_SET_MINIMUM_WIDTH
: {
1047 size_t w
= va_arg(ap
, size_t);
1049 r
= table_set_minimum_width(t
, last_cell
, w
);
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
);
1059 case TABLE_SET_WEIGHT
: {
1060 unsigned w
= va_arg(ap
, unsigned);
1061 r
= table_set_weight(t
, last_cell
, w
);
1065 case TABLE_SET_ALIGN_PERCENT
: {
1066 unsigned p
= va_arg(ap
, unsigned);
1067 r
= table_set_align_percent(t
, last_cell
, p
);
1071 case TABLE_SET_ELLIPSIZE_PERCENT
: {
1072 unsigned p
= va_arg(ap
, unsigned);
1073 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
1077 case TABLE_SET_COLOR
: {
1078 const char *c
= va_arg(ap
, const char*);
1079 r
= table_set_color(t
, last_cell
, c
);
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
);
1089 case TABLE_SET_BOTH_COLORS
: {
1090 const char *c
= va_arg(ap
, const char*);
1092 r
= table_set_color(t
, last_cell
, c
);
1098 r
= table_set_rgap_color(t
, last_cell
, c
);
1102 case TABLE_SET_URL
: {
1103 const char *u
= va_arg(ap
, const char*);
1104 r
= table_set_url(t
, last_cell
, u
);
1108 case TABLE_SET_UPPERCASE
: {
1109 int u
= va_arg(ap
, int);
1110 r
= table_set_uppercase(t
, last_cell
, u
);
1114 case _TABLE_DATA_TYPE_MAX
:
1115 /* Used as end marker */
1120 assert_not_reached();
1123 r
= table_add_cell(t
, &last_cell
, type
, data
);
1132 void table_set_header(Table
*t
, bool b
) {
1138 void table_set_width(Table
*t
, size_t width
) {
1144 void table_set_cell_height_max(Table
*t
, size_t height
) {
1146 assert(height
>= 1 || height
== SIZE_MAX
);
1148 t
->cell_height_max
= height
;
1151 void table_set_ersatz_string(Table
*t
, TableErsatz ersatz
) {
1153 assert(ersatz
>= 0 && ersatz
< _TABLE_ERSATZ_MAX
);
1158 static const char* table_ersatz_string(const Table
*t
) {
1159 switch (t
->ersatz
) {
1160 case TABLE_ERSATZ_EMPTY
:
1162 case TABLE_ERSATZ_DASH
:
1164 case TABLE_ERSATZ_UNSET
:
1166 case TABLE_ERSATZ_NA
:
1169 assert_not_reached();
1173 static int table_set_display_all(Table
*t
) {
1178 /* Initialize the display map to the identity */
1180 d
= reallocarray(t
->display_map
, t
->n_columns
, sizeof(size_t));
1184 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1188 t
->n_display_map
= t
->n_columns
;
1193 int table_set_display_internal(Table
*t
, size_t first_column
, ...) {
1199 column
= first_column
;
1201 va_start(ap
, first_column
);
1203 assert(column
< t
->n_columns
);
1205 if (!GREEDY_REALLOC(t
->display_map
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1210 t
->display_map
[t
->n_display_map
++] = column
;
1212 column
= va_arg(ap
, size_t);
1213 if (column
== SIZE_MAX
)
1222 int table_set_sort_internal(Table
*t
, size_t first_column
, ...) {
1228 column
= first_column
;
1230 va_start(ap
, first_column
);
1232 assert(column
< t
->n_columns
);
1234 if (!GREEDY_REALLOC(t
->sort_map
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1239 t
->sort_map
[t
->n_sort_map
++] = column
;
1241 column
= va_arg(ap
, size_t);
1242 if (column
== SIZE_MAX
)
1250 int table_hide_column_from_display_internal(Table
*t
, ...) {
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
);
1263 for (size_t i
= 0; i
< t
->n_display_map
; i
++) {
1264 bool listed
= false;
1271 column
= va_arg(ap
, size_t);
1272 if (column
== SIZE_MAX
)
1274 if (column
== t
->display_map
[i
]) {
1284 t
->display_map
[cur
++] = t
->display_map
[i
];
1287 t
->n_display_map
= cur
;
1292 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1298 if (a
->type
== b
->type
) {
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 */
1308 return strcmp(a
->string
, b
->string
);
1311 case TABLE_PATH_BASENAME
:
1312 return path_compare(a
->string
, b
->string
);
1315 case TABLE_STRV_WRAPPED
:
1316 return strv_compare(a
->strv
, b
->strv
);
1319 if (!a
->boolean
&& b
->boolean
)
1321 if (a
->boolean
&& !b
->boolean
)
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
);
1333 case TABLE_TIMESPAN
:
1334 case TABLE_TIMESPAN_MSEC
:
1335 case TABLE_TIMESPAN_DAY
:
1336 return CMP(a
->timespan
, b
->timespan
);
1340 return CMP(a
->size
, b
->size
);
1344 return CMP(a
->int_val
, b
->int_val
);
1347 return CMP(a
->int8
, b
->int8
);
1350 return CMP(a
->int16
, b
->int16
);
1353 return CMP(a
->int32
, b
->int32
);
1356 return CMP(a
->int64
, b
->int64
);
1359 return CMP(a
->uint_val
, b
->uint_val
);
1362 return CMP(a
->uint8
, b
->uint8
);
1365 return CMP(a
->uint16
, b
->uint16
);
1368 return CMP(a
->uint32
, b
->uint32
);
1371 case TABLE_UINT64_HEX
:
1372 return CMP(a
->uint64
, b
->uint64
);
1375 return CMP(a
->percent
, b
->percent
);
1378 return CMP(a
->ifindex
, b
->ifindex
);
1381 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1383 case TABLE_IN6_ADDR
:
1384 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1388 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1391 return CMP(a
->uid
, b
->uid
);
1394 return CMP(a
->gid
, b
->gid
);
1397 return CMP(a
->pid
, b
->pid
);
1400 case TABLE_MODE_INODE_TYPE
:
1401 return CMP(a
->mode
, b
->mode
);
1404 r
= CMP(major(a
->devnum
), major(b
->devnum
));
1408 return CMP(minor(a
->devnum
), minor(b
->devnum
));
1415 /* Generic fallback using the original order in which the cells where added. */
1416 return CMP(index_a
, index_b
);
1419 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1423 assert(t
->sort_map
);
1425 /* Make sure the header stays at the beginning */
1426 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1428 if (*a
< t
->n_columns
)
1430 if (*b
< t
->n_columns
)
1433 /* Order other lines by the sorting map */
1434 for (size_t i
= 0; i
< t
->n_sort_map
; i
++) {
1437 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1438 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1440 r
= cell_data_compare(d
, *a
, dd
, *b
);
1442 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1445 /* Order identical lines by the order there were originally added in */
1449 static char* format_strv_width(char **strv
, size_t column_width
) {
1450 _cleanup_(memstream_done
) MemStream m
= {};
1453 f
= memstream_init(&m
);
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. */
1462 if (position
== 0) {
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
);
1469 fprintf(f
, "\n%s", *p
);
1475 if (memstream_finalize(&m
, &buf
, NULL
) < 0)
1481 static const char *table_data_format(Table
*t
, TableData
*d
, bool avoid_uppercasing
, size_t column_width
, bool *have_soft
) {
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
;
1491 return table_ersatz_string(t
);
1495 case TABLE_PATH_BASENAME
:
1497 case TABLE_HEADER
: {
1498 _cleanup_free_
char *bn
= NULL
;
1501 if (d
->type
== TABLE_PATH_BASENAME
)
1502 s
= path_extract_filename(d
->string
, &bn
) < 0 ? d
->string
: bn
;
1506 if (d
->uppercase
&& !avoid_uppercasing
) {
1507 d
->formatted
= new(char, strlen(s
) + (d
->type
== TABLE_FIELD
) + 1);
1511 char *q
= d
->formatted
;
1512 for (const char *p
= s
; *p
; p
++)
1513 *(q
++) = (char) toupper((unsigned char) *p
);
1515 if (d
->type
== TABLE_FIELD
)
1519 return d
->formatted
;
1520 } else if (d
->type
== TABLE_FIELD
) {
1521 d
->formatted
= strjoin(s
, ":");
1525 return d
->formatted
;
1529 d
->formatted
= TAKE_PTR(bn
);
1530 return d
->formatted
;
1537 if (strv_isempty(d
->strv
))
1538 return table_ersatz_string(t
);
1540 d
->formatted
= strv_join(d
->strv
, "\n");
1545 case TABLE_STRV_WRAPPED
: {
1546 if (strv_isempty(d
->strv
))
1547 return table_ersatz_string(t
);
1549 char *buf
= format_strv_width(d
->strv
, column_width
);
1553 free_and_replace(d
->formatted
, buf
);
1554 d
->formatted_for_width
= column_width
;
1562 return yes_no(d
->boolean
);
1564 case TABLE_BOOLEAN_CHECKMARK
:
1565 return special_glyph(d
->boolean
? SPECIAL_GLYPH_CHECK_MARK
: SPECIAL_GLYPH_CROSS_MARK
);
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
;
1577 IN_SET(d
->type
, TABLE_TIMESTAMP_RELATIVE
, TABLE_TIMESTAMP_RELATIVE_MONOTONIC
, TABLE_TIMESTAMP_LEFT
) ?
1578 FORMAT_TIMESTAMP_RELATIVE_MAX
: FORMAT_TIMESTAMP_MAX
);
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);
1593 ret
= format_timestamp_relative_full(p
, FORMAT_TIMESTAMP_RELATIVE_MAX
,
1594 d
->timestamp
, CLOCK_REALTIME
,
1595 /* implicit_left = */ d
->type
== TABLE_TIMESTAMP_LEFT
);
1599 d
->formatted
= TAKE_PTR(p
);
1603 case TABLE_TIMESPAN
:
1604 case TABLE_TIMESPAN_MSEC
:
1605 case TABLE_TIMESPAN_DAY
: {
1606 _cleanup_free_
char *p
= NULL
;
1608 p
= new(char, FORMAT_TIMESPAN_MAX
);
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
))
1617 d
->formatted
= TAKE_PTR(p
);
1622 _cleanup_free_
char *p
= NULL
;
1624 p
= new(char, FORMAT_BYTES_MAX
);
1628 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1629 return table_ersatz_string(t
);
1631 d
->formatted
= TAKE_PTR(p
);
1636 _cleanup_free_
char *p
= NULL
;
1639 p
= new(char, FORMAT_BYTES_MAX
+2);
1643 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1644 return table_ersatz_string(t
);
1647 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1649 d
->formatted
= TAKE_PTR(p
);
1654 _cleanup_free_
char *p
= NULL
;
1656 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1660 sprintf(p
, "%i", d
->int_val
);
1661 d
->formatted
= TAKE_PTR(p
);
1666 _cleanup_free_
char *p
= NULL
;
1668 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1672 sprintf(p
, "%" PRIi8
, d
->int8
);
1673 d
->formatted
= TAKE_PTR(p
);
1678 _cleanup_free_
char *p
= NULL
;
1680 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1684 sprintf(p
, "%" PRIi16
, d
->int16
);
1685 d
->formatted
= TAKE_PTR(p
);
1690 _cleanup_free_
char *p
= NULL
;
1692 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1696 sprintf(p
, "%" PRIi32
, d
->int32
);
1697 d
->formatted
= TAKE_PTR(p
);
1702 _cleanup_free_
char *p
= NULL
;
1704 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1708 sprintf(p
, "%" PRIi64
, d
->int64
);
1709 d
->formatted
= TAKE_PTR(p
);
1714 _cleanup_free_
char *p
= NULL
;
1716 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1720 sprintf(p
, "%u", d
->uint_val
);
1721 d
->formatted
= TAKE_PTR(p
);
1726 _cleanup_free_
char *p
= NULL
;
1728 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1732 sprintf(p
, "%" PRIu8
, d
->uint8
);
1733 d
->formatted
= TAKE_PTR(p
);
1737 case TABLE_UINT16
: {
1738 _cleanup_free_
char *p
= NULL
;
1740 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1744 sprintf(p
, "%" PRIu16
, d
->uint16
);
1745 d
->formatted
= TAKE_PTR(p
);
1749 case TABLE_UINT32
: {
1750 _cleanup_free_
char *p
= NULL
;
1752 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1756 sprintf(p
, "%" PRIu32
, d
->uint32
);
1757 d
->formatted
= TAKE_PTR(p
);
1761 case TABLE_UINT64
: {
1762 _cleanup_free_
char *p
= NULL
;
1764 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1768 sprintf(p
, "%" PRIu64
, d
->uint64
);
1769 d
->formatted
= TAKE_PTR(p
);
1773 case TABLE_UINT64_HEX
: {
1774 _cleanup_free_
char *p
= NULL
;
1776 p
= new(char, 16 + 1);
1780 sprintf(p
, "%" PRIx64
, d
->uint64
);
1781 d
->formatted
= TAKE_PTR(p
);
1785 case TABLE_PERCENT
: {
1786 _cleanup_free_
char *p
= NULL
;
1788 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1792 sprintf(p
, "%i%%" , d
->percent
);
1793 d
->formatted
= TAKE_PTR(p
);
1797 case TABLE_IFINDEX
: {
1798 _cleanup_free_
char *p
= NULL
;
1800 if (format_ifname_full_alloc(d
->ifindex
, FORMAT_IFNAME_IFINDEX
, &p
) < 0)
1803 d
->formatted
= TAKE_PTR(p
);
1808 case TABLE_IN6_ADDR
: {
1809 _cleanup_free_
char *p
= NULL
;
1811 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1812 &d
->address
, &p
) < 0)
1815 d
->formatted
= TAKE_PTR(p
);
1822 p
= new(char, SD_ID128_STRING_MAX
);
1826 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1833 p
= new(char, SD_ID128_UUID_STRING_MAX
);
1837 d
->formatted
= sd_id128_to_uuid_string(d
->id128
, p
);
1844 if (!uid_is_valid(d
->uid
))
1845 return table_ersatz_string(t
);
1847 p
= new(char, DECIMAL_STR_WIDTH(d
->uid
) + 1);
1850 sprintf(p
, UID_FMT
, d
->uid
);
1859 if (!gid_is_valid(d
->gid
))
1860 return table_ersatz_string(t
);
1862 p
= new(char, DECIMAL_STR_WIDTH(d
->gid
) + 1);
1865 sprintf(p
, GID_FMT
, d
->gid
);
1874 if (!pid_is_valid(d
->pid
))
1875 return table_ersatz_string(t
);
1877 p
= new(char, DECIMAL_STR_WIDTH(d
->pid
) + 1);
1880 sprintf(p
, PID_FMT
, d
->pid
);
1886 case TABLE_SIGNAL
: {
1890 suffix
= signal_to_string(d
->int_val
);
1892 return table_ersatz_string(t
);
1894 p
= strjoin("SIG", suffix
);
1905 if (d
->mode
== MODE_INVALID
)
1906 return table_ersatz_string(t
);
1908 p
= new(char, 4 + 1);
1912 sprintf(p
, "%04o", d
->mode
& 07777);
1917 case TABLE_MODE_INODE_TYPE
:
1919 if (d
->mode
== MODE_INVALID
)
1920 return table_ersatz_string(t
);
1922 return inode_type_to_string(d
->mode
);
1925 if (devnum_is_zero(d
->devnum
))
1926 return table_ersatz_string(t
);
1928 if (asprintf(&d
->formatted
, DEVNUM_FORMAT_STR
, DEVNUM_FORMAT_VAL(d
->devnum
)) < 0)
1934 assert_not_reached();
1937 return d
->formatted
;
1940 static int console_width_height(
1943 size_t *ret_height
) {
1945 size_t max_width
= 0, height
= 0;
1950 /* Determine the width and height in console character cells the specified string needs. */
1955 p
= strchr(s
, '\n');
1957 _cleanup_free_
char *c
= NULL
;
1959 c
= strndup(s
, p
- s
);
1963 k
= utf8_console_width(c
);
1966 k
= utf8_console_width(s
);
1975 } while (!isempty(s
));
1978 *ret_width
= max_width
;
1981 *ret_height
= height
;
1986 static int table_data_requested_width_height(
1989 size_t available_width
,
1994 _cleanup_free_
char *truncated
= NULL
;
1995 bool truncation_applied
= false;
1996 size_t width
, height
;
2001 t
= table_data_format(table
, d
, false, available_width
, &soft
);
2005 if (table
->cell_height_max
!= SIZE_MAX
) {
2006 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
2010 truncation_applied
= true;
2015 r
= console_width_height(t
, &width
, &height
);
2019 if (d
->maximum_width
!= SIZE_MAX
&& width
> d
->maximum_width
)
2020 width
= d
->maximum_width
;
2022 if (width
< d
->minimum_width
)
2023 width
= d
->minimum_width
;
2028 *ret_height
= height
;
2029 if (have_soft
&& soft
)
2032 return truncation_applied
;
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
;
2042 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
2045 assert(percent
<= 100);
2047 old_length
= strlen(str
);
2050 r
= terminal_urlify(url
, str
, &clickable
);
2054 clickable_length
= strlen(clickable
);
2056 clickable_length
= old_length
;
2058 /* Determine current width on screen */
2060 while (p
< str
+ old_length
) {
2063 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
2064 p
++, w
++; /* count invalid chars as 1 */
2068 p
= utf8_next_char(p
);
2069 w
+= unichar_iswide(c
) ? 2 : 1;
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
);
2076 /* How much spaces shall we add? An how much on the left side? */
2077 space
= new_length
- w
;
2078 lspace
= space
* percent
/ 100U;
2080 ret
= new(char, space
+ clickable_length
+ 1);
2084 for (size_t i
= 0; i
< lspace
; i
++)
2086 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
2087 for (size_t i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
2090 ret
[space
+ clickable_length
] = 0;
2094 static bool table_data_isempty(TableData
*d
) {
2097 if (d
->type
== TABLE_EMPTY
)
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
);
2104 /* Note that an empty string we do not consider empty here! */
2108 static const char* table_data_color(TableData
*d
) {
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
))
2118 if (d
->type
== TABLE_FIELD
)
2119 return ansi_bright_blue();
2120 if (d
->type
== TABLE_HEADER
)
2121 return ansi_underline();
2126 static const char* table_data_rgap_color(TableData
*d
) {
2130 return d
->rgap_color
;
2132 if (d
->type
== TABLE_HEADER
)
2133 return ansi_underline();
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
,
2142 _cleanup_free_
size_t *sorted
= NULL
;
2143 uint64_t *column_weight
, weight_sum
;
2151 /* Ensure we have no incomplete rows */
2152 assert(t
->n_cells
% t
->n_columns
== 0);
2154 n_rows
= t
->n_cells
/ t
->n_columns
;
2155 assert(n_rows
> 0); /* at least the header row must be complete */
2158 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2160 sorted
= new(size_t, n_rows
);
2164 for (size_t i
= 0; i
< n_rows
; i
++)
2165 sorted
[i
] = i
* t
->n_columns
;
2167 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2171 display_columns
= t
->n_display_map
;
2173 display_columns
= t
->n_columns
;
2175 assert(display_columns
> 0);
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
);
2182 for (size_t j
= 0; j
< display_columns
; j
++) {
2183 minimum_width
[j
] = 1;
2184 maximum_width
[j
] = SIZE_MAX
;
2187 for (unsigned pass
= 0; pass
< 2; pass
++) {
2188 /* First pass: determine column sizes */
2190 for (size_t j
= 0; j
< display_columns
; j
++)
2191 requested_width
[j
] = SIZE_MAX
;
2193 bool any_soft
= false;
2195 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
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
;
2202 for (size_t j
= 0; j
< display_columns
; j
++) {
2204 size_t req_width
, req_height
;
2206 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2208 r
= table_data_requested_width_height(t
, d
,
2209 width
? width
[j
] : SIZE_MAX
,
2210 &req_width
, &req_height
, &any_soft
);
2213 if (r
> 0) { /* Truncated because too many lines? */
2214 _cleanup_free_
char *last
= NULL
;
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. */
2222 field
= table_data_format(t
, d
, false,
2223 width
? width
[j
] : SIZE_MAX
,
2228 assert_se(t
->cell_height_max
> 0);
2229 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
2233 req_width
= MAX(req_width
,
2234 utf8_console_width(last
) +
2235 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
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
;
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
;
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
;
2253 /* Determine the full columns weight */
2254 column_weight
[j
] += d
->weight
;
2258 /* One space between each column */
2259 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
2261 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2263 for (size_t j
= 0; j
< display_columns
; j
++) {
2264 weight_sum
+= column_weight
[j
];
2266 table_minimum_width
+= minimum_width
[j
];
2268 if (maximum_width
[j
] == SIZE_MAX
)
2269 table_maximum_width
= SIZE_MAX
;
2271 table_maximum_width
+= maximum_width
[j
];
2273 table_requested_width
+= requested_width
[j
];
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
;
2283 table_effective_width
= MIN(table_requested_width
, columns());
2285 if (table_maximum_width
!= SIZE_MAX
&& table_effective_width
> table_maximum_width
)
2286 table_effective_width
= table_maximum_width
;
2288 if (table_effective_width
< table_minimum_width
)
2289 table_effective_width
= table_minimum_width
;
2292 width
= newa(size_t, display_columns
);
2294 if (table_effective_width
>= table_requested_width
) {
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. */
2300 extra
= table_effective_width
- table_requested_width
;
2302 for (size_t j
= 0; j
< display_columns
; j
++) {
2305 if (weight_sum
== 0)
2306 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
2308 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2310 if (maximum_width
[j
] != SIZE_MAX
&& width
[j
] > maximum_width
[j
])
2311 width
[j
] = maximum_width
[j
];
2313 if (width
[j
] < minimum_width
[j
])
2314 width
[j
] = minimum_width
[j
];
2316 delta
= LESS_BY(width
[j
], requested_width
[j
]);
2318 /* Subtract what we just added from the rest */
2324 assert(weight_sum
>= column_weight
[j
]);
2325 weight_sum
-= column_weight
[j
];
2328 break; /* Every column should be happy, no need to repeat calculations. */
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;
2335 extra
= table_effective_width
- table_minimum_width
;
2337 for (size_t j
= 0; j
< display_columns
; j
++)
2338 width
[j
] = SIZE_MAX
;
2341 bool restart
= false;
2343 for (size_t j
= 0; j
< display_columns
; j
++) {
2346 /* Did this column already get something assigned? If so, let's skip to the next */
2347 if (width
[j
] != SIZE_MAX
)
2350 if (weight_sum
== 0)
2351 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
2353 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
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. */
2361 w
= requested_width
[j
];
2364 } else if (!finalize
)
2369 assert(w
>= minimum_width
[j
]);
2370 delta
= w
- minimum_width
[j
];
2372 assert(delta
<= extra
);
2375 assert(weight_sum
>= column_weight
[j
]);
2376 weight_sum
-= column_weight
[j
];
2378 if (restart
&& !finalize
)
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
2396 /* Second pass: show output */
2397 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2398 size_t n_subline
= 0;
2403 row
= t
->data
+ sorted
[i
];
2405 row
= t
->data
+ i
* t
->n_columns
;
2408 const char *gap_color
= NULL
;
2409 more_sublines
= false;
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
;
2418 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2420 field
= table_data_format(t
, d
, false, width
[j
], NULL
);
2424 r
= string_extract_line(field
, n_subline
, &extracted
);
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 */
2432 lines_truncated
= true;
2437 l
= utf8_console_width(field
);
2439 /* Field is wider than allocated space. Let's ellipsize */
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
);
2448 if (lines_truncated
) {
2449 _cleanup_free_
char *padded
= NULL
;
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
2457 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2461 buffer
= ellipsize(padded
, width
[j
], 100);
2466 l
= utf8_console_width(field
);
2470 _cleanup_free_
char *aligned
= NULL
;
2471 /* Field is shorter than allocated space. Let's align with spaces */
2473 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
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
);
2483 free_and_replace(buffer
, aligned
);
2488 if (l
>= width
[j
] && d
->url
) {
2489 _cleanup_free_
char *clickable
= NULL
;
2491 r
= terminal_urlify(d
->url
, field
, &clickable
);
2495 free_and_replace(buffer
, clickable
);
2499 if (colors_enabled() && gap_color
)
2500 fputs(gap_color
, f
);
2503 fputc(' ', f
); /* column separator left of cell */
2505 if (colors_enabled()) {
2506 color
= table_data_color(d
);
2508 /* Undo gap color */
2510 fputs(ANSI_NORMAL
, f
);
2518 if (colors_enabled() && color
)
2519 fputs(ANSI_NORMAL
, f
);
2521 gap_color
= table_data_rgap_color(d
);
2526 } while (more_sublines
);
2529 return fflush_and_check(f
);
2532 int table_format(Table
*t
, char **ret
) {
2533 _cleanup_(memstream_done
) MemStream m
= {};
2540 f
= memstream_init(&m
);
2544 r
= table_print(t
, f
);
2548 return memstream_finalize(&m
, ret
, NULL
);
2551 size_t table_get_rows(Table
*t
) {
2555 assert(t
->n_columns
> 0);
2556 return t
->n_cells
/ t
->n_columns
;
2559 size_t table_get_columns(Table
*t
) {
2563 assert(t
->n_columns
> 0);
2564 return t
->n_columns
;
2567 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2569 assert(column
< t
->n_columns
);
2571 if (!t
->reverse_map
) {
2575 t
->reverse_map
= new0(bool, t
->n_columns
);
2576 if (!t
->reverse_map
)
2580 t
->reverse_map
[column
] = b
;
2584 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2589 if (column
>= t
->n_columns
)
2592 i
= row
* t
->n_columns
+ column
;
2593 if (i
>= t
->n_cells
)
2596 return TABLE_INDEX_TO_CELL(i
);
2599 const void *table_get(Table
*t
, TableCell
*cell
) {
2604 d
= table_get_data(t
, cell
);
2611 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2614 cell
= table_get_cell(t
, row
, column
);
2618 return table_get(t
, cell
);
2621 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2626 return json_variant_new_null(ret
);
2630 case TABLE_PATH_BASENAME
:
2633 return json_variant_new_string(ret
, d
->string
);
2636 case TABLE_STRV_WRAPPED
:
2637 return json_variant_new_array_strv(ret
, d
->strv
);
2639 case TABLE_BOOLEAN_CHECKMARK
:
2641 return json_variant_new_boolean(ret
, d
->boolean
);
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
);
2652 return json_variant_new_unsigned(ret
, d
->timestamp
);
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
);
2660 return json_variant_new_unsigned(ret
, d
->timespan
);
2664 if (d
->size
== UINT64_MAX
)
2665 return json_variant_new_null(ret
);
2667 return json_variant_new_unsigned(ret
, d
->size
);
2670 return json_variant_new_integer(ret
, d
->int_val
);
2673 return json_variant_new_integer(ret
, d
->int8
);
2676 return json_variant_new_integer(ret
, d
->int16
);
2679 return json_variant_new_integer(ret
, d
->int32
);
2682 return json_variant_new_integer(ret
, d
->int64
);
2685 return json_variant_new_unsigned(ret
, d
->uint_val
);
2688 return json_variant_new_unsigned(ret
, d
->uint8
);
2691 return json_variant_new_unsigned(ret
, d
->uint16
);
2694 return json_variant_new_unsigned(ret
, d
->uint32
);
2697 case TABLE_UINT64_HEX
:
2698 return json_variant_new_unsigned(ret
, d
->uint64
);
2701 return json_variant_new_integer(ret
, d
->percent
);
2704 if (d
->ifindex
<= 0)
2705 return json_variant_new_null(ret
);
2707 return json_variant_new_integer(ret
, d
->ifindex
);
2710 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2712 case TABLE_IN6_ADDR
:
2713 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2716 return json_variant_new_id128(ret
, d
->id128
);
2719 return json_variant_new_uuid(ret
, d
->id128
);
2722 if (!uid_is_valid(d
->uid
))
2723 return json_variant_new_null(ret
);
2725 return json_variant_new_integer(ret
, d
->uid
);
2728 if (!gid_is_valid(d
->gid
))
2729 return json_variant_new_null(ret
);
2731 return json_variant_new_integer(ret
, d
->gid
);
2734 if (!pid_is_valid(d
->pid
))
2735 return json_variant_new_null(ret
);
2737 return json_variant_new_integer(ret
, d
->pid
);
2740 if (!SIGNAL_VALID(d
->int_val
))
2741 return json_variant_new_null(ret
);
2743 return json_variant_new_integer(ret
, d
->int_val
);
2746 case TABLE_MODE_INODE_TYPE
:
2747 if (d
->mode
== MODE_INVALID
)
2748 return json_variant_new_null(ret
);
2750 return json_variant_new_unsigned(ret
, d
->mode
);
2753 if (devnum_is_zero(d
->devnum
))
2754 return json_variant_new_null(ret
);
2756 return json_build(ret
, JSON_BUILD_ARRAY(
2757 JSON_BUILD_UNSIGNED(major(d
->devnum
)),
2758 JSON_BUILD_UNSIGNED(minor(d
->devnum
))));
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. */
2770 char *c
= strdup(f
);
2774 for (char *x
= c
; *x
; x
++)
2781 static int table_make_json_field_name(Table
*t
, TableData
*d
, char **ret
) {
2782 _cleanup_free_
char *mangled
= NULL
;
2789 if (IN_SET(d
->type
, TABLE_HEADER
, TABLE_FIELD
))
2792 n
= table_data_format(t
, d
, /* avoid_uppercasing= */ true, SIZE_MAX
, NULL
);
2797 mangled
= string_to_json_field_name(n
);
2801 *ret
= TAKE_PTR(mangled
);
2805 static const char *table_get_json_field_name(Table
*t
, size_t idx
) {
2808 return idx
< t
->n_json_fields
? t
->json_fields
[idx
] : NULL
;
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
;
2818 assert(!t
->vertical
);
2820 /* Ensure we have no incomplete rows */
2821 assert(t
->n_columns
> 0);
2822 assert(t
->n_cells
% t
->n_columns
== 0);
2824 n_rows
= t
->n_cells
/ t
->n_columns
;
2825 assert(n_rows
> 0); /* at least the header row must be complete */
2828 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2830 sorted
= new(size_t, n_rows
);
2836 for (size_t i
= 0; i
< n_rows
; i
++)
2837 sorted
[i
] = i
* t
->n_columns
;
2839 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2843 display_columns
= t
->n_display_map
;
2845 display_columns
= t
->n_columns
;
2846 assert(display_columns
> 0);
2848 elements
= new0(JsonVariant
*, display_columns
* 2);
2854 for (size_t j
= 0; j
< display_columns
; j
++) {
2855 _cleanup_free_
char *mangled
= NULL
;
2859 c
= t
->display_map
? t
->display_map
[j
] : j
;
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
);
2864 r
= table_make_json_field_name(t
, ASSERT_PTR(t
->data
[c
]), &mangled
);
2871 r
= json_variant_new_string(elements
+ j
*2, n
);
2876 rows
= new0(JsonVariant
*, n_rows
-1);
2882 for (size_t i
= 1; i
< n_rows
; i
++) {
2886 row
= t
->data
+ sorted
[i
];
2888 row
= t
->data
+ i
* t
->n_columns
;
2890 for (size_t j
= 0; j
< display_columns
; j
++) {
2894 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2897 elements
[k
] = json_variant_unref(elements
[k
]);
2899 r
= table_data_to_json(d
, elements
+ k
);
2904 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2909 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2913 json_variant_unref_many(rows
, n_rows
-1);
2918 json_variant_unref_many(elements
, display_columns
*2);
2925 static int table_to_json_vertical(Table
*t
, JsonVariant
**ret
) {
2926 JsonVariant
**elements
= NULL
;
2927 size_t n_elements
= 0;
2931 assert(t
->vertical
);
2933 if (t
->n_columns
!= 2)
2936 /* Ensure we have no incomplete rows */
2937 assert(t
->n_cells
% t
->n_columns
== 0);
2939 elements
= new0(JsonVariant
*, t
->n_cells
);
2945 for (size_t i
= t
->n_columns
; i
< t
->n_cells
; i
++) {
2947 if (i
% t
->n_columns
== 0) {
2948 _cleanup_free_
char *mangled
= NULL
;
2951 n
= table_get_json_field_name(t
, i
/ t
->n_columns
- 1);
2953 r
= table_make_json_field_name(t
, ASSERT_PTR(t
->data
[i
]), &mangled
);
2960 r
= json_variant_new_string(elements
+ n_elements
, n
);
2962 r
= table_data_to_json(t
->data
[i
], elements
+ n_elements
);
2969 r
= json_variant_new_object(ret
, elements
, n_elements
);
2973 json_variant_unref_many(elements
, n_elements
);
2980 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2984 return table_to_json_vertical(t
, ret
);
2986 return table_to_json_regular(t
, ret
);
2989 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2990 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2995 if (flags
& JSON_FORMAT_OFF
) /* If JSON output is turned off, use regular output */
2996 return table_print(t
, f
);
3001 r
= table_to_json(t
, &v
);
3005 json_variant_dump(v
, flags
, f
, NULL
);
3007 return fflush_and_check(f
);
3010 int table_print_with_pager(
3012 JsonFormatFlags json_format_flags
,
3013 PagerFlags pager_flags
,
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. */
3024 if (json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
3025 pager_open(pager_flags
);
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
;
3032 return table_log_print_error(r
);
3037 int table_set_json_field_name(Table
*t
, size_t idx
, const char *name
) {
3045 m
= MAX(idx
+ 1, t
->n_json_fields
);
3046 if (!GREEDY_REALLOC0(t
->json_fields
, m
))
3049 r
= free_and_strdup(t
->json_fields
+ idx
, name
);
3053 t
->n_json_fields
= m
;
3056 if (idx
>= t
->n_json_fields
)
3059 t
->json_fields
[idx
] = mfree(t
->json_fields
[idx
]);