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