1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
12 #include "format-table.h"
13 #include "format-util.h"
15 #include "glyph-util.h"
17 #include "id128-util.h"
18 #include "in-addr-util.h"
19 #include "memory-util.h"
21 #include "parse-util.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "process-util.h"
25 #include "signal-util.h"
26 #include "sort-util.h"
27 #include "string-util.h"
29 #include "terminal-util.h"
30 #include "time-util.h"
31 #include "user-util.h"
35 #define DEFAULT_WEIGHT 100
38 A few notes on implementation details:
40 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
41 table. It can be easily converted to an index number and back.
43 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
44 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
45 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
46 outside only sees Table and TableCell.
48 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
51 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
52 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
53 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
54 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
56 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
57 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
58 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
59 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
62 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
63 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
64 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
67 typedef struct TableData
{
71 size_t minimum_width
; /* minimum width for the column */
72 size_t maximum_width
; /* maximum width for the column */
73 size_t formatted_for_width
; /* the width we tried to format for */
74 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
75 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
76 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
78 bool uppercase
; /* Uppercase string on display */
80 const char *color
; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
81 const char *rgap_color
; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
82 char *url
; /* A URL to use for a clickable hyperlink */
83 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
86 uint8_t data
[0]; /* data is generic array */
103 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
105 union in_addr_union address
;
111 /* … add more here as we start supporting more cell data types … */
115 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
120 i
= PTR_TO_SIZE(cell
);
126 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
127 assert(index
!= SIZE_MAX
);
128 return SIZE_TO_PTR(index
+ 1);
135 bool header
; /* Whether to show the header row? */
136 size_t width
; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
137 * width or less wide, but not wider. Otherwise the width to format this table in. */
138 size_t cell_height_max
; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If SIZE_MAX then no limit is set, the default. == 0 is not allowed.) */
142 size_t *display_map
; /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
143 size_t n_display_map
;
145 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
149 size_t n_json_fields
;
156 Table
*table_new_raw(size_t n_columns
) {
157 _cleanup_(table_unrefp
) Table
*t
= NULL
;
159 assert(n_columns
> 0);
165 *t
= (struct Table
) {
166 .n_columns
= n_columns
,
169 .cell_height_max
= SIZE_MAX
,
175 Table
*table_new_internal(const char *first_header
, ...) {
176 _cleanup_(table_unrefp
) Table
*t
= NULL
;
177 size_t n_columns
= 1;
181 assert(first_header
);
183 va_start(ap
, first_header
);
185 if (!va_arg(ap
, const char*))
192 t
= table_new_raw(n_columns
);
196 va_start(ap
, first_header
);
197 for (const char *h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
200 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
206 /* Make the table header uppercase */
207 r
= table_set_uppercase(t
, cell
, true);
215 assert(t
->n_columns
== t
->n_cells
);
219 static TableData
*table_data_free(TableData
*d
) {
225 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
231 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
232 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
234 Table
*table_unref(Table
*t
) {
238 for (size_t i
= 0; i
< t
->n_cells
; i
++)
239 table_data_unref(t
->data
[i
]);
242 free(t
->display_map
);
244 free(t
->reverse_map
);
245 free(t
->empty_string
);
247 for (size_t i
= 0; i
< t
->n_json_fields
; i
++)
248 free(t
->json_fields
[i
]);
250 free(t
->json_fields
);
255 static size_t table_data_size(TableDataType type
, const void *data
) {
264 return strlen(data
) + 1;
267 case TABLE_STRV_WRAPPED
:
268 return sizeof(char **);
270 case TABLE_BOOLEAN_CHECKMARK
:
274 case TABLE_TIMESTAMP
:
275 case TABLE_TIMESTAMP_UTC
:
276 case TABLE_TIMESTAMP_RELATIVE
:
278 case TABLE_TIMESPAN_MSEC
:
279 return sizeof(usec_t
);
284 case TABLE_UINT64_HEX
:
286 return sizeof(uint64_t);
290 return sizeof(uint32_t);
294 return sizeof(uint16_t);
298 return sizeof(uint8_t);
308 return sizeof(struct in_addr
);
311 return sizeof(struct in6_addr
);
315 return sizeof(sd_id128_t
);
318 return sizeof(uid_t
);
320 return sizeof(gid_t
);
322 return sizeof(pid_t
);
325 return sizeof(mode_t
);
328 assert_not_reached();
332 static bool table_data_matches(
336 size_t minimum_width
,
337 size_t maximum_width
,
339 unsigned align_percent
,
340 unsigned ellipsize_percent
) {
348 if (d
->minimum_width
!= minimum_width
)
351 if (d
->maximum_width
!= maximum_width
)
354 if (d
->weight
!= weight
)
357 if (d
->align_percent
!= align_percent
)
360 if (d
->ellipsize_percent
!= ellipsize_percent
)
363 /* If a color/url/uppercase flag is set, refuse to merge */
364 if (d
->color
|| d
->rgap_color
)
371 k
= table_data_size(type
, data
);
372 l
= table_data_size(d
->type
, d
->data
);
376 return memcmp_safe(data
, d
->data
, l
) == 0;
379 static TableData
*table_data_new(
382 size_t minimum_width
,
383 size_t maximum_width
,
385 unsigned align_percent
,
386 unsigned ellipsize_percent
) {
388 _cleanup_free_ TableData
*d
= NULL
;
391 data_size
= table_data_size(type
, data
);
393 d
= malloc0(offsetof(TableData
, data
) + data_size
);
399 d
->minimum_width
= minimum_width
;
400 d
->maximum_width
= maximum_width
;
402 d
->align_percent
= align_percent
;
403 d
->ellipsize_percent
= ellipsize_percent
;
405 if (IN_SET(type
, TABLE_STRV
, TABLE_STRV_WRAPPED
)) {
406 d
->strv
= strv_copy(data
);
410 memcpy_safe(d
->data
, data
, data_size
);
415 int table_add_cell_full(
417 TableCell
**ret_cell
,
420 size_t minimum_width
,
421 size_t maximum_width
,
423 unsigned align_percent
,
424 unsigned ellipsize_percent
) {
426 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
431 assert(type
< _TABLE_DATA_TYPE_MAX
);
433 /* Special rule: patch NULL data fields to the empty field */
437 /* Determine the cell adjacent to the current one, but one row up */
438 if (t
->n_cells
>= t
->n_columns
)
439 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
443 /* If formatting parameters are left unspecified, copy from the previous row */
444 if (minimum_width
== SIZE_MAX
)
445 minimum_width
= p
? p
->minimum_width
: 1;
447 if (weight
== UINT_MAX
)
448 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
450 if (align_percent
== UINT_MAX
)
451 align_percent
= p
? p
->align_percent
: 0;
453 if (ellipsize_percent
== UINT_MAX
)
454 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
456 assert(align_percent
<= 100);
457 assert(ellipsize_percent
<= 100);
459 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
460 * formatting. Let's see if we can reuse the cell data and ref it once more. */
462 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
463 d
= table_data_ref(p
);
465 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
470 if (!GREEDY_REALLOC(t
->data
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
474 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
476 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
481 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
482 _cleanup_free_
char *buffer
= NULL
;
486 va_start(ap
, format
);
487 r
= vasprintf(&buffer
, format
, ap
);
492 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
495 int table_fill_empty(Table
*t
, size_t until_column
) {
500 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
501 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
503 if (until_column
>= t
->n_columns
)
507 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
511 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
516 int table_dup_cell(Table
*t
, TableCell
*cell
) {
521 /* Add the data of the specified cell a second time as a new cell to the end. */
523 i
= TABLE_CELL_TO_INDEX(cell
);
527 if (!GREEDY_REALLOC(t
->data
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
530 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
534 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
535 _cleanup_free_
char *curl
= NULL
;
541 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
542 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
544 i
= TABLE_CELL_TO_INDEX(cell
);
548 assert_se(od
= t
->data
[i
]);
552 assert(od
->n_ref
> 1);
555 curl
= strdup(od
->url
);
567 od
->ellipsize_percent
);
571 nd
->color
= od
->color
;
572 nd
->rgap_color
= od
->rgap_color
;
573 nd
->url
= TAKE_PTR(curl
);
574 nd
->uppercase
= od
->uppercase
;
576 table_data_unref(od
);
579 assert(nd
->n_ref
== 1);
584 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
590 /* Get the data object of the specified cell, or NULL if it doesn't exist */
592 i
= TABLE_CELL_TO_INDEX(cell
);
597 assert(t
->data
[i
]->n_ref
> 0);
602 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
608 if (minimum_width
== SIZE_MAX
)
611 r
= table_dedup_cell(t
, cell
);
615 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
619 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
625 r
= table_dedup_cell(t
, cell
);
629 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
633 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
639 if (weight
== UINT_MAX
)
640 weight
= DEFAULT_WEIGHT
;
642 r
= table_dedup_cell(t
, cell
);
646 table_get_data(t
, cell
)->weight
= weight
;
650 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
656 if (percent
== UINT_MAX
)
659 assert(percent
<= 100);
661 r
= table_dedup_cell(t
, cell
);
665 table_get_data(t
, cell
)->align_percent
= percent
;
669 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
675 if (percent
== UINT_MAX
)
678 assert(percent
<= 100);
680 r
= table_dedup_cell(t
, cell
);
684 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
688 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
694 r
= table_dedup_cell(t
, cell
);
698 table_get_data(t
, cell
)->color
= empty_to_null(color
);
702 int table_set_rgap_color(Table
*t
, TableCell
*cell
, const char *color
) {
708 r
= table_dedup_cell(t
, cell
);
712 table_get_data(t
, cell
)->rgap_color
= empty_to_null(color
);
716 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
717 _cleanup_free_
char *copy
= NULL
;
729 r
= table_dedup_cell(t
, cell
);
733 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
736 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
743 r
= table_dedup_cell(t
, cell
);
747 assert_se(d
= table_get_data(t
, cell
));
749 if (d
->uppercase
== b
)
752 d
->formatted
= mfree(d
->formatted
);
757 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
758 _cleanup_free_
char *curl
= NULL
;
765 i
= TABLE_CELL_TO_INDEX(cell
);
769 assert_se(od
= t
->data
[i
]);
772 curl
= strdup(od
->url
);
784 od
->ellipsize_percent
);
788 nd
->color
= od
->color
;
789 nd
->rgap_color
= od
->rgap_color
;
790 nd
->url
= TAKE_PTR(curl
);
791 nd
->uppercase
= od
->uppercase
;
793 table_data_unref(od
);
799 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
800 TableCell
*last_cell
= NULL
;
805 assert(first_type
>= 0);
806 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
808 va_start(ap
, first_type
);
810 for (TableDataType type
= first_type
;; type
= va_arg(ap
, TableDataType
)) {
828 union in_addr_union address
;
844 data
= va_arg(ap
, const char *);
848 case TABLE_STRV_WRAPPED
:
849 data
= va_arg(ap
, char * const *);
852 case TABLE_BOOLEAN_CHECKMARK
:
854 buffer
.b
= va_arg(ap
, int);
858 case TABLE_TIMESTAMP
:
859 case TABLE_TIMESTAMP_UTC
:
860 case TABLE_TIMESTAMP_RELATIVE
:
862 case TABLE_TIMESPAN_MSEC
:
863 buffer
.usec
= va_arg(ap
, usec_t
);
869 buffer
.size
= va_arg(ap
, uint64_t);
875 buffer
.int_val
= va_arg(ap
, int);
876 data
= &buffer
.int_val
;
880 int x
= va_arg(ap
, int);
881 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
889 int x
= va_arg(ap
, int);
890 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
893 data
= &buffer
.int16
;
898 buffer
.int32
= va_arg(ap
, int32_t);
899 data
= &buffer
.int32
;
903 buffer
.int64
= va_arg(ap
, int64_t);
904 data
= &buffer
.int64
;
908 buffer
.uint_val
= va_arg(ap
, unsigned);
909 data
= &buffer
.uint_val
;
913 unsigned x
= va_arg(ap
, unsigned);
914 assert(x
<= UINT8_MAX
);
917 data
= &buffer
.uint8
;
922 unsigned x
= va_arg(ap
, unsigned);
923 assert(x
<= UINT16_MAX
);
926 data
= &buffer
.uint16
;
931 buffer
.uint32
= va_arg(ap
, uint32_t);
932 data
= &buffer
.uint32
;
936 case TABLE_UINT64_HEX
:
937 buffer
.uint64
= va_arg(ap
, uint64_t);
938 data
= &buffer
.uint64
;
942 buffer
.percent
= va_arg(ap
, int);
943 data
= &buffer
.percent
;
947 buffer
.ifindex
= va_arg(ap
, int);
948 data
= &buffer
.ifindex
;
952 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
953 data
= &buffer
.address
.in
;
957 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
958 data
= &buffer
.address
.in6
;
963 buffer
.id128
= va_arg(ap
, sd_id128_t
);
964 data
= &buffer
.id128
;
968 buffer
.uid
= va_arg(ap
, uid_t
);
973 buffer
.gid
= va_arg(ap
, gid_t
);
978 buffer
.pid
= va_arg(ap
, pid_t
);
983 buffer
.mode
= va_arg(ap
, mode_t
);
987 case TABLE_SET_MINIMUM_WIDTH
: {
988 size_t w
= va_arg(ap
, size_t);
990 r
= table_set_minimum_width(t
, last_cell
, w
);
994 case TABLE_SET_MAXIMUM_WIDTH
: {
995 size_t w
= va_arg(ap
, size_t);
996 r
= table_set_maximum_width(t
, last_cell
, w
);
1000 case TABLE_SET_WEIGHT
: {
1001 unsigned w
= va_arg(ap
, unsigned);
1002 r
= table_set_weight(t
, last_cell
, w
);
1006 case TABLE_SET_ALIGN_PERCENT
: {
1007 unsigned p
= va_arg(ap
, unsigned);
1008 r
= table_set_align_percent(t
, last_cell
, p
);
1012 case TABLE_SET_ELLIPSIZE_PERCENT
: {
1013 unsigned p
= va_arg(ap
, unsigned);
1014 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
1018 case TABLE_SET_COLOR
: {
1019 const char *c
= va_arg(ap
, const char*);
1020 r
= table_set_color(t
, last_cell
, c
);
1024 case TABLE_SET_RGAP_COLOR
: {
1025 const char *c
= va_arg(ap
, const char*);
1026 r
= table_set_rgap_color(t
, last_cell
, c
);
1030 case TABLE_SET_BOTH_COLORS
: {
1031 const char *c
= va_arg(ap
, const char*);
1033 r
= table_set_color(t
, last_cell
, c
);
1039 r
= table_set_rgap_color(t
, last_cell
, c
);
1043 case TABLE_SET_URL
: {
1044 const char *u
= va_arg(ap
, const char*);
1045 r
= table_set_url(t
, last_cell
, u
);
1049 case TABLE_SET_UPPERCASE
: {
1050 int u
= va_arg(ap
, int);
1051 r
= table_set_uppercase(t
, last_cell
, u
);
1055 case _TABLE_DATA_TYPE_MAX
:
1056 /* Used as end marker */
1061 assert_not_reached();
1064 r
= table_add_cell(t
, &last_cell
, type
, data
);
1073 void table_set_header(Table
*t
, bool b
) {
1079 void table_set_width(Table
*t
, size_t width
) {
1085 void table_set_cell_height_max(Table
*t
, size_t height
) {
1087 assert(height
>= 1 || height
== SIZE_MAX
);
1089 t
->cell_height_max
= height
;
1092 int table_set_empty_string(Table
*t
, const char *empty
) {
1095 return free_and_strdup(&t
->empty_string
, empty
);
1098 static int table_set_display_all(Table
*t
) {
1103 /* Initialize the display map to the identity */
1105 d
= reallocarray(t
->display_map
, t
->n_columns
, sizeof(size_t));
1109 for (size_t i
= 0; i
< t
->n_columns
; i
++)
1113 t
->n_display_map
= t
->n_columns
;
1118 int table_set_display_internal(Table
*t
, size_t first_column
, ...) {
1124 column
= first_column
;
1126 va_start(ap
, first_column
);
1128 assert(column
< t
->n_columns
);
1130 if (!GREEDY_REALLOC(t
->display_map
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
1135 t
->display_map
[t
->n_display_map
++] = column
;
1137 column
= va_arg(ap
, size_t);
1138 if (column
== SIZE_MAX
)
1147 int table_set_sort_internal(Table
*t
, size_t first_column
, ...) {
1153 column
= first_column
;
1155 va_start(ap
, first_column
);
1157 assert(column
< t
->n_columns
);
1159 if (!GREEDY_REALLOC(t
->sort_map
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1164 t
->sort_map
[t
->n_sort_map
++] = column
;
1166 column
= va_arg(ap
, size_t);
1167 if (column
== SIZE_MAX
)
1175 int table_hide_column_from_display_internal(Table
*t
, ...) {
1181 /* If the display map is empty, initialize it with all available columns */
1182 if (!t
->display_map
) {
1183 r
= table_set_display_all(t
);
1188 for (size_t i
= 0; i
< t
->n_display_map
; i
++) {
1189 bool listed
= false;
1196 column
= va_arg(ap
, size_t);
1197 if (column
== SIZE_MAX
)
1199 if (column
== t
->display_map
[i
]) {
1209 t
->display_map
[cur
++] = t
->display_map
[i
];
1212 t
->n_display_map
= cur
;
1217 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1221 if (a
->type
== b
->type
) {
1223 /* We only define ordering for cells of the same data type. If cells with different data types are
1224 * compared we follow the order the cells were originally added in */
1229 return strcmp(a
->string
, b
->string
);
1232 return path_compare(a
->string
, b
->string
);
1235 case TABLE_STRV_WRAPPED
:
1236 return strv_compare(a
->strv
, b
->strv
);
1239 if (!a
->boolean
&& b
->boolean
)
1241 if (a
->boolean
&& !b
->boolean
)
1245 case TABLE_TIMESTAMP
:
1246 case TABLE_TIMESTAMP_UTC
:
1247 case TABLE_TIMESTAMP_RELATIVE
:
1248 return CMP(a
->timestamp
, b
->timestamp
);
1250 case TABLE_TIMESPAN
:
1251 case TABLE_TIMESPAN_MSEC
:
1252 return CMP(a
->timespan
, b
->timespan
);
1256 return CMP(a
->size
, b
->size
);
1260 return CMP(a
->int_val
, b
->int_val
);
1263 return CMP(a
->int8
, b
->int8
);
1266 return CMP(a
->int16
, b
->int16
);
1269 return CMP(a
->int32
, b
->int32
);
1272 return CMP(a
->int64
, b
->int64
);
1275 return CMP(a
->uint_val
, b
->uint_val
);
1278 return CMP(a
->uint8
, b
->uint8
);
1281 return CMP(a
->uint16
, b
->uint16
);
1284 return CMP(a
->uint32
, b
->uint32
);
1287 case TABLE_UINT64_HEX
:
1288 return CMP(a
->uint64
, b
->uint64
);
1291 return CMP(a
->percent
, b
->percent
);
1294 return CMP(a
->ifindex
, b
->ifindex
);
1297 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1299 case TABLE_IN6_ADDR
:
1300 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1304 return memcmp(&a
->id128
, &b
->id128
, sizeof(sd_id128_t
));
1307 return CMP(a
->uid
, b
->uid
);
1310 return CMP(a
->gid
, b
->gid
);
1313 return CMP(a
->pid
, b
->pid
);
1316 return CMP(a
->mode
, b
->mode
);
1323 /* Generic fallback using the original order in which the cells where added. */
1324 return CMP(index_a
, index_b
);
1327 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1331 assert(t
->sort_map
);
1333 /* Make sure the header stays at the beginning */
1334 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1336 if (*a
< t
->n_columns
)
1338 if (*b
< t
->n_columns
)
1341 /* Order other lines by the sorting map */
1342 for (size_t i
= 0; i
< t
->n_sort_map
; i
++) {
1345 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1346 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1348 r
= cell_data_compare(d
, *a
, dd
, *b
);
1350 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1353 /* Order identical lines by the order there were originally added in */
1357 static char* format_strv_width(char **strv
, size_t column_width
) {
1358 _cleanup_free_
char *buf
= NULL
; /* buf must be freed after f */
1359 _cleanup_fclose_
FILE *f
= NULL
;
1362 f
= open_memstream_unlocked(&buf
, &sz
);
1366 size_t position
= 0;
1367 STRV_FOREACH(p
, strv
) {
1368 size_t our_len
= utf8_console_width(*p
); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1369 * If that happens, we'll just print one item per line. */
1371 if (position
== 0) {
1374 } else if (size_add(size_add(position
, 1), our_len
) <= column_width
) {
1375 fprintf(f
, " %s", *p
);
1376 position
= size_add(size_add(position
, 1), our_len
);
1378 fprintf(f
, "\n%s", *p
);
1383 if (fflush_and_check(f
) < 0)
1387 return TAKE_PTR(buf
);
1390 static const char *table_data_format(Table
*t
, TableData
*d
, bool avoid_uppercasing
, size_t column_width
, bool *have_soft
) {
1394 /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1395 (d
->type
!= TABLE_STRV_WRAPPED
|| d
->formatted_for_width
== column_width
))
1396 return d
->formatted
;
1400 return strempty(t
->empty_string
);
1404 if (d
->uppercase
&& !avoid_uppercasing
) {
1405 d
->formatted
= new(char, strlen(d
->string
) + 1);
1409 char *q
= d
->formatted
;
1410 for (char *p
= d
->string
; *p
; p
++, q
++)
1411 *q
= (char) toupper((unsigned char) *p
);
1414 return d
->formatted
;
1420 if (strv_isempty(d
->strv
))
1421 return strempty(t
->empty_string
);
1423 d
->formatted
= strv_join(d
->strv
, "\n");
1428 case TABLE_STRV_WRAPPED
: {
1429 if (strv_isempty(d
->strv
))
1430 return strempty(t
->empty_string
);
1432 char *buf
= format_strv_width(d
->strv
, column_width
);
1436 free_and_replace(d
->formatted
, buf
);
1437 d
->formatted_for_width
= column_width
;
1445 return yes_no(d
->boolean
);
1447 case TABLE_BOOLEAN_CHECKMARK
:
1448 return special_glyph(d
->boolean
? SPECIAL_GLYPH_CHECK_MARK
: SPECIAL_GLYPH_CROSS_MARK
);
1450 case TABLE_TIMESTAMP
:
1451 case TABLE_TIMESTAMP_UTC
:
1452 case TABLE_TIMESTAMP_RELATIVE
: {
1453 _cleanup_free_
char *p
= NULL
;
1456 p
= new(char, d
->type
== TABLE_TIMESTAMP_RELATIVE
? FORMAT_TIMESTAMP_RELATIVE_MAX
: FORMAT_TIMESTAMP_MAX
);
1460 if (d
->type
== TABLE_TIMESTAMP
)
1461 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1462 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1463 ret
= format_timestamp_style(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
, TIMESTAMP_UTC
);
1465 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_RELATIVE_MAX
, d
->timestamp
);
1469 d
->formatted
= TAKE_PTR(p
);
1473 case TABLE_TIMESPAN
:
1474 case TABLE_TIMESPAN_MSEC
: {
1475 _cleanup_free_
char *p
= NULL
;
1477 p
= new(char, FORMAT_TIMESPAN_MAX
);
1481 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1482 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1485 d
->formatted
= TAKE_PTR(p
);
1490 _cleanup_free_
char *p
= NULL
;
1492 p
= new(char, FORMAT_BYTES_MAX
);
1496 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1499 d
->formatted
= TAKE_PTR(p
);
1504 _cleanup_free_
char *p
= NULL
;
1507 p
= new(char, FORMAT_BYTES_MAX
+2);
1511 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1515 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1517 d
->formatted
= TAKE_PTR(p
);
1522 _cleanup_free_
char *p
= NULL
;
1524 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1528 sprintf(p
, "%i", d
->int_val
);
1529 d
->formatted
= TAKE_PTR(p
);
1534 _cleanup_free_
char *p
= NULL
;
1536 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1540 sprintf(p
, "%" PRIi8
, d
->int8
);
1541 d
->formatted
= TAKE_PTR(p
);
1546 _cleanup_free_
char *p
= NULL
;
1548 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1552 sprintf(p
, "%" PRIi16
, d
->int16
);
1553 d
->formatted
= TAKE_PTR(p
);
1558 _cleanup_free_
char *p
= NULL
;
1560 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1564 sprintf(p
, "%" PRIi32
, d
->int32
);
1565 d
->formatted
= TAKE_PTR(p
);
1570 _cleanup_free_
char *p
= NULL
;
1572 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1576 sprintf(p
, "%" PRIi64
, d
->int64
);
1577 d
->formatted
= TAKE_PTR(p
);
1582 _cleanup_free_
char *p
= NULL
;
1584 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1588 sprintf(p
, "%u", d
->uint_val
);
1589 d
->formatted
= TAKE_PTR(p
);
1594 _cleanup_free_
char *p
= NULL
;
1596 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1600 sprintf(p
, "%" PRIu8
, d
->uint8
);
1601 d
->formatted
= TAKE_PTR(p
);
1605 case TABLE_UINT16
: {
1606 _cleanup_free_
char *p
= NULL
;
1608 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1612 sprintf(p
, "%" PRIu16
, d
->uint16
);
1613 d
->formatted
= TAKE_PTR(p
);
1617 case TABLE_UINT32
: {
1618 _cleanup_free_
char *p
= NULL
;
1620 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1624 sprintf(p
, "%" PRIu32
, d
->uint32
);
1625 d
->formatted
= TAKE_PTR(p
);
1629 case TABLE_UINT64
: {
1630 _cleanup_free_
char *p
= NULL
;
1632 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1636 sprintf(p
, "%" PRIu64
, d
->uint64
);
1637 d
->formatted
= TAKE_PTR(p
);
1641 case TABLE_UINT64_HEX
: {
1642 _cleanup_free_
char *p
= NULL
;
1644 p
= new(char, 16 + 1);
1648 sprintf(p
, "%" PRIx64
, d
->uint64
);
1649 d
->formatted
= TAKE_PTR(p
);
1653 case TABLE_PERCENT
: {
1654 _cleanup_free_
char *p
= NULL
;
1656 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1660 sprintf(p
, "%i%%" , d
->percent
);
1661 d
->formatted
= TAKE_PTR(p
);
1665 case TABLE_IFINDEX
: {
1666 _cleanup_free_
char *p
= NULL
;
1668 if (format_ifname_full_alloc(d
->ifindex
, FORMAT_IFNAME_IFINDEX
, &p
) < 0)
1671 d
->formatted
= TAKE_PTR(p
);
1676 case TABLE_IN6_ADDR
: {
1677 _cleanup_free_
char *p
= NULL
;
1679 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1680 &d
->address
, &p
) < 0)
1683 d
->formatted
= TAKE_PTR(p
);
1690 p
= new(char, SD_ID128_STRING_MAX
);
1694 d
->formatted
= sd_id128_to_string(d
->id128
, p
);
1701 p
= new(char, SD_ID128_UUID_STRING_MAX
);
1705 d
->formatted
= sd_id128_to_uuid_string(d
->id128
, p
);
1712 if (!uid_is_valid(d
->uid
))
1715 p
= new(char, DECIMAL_STR_WIDTH(d
->uid
) + 1);
1718 sprintf(p
, UID_FMT
, d
->uid
);
1727 if (!gid_is_valid(d
->gid
))
1730 p
= new(char, DECIMAL_STR_WIDTH(d
->gid
) + 1);
1733 sprintf(p
, GID_FMT
, d
->gid
);
1742 if (!pid_is_valid(d
->pid
))
1745 p
= new(char, DECIMAL_STR_WIDTH(d
->pid
) + 1);
1748 sprintf(p
, PID_FMT
, d
->pid
);
1754 case TABLE_SIGNAL
: {
1758 suffix
= signal_to_string(d
->int_val
);
1762 p
= strjoin("SIG", suffix
);
1773 if (d
->mode
== MODE_INVALID
)
1776 p
= new(char, 4 + 1);
1780 sprintf(p
, "%04o", d
->mode
& 07777);
1786 assert_not_reached();
1789 return d
->formatted
;
1792 static int console_width_height(
1795 size_t *ret_height
) {
1797 size_t max_width
= 0, height
= 0;
1802 /* Determine the width and height in console character cells the specified string needs. */
1807 p
= strchr(s
, '\n');
1809 _cleanup_free_
char *c
= NULL
;
1811 c
= strndup(s
, p
- s
);
1815 k
= utf8_console_width(c
);
1818 k
= utf8_console_width(s
);
1827 } while (!isempty(s
));
1830 *ret_width
= max_width
;
1833 *ret_height
= height
;
1838 static int table_data_requested_width_height(
1841 size_t available_width
,
1846 _cleanup_free_
char *truncated
= NULL
;
1847 bool truncation_applied
= false;
1848 size_t width
, height
;
1853 t
= table_data_format(table
, d
, false, available_width
, &soft
);
1857 if (table
->cell_height_max
!= SIZE_MAX
) {
1858 r
= string_truncate_lines(t
, table
->cell_height_max
, &truncated
);
1862 truncation_applied
= true;
1867 r
= console_width_height(t
, &width
, &height
);
1871 if (d
->maximum_width
!= SIZE_MAX
&& width
> d
->maximum_width
)
1872 width
= d
->maximum_width
;
1874 if (width
< d
->minimum_width
)
1875 width
= d
->minimum_width
;
1880 *ret_height
= height
;
1881 if (have_soft
&& soft
)
1884 return truncation_applied
;
1887 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1888 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1889 _cleanup_free_
char *clickable
= NULL
;
1894 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1897 assert(percent
<= 100);
1899 old_length
= strlen(str
);
1902 r
= terminal_urlify(url
, str
, &clickable
);
1906 clickable_length
= strlen(clickable
);
1908 clickable_length
= old_length
;
1910 /* Determine current width on screen */
1912 while (p
< str
+ old_length
) {
1915 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1916 p
++, w
++; /* count invalid chars as 1 */
1920 p
= utf8_next_char(p
);
1921 w
+= unichar_iswide(c
) ? 2 : 1;
1924 /* Already wider than the target, if so, don't do anything */
1925 if (w
>= new_length
)
1926 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1928 /* How much spaces shall we add? An how much on the left side? */
1929 space
= new_length
- w
;
1930 lspace
= space
* percent
/ 100U;
1932 ret
= new(char, space
+ clickable_length
+ 1);
1936 for (size_t i
= 0; i
< lspace
; i
++)
1938 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1939 for (size_t i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1942 ret
[space
+ clickable_length
] = 0;
1946 static bool table_data_isempty(TableData
*d
) {
1949 if (d
->type
== TABLE_EMPTY
)
1952 /* Let's also consider an empty strv as truly empty. */
1953 if (IN_SET(d
->type
, TABLE_STRV
, TABLE_STRV_WRAPPED
))
1954 return strv_isempty(d
->strv
);
1956 /* Note that an empty string we do not consider empty here! */
1960 static const char* table_data_color(TableData
*d
) {
1966 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1967 if (table_data_isempty(d
))
1973 static const char* table_data_rgap_color(TableData
*d
) {
1977 return d
->rgap_color
;
1982 int table_print(Table
*t
, FILE *f
) {
1983 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1984 table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1986 _cleanup_free_
size_t *sorted
= NULL
;
1987 uint64_t *column_weight
, weight_sum
;
1995 /* Ensure we have no incomplete rows */
1996 assert(t
->n_cells
% t
->n_columns
== 0);
1998 n_rows
= t
->n_cells
/ t
->n_columns
;
1999 assert(n_rows
> 0); /* at least the header row must be complete */
2002 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2004 sorted
= new(size_t, n_rows
);
2008 for (size_t i
= 0; i
< n_rows
; i
++)
2009 sorted
[i
] = i
* t
->n_columns
;
2011 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2015 display_columns
= t
->n_display_map
;
2017 display_columns
= t
->n_columns
;
2019 assert(display_columns
> 0);
2021 minimum_width
= newa(size_t, display_columns
);
2022 maximum_width
= newa(size_t, display_columns
);
2023 requested_width
= newa(size_t, display_columns
);
2024 column_weight
= newa0(uint64_t, display_columns
);
2026 for (size_t j
= 0; j
< display_columns
; j
++) {
2027 minimum_width
[j
] = 1;
2028 maximum_width
[j
] = SIZE_MAX
;
2031 for (unsigned pass
= 0; pass
< 2; pass
++) {
2032 /* First pass: determine column sizes */
2034 for (size_t j
= 0; j
< display_columns
; j
++)
2035 requested_width
[j
] = SIZE_MAX
;
2037 bool any_soft
= false;
2039 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2042 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2043 * hence we don't care for sorted[] during the first pass. */
2044 row
= t
->data
+ i
* t
->n_columns
;
2046 for (size_t j
= 0; j
< display_columns
; j
++) {
2048 size_t req_width
, req_height
;
2050 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2052 r
= table_data_requested_width_height(t
, d
,
2053 width
? width
[j
] : SIZE_MAX
,
2054 &req_width
, &req_height
, &any_soft
);
2057 if (r
> 0) { /* Truncated because too many lines? */
2058 _cleanup_free_
char *last
= NULL
;
2061 /* If we are going to show only the first few lines of a cell that has
2062 * multiple make sure that we have enough space horizontally to show an
2063 * ellipsis. Hence, let's figure out the last line, and account for its
2064 * length plus ellipsis. */
2066 field
= table_data_format(t
, d
, false,
2067 width
? width
[j
] : SIZE_MAX
,
2072 assert_se(t
->cell_height_max
> 0);
2073 r
= string_extract_line(field
, t
->cell_height_max
-1, &last
);
2077 req_width
= MAX(req_width
,
2078 utf8_console_width(last
) +
2079 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS
)));
2082 /* Determine the biggest width that any cell in this column would like to have */
2083 if (requested_width
[j
] == SIZE_MAX
||
2084 requested_width
[j
] < req_width
)
2085 requested_width
[j
] = req_width
;
2087 /* Determine the minimum width any cell in this column needs */
2088 if (minimum_width
[j
] < d
->minimum_width
)
2089 minimum_width
[j
] = d
->minimum_width
;
2091 /* Determine the maximum width any cell in this column needs */
2092 if (d
->maximum_width
!= SIZE_MAX
&&
2093 (maximum_width
[j
] == SIZE_MAX
||
2094 maximum_width
[j
] > d
->maximum_width
))
2095 maximum_width
[j
] = d
->maximum_width
;
2097 /* Determine the full columns weight */
2098 column_weight
[j
] += d
->weight
;
2102 /* One space between each column */
2103 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
2105 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2107 for (size_t j
= 0; j
< display_columns
; j
++) {
2108 weight_sum
+= column_weight
[j
];
2110 table_minimum_width
+= minimum_width
[j
];
2112 if (maximum_width
[j
] == SIZE_MAX
)
2113 table_maximum_width
= SIZE_MAX
;
2115 table_maximum_width
+= maximum_width
[j
];
2117 table_requested_width
+= requested_width
[j
];
2120 /* Calculate effective table width */
2121 if (t
->width
!= 0 && t
->width
!= SIZE_MAX
)
2122 table_effective_width
= t
->width
;
2123 else if (t
->width
== 0 ||
2124 ((pass
> 0 || !any_soft
) && (pager_have() || !isatty(STDOUT_FILENO
))))
2125 table_effective_width
= table_requested_width
;
2127 table_effective_width
= MIN(table_requested_width
, columns());
2129 if (table_maximum_width
!= SIZE_MAX
&& table_effective_width
> table_maximum_width
)
2130 table_effective_width
= table_maximum_width
;
2132 if (table_effective_width
< table_minimum_width
)
2133 table_effective_width
= table_minimum_width
;
2136 width
= newa(size_t, display_columns
);
2138 if (table_effective_width
>= table_requested_width
) {
2141 /* We have extra room, let's distribute it among columns according to their weights. We first provide
2142 * each column with what it asked for and the distribute the rest. */
2144 extra
= table_effective_width
- table_requested_width
;
2146 for (size_t j
= 0; j
< display_columns
; j
++) {
2149 if (weight_sum
== 0)
2150 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
2152 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2154 if (maximum_width
[j
] != SIZE_MAX
&& width
[j
] > maximum_width
[j
])
2155 width
[j
] = maximum_width
[j
];
2157 if (width
[j
] < minimum_width
[j
])
2158 width
[j
] = minimum_width
[j
];
2160 delta
= LESS_BY(width
[j
], requested_width
[j
]);
2162 /* Subtract what we just added from the rest */
2168 assert(weight_sum
>= column_weight
[j
]);
2169 weight_sum
-= column_weight
[j
];
2172 break; /* Every column should be happy, no need to repeat calculations. */
2174 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2175 * with the minimum they need, and then distribute anything left. */
2176 bool finalize
= false;
2179 extra
= table_effective_width
- table_minimum_width
;
2181 for (size_t j
= 0; j
< display_columns
; j
++)
2182 width
[j
] = SIZE_MAX
;
2185 bool restart
= false;
2187 for (size_t j
= 0; j
< display_columns
; j
++) {
2190 /* Did this column already get something assigned? If so, let's skip to the next */
2191 if (width
[j
] != SIZE_MAX
)
2194 if (weight_sum
== 0)
2195 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
2197 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
2199 if (w
>= requested_width
[j
]) {
2200 /* Never give more than requested. If we hit a column like this, there's more
2201 * space to allocate to other columns which means we need to restart the
2202 * iteration. However, if we hit a column like this, let's assign it the space
2203 * it wanted for good early. */
2205 w
= requested_width
[j
];
2208 } else if (!finalize
)
2213 assert(w
>= minimum_width
[j
]);
2214 delta
= w
- minimum_width
[j
];
2216 assert(delta
<= extra
);
2219 assert(weight_sum
>= column_weight
[j
]);
2220 weight_sum
-= column_weight
[j
];
2222 if (restart
&& !finalize
)
2233 if (!any_soft
) /* Some columns got less than requested. If some cells were "soft",
2234 * let's try to reformat them with the new widths. Otherwise, let's
2240 /* Second pass: show output */
2241 for (size_t i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
2242 size_t n_subline
= 0;
2247 row
= t
->data
+ sorted
[i
];
2249 row
= t
->data
+ i
* t
->n_columns
;
2252 const char *gap_color
= NULL
;
2253 more_sublines
= false;
2255 for (size_t j
= 0; j
< display_columns
; j
++) {
2256 _cleanup_free_
char *buffer
= NULL
, *extracted
= NULL
;
2257 bool lines_truncated
= false;
2258 const char *field
, *color
= NULL
;
2262 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2264 field
= table_data_format(t
, d
, false, width
[j
], NULL
);
2268 r
= string_extract_line(field
, n_subline
, &extracted
);
2272 /* There are more lines to come */
2273 if ((t
->cell_height_max
== SIZE_MAX
|| n_subline
+ 1 < t
->cell_height_max
))
2274 more_sublines
= true; /* There are more lines to come */
2276 lines_truncated
= true;
2281 l
= utf8_console_width(field
);
2283 /* Field is wider than allocated space. Let's ellipsize */
2285 buffer
= ellipsize(field
, width
[j
], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2286 lines_truncated
? 100 : d
->ellipsize_percent
);
2292 if (lines_truncated
) {
2293 _cleanup_free_
char *padded
= NULL
;
2295 /* We truncated more lines of this cell, let's add an
2296 * ellipsis. We first append it, but that might make our
2297 * string grow above what we have space for, hence ellipsize
2298 * right after. This will truncate the ellipsis and add a new
2301 padded
= strjoin(field
, special_glyph(SPECIAL_GLYPH_ELLIPSIS
));
2305 buffer
= ellipsize(padded
, width
[j
], 100);
2310 l
= utf8_console_width(field
);
2314 _cleanup_free_
char *aligned
= NULL
;
2315 /* Field is shorter than allocated space. Let's align with spaces */
2317 aligned
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
2321 /* Drop trailing white spaces of last column when no cosmetics is set. */
2322 if (j
== display_columns
- 1 &&
2323 (!colors_enabled() || (!table_data_color(d
) && row
!= t
->data
)) &&
2324 (!urlify_enabled() || !d
->url
))
2325 delete_trailing_chars(aligned
, NULL
);
2327 free_and_replace(buffer
, aligned
);
2332 if (l
>= width
[j
] && d
->url
) {
2333 _cleanup_free_
char *clickable
= NULL
;
2335 r
= terminal_urlify(d
->url
, field
, &clickable
);
2339 free_and_replace(buffer
, clickable
);
2343 if (colors_enabled()) {
2345 fputs(gap_color
, f
);
2346 else if (row
== t
->data
) /* underline header line fully, including the column separator */
2347 fputs(ansi_underline(), f
);
2351 fputc(' ', f
); /* column separator left of cell */
2353 if (colors_enabled()) {
2354 color
= table_data_color(d
);
2356 /* Undo gap color */
2357 if (gap_color
|| (color
&& row
== t
->data
))
2358 fputs(ANSI_NORMAL
, f
);
2362 else if (gap_color
&& row
== t
->data
) /* underline header line cell */
2363 fputs(ansi_underline(), f
);
2368 if (colors_enabled() && (color
|| row
== t
->data
))
2369 fputs(ANSI_NORMAL
, f
);
2371 gap_color
= table_data_rgap_color(d
);
2376 } while (more_sublines
);
2379 return fflush_and_check(f
);
2382 int table_format(Table
*t
, char **ret
) {
2383 _cleanup_free_
char *buf
= NULL
;
2384 _cleanup_fclose_
FILE *f
= NULL
;
2388 f
= open_memstream_unlocked(&buf
, &sz
);
2392 r
= table_print(t
, f
);
2398 *ret
= TAKE_PTR(buf
);
2403 size_t table_get_rows(Table
*t
) {
2407 assert(t
->n_columns
> 0);
2408 return t
->n_cells
/ t
->n_columns
;
2411 size_t table_get_columns(Table
*t
) {
2415 assert(t
->n_columns
> 0);
2416 return t
->n_columns
;
2419 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
2421 assert(column
< t
->n_columns
);
2423 if (!t
->reverse_map
) {
2427 t
->reverse_map
= new0(bool, t
->n_columns
);
2428 if (!t
->reverse_map
)
2432 t
->reverse_map
[column
] = b
;
2436 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
2441 if (column
>= t
->n_columns
)
2444 i
= row
* t
->n_columns
+ column
;
2445 if (i
>= t
->n_cells
)
2448 return TABLE_INDEX_TO_CELL(i
);
2451 const void *table_get(Table
*t
, TableCell
*cell
) {
2456 d
= table_get_data(t
, cell
);
2463 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
2466 cell
= table_get_cell(t
, row
, column
);
2470 return table_get(t
, cell
);
2473 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
2478 return json_variant_new_null(ret
);
2482 return json_variant_new_string(ret
, d
->string
);
2485 case TABLE_STRV_WRAPPED
:
2486 return json_variant_new_array_strv(ret
, d
->strv
);
2488 case TABLE_BOOLEAN_CHECKMARK
:
2490 return json_variant_new_boolean(ret
, d
->boolean
);
2492 case TABLE_TIMESTAMP
:
2493 case TABLE_TIMESTAMP_UTC
:
2494 case TABLE_TIMESTAMP_RELATIVE
:
2495 if (d
->timestamp
== USEC_INFINITY
)
2496 return json_variant_new_null(ret
);
2498 return json_variant_new_unsigned(ret
, d
->timestamp
);
2500 case TABLE_TIMESPAN
:
2501 case TABLE_TIMESPAN_MSEC
:
2502 if (d
->timespan
== USEC_INFINITY
)
2503 return json_variant_new_null(ret
);
2505 return json_variant_new_unsigned(ret
, d
->timespan
);
2509 if (d
->size
== UINT64_MAX
)
2510 return json_variant_new_null(ret
);
2512 return json_variant_new_unsigned(ret
, d
->size
);
2515 return json_variant_new_integer(ret
, d
->int_val
);
2518 return json_variant_new_integer(ret
, d
->int8
);
2521 return json_variant_new_integer(ret
, d
->int16
);
2524 return json_variant_new_integer(ret
, d
->int32
);
2527 return json_variant_new_integer(ret
, d
->int64
);
2530 return json_variant_new_unsigned(ret
, d
->uint_val
);
2533 return json_variant_new_unsigned(ret
, d
->uint8
);
2536 return json_variant_new_unsigned(ret
, d
->uint16
);
2539 return json_variant_new_unsigned(ret
, d
->uint32
);
2542 case TABLE_UINT64_HEX
:
2543 return json_variant_new_unsigned(ret
, d
->uint64
);
2546 return json_variant_new_integer(ret
, d
->percent
);
2549 if (d
->ifindex
<= 0)
2550 return json_variant_new_null(ret
);
2552 return json_variant_new_integer(ret
, d
->ifindex
);
2555 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
2557 case TABLE_IN6_ADDR
:
2558 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
2561 return json_variant_new_string(ret
, SD_ID128_TO_STRING(d
->id128
));
2564 return json_variant_new_string(ret
, SD_ID128_TO_UUID_STRING(d
->id128
));
2567 if (!uid_is_valid(d
->uid
))
2568 return json_variant_new_null(ret
);
2570 return json_variant_new_integer(ret
, d
->uid
);
2573 if (!gid_is_valid(d
->gid
))
2574 return json_variant_new_null(ret
);
2576 return json_variant_new_integer(ret
, d
->gid
);
2579 if (!pid_is_valid(d
->pid
))
2580 return json_variant_new_null(ret
);
2582 return json_variant_new_integer(ret
, d
->pid
);
2585 if (!SIGNAL_VALID(d
->int_val
))
2586 return json_variant_new_null(ret
);
2588 return json_variant_new_integer(ret
, d
->int_val
);
2591 if (d
->mode
== MODE_INVALID
)
2592 return json_variant_new_null(ret
);
2594 return json_variant_new_unsigned(ret
, d
->mode
);
2601 static char* string_to_json_field_name(const char *f
) {
2602 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2603 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2604 * underscores and leave everything as is. */
2606 char *c
= strdup(f
);
2610 for (char *x
= c
; *x
; x
++)
2617 static const char *table_get_json_field_name(Table
*t
, size_t column
) {
2620 return column
< t
->n_json_fields
? t
->json_fields
[column
] : NULL
;
2623 int table_to_json(Table
*t
, JsonVariant
**ret
) {
2624 JsonVariant
**rows
= NULL
, **elements
= NULL
;
2625 _cleanup_free_
size_t *sorted
= NULL
;
2626 size_t n_rows
, display_columns
;
2631 /* Ensure we have no incomplete rows */
2632 assert(t
->n_cells
% t
->n_columns
== 0);
2634 n_rows
= t
->n_cells
/ t
->n_columns
;
2635 assert(n_rows
> 0); /* at least the header row must be complete */
2638 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2640 sorted
= new(size_t, n_rows
);
2646 for (size_t i
= 0; i
< n_rows
; i
++)
2647 sorted
[i
] = i
* t
->n_columns
;
2649 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2653 display_columns
= t
->n_display_map
;
2655 display_columns
= t
->n_columns
;
2656 assert(display_columns
> 0);
2658 elements
= new0(JsonVariant
*, display_columns
* 2);
2664 for (size_t j
= 0; j
< display_columns
; j
++) {
2665 _cleanup_free_
char *mangled
= NULL
;
2669 c
= t
->display_map
? t
->display_map
[j
] : j
;
2671 /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
2672 n
= table_get_json_field_name(t
, c
);
2674 const char *formatted
;
2677 assert_se(d
= t
->data
[c
]);
2679 /* Field names must be strings, hence format whatever we got here as a string first */
2680 formatted
= table_data_format(t
, d
, true, SIZE_MAX
, NULL
);
2686 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2687 mangled
= string_to_json_field_name(formatted
);
2695 r
= json_variant_new_string(elements
+ j
*2, n
);
2700 rows
= new0(JsonVariant
*, n_rows
-1);
2706 for (size_t i
= 1; i
< n_rows
; i
++) {
2710 row
= t
->data
+ sorted
[i
];
2712 row
= t
->data
+ i
* t
->n_columns
;
2714 for (size_t j
= 0; j
< display_columns
; j
++) {
2718 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2721 elements
[k
] = json_variant_unref(elements
[k
]);
2723 r
= table_data_to_json(d
, elements
+ k
);
2728 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2733 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2737 json_variant_unref_many(rows
, n_rows
-1);
2742 json_variant_unref_many(elements
, display_columns
*2);
2749 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2750 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2755 if (flags
& JSON_FORMAT_OFF
) /* If JSON output is turned off, use regular output */
2756 return table_print(t
, f
);
2761 r
= table_to_json(t
, &v
);
2765 json_variant_dump(v
, flags
, f
, NULL
);
2767 return fflush_and_check(f
);
2770 int table_print_with_pager(
2772 JsonFormatFlags json_format_flags
,
2773 PagerFlags pager_flags
,
2781 /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
2782 * the table header and logs about any error. */
2784 if (json_format_flags
& (JSON_FORMAT_OFF
|JSON_FORMAT_PRETTY
|JSON_FORMAT_PRETTY_AUTO
))
2785 pager_open(pager_flags
);
2787 saved_header
= t
->header
;
2788 t
->header
= show_header
;
2789 r
= table_print_json(t
, stdout
, json_format_flags
);
2790 t
->header
= saved_header
;
2792 return table_log_print_error(r
);
2797 int table_set_json_field_name(Table
*t
, size_t column
, const char *name
) {
2805 m
= MAX(column
+ 1, t
->n_json_fields
);
2806 if (!GREEDY_REALLOC0(t
->json_fields
, m
))
2809 r
= free_and_strdup(t
->json_fields
+ column
, name
);
2813 t
->n_json_fields
= m
;
2816 if (column
>= t
->n_json_fields
)
2819 t
->json_fields
[column
] = mfree(t
->json_fields
[column
]);