1 /* SPDX-License-Identifier: LGPL-2.1+ */
7 #include "alloc-util.h"
10 #include "format-table.h"
11 #include "format-util.h"
13 #include "in-addr-util.h"
14 #include "memory-util.h"
16 #include "parse-util.h"
17 #include "path-util.h"
18 #include "pretty-print.h"
19 #include "sort-util.h"
20 #include "string-util.h"
22 #include "terminal-util.h"
23 #include "time-util.h"
27 #define DEFAULT_WEIGHT 100
30 A few notes on implementation details:
32 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
33 table. It can be easily converted to an index number and back.
35 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
36 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
37 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
38 outside only sees Table and TableCell.
40 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
43 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
44 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
45 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
46 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
48 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
49 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
50 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
51 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
54 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
55 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
56 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
59 typedef struct TableData
{
63 size_t minimum_width
; /* minimum width for the column */
64 size_t maximum_width
; /* maximum width for the column */
65 unsigned weight
; /* the horizontal weight for this column, in case the table is expanded/compressed */
66 unsigned ellipsize_percent
; /* 0 … 100, where to place the ellipsis when compression is needed */
67 unsigned align_percent
; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
69 bool uppercase
; /* Uppercase string on display */
71 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 */
72 char *url
; /* A URL to use for a clickable hyperlink */
73 char *formatted
; /* A cached textual representation of the cell data, before ellipsation/alignment */
76 uint8_t data
[0]; /* data is generic array */
92 int percent
; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
94 union in_addr_union address
;
95 /* … add more here as we start supporting more cell data types … */
99 static size_t TABLE_CELL_TO_INDEX(TableCell
*cell
) {
104 i
= PTR_TO_SIZE(cell
);
110 static TableCell
* TABLE_INDEX_TO_CELL(size_t index
) {
111 assert(index
!= (size_t) -1);
112 return SIZE_TO_PTR(index
+ 1);
119 bool header
; /* Whether to show the header row? */
120 size_t width
; /* If == 0 format this as wide as necessary. If (size_t) -1 format this to console
121 * width or less wide, but not wider. Otherwise the width to format this table in. */
126 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 */
127 size_t n_display_map
;
129 size_t *sort_map
; /* The columns to order rows by, in order of preference. */
137 Table
*table_new_raw(size_t n_columns
) {
138 _cleanup_(table_unrefp
) Table
*t
= NULL
;
140 assert(n_columns
> 0);
146 *t
= (struct Table
) {
147 .n_columns
= n_columns
,
149 .width
= (size_t) -1,
155 Table
*table_new_internal(const char *first_header
, ...) {
156 _cleanup_(table_unrefp
) Table
*t
= NULL
;
157 size_t n_columns
= 1;
162 assert(first_header
);
164 va_start(ap
, first_header
);
166 h
= va_arg(ap
, const char*);
174 t
= table_new_raw(n_columns
);
178 va_start(ap
, first_header
);
179 for (h
= first_header
; h
; h
= va_arg(ap
, const char*)) {
182 r
= table_add_cell(t
, &cell
, TABLE_STRING
, h
);
188 /* Make the table header uppercase */
189 r
= table_set_uppercase(t
, cell
, true);
197 assert(t
->n_columns
== t
->n_cells
);
201 static TableData
*table_data_free(TableData
*d
) {
210 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData
, table_data
, table_data_free
);
211 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData
*, table_data_unref
);
213 Table
*table_unref(Table
*t
) {
219 for (i
= 0; i
< t
->n_cells
; i
++)
220 table_data_unref(t
->data
[i
]);
223 free(t
->display_map
);
225 free(t
->reverse_map
);
226 free(t
->empty_string
);
231 static size_t table_data_size(TableDataType type
, const void *data
) {
240 return strlen(data
) + 1;
245 case TABLE_TIMESTAMP
:
246 case TABLE_TIMESTAMP_UTC
:
247 case TABLE_TIMESTAMP_RELATIVE
:
249 case TABLE_TIMESPAN_MSEC
:
250 return sizeof(usec_t
);
256 return sizeof(uint64_t);
260 return sizeof(uint32_t);
264 return sizeof(uint16_t);
268 return sizeof(uint8_t);
277 return sizeof(struct in_addr
);
280 return sizeof(struct in6_addr
);
283 assert_not_reached("Uh? Unexpected cell type");
287 static bool table_data_matches(
291 size_t minimum_width
,
292 size_t maximum_width
,
294 unsigned align_percent
,
295 unsigned ellipsize_percent
) {
303 if (d
->minimum_width
!= minimum_width
)
306 if (d
->maximum_width
!= maximum_width
)
309 if (d
->weight
!= weight
)
312 if (d
->align_percent
!= align_percent
)
315 if (d
->ellipsize_percent
!= ellipsize_percent
)
318 /* If a color/url/uppercase flag is set, refuse to merge */
326 k
= table_data_size(type
, data
);
327 l
= table_data_size(d
->type
, d
->data
);
332 return memcmp_safe(data
, d
->data
, l
) == 0;
335 static TableData
*table_data_new(
338 size_t minimum_width
,
339 size_t maximum_width
,
341 unsigned align_percent
,
342 unsigned ellipsize_percent
) {
347 data_size
= table_data_size(type
, data
);
349 d
= malloc0(offsetof(TableData
, data
) + data_size
);
355 d
->minimum_width
= minimum_width
;
356 d
->maximum_width
= maximum_width
;
358 d
->align_percent
= align_percent
;
359 d
->ellipsize_percent
= ellipsize_percent
;
360 memcpy_safe(d
->data
, data
, data_size
);
365 int table_add_cell_full(
367 TableCell
**ret_cell
,
370 size_t minimum_width
,
371 size_t maximum_width
,
373 unsigned align_percent
,
374 unsigned ellipsize_percent
) {
376 _cleanup_(table_data_unrefp
) TableData
*d
= NULL
;
381 assert(type
< _TABLE_DATA_TYPE_MAX
);
383 /* Special rule: patch NULL data fields to the empty field */
387 /* Determine the cell adjacent to the current one, but one row up */
388 if (t
->n_cells
>= t
->n_columns
)
389 assert_se(p
= t
->data
[t
->n_cells
- t
->n_columns
]);
393 /* If formatting parameters are left unspecified, copy from the previous row */
394 if (minimum_width
== (size_t) -1)
395 minimum_width
= p
? p
->minimum_width
: 1;
397 if (weight
== (unsigned) -1)
398 weight
= p
? p
->weight
: DEFAULT_WEIGHT
;
400 if (align_percent
== (unsigned) -1)
401 align_percent
= p
? p
->align_percent
: 0;
403 if (ellipsize_percent
== (unsigned) -1)
404 ellipsize_percent
= p
? p
->ellipsize_percent
: 100;
406 assert(align_percent
<= 100);
407 assert(ellipsize_percent
<= 100);
409 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
410 * formatting. Let's see if we can reuse the cell data and ref it once more. */
412 if (p
&& table_data_matches(p
, type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
))
413 d
= table_data_ref(p
);
415 d
= table_data_new(type
, data
, minimum_width
, maximum_width
, weight
, align_percent
, ellipsize_percent
);
420 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
424 *ret_cell
= TABLE_INDEX_TO_CELL(t
->n_cells
);
426 t
->data
[t
->n_cells
++] = TAKE_PTR(d
);
431 int table_add_cell_stringf(Table
*t
, TableCell
**ret_cell
, const char *format
, ...) {
432 _cleanup_free_
char *buffer
= NULL
;
436 va_start(ap
, format
);
437 r
= vasprintf(&buffer
, format
, ap
);
442 return table_add_cell(t
, ret_cell
, TABLE_STRING
, buffer
);
445 int table_fill_empty(Table
*t
, size_t until_column
) {
450 /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
451 * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
453 if (until_column
>= t
->n_columns
)
457 r
= table_add_cell(t
, NULL
, TABLE_EMPTY
, NULL
);
461 } while ((t
->n_cells
% t
->n_columns
) != until_column
);
466 int table_dup_cell(Table
*t
, TableCell
*cell
) {
471 /* Add the data of the specified cell a second time as a new cell to the end. */
473 i
= TABLE_CELL_TO_INDEX(cell
);
477 if (!GREEDY_REALLOC(t
->data
, t
->n_allocated
, MAX(t
->n_cells
+ 1, t
->n_columns
)))
480 t
->data
[t
->n_cells
++] = table_data_ref(t
->data
[i
]);
484 static int table_dedup_cell(Table
*t
, TableCell
*cell
) {
485 _cleanup_free_
char *curl
= NULL
;
491 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
492 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
494 i
= TABLE_CELL_TO_INDEX(cell
);
498 assert_se(od
= t
->data
[i
]);
502 assert(od
->n_ref
> 1);
505 curl
= strdup(od
->url
);
517 od
->ellipsize_percent
);
521 nd
->color
= od
->color
;
522 nd
->url
= TAKE_PTR(curl
);
523 nd
->uppercase
= od
->uppercase
;
525 table_data_unref(od
);
528 assert(nd
->n_ref
== 1);
533 static TableData
*table_get_data(Table
*t
, TableCell
*cell
) {
539 /* Get the data object of the specified cell, or NULL if it doesn't exist */
541 i
= TABLE_CELL_TO_INDEX(cell
);
546 assert(t
->data
[i
]->n_ref
> 0);
551 int table_set_minimum_width(Table
*t
, TableCell
*cell
, size_t minimum_width
) {
557 if (minimum_width
== (size_t) -1)
560 r
= table_dedup_cell(t
, cell
);
564 table_get_data(t
, cell
)->minimum_width
= minimum_width
;
568 int table_set_maximum_width(Table
*t
, TableCell
*cell
, size_t maximum_width
) {
574 r
= table_dedup_cell(t
, cell
);
578 table_get_data(t
, cell
)->maximum_width
= maximum_width
;
582 int table_set_weight(Table
*t
, TableCell
*cell
, unsigned weight
) {
588 if (weight
== (unsigned) -1)
589 weight
= DEFAULT_WEIGHT
;
591 r
= table_dedup_cell(t
, cell
);
595 table_get_data(t
, cell
)->weight
= weight
;
599 int table_set_align_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
605 if (percent
== (unsigned) -1)
608 assert(percent
<= 100);
610 r
= table_dedup_cell(t
, cell
);
614 table_get_data(t
, cell
)->align_percent
= percent
;
618 int table_set_ellipsize_percent(Table
*t
, TableCell
*cell
, unsigned percent
) {
624 if (percent
== (unsigned) -1)
627 assert(percent
<= 100);
629 r
= table_dedup_cell(t
, cell
);
633 table_get_data(t
, cell
)->ellipsize_percent
= percent
;
637 int table_set_color(Table
*t
, TableCell
*cell
, const char *color
) {
643 r
= table_dedup_cell(t
, cell
);
647 table_get_data(t
, cell
)->color
= empty_to_null(color
);
651 int table_set_url(Table
*t
, TableCell
*cell
, const char *url
) {
652 _cleanup_free_
char *copy
= NULL
;
664 r
= table_dedup_cell(t
, cell
);
668 return free_and_replace(table_get_data(t
, cell
)->url
, copy
);
671 int table_set_uppercase(Table
*t
, TableCell
*cell
, bool b
) {
678 r
= table_dedup_cell(t
, cell
);
682 assert_se(d
= table_get_data(t
, cell
));
684 if (d
->uppercase
== b
)
687 d
->formatted
= mfree(d
->formatted
);
692 int table_update(Table
*t
, TableCell
*cell
, TableDataType type
, const void *data
) {
693 _cleanup_free_
char *curl
= NULL
;
700 i
= TABLE_CELL_TO_INDEX(cell
);
704 assert_se(od
= t
->data
[i
]);
707 curl
= strdup(od
->url
);
719 od
->ellipsize_percent
);
723 nd
->color
= od
->color
;
724 nd
->url
= TAKE_PTR(curl
);
725 nd
->uppercase
= od
->uppercase
;
727 table_data_unref(od
);
733 int table_add_many_internal(Table
*t
, TableDataType first_type
, ...) {
736 TableCell
*last_cell
= NULL
;
740 assert(first_type
>= 0);
741 assert(first_type
< _TABLE_DATA_TYPE_MAX
);
745 va_start(ap
, first_type
);
764 union in_addr_union address
;
775 data
= va_arg(ap
, const char *);
779 buffer
.b
= va_arg(ap
, int);
783 case TABLE_TIMESTAMP
:
784 case TABLE_TIMESTAMP_UTC
:
785 case TABLE_TIMESTAMP_RELATIVE
:
787 case TABLE_TIMESPAN_MSEC
:
788 buffer
.usec
= va_arg(ap
, usec_t
);
794 buffer
.size
= va_arg(ap
, uint64_t);
799 buffer
.int_val
= va_arg(ap
, int);
800 data
= &buffer
.int_val
;
804 int x
= va_arg(ap
, int);
805 assert(x
>= INT8_MIN
&& x
<= INT8_MAX
);
813 int x
= va_arg(ap
, int);
814 assert(x
>= INT16_MIN
&& x
<= INT16_MAX
);
817 data
= &buffer
.int16
;
822 buffer
.int32
= va_arg(ap
, int32_t);
823 data
= &buffer
.int32
;
827 buffer
.int64
= va_arg(ap
, int64_t);
828 data
= &buffer
.int64
;
832 buffer
.uint_val
= va_arg(ap
, unsigned);
833 data
= &buffer
.uint_val
;
837 unsigned x
= va_arg(ap
, unsigned);
838 assert(x
<= UINT8_MAX
);
841 data
= &buffer
.uint8
;
846 unsigned x
= va_arg(ap
, unsigned);
847 assert(x
<= UINT16_MAX
);
850 data
= &buffer
.uint16
;
855 buffer
.uint32
= va_arg(ap
, uint32_t);
856 data
= &buffer
.uint32
;
860 buffer
.uint64
= va_arg(ap
, uint64_t);
861 data
= &buffer
.uint64
;
865 buffer
.percent
= va_arg(ap
, int);
866 data
= &buffer
.percent
;
870 buffer
.ifindex
= va_arg(ap
, int);
871 data
= &buffer
.ifindex
;
875 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
876 data
= &buffer
.address
.in
;
880 buffer
.address
= *va_arg(ap
, union in_addr_union
*);
881 data
= &buffer
.address
.in6
;
884 case TABLE_SET_MINIMUM_WIDTH
: {
885 size_t w
= va_arg(ap
, size_t);
887 r
= table_set_minimum_width(t
, last_cell
, w
);
891 case TABLE_SET_MAXIMUM_WIDTH
: {
892 size_t w
= va_arg(ap
, size_t);
893 r
= table_set_maximum_width(t
, last_cell
, w
);
897 case TABLE_SET_WEIGHT
: {
898 unsigned w
= va_arg(ap
, unsigned);
899 r
= table_set_weight(t
, last_cell
, w
);
903 case TABLE_SET_ALIGN_PERCENT
: {
904 unsigned p
= va_arg(ap
, unsigned);
905 r
= table_set_align_percent(t
, last_cell
, p
);
909 case TABLE_SET_ELLIPSIZE_PERCENT
: {
910 unsigned p
= va_arg(ap
, unsigned);
911 r
= table_set_ellipsize_percent(t
, last_cell
, p
);
915 case TABLE_SET_COLOR
: {
916 const char *c
= va_arg(ap
, const char*);
917 r
= table_set_color(t
, last_cell
, c
);
921 case TABLE_SET_URL
: {
922 const char *u
= va_arg(ap
, const char*);
923 r
= table_set_url(t
, last_cell
, u
);
927 case TABLE_SET_UPPERCASE
: {
928 int u
= va_arg(ap
, int);
929 r
= table_set_uppercase(t
, last_cell
, u
);
933 case _TABLE_DATA_TYPE_MAX
:
934 /* Used as end marker */
939 assert_not_reached("Uh? Unexpected data type.");
942 if (type
< _TABLE_DATA_TYPE_MAX
)
943 r
= table_add_cell(t
, &last_cell
, type
, data
);
950 type
= va_arg(ap
, TableDataType
);
954 void table_set_header(Table
*t
, bool b
) {
960 void table_set_width(Table
*t
, size_t width
) {
966 int table_set_empty_string(Table
*t
, const char *empty
) {
969 return free_and_strdup(&t
->empty_string
, empty
);
972 int table_set_display(Table
*t
, size_t first_column
, ...) {
973 size_t allocated
, column
;
978 allocated
= t
->n_display_map
;
979 column
= first_column
;
981 va_start(ap
, first_column
);
983 assert(column
< t
->n_columns
);
985 if (!GREEDY_REALLOC(t
->display_map
, allocated
, MAX(t
->n_columns
, t
->n_display_map
+1))) {
990 t
->display_map
[t
->n_display_map
++] = column
;
992 column
= va_arg(ap
, size_t);
993 if (column
== (size_t) -1)
1002 int table_set_sort(Table
*t
, size_t first_column
, ...) {
1003 size_t allocated
, column
;
1008 allocated
= t
->n_sort_map
;
1009 column
= first_column
;
1011 va_start(ap
, first_column
);
1013 assert(column
< t
->n_columns
);
1015 if (!GREEDY_REALLOC(t
->sort_map
, allocated
, MAX(t
->n_columns
, t
->n_sort_map
+1))) {
1020 t
->sort_map
[t
->n_sort_map
++] = column
;
1022 column
= va_arg(ap
, size_t);
1023 if (column
== (size_t) -1)
1031 static int cell_data_compare(TableData
*a
, size_t index_a
, TableData
*b
, size_t index_b
) {
1035 if (a
->type
== b
->type
) {
1037 /* We only define ordering for cells of the same data type. If cells with different data types are
1038 * compared we follow the order the cells were originally added in */
1043 return strcmp(a
->string
, b
->string
);
1046 return path_compare(a
->string
, b
->string
);
1049 if (!a
->boolean
&& b
->boolean
)
1051 if (a
->boolean
&& !b
->boolean
)
1055 case TABLE_TIMESTAMP
:
1056 case TABLE_TIMESTAMP_UTC
:
1057 case TABLE_TIMESTAMP_RELATIVE
:
1058 return CMP(a
->timestamp
, b
->timestamp
);
1060 case TABLE_TIMESPAN
:
1061 case TABLE_TIMESPAN_MSEC
:
1062 return CMP(a
->timespan
, b
->timespan
);
1066 return CMP(a
->size
, b
->size
);
1069 return CMP(a
->int_val
, b
->int_val
);
1072 return CMP(a
->int8
, b
->int8
);
1075 return CMP(a
->int16
, b
->int16
);
1078 return CMP(a
->int32
, b
->int32
);
1081 return CMP(a
->int64
, b
->int64
);
1084 return CMP(a
->uint_val
, b
->uint_val
);
1087 return CMP(a
->uint8
, b
->uint8
);
1090 return CMP(a
->uint16
, b
->uint16
);
1093 return CMP(a
->uint32
, b
->uint32
);
1096 return CMP(a
->uint64
, b
->uint64
);
1099 return CMP(a
->percent
, b
->percent
);
1102 return CMP(a
->ifindex
, b
->ifindex
);
1105 return CMP(a
->address
.in
.s_addr
, b
->address
.in
.s_addr
);
1107 case TABLE_IN6_ADDR
:
1108 return memcmp(&a
->address
.in6
, &b
->address
.in6
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1115 /* Generic fallback using the original order in which the cells where added. */
1116 return CMP(index_a
, index_b
);
1119 static int table_data_compare(const size_t *a
, const size_t *b
, Table
*t
) {
1124 assert(t
->sort_map
);
1126 /* Make sure the header stays at the beginning */
1127 if (*a
< t
->n_columns
&& *b
< t
->n_columns
)
1129 if (*a
< t
->n_columns
)
1131 if (*b
< t
->n_columns
)
1134 /* Order other lines by the sorting map */
1135 for (i
= 0; i
< t
->n_sort_map
; i
++) {
1138 d
= t
->data
[*a
+ t
->sort_map
[i
]];
1139 dd
= t
->data
[*b
+ t
->sort_map
[i
]];
1141 r
= cell_data_compare(d
, *a
, dd
, *b
);
1143 return t
->reverse_map
&& t
->reverse_map
[t
->sort_map
[i
]] ? -r
: r
;
1146 /* Order identical lines by the order there were originally added in */
1150 static const char *table_data_format(Table
*t
, TableData
*d
) {
1154 return d
->formatted
;
1158 return strempty(t
->empty_string
);
1165 d
->formatted
= new(char, strlen(d
->string
) + 1);
1169 for (p
= d
->string
, q
= d
->formatted
; *p
; p
++, q
++)
1170 *q
= (char) toupper((unsigned char) *p
);
1173 return d
->formatted
;
1179 return yes_no(d
->boolean
);
1181 case TABLE_TIMESTAMP
:
1182 case TABLE_TIMESTAMP_UTC
:
1183 case TABLE_TIMESTAMP_RELATIVE
: {
1184 _cleanup_free_
char *p
;
1187 p
= new(char, FORMAT_TIMESTAMP_MAX
);
1191 if (d
->type
== TABLE_TIMESTAMP
)
1192 ret
= format_timestamp(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1193 else if (d
->type
== TABLE_TIMESTAMP_UTC
)
1194 ret
= format_timestamp_utc(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1196 ret
= format_timestamp_relative(p
, FORMAT_TIMESTAMP_MAX
, d
->timestamp
);
1200 d
->formatted
= TAKE_PTR(p
);
1204 case TABLE_TIMESPAN
:
1205 case TABLE_TIMESPAN_MSEC
: {
1206 _cleanup_free_
char *p
;
1208 p
= new(char, FORMAT_TIMESPAN_MAX
);
1212 if (!format_timespan(p
, FORMAT_TIMESPAN_MAX
, d
->timespan
,
1213 d
->type
== TABLE_TIMESPAN
? 0 : USEC_PER_MSEC
))
1216 d
->formatted
= TAKE_PTR(p
);
1221 _cleanup_free_
char *p
;
1223 p
= new(char, FORMAT_BYTES_MAX
);
1227 if (!format_bytes(p
, FORMAT_BYTES_MAX
, d
->size
))
1230 d
->formatted
= TAKE_PTR(p
);
1235 _cleanup_free_
char *p
;
1238 p
= new(char, FORMAT_BYTES_MAX
+2);
1242 if (!format_bytes_full(p
, FORMAT_BYTES_MAX
, d
->size
, 0))
1246 strscpy(p
+ n
, FORMAT_BYTES_MAX
+ 2 - n
, "bps");
1248 d
->formatted
= TAKE_PTR(p
);
1253 _cleanup_free_
char *p
;
1255 p
= new(char, DECIMAL_STR_WIDTH(d
->int_val
) + 1);
1259 sprintf(p
, "%i", d
->int_val
);
1260 d
->formatted
= TAKE_PTR(p
);
1265 _cleanup_free_
char *p
;
1267 p
= new(char, DECIMAL_STR_WIDTH(d
->int8
) + 1);
1271 sprintf(p
, "%" PRIi8
, d
->int8
);
1272 d
->formatted
= TAKE_PTR(p
);
1277 _cleanup_free_
char *p
;
1279 p
= new(char, DECIMAL_STR_WIDTH(d
->int16
) + 1);
1283 sprintf(p
, "%" PRIi16
, d
->int16
);
1284 d
->formatted
= TAKE_PTR(p
);
1289 _cleanup_free_
char *p
;
1291 p
= new(char, DECIMAL_STR_WIDTH(d
->int32
) + 1);
1295 sprintf(p
, "%" PRIi32
, d
->int32
);
1296 d
->formatted
= TAKE_PTR(p
);
1301 _cleanup_free_
char *p
;
1303 p
= new(char, DECIMAL_STR_WIDTH(d
->int64
) + 1);
1307 sprintf(p
, "%" PRIi64
, d
->int64
);
1308 d
->formatted
= TAKE_PTR(p
);
1313 _cleanup_free_
char *p
;
1315 p
= new(char, DECIMAL_STR_WIDTH(d
->uint_val
) + 1);
1319 sprintf(p
, "%u", d
->uint_val
);
1320 d
->formatted
= TAKE_PTR(p
);
1325 _cleanup_free_
char *p
;
1327 p
= new(char, DECIMAL_STR_WIDTH(d
->uint8
) + 1);
1331 sprintf(p
, "%" PRIu8
, d
->uint8
);
1332 d
->formatted
= TAKE_PTR(p
);
1336 case TABLE_UINT16
: {
1337 _cleanup_free_
char *p
;
1339 p
= new(char, DECIMAL_STR_WIDTH(d
->uint16
) + 1);
1343 sprintf(p
, "%" PRIu16
, d
->uint16
);
1344 d
->formatted
= TAKE_PTR(p
);
1348 case TABLE_UINT32
: {
1349 _cleanup_free_
char *p
;
1351 p
= new(char, DECIMAL_STR_WIDTH(d
->uint32
) + 1);
1355 sprintf(p
, "%" PRIu32
, d
->uint32
);
1356 d
->formatted
= TAKE_PTR(p
);
1360 case TABLE_UINT64
: {
1361 _cleanup_free_
char *p
;
1363 p
= new(char, DECIMAL_STR_WIDTH(d
->uint64
) + 1);
1367 sprintf(p
, "%" PRIu64
, d
->uint64
);
1368 d
->formatted
= TAKE_PTR(p
);
1372 case TABLE_PERCENT
: {
1373 _cleanup_free_
char *p
;
1375 p
= new(char, DECIMAL_STR_WIDTH(d
->percent
) + 2);
1379 sprintf(p
, "%i%%" , d
->percent
);
1380 d
->formatted
= TAKE_PTR(p
);
1384 case TABLE_IFINDEX
: {
1385 _cleanup_free_
char *p
= NULL
;
1386 char name
[IF_NAMESIZE
+ 1];
1388 if (format_ifname(d
->ifindex
, name
)) {
1393 if (asprintf(&p
, "%i" , d
->ifindex
) < 0)
1397 d
->formatted
= TAKE_PTR(p
);
1402 case TABLE_IN6_ADDR
: {
1403 _cleanup_free_
char *p
= NULL
;
1405 if (in_addr_to_string(d
->type
== TABLE_IN_ADDR
? AF_INET
: AF_INET6
,
1406 &d
->address
, &p
) < 0)
1409 d
->formatted
= TAKE_PTR(p
);
1414 assert_not_reached("Unexpected type?");
1417 return d
->formatted
;
1420 static int table_data_requested_width(Table
*table
, TableData
*d
, size_t *ret
) {
1424 t
= table_data_format(table
, d
);
1428 l
= utf8_console_width(t
);
1429 if (l
== (size_t) -1)
1432 if (d
->maximum_width
!= (size_t) -1 && l
> d
->maximum_width
)
1433 l
= d
->maximum_width
;
1435 if (l
< d
->minimum_width
)
1436 l
= d
->minimum_width
;
1442 static char *align_string_mem(const char *str
, const char *url
, size_t new_length
, unsigned percent
) {
1443 size_t w
= 0, space
, lspace
, old_length
, clickable_length
;
1444 _cleanup_free_
char *clickable
= NULL
;
1450 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1453 assert(percent
<= 100);
1455 old_length
= strlen(str
);
1458 r
= terminal_urlify(url
, str
, &clickable
);
1462 clickable_length
= strlen(clickable
);
1464 clickable_length
= old_length
;
1466 /* Determine current width on screen */
1468 while (p
< str
+ old_length
) {
1471 if (utf8_encoded_to_unichar(p
, &c
) < 0) {
1472 p
++, w
++; /* count invalid chars as 1 */
1476 p
= utf8_next_char(p
);
1477 w
+= unichar_iswide(c
) ? 2 : 1;
1480 /* Already wider than the target, if so, don't do anything */
1481 if (w
>= new_length
)
1482 return clickable
? TAKE_PTR(clickable
) : strdup(str
);
1484 /* How much spaces shall we add? An how much on the left side? */
1485 space
= new_length
- w
;
1486 lspace
= space
* percent
/ 100U;
1488 ret
= new(char, space
+ clickable_length
+ 1);
1492 for (i
= 0; i
< lspace
; i
++)
1494 memcpy(ret
+ lspace
, clickable
?: str
, clickable_length
);
1495 for (i
= lspace
+ clickable_length
; i
< space
+ clickable_length
; i
++)
1498 ret
[space
+ clickable_length
] = 0;
1502 static const char* table_data_color(TableData
*d
) {
1508 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1509 if (d
->type
== TABLE_EMPTY
)
1515 int table_print(Table
*t
, FILE *f
) {
1516 size_t n_rows
, *minimum_width
, *maximum_width
, display_columns
, *requested_width
,
1517 i
, j
, table_minimum_width
, table_maximum_width
, table_requested_width
, table_effective_width
,
1519 _cleanup_free_
size_t *sorted
= NULL
;
1520 uint64_t *column_weight
, weight_sum
;
1528 /* Ensure we have no incomplete rows */
1529 assert(t
->n_cells
% t
->n_columns
== 0);
1531 n_rows
= t
->n_cells
/ t
->n_columns
;
1532 assert(n_rows
> 0); /* at least the header row must be complete */
1535 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1537 sorted
= new(size_t, n_rows
);
1541 for (i
= 0; i
< n_rows
; i
++)
1542 sorted
[i
] = i
* t
->n_columns
;
1544 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
1548 display_columns
= t
->n_display_map
;
1550 display_columns
= t
->n_columns
;
1552 assert(display_columns
> 0);
1554 minimum_width
= newa(size_t, display_columns
);
1555 maximum_width
= newa(size_t, display_columns
);
1556 requested_width
= newa(size_t, display_columns
);
1557 width
= newa(size_t, display_columns
);
1558 column_weight
= newa0(uint64_t, display_columns
);
1560 for (j
= 0; j
< display_columns
; j
++) {
1561 minimum_width
[j
] = 1;
1562 maximum_width
[j
] = (size_t) -1;
1563 requested_width
[j
] = (size_t) -1;
1566 /* First pass: determine column sizes */
1567 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1570 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1571 * hence we don't care for sorted[] during the first pass. */
1572 row
= t
->data
+ i
* t
->n_columns
;
1574 for (j
= 0; j
< display_columns
; j
++) {
1578 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1580 r
= table_data_requested_width(t
, d
, &req
);
1584 /* Determine the biggest width that any cell in this column would like to have */
1585 if (requested_width
[j
] == (size_t) -1 ||
1586 requested_width
[j
] < req
)
1587 requested_width
[j
] = req
;
1589 /* Determine the minimum width any cell in this column needs */
1590 if (minimum_width
[j
] < d
->minimum_width
)
1591 minimum_width
[j
] = d
->minimum_width
;
1593 /* Determine the maximum width any cell in this column needs */
1594 if (d
->maximum_width
!= (size_t) -1 &&
1595 (maximum_width
[j
] == (size_t) -1 ||
1596 maximum_width
[j
] > d
->maximum_width
))
1597 maximum_width
[j
] = d
->maximum_width
;
1599 /* Determine the full columns weight */
1600 column_weight
[j
] += d
->weight
;
1604 /* One space between each column */
1605 table_requested_width
= table_minimum_width
= table_maximum_width
= display_columns
- 1;
1607 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1609 for (j
= 0; j
< display_columns
; j
++) {
1610 weight_sum
+= column_weight
[j
];
1612 table_minimum_width
+= minimum_width
[j
];
1614 if (maximum_width
[j
] == (size_t) -1)
1615 table_maximum_width
= (size_t) -1;
1617 table_maximum_width
+= maximum_width
[j
];
1619 table_requested_width
+= requested_width
[j
];
1622 /* Calculate effective table width */
1623 if (t
->width
!= 0 && t
->width
!= (size_t) -1)
1624 table_effective_width
= t
->width
;
1625 else if (t
->width
== 0 || pager_have() || !isatty(STDOUT_FILENO
))
1626 table_effective_width
= table_requested_width
;
1628 table_effective_width
= MIN(table_requested_width
, columns());
1630 if (table_maximum_width
!= (size_t) -1 && table_effective_width
> table_maximum_width
)
1631 table_effective_width
= table_maximum_width
;
1633 if (table_effective_width
< table_minimum_width
)
1634 table_effective_width
= table_minimum_width
;
1636 if (table_effective_width
>= table_requested_width
) {
1639 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1640 * each column with what it asked for and the distribute the rest. */
1642 extra
= table_effective_width
- table_requested_width
;
1644 for (j
= 0; j
< display_columns
; j
++) {
1647 if (weight_sum
== 0)
1648 width
[j
] = requested_width
[j
] + extra
/ (display_columns
- j
); /* Avoid division by zero */
1650 width
[j
] = requested_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1652 if (maximum_width
[j
] != (size_t) -1 && width
[j
] > maximum_width
[j
])
1653 width
[j
] = maximum_width
[j
];
1655 if (width
[j
] < minimum_width
[j
])
1656 width
[j
] = minimum_width
[j
];
1658 assert(width
[j
] >= requested_width
[j
]);
1659 delta
= width
[j
] - requested_width
[j
];
1661 /* Subtract what we just added from the rest */
1667 assert(weight_sum
>= column_weight
[j
]);
1668 weight_sum
-= column_weight
[j
];
1672 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1673 * with the minimum they need, and then distribute anything left. */
1674 bool finalize
= false;
1677 extra
= table_effective_width
- table_minimum_width
;
1679 for (j
= 0; j
< display_columns
; j
++)
1680 width
[j
] = (size_t) -1;
1683 bool restart
= false;
1685 for (j
= 0; j
< display_columns
; j
++) {
1688 /* Did this column already get something assigned? If so, let's skip to the next */
1689 if (width
[j
] != (size_t) -1)
1692 if (weight_sum
== 0)
1693 w
= minimum_width
[j
] + extra
/ (display_columns
- j
); /* avoid division by zero */
1695 w
= minimum_width
[j
] + (extra
* column_weight
[j
]) / weight_sum
;
1697 if (w
>= requested_width
[j
]) {
1698 /* Never give more than requested. If we hit a column like this, there's more
1699 * space to allocate to other columns which means we need to restart the
1700 * iteration. However, if we hit a column like this, let's assign it the space
1701 * it wanted for good early.*/
1703 w
= requested_width
[j
];
1706 } else if (!finalize
)
1711 assert(w
>= minimum_width
[j
]);
1712 delta
= w
- minimum_width
[j
];
1714 assert(delta
<= extra
);
1717 assert(weight_sum
>= column_weight
[j
]);
1718 weight_sum
-= column_weight
[j
];
1720 if (restart
&& !finalize
)
1732 /* Second pass: show output */
1733 for (i
= t
->header
? 0 : 1; i
< n_rows
; i
++) {
1737 row
= t
->data
+ sorted
[i
];
1739 row
= t
->data
+ i
* t
->n_columns
;
1741 for (j
= 0; j
< display_columns
; j
++) {
1742 _cleanup_free_
char *buffer
= NULL
;
1747 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
1749 field
= table_data_format(t
, d
);
1753 l
= utf8_console_width(field
);
1755 /* Field is wider than allocated space. Let's ellipsize */
1757 buffer
= ellipsize(field
, width
[j
], d
->ellipsize_percent
);
1763 } else if (l
< width
[j
]) {
1764 /* Field is shorter than allocated space. Let's align with spaces */
1766 buffer
= align_string_mem(field
, d
->url
, width
[j
], d
->align_percent
);
1773 if (l
>= width
[j
] && d
->url
) {
1774 _cleanup_free_
char *clickable
= NULL
;
1776 r
= terminal_urlify(d
->url
, field
, &clickable
);
1780 free_and_replace(buffer
, clickable
);
1784 if (row
== t
->data
) /* underline header line fully, including the column separator */
1785 fputs(ansi_underline(), f
);
1788 fputc(' ', f
); /* column separator */
1790 if (table_data_color(d
) && colors_enabled()) {
1791 if (row
== t
->data
) /* first undo header underliner */
1792 fputs(ANSI_NORMAL
, f
);
1794 fputs(table_data_color(d
), f
);
1799 if (colors_enabled() && (table_data_color(d
) || row
== t
->data
))
1800 fputs(ANSI_NORMAL
, f
);
1806 return fflush_and_check(f
);
1809 int table_format(Table
*t
, char **ret
) {
1810 _cleanup_fclose_
FILE *f
= NULL
;
1815 f
= open_memstream_unlocked(&buf
, &sz
);
1819 r
= table_print(t
, f
);
1830 size_t table_get_rows(Table
*t
) {
1834 assert(t
->n_columns
> 0);
1835 return t
->n_cells
/ t
->n_columns
;
1838 size_t table_get_columns(Table
*t
) {
1842 assert(t
->n_columns
> 0);
1843 return t
->n_columns
;
1846 int table_set_reverse(Table
*t
, size_t column
, bool b
) {
1848 assert(column
< t
->n_columns
);
1850 if (!t
->reverse_map
) {
1854 t
->reverse_map
= new0(bool, t
->n_columns
);
1855 if (!t
->reverse_map
)
1859 t
->reverse_map
[column
] = b
;
1863 TableCell
*table_get_cell(Table
*t
, size_t row
, size_t column
) {
1868 if (column
>= t
->n_columns
)
1871 i
= row
* t
->n_columns
+ column
;
1872 if (i
>= t
->n_cells
)
1875 return TABLE_INDEX_TO_CELL(i
);
1878 const void *table_get(Table
*t
, TableCell
*cell
) {
1883 d
= table_get_data(t
, cell
);
1890 const void* table_get_at(Table
*t
, size_t row
, size_t column
) {
1893 cell
= table_get_cell(t
, row
, column
);
1897 return table_get(t
, cell
);
1900 static int table_data_to_json(TableData
*d
, JsonVariant
**ret
) {
1905 return json_variant_new_null(ret
);
1909 return json_variant_new_string(ret
, d
->string
);
1912 return json_variant_new_boolean(ret
, d
->boolean
);
1914 case TABLE_TIMESTAMP
:
1915 case TABLE_TIMESTAMP_UTC
:
1916 case TABLE_TIMESTAMP_RELATIVE
:
1917 if (d
->timestamp
== USEC_INFINITY
)
1918 return json_variant_new_null(ret
);
1920 return json_variant_new_unsigned(ret
, d
->timestamp
);
1922 case TABLE_TIMESPAN
:
1923 case TABLE_TIMESPAN_MSEC
:
1924 if (d
->timespan
== USEC_INFINITY
)
1925 return json_variant_new_null(ret
);
1927 return json_variant_new_unsigned(ret
, d
->timespan
);
1931 if (d
->size
== (size_t) -1)
1932 return json_variant_new_null(ret
);
1934 return json_variant_new_unsigned(ret
, d
->size
);
1937 return json_variant_new_integer(ret
, d
->int_val
);
1940 return json_variant_new_integer(ret
, d
->int8
);
1943 return json_variant_new_integer(ret
, d
->int16
);
1946 return json_variant_new_integer(ret
, d
->int32
);
1949 return json_variant_new_integer(ret
, d
->int64
);
1952 return json_variant_new_unsigned(ret
, d
->uint_val
);
1955 return json_variant_new_unsigned(ret
, d
->uint8
);
1958 return json_variant_new_unsigned(ret
, d
->uint16
);
1961 return json_variant_new_unsigned(ret
, d
->uint32
);
1964 return json_variant_new_unsigned(ret
, d
->uint64
);
1967 return json_variant_new_integer(ret
, d
->percent
);
1970 return json_variant_new_integer(ret
, d
->ifindex
);
1973 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET
));
1975 case TABLE_IN6_ADDR
:
1976 return json_variant_new_array_bytes(ret
, &d
->address
, FAMILY_ADDRESS_SIZE(AF_INET6
));
1983 int table_to_json(Table
*t
, JsonVariant
**ret
) {
1984 JsonVariant
**rows
= NULL
, **elements
= NULL
;
1985 _cleanup_free_
size_t *sorted
= NULL
;
1986 size_t n_rows
, i
, j
, display_columns
;
1991 /* Ensure we have no incomplete rows */
1992 assert(t
->n_cells
% t
->n_columns
== 0);
1994 n_rows
= t
->n_cells
/ t
->n_columns
;
1995 assert(n_rows
> 0); /* at least the header row must be complete */
1998 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2000 sorted
= new(size_t, n_rows
);
2006 for (i
= 0; i
< n_rows
; i
++)
2007 sorted
[i
] = i
* t
->n_columns
;
2009 typesafe_qsort_r(sorted
, n_rows
, table_data_compare
, t
);
2013 display_columns
= t
->n_display_map
;
2015 display_columns
= t
->n_columns
;
2016 assert(display_columns
> 0);
2018 elements
= new0(JsonVariant
*, display_columns
* 2);
2024 for (j
= 0; j
< display_columns
; j
++) {
2027 assert_se(d
= t
->data
[t
->display_map
? t
->display_map
[j
] : j
]);
2029 r
= table_data_to_json(d
, elements
+ j
*2);
2034 rows
= new0(JsonVariant
*, n_rows
-1);
2040 for (i
= 1; i
< n_rows
; i
++) {
2044 row
= t
->data
+ sorted
[i
];
2046 row
= t
->data
+ i
* t
->n_columns
;
2048 for (j
= 0; j
< display_columns
; j
++) {
2052 assert_se(d
= row
[t
->display_map
? t
->display_map
[j
] : j
]);
2055 elements
[k
] = json_variant_unref(elements
[k
]);
2057 r
= table_data_to_json(d
, elements
+ k
);
2062 r
= json_variant_new_object(rows
+ i
- 1, elements
, display_columns
* 2);
2067 r
= json_variant_new_array(ret
, rows
, n_rows
- 1);
2071 json_variant_unref_many(rows
, n_rows
-1);
2076 json_variant_unref_many(elements
, display_columns
*2);
2083 int table_print_json(Table
*t
, FILE *f
, JsonFormatFlags flags
) {
2084 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
2092 r
= table_to_json(t
, &v
);
2096 json_variant_dump(v
, flags
, f
, NULL
);
2098 return fflush_and_check(f
);