Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <ctype.h>
4 : #include <net/if.h>
5 :
6 : #include "alloc-util.h"
7 : #include "fd-util.h"
8 : #include "fileio.h"
9 : #include "format-table.h"
10 : #include "format-util.h"
11 : #include "gunicode.h"
12 : #include "in-addr-util.h"
13 : #include "memory-util.h"
14 : #include "pager.h"
15 : #include "parse-util.h"
16 : #include "pretty-print.h"
17 : #include "sort-util.h"
18 : #include "string-util.h"
19 : #include "strxcpyx.h"
20 : #include "terminal-util.h"
21 : #include "time-util.h"
22 : #include "utf8.h"
23 : #include "util.h"
24 :
25 : #define DEFAULT_WEIGHT 100
26 :
27 : /*
28 : A few notes on implementation details:
29 :
30 : - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
31 : table. It can be easily converted to an index number and back.
32 :
33 : - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
34 : 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
35 : ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
36 : outside only sees Table and TableCell.
37 :
38 : - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
39 : previous one.
40 :
41 : - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
42 : derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
43 : that. The first row is always the header row. If header display is turned off we simply skip outputting the first
44 : row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
45 :
46 : - Note because there's no row and no column object some properties that might be appropriate as row/column properties
47 : are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
48 : add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
49 : cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
50 : instead.
51 :
52 : - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
53 : from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
54 : this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
55 : */
56 :
57 : typedef struct TableData {
58 : unsigned n_ref;
59 : TableDataType type;
60 :
61 : size_t minimum_width; /* minimum width for the column */
62 : size_t maximum_width; /* maximum width for the column */
63 : unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
64 : unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
65 : unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
66 :
67 : bool uppercase; /* Uppercase string on display */
68 :
69 : 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 */
70 : char *url; /* A URL to use for a clickable hyperlink */
71 : char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
72 :
73 : union {
74 : uint8_t data[0]; /* data is generic array */
75 : bool boolean;
76 : usec_t timestamp;
77 : usec_t timespan;
78 : uint64_t size;
79 : char string[0];
80 : int int_val;
81 : int8_t int8;
82 : int16_t int16;
83 : int32_t int32;
84 : int64_t int64;
85 : unsigned uint_val;
86 : uint8_t uint8;
87 : uint16_t uint16;
88 : uint32_t uint32;
89 : uint64_t uint64;
90 : int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
91 : int ifindex;
92 : union in_addr_union address;
93 : /* … add more here as we start supporting more cell data types … */
94 : };
95 : } TableData;
96 :
97 24 : static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
98 : size_t i;
99 :
100 24 : assert(cell);
101 :
102 24 : i = PTR_TO_SIZE(cell);
103 24 : assert(i > 0);
104 :
105 24 : return i-1;
106 : }
107 :
108 30 : static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
109 30 : assert(index != (size_t) -1);
110 30 : return SIZE_TO_PTR(index + 1);
111 : }
112 :
113 : struct Table {
114 : size_t n_columns;
115 : size_t n_cells;
116 :
117 : bool header; /* Whether to show the header row? */
118 : size_t width; /* If != (size_t) -1 the width to format this table in */
119 :
120 : TableData **data;
121 : size_t n_allocated;
122 :
123 : 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 */
124 : size_t n_display_map;
125 :
126 : size_t *sort_map; /* The columns to order rows by, in order of preference. */
127 : size_t n_sort_map;
128 :
129 : bool *reverse_map;
130 :
131 : char *empty_string;
132 : };
133 :
134 2 : Table *table_new_raw(size_t n_columns) {
135 2 : _cleanup_(table_unrefp) Table *t = NULL;
136 :
137 2 : assert(n_columns > 0);
138 :
139 2 : t = new(Table, 1);
140 2 : if (!t)
141 0 : return NULL;
142 :
143 2 : *t = (struct Table) {
144 : .n_columns = n_columns,
145 : .header = true,
146 : .width = (size_t) -1,
147 : };
148 :
149 2 : return TAKE_PTR(t);
150 : }
151 :
152 2 : Table *table_new_internal(const char *first_header, ...) {
153 2 : _cleanup_(table_unrefp) Table *t = NULL;
154 2 : size_t n_columns = 1;
155 : const char *h;
156 : va_list ap;
157 : int r;
158 :
159 2 : assert(first_header);
160 :
161 2 : va_start(ap, first_header);
162 : for (;;) {
163 9 : h = va_arg(ap, const char*);
164 9 : if (!h)
165 2 : break;
166 :
167 7 : n_columns++;
168 : }
169 2 : va_end(ap);
170 :
171 2 : t = table_new_raw(n_columns);
172 2 : if (!t)
173 0 : return NULL;
174 :
175 2 : va_start(ap, first_header);
176 11 : for (h = first_header; h; h = va_arg(ap, const char*)) {
177 : TableCell *cell;
178 :
179 9 : r = table_add_cell(t, &cell, TABLE_STRING, h);
180 9 : if (r < 0) {
181 0 : va_end(ap);
182 0 : return NULL;
183 : }
184 :
185 : /* Make the table header uppercase */
186 9 : r = table_set_uppercase(t, cell, true);
187 9 : if (r < 0) {
188 0 : va_end(ap);
189 0 : return NULL;
190 : }
191 : }
192 2 : va_end(ap);
193 :
194 2 : assert(t->n_columns == t->n_cells);
195 2 : return TAKE_PTR(t);
196 : }
197 :
198 29 : static TableData *table_data_free(TableData *d) {
199 29 : assert(d);
200 :
201 29 : free(d->formatted);
202 29 : free(d->url);
203 :
204 29 : return mfree(d);
205 : }
206 :
207 33 : DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
208 30 : DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
209 :
210 2 : Table *table_unref(Table *t) {
211 : size_t i;
212 :
213 2 : if (!t)
214 0 : return NULL;
215 :
216 32 : for (i = 0; i < t->n_cells; i++)
217 30 : table_data_unref(t->data[i]);
218 :
219 2 : free(t->data);
220 2 : free(t->display_map);
221 2 : free(t->sort_map);
222 2 : free(t->reverse_map);
223 2 : free(t->empty_string);
224 :
225 2 : return mfree(t);
226 : }
227 :
228 45 : static size_t table_data_size(TableDataType type, const void *data) {
229 :
230 45 : switch (type) {
231 :
232 1 : case TABLE_EMPTY:
233 1 : return 0;
234 :
235 30 : case TABLE_STRING:
236 30 : return strlen(data) + 1;
237 :
238 11 : case TABLE_BOOLEAN:
239 11 : return sizeof(bool);
240 :
241 1 : case TABLE_TIMESTAMP:
242 : case TABLE_TIMESTAMP_UTC:
243 : case TABLE_TIMESTAMP_RELATIVE:
244 : case TABLE_TIMESPAN:
245 : case TABLE_TIMESPAN_MSEC:
246 1 : return sizeof(usec_t);
247 :
248 2 : case TABLE_SIZE:
249 : case TABLE_INT64:
250 : case TABLE_UINT64:
251 : case TABLE_BPS:
252 2 : return sizeof(uint64_t);
253 :
254 0 : case TABLE_INT32:
255 : case TABLE_UINT32:
256 0 : return sizeof(uint32_t);
257 :
258 0 : case TABLE_INT16:
259 : case TABLE_UINT16:
260 0 : return sizeof(uint16_t);
261 :
262 0 : case TABLE_INT8:
263 : case TABLE_UINT8:
264 0 : return sizeof(uint8_t);
265 :
266 0 : case TABLE_INT:
267 : case TABLE_UINT:
268 : case TABLE_PERCENT:
269 : case TABLE_IFINDEX:
270 0 : return sizeof(int);
271 :
272 0 : case TABLE_IN_ADDR:
273 0 : return sizeof(struct in_addr);
274 :
275 0 : case TABLE_IN6_ADDR:
276 0 : return sizeof(struct in6_addr);
277 :
278 0 : default:
279 0 : assert_not_reached("Uh? Unexpected cell type");
280 : }
281 : }
282 :
283 21 : static bool table_data_matches(
284 : TableData *d,
285 : TableDataType type,
286 : const void *data,
287 : size_t minimum_width,
288 : size_t maximum_width,
289 : unsigned weight,
290 : unsigned align_percent,
291 : unsigned ellipsize_percent) {
292 :
293 : size_t k, l;
294 21 : assert(d);
295 :
296 21 : if (d->type != type)
297 6 : return false;
298 :
299 15 : if (d->minimum_width != minimum_width)
300 0 : return false;
301 :
302 15 : if (d->maximum_width != maximum_width)
303 0 : return false;
304 :
305 15 : if (d->weight != weight)
306 0 : return false;
307 :
308 15 : if (d->align_percent != align_percent)
309 0 : return false;
310 :
311 15 : if (d->ellipsize_percent != ellipsize_percent)
312 0 : return false;
313 :
314 : /* If a color/url/uppercase flag is set, refuse to merge */
315 15 : if (d->color)
316 0 : return false;
317 15 : if (d->url)
318 0 : return false;
319 15 : if (d->uppercase)
320 7 : return false;
321 :
322 8 : k = table_data_size(type, data);
323 8 : l = table_data_size(d->type, d->data);
324 :
325 8 : if (k != l)
326 2 : return false;
327 :
328 6 : return memcmp_safe(data, d->data, l) == 0;
329 : }
330 :
331 29 : static TableData *table_data_new(
332 : TableDataType type,
333 : const void *data,
334 : size_t minimum_width,
335 : size_t maximum_width,
336 : unsigned weight,
337 : unsigned align_percent,
338 : unsigned ellipsize_percent) {
339 :
340 : size_t data_size;
341 : TableData *d;
342 :
343 29 : data_size = table_data_size(type, data);
344 :
345 29 : d = malloc0(offsetof(TableData, data) + data_size);
346 29 : if (!d)
347 0 : return NULL;
348 :
349 29 : d->n_ref = 1;
350 29 : d->type = type;
351 29 : d->minimum_width = minimum_width;
352 29 : d->maximum_width = maximum_width;
353 29 : d->weight = weight;
354 29 : d->align_percent = align_percent;
355 29 : d->ellipsize_percent = ellipsize_percent;
356 29 : memcpy_safe(d->data, data, data_size);
357 :
358 29 : return d;
359 : }
360 :
361 30 : int table_add_cell_full(
362 : Table *t,
363 : TableCell **ret_cell,
364 : TableDataType type,
365 : const void *data,
366 : size_t minimum_width,
367 : size_t maximum_width,
368 : unsigned weight,
369 : unsigned align_percent,
370 : unsigned ellipsize_percent) {
371 :
372 30 : _cleanup_(table_data_unrefp) TableData *d = NULL;
373 : TableData *p;
374 :
375 30 : assert(t);
376 30 : assert(type >= 0);
377 30 : assert(type < _TABLE_DATA_TYPE_MAX);
378 :
379 : /* Special rule: patch NULL data fields to the empty field */
380 30 : if (!data)
381 1 : type = TABLE_EMPTY;
382 :
383 : /* Determine the cell adjacent to the current one, but one row up */
384 30 : if (t->n_cells >= t->n_columns)
385 21 : assert_se(p = t->data[t->n_cells - t->n_columns]);
386 : else
387 9 : p = NULL;
388 :
389 : /* If formatting parameters are left unspecified, copy from the previous row */
390 30 : if (minimum_width == (size_t) -1)
391 30 : minimum_width = p ? p->minimum_width : 1;
392 :
393 30 : if (weight == (unsigned) -1)
394 30 : weight = p ? p->weight : DEFAULT_WEIGHT;
395 :
396 30 : if (align_percent == (unsigned) -1)
397 30 : align_percent = p ? p->align_percent : 0;
398 :
399 30 : if (ellipsize_percent == (unsigned) -1)
400 30 : ellipsize_percent = p ? p->ellipsize_percent : 100;
401 :
402 30 : assert(align_percent <= 100);
403 30 : assert(ellipsize_percent <= 100);
404 :
405 : /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
406 : * formatting. Let's see if we can reuse the cell data and ref it once more. */
407 :
408 30 : if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
409 2 : d = table_data_ref(p);
410 : else {
411 28 : d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
412 28 : if (!d)
413 0 : return -ENOMEM;
414 : }
415 :
416 30 : if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
417 0 : return -ENOMEM;
418 :
419 30 : if (ret_cell)
420 30 : *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
421 :
422 30 : t->data[t->n_cells++] = TAKE_PTR(d);
423 :
424 30 : return 0;
425 : }
426 :
427 0 : int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
428 0 : _cleanup_free_ char *buffer = NULL;
429 : va_list ap;
430 : int r;
431 :
432 0 : va_start(ap, format);
433 0 : r = vasprintf(&buffer, format, ap);
434 0 : va_end(ap);
435 0 : if (r < 0)
436 0 : return -ENOMEM;
437 :
438 0 : return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
439 : }
440 :
441 0 : int table_fill_empty(Table *t, size_t until_column) {
442 : int r;
443 :
444 0 : assert(t);
445 :
446 : /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
447 : * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
448 :
449 0 : if (until_column >= t->n_columns)
450 0 : return -EINVAL;
451 :
452 : do {
453 0 : r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
454 0 : if (r < 0)
455 0 : return r;
456 :
457 0 : } while ((t->n_cells % t->n_columns) != until_column);
458 :
459 0 : return 0;
460 : }
461 :
462 0 : int table_dup_cell(Table *t, TableCell *cell) {
463 : size_t i;
464 :
465 0 : assert(t);
466 :
467 : /* Add the data of the specified cell a second time as a new cell to the end. */
468 :
469 0 : i = TABLE_CELL_TO_INDEX(cell);
470 0 : if (i >= t->n_cells)
471 0 : return -ENXIO;
472 :
473 0 : if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
474 0 : return -ENOMEM;
475 :
476 0 : t->data[t->n_cells++] = table_data_ref(t->data[i]);
477 0 : return 0;
478 : }
479 :
480 12 : static int table_dedup_cell(Table *t, TableCell *cell) {
481 12 : _cleanup_free_ char *curl = NULL;
482 : TableData *nd, *od;
483 : size_t i;
484 :
485 12 : assert(t);
486 :
487 : /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
488 : * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
489 :
490 12 : i = TABLE_CELL_TO_INDEX(cell);
491 12 : if (i >= t->n_cells)
492 0 : return -ENXIO;
493 :
494 12 : assert_se(od = t->data[i]);
495 12 : if (od->n_ref == 1)
496 11 : return 0;
497 :
498 1 : assert(od->n_ref > 1);
499 :
500 1 : if (od->url) {
501 0 : curl = strdup(od->url);
502 0 : if (!curl)
503 0 : return -ENOMEM;
504 : }
505 :
506 2 : nd = table_data_new(
507 : od->type,
508 1 : od->data,
509 : od->minimum_width,
510 : od->maximum_width,
511 : od->weight,
512 : od->align_percent,
513 : od->ellipsize_percent);
514 1 : if (!nd)
515 0 : return -ENOMEM;
516 :
517 1 : nd->color = od->color;
518 1 : nd->url = TAKE_PTR(curl);
519 1 : nd->uppercase = od->uppercase;
520 :
521 1 : table_data_unref(od);
522 1 : t->data[i] = nd;
523 :
524 1 : assert(nd->n_ref == 1);
525 :
526 1 : return 1;
527 : }
528 :
529 12 : static TableData *table_get_data(Table *t, TableCell *cell) {
530 : size_t i;
531 :
532 12 : assert(t);
533 12 : assert(cell);
534 :
535 : /* Get the data object of the specified cell, or NULL if it doesn't exist */
536 :
537 12 : i = TABLE_CELL_TO_INDEX(cell);
538 12 : if (i >= t->n_cells)
539 0 : return NULL;
540 :
541 12 : assert(t->data[i]);
542 12 : assert(t->data[i]->n_ref > 0);
543 :
544 12 : return t->data[i];
545 : }
546 :
547 0 : int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
548 : int r;
549 :
550 0 : assert(t);
551 0 : assert(cell);
552 :
553 0 : if (minimum_width == (size_t) -1)
554 0 : minimum_width = 1;
555 :
556 0 : r = table_dedup_cell(t, cell);
557 0 : if (r < 0)
558 0 : return r;
559 :
560 0 : table_get_data(t, cell)->minimum_width = minimum_width;
561 0 : return 0;
562 : }
563 :
564 0 : int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
565 : int r;
566 :
567 0 : assert(t);
568 0 : assert(cell);
569 :
570 0 : r = table_dedup_cell(t, cell);
571 0 : if (r < 0)
572 0 : return r;
573 :
574 0 : table_get_data(t, cell)->maximum_width = maximum_width;
575 0 : return 0;
576 : }
577 :
578 0 : int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
579 : int r;
580 :
581 0 : assert(t);
582 0 : assert(cell);
583 :
584 0 : if (weight == (unsigned) -1)
585 0 : weight = DEFAULT_WEIGHT;
586 :
587 0 : r = table_dedup_cell(t, cell);
588 0 : if (r < 0)
589 0 : return r;
590 :
591 0 : table_get_data(t, cell)->weight = weight;
592 0 : return 0;
593 : }
594 :
595 2 : int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
596 : int r;
597 :
598 2 : assert(t);
599 2 : assert(cell);
600 :
601 2 : if (percent == (unsigned) -1)
602 0 : percent = 0;
603 :
604 2 : assert(percent <= 100);
605 :
606 2 : r = table_dedup_cell(t, cell);
607 2 : if (r < 0)
608 0 : return r;
609 :
610 2 : table_get_data(t, cell)->align_percent = percent;
611 2 : return 0;
612 : }
613 :
614 0 : int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
615 : int r;
616 :
617 0 : assert(t);
618 0 : assert(cell);
619 :
620 0 : if (percent == (unsigned) -1)
621 0 : percent = 100;
622 :
623 0 : assert(percent <= 100);
624 :
625 0 : r = table_dedup_cell(t, cell);
626 0 : if (r < 0)
627 0 : return r;
628 :
629 0 : table_get_data(t, cell)->ellipsize_percent = percent;
630 0 : return 0;
631 : }
632 :
633 0 : int table_set_color(Table *t, TableCell *cell, const char *color) {
634 : int r;
635 :
636 0 : assert(t);
637 0 : assert(cell);
638 :
639 0 : r = table_dedup_cell(t, cell);
640 0 : if (r < 0)
641 0 : return r;
642 :
643 0 : table_get_data(t, cell)->color = empty_to_null(color);
644 0 : return 0;
645 : }
646 :
647 0 : int table_set_url(Table *t, TableCell *cell, const char *url) {
648 0 : _cleanup_free_ char *copy = NULL;
649 : int r;
650 :
651 0 : assert(t);
652 0 : assert(cell);
653 :
654 0 : if (url) {
655 0 : copy = strdup(url);
656 0 : if (!copy)
657 0 : return -ENOMEM;
658 : }
659 :
660 0 : r = table_dedup_cell(t, cell);
661 0 : if (r < 0)
662 0 : return r;
663 :
664 0 : return free_and_replace(table_get_data(t, cell)->url, copy);
665 : }
666 :
667 10 : int table_set_uppercase(Table *t, TableCell *cell, bool b) {
668 : TableData *d;
669 : int r;
670 :
671 10 : assert(t);
672 10 : assert(cell);
673 :
674 10 : r = table_dedup_cell(t, cell);
675 10 : if (r < 0)
676 0 : return r;
677 :
678 10 : assert_se(d = table_get_data(t, cell));
679 :
680 10 : if (d->uppercase == b)
681 0 : return 0;
682 :
683 10 : d->formatted = mfree(d->formatted);
684 10 : d->uppercase = b;
685 10 : return 1;
686 : }
687 :
688 0 : int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
689 0 : _cleanup_free_ char *curl = NULL;
690 : TableData *nd, *od;
691 : size_t i;
692 :
693 0 : assert(t);
694 0 : assert(cell);
695 :
696 0 : i = TABLE_CELL_TO_INDEX(cell);
697 0 : if (i >= t->n_cells)
698 0 : return -ENXIO;
699 :
700 0 : assert_se(od = t->data[i]);
701 :
702 0 : if (od->url) {
703 0 : curl = strdup(od->url);
704 0 : if (!curl)
705 0 : return -ENOMEM;
706 : }
707 :
708 0 : nd = table_data_new(
709 : type,
710 : data,
711 : od->minimum_width,
712 : od->maximum_width,
713 : od->weight,
714 : od->align_percent,
715 : od->ellipsize_percent);
716 0 : if (!nd)
717 0 : return -ENOMEM;
718 :
719 0 : nd->color = od->color;
720 0 : nd->url = TAKE_PTR(curl);
721 0 : nd->uppercase = od->uppercase;
722 :
723 0 : table_data_unref(od);
724 0 : t->data[i] = nd;
725 :
726 0 : return 0;
727 : }
728 :
729 6 : int table_add_many_internal(Table *t, TableDataType first_type, ...) {
730 : TableDataType type;
731 : va_list ap;
732 6 : TableCell *last_cell = NULL;
733 : int r;
734 :
735 6 : assert(t);
736 6 : assert(first_type >= 0);
737 6 : assert(first_type < _TABLE_DATA_TYPE_MAX);
738 :
739 6 : type = first_type;
740 :
741 6 : va_start(ap, first_type);
742 22 : for (;;) {
743 : const void *data;
744 : union {
745 : uint64_t size;
746 : usec_t usec;
747 : int int_val;
748 : int8_t int8;
749 : int16_t int16;
750 : int32_t int32;
751 : int64_t int64;
752 : unsigned uint_val;
753 : uint8_t uint8;
754 : uint16_t uint16;
755 : uint32_t uint32;
756 : uint64_t uint64;
757 : int percent;
758 : int ifindex;
759 : bool b;
760 : union in_addr_union address;
761 : } buffer;
762 :
763 28 : switch (type) {
764 :
765 1 : case TABLE_EMPTY:
766 1 : data = NULL;
767 1 : break;
768 :
769 12 : case TABLE_STRING:
770 12 : data = va_arg(ap, const char *);
771 12 : break;
772 :
773 5 : case TABLE_BOOLEAN:
774 5 : buffer.b = va_arg(ap, int);
775 5 : data = &buffer.b;
776 5 : break;
777 :
778 1 : case TABLE_TIMESTAMP:
779 : case TABLE_TIMESTAMP_UTC:
780 : case TABLE_TIMESTAMP_RELATIVE:
781 : case TABLE_TIMESPAN:
782 : case TABLE_TIMESPAN_MSEC:
783 1 : buffer.usec = va_arg(ap, usec_t);
784 1 : data = &buffer.usec;
785 1 : break;
786 :
787 2 : case TABLE_SIZE:
788 : case TABLE_BPS:
789 2 : buffer.size = va_arg(ap, uint64_t);
790 2 : data = &buffer.size;
791 2 : break;
792 :
793 0 : case TABLE_INT:
794 0 : buffer.int_val = va_arg(ap, int);
795 0 : data = &buffer.int_val;
796 0 : break;
797 :
798 0 : case TABLE_INT8: {
799 0 : int x = va_arg(ap, int);
800 0 : assert(x >= INT8_MIN && x <= INT8_MAX);
801 :
802 0 : buffer.int8 = x;
803 0 : data = &buffer.int8;
804 0 : break;
805 : }
806 :
807 0 : case TABLE_INT16: {
808 0 : int x = va_arg(ap, int);
809 0 : assert(x >= INT16_MIN && x <= INT16_MAX);
810 :
811 0 : buffer.int16 = x;
812 0 : data = &buffer.int16;
813 0 : break;
814 : }
815 :
816 0 : case TABLE_INT32:
817 0 : buffer.int32 = va_arg(ap, int32_t);
818 0 : data = &buffer.int32;
819 0 : break;
820 :
821 0 : case TABLE_INT64:
822 0 : buffer.int64 = va_arg(ap, int64_t);
823 0 : data = &buffer.int64;
824 0 : break;
825 :
826 0 : case TABLE_UINT:
827 0 : buffer.uint_val = va_arg(ap, unsigned);
828 0 : data = &buffer.uint_val;
829 0 : break;
830 :
831 0 : case TABLE_UINT8: {
832 0 : unsigned x = va_arg(ap, unsigned);
833 0 : assert(x <= UINT8_MAX);
834 :
835 0 : buffer.uint8 = x;
836 0 : data = &buffer.uint8;
837 0 : break;
838 : }
839 :
840 0 : case TABLE_UINT16: {
841 0 : unsigned x = va_arg(ap, unsigned);
842 0 : assert(x <= UINT16_MAX);
843 :
844 0 : buffer.uint16 = x;
845 0 : data = &buffer.uint16;
846 0 : break;
847 : }
848 :
849 0 : case TABLE_UINT32:
850 0 : buffer.uint32 = va_arg(ap, uint32_t);
851 0 : data = &buffer.uint32;
852 0 : break;
853 :
854 0 : case TABLE_UINT64:
855 0 : buffer.uint64 = va_arg(ap, uint64_t);
856 0 : data = &buffer.uint64;
857 0 : break;
858 :
859 0 : case TABLE_PERCENT:
860 0 : buffer.percent = va_arg(ap, int);
861 0 : data = &buffer.percent;
862 0 : break;
863 :
864 0 : case TABLE_IFINDEX:
865 0 : buffer.ifindex = va_arg(ap, int);
866 0 : data = &buffer.ifindex;
867 0 : break;
868 :
869 0 : case TABLE_IN_ADDR:
870 0 : buffer.address = *va_arg(ap, union in_addr_union *);
871 0 : data = &buffer.address.in;
872 0 : break;
873 :
874 0 : case TABLE_IN6_ADDR:
875 0 : buffer.address = *va_arg(ap, union in_addr_union *);
876 0 : data = &buffer.address.in6;
877 0 : break;
878 :
879 0 : case TABLE_SET_MINIMUM_WIDTH: {
880 0 : size_t w = va_arg(ap, size_t);
881 :
882 0 : r = table_set_minimum_width(t, last_cell, w);
883 0 : break;
884 : }
885 :
886 0 : case TABLE_SET_MAXIMUM_WIDTH: {
887 0 : size_t w = va_arg(ap, size_t);
888 0 : r = table_set_maximum_width(t, last_cell, w);
889 0 : break;
890 : }
891 :
892 0 : case TABLE_SET_WEIGHT: {
893 0 : unsigned w = va_arg(ap, unsigned);
894 0 : r = table_set_weight(t, last_cell, w);
895 0 : break;
896 : }
897 :
898 0 : case TABLE_SET_ALIGN_PERCENT: {
899 0 : unsigned p = va_arg(ap, unsigned);
900 0 : r = table_set_align_percent(t, last_cell, p);
901 0 : break;
902 : }
903 :
904 0 : case TABLE_SET_ELLIPSIZE_PERCENT: {
905 0 : unsigned p = va_arg(ap, unsigned);
906 0 : r = table_set_ellipsize_percent(t, last_cell, p);
907 0 : break;
908 : }
909 :
910 0 : case TABLE_SET_COLOR: {
911 0 : const char *c = va_arg(ap, const char*);
912 0 : r = table_set_color(t, last_cell, c);
913 0 : break;
914 : }
915 :
916 0 : case TABLE_SET_URL: {
917 0 : const char *u = va_arg(ap, const char*);
918 0 : r = table_set_url(t, last_cell, u);
919 0 : break;
920 : }
921 :
922 1 : case TABLE_SET_UPPERCASE: {
923 1 : int u = va_arg(ap, int);
924 1 : r = table_set_uppercase(t, last_cell, u);
925 1 : break;
926 : }
927 :
928 6 : case _TABLE_DATA_TYPE_MAX:
929 : /* Used as end marker */
930 6 : va_end(ap);
931 6 : return 0;
932 :
933 0 : default:
934 0 : assert_not_reached("Uh? Unexpected data type.");
935 : }
936 :
937 22 : if (type < _TABLE_DATA_TYPE_MAX)
938 21 : r = table_add_cell(t, &last_cell, type, data);
939 :
940 22 : if (r < 0) {
941 0 : va_end(ap);
942 0 : return r;
943 : }
944 :
945 22 : type = va_arg(ap, TableDataType);
946 : }
947 : }
948 :
949 1 : void table_set_header(Table *t, bool b) {
950 1 : assert(t);
951 :
952 1 : t->header = b;
953 1 : }
954 :
955 6 : void table_set_width(Table *t, size_t width) {
956 6 : assert(t);
957 :
958 6 : t->width = width;
959 6 : }
960 :
961 0 : int table_set_empty_string(Table *t, const char *empty) {
962 0 : assert(t);
963 :
964 0 : return free_and_strdup(&t->empty_string, empty);
965 : }
966 :
967 1 : int table_set_display(Table *t, size_t first_column, ...) {
968 : size_t allocated, column;
969 : va_list ap;
970 :
971 1 : assert(t);
972 :
973 1 : allocated = t->n_display_map;
974 1 : column = first_column;
975 :
976 1 : va_start(ap, first_column);
977 : for (;;) {
978 5 : assert(column < t->n_columns);
979 :
980 5 : if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
981 0 : va_end(ap);
982 0 : return -ENOMEM;
983 : }
984 :
985 5 : t->display_map[t->n_display_map++] = column;
986 :
987 5 : column = va_arg(ap, size_t);
988 5 : if (column == (size_t) -1)
989 1 : break;
990 :
991 : }
992 1 : va_end(ap);
993 :
994 1 : return 0;
995 : }
996 :
997 1 : int table_set_sort(Table *t, size_t first_column, ...) {
998 : size_t allocated, column;
999 : va_list ap;
1000 :
1001 1 : assert(t);
1002 :
1003 1 : allocated = t->n_sort_map;
1004 1 : column = first_column;
1005 :
1006 1 : va_start(ap, first_column);
1007 : for (;;) {
1008 2 : assert(column < t->n_columns);
1009 :
1010 2 : if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
1011 0 : va_end(ap);
1012 0 : return -ENOMEM;
1013 : }
1014 :
1015 2 : t->sort_map[t->n_sort_map++] = column;
1016 :
1017 2 : column = va_arg(ap, size_t);
1018 2 : if (column == (size_t) -1)
1019 1 : break;
1020 : }
1021 1 : va_end(ap);
1022 :
1023 1 : return 0;
1024 : }
1025 :
1026 19 : static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1027 19 : assert(a);
1028 19 : assert(b);
1029 :
1030 19 : if (a->type == b->type) {
1031 :
1032 : /* We only define ordering for cells of the same data type. If cells with different data types are
1033 : * compared we follow the order the cells were originally added in */
1034 :
1035 13 : switch (a->type) {
1036 :
1037 11 : case TABLE_STRING:
1038 11 : return strcmp(a->string, b->string);
1039 :
1040 2 : case TABLE_BOOLEAN:
1041 2 : if (!a->boolean && b->boolean)
1042 0 : return -1;
1043 2 : if (a->boolean && !b->boolean)
1044 2 : return 1;
1045 0 : return 0;
1046 :
1047 0 : case TABLE_TIMESTAMP:
1048 : case TABLE_TIMESTAMP_UTC:
1049 : case TABLE_TIMESTAMP_RELATIVE:
1050 0 : return CMP(a->timestamp, b->timestamp);
1051 :
1052 0 : case TABLE_TIMESPAN:
1053 : case TABLE_TIMESPAN_MSEC:
1054 0 : return CMP(a->timespan, b->timespan);
1055 :
1056 0 : case TABLE_SIZE:
1057 : case TABLE_BPS:
1058 0 : return CMP(a->size, b->size);
1059 :
1060 0 : case TABLE_INT:
1061 0 : return CMP(a->int_val, b->int_val);
1062 :
1063 0 : case TABLE_INT8:
1064 0 : return CMP(a->int8, b->int8);
1065 :
1066 0 : case TABLE_INT16:
1067 0 : return CMP(a->int16, b->int16);
1068 :
1069 0 : case TABLE_INT32:
1070 0 : return CMP(a->int32, b->int32);
1071 :
1072 0 : case TABLE_INT64:
1073 0 : return CMP(a->int64, b->int64);
1074 :
1075 0 : case TABLE_UINT:
1076 0 : return CMP(a->uint_val, b->uint_val);
1077 :
1078 0 : case TABLE_UINT8:
1079 0 : return CMP(a->uint8, b->uint8);
1080 :
1081 0 : case TABLE_UINT16:
1082 0 : return CMP(a->uint16, b->uint16);
1083 :
1084 0 : case TABLE_UINT32:
1085 0 : return CMP(a->uint32, b->uint32);
1086 :
1087 0 : case TABLE_UINT64:
1088 0 : return CMP(a->uint64, b->uint64);
1089 :
1090 0 : case TABLE_PERCENT:
1091 0 : return CMP(a->percent, b->percent);
1092 :
1093 0 : case TABLE_IFINDEX:
1094 0 : return CMP(a->ifindex, b->ifindex);
1095 :
1096 0 : case TABLE_IN_ADDR:
1097 0 : return CMP(a->address.in.s_addr, b->address.in.s_addr);
1098 :
1099 0 : case TABLE_IN6_ADDR:
1100 0 : return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
1101 :
1102 6 : default:
1103 : ;
1104 : }
1105 : }
1106 :
1107 : /* Generic fallback using the original order in which the cells where added. */
1108 6 : return CMP(index_a, index_b);
1109 : }
1110 :
1111 22 : static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1112 : size_t i;
1113 : int r;
1114 :
1115 22 : assert(t);
1116 22 : assert(t->sort_map);
1117 :
1118 : /* Make sure the header stays at the beginning */
1119 22 : if (*a < t->n_columns && *b < t->n_columns)
1120 0 : return 0;
1121 22 : if (*a < t->n_columns)
1122 5 : return -1;
1123 17 : if (*b < t->n_columns)
1124 0 : return 1;
1125 :
1126 : /* Order other lines by the sorting map */
1127 19 : for (i = 0; i < t->n_sort_map; i++) {
1128 : TableData *d, *dd;
1129 :
1130 19 : d = t->data[*a + t->sort_map[i]];
1131 19 : dd = t->data[*b + t->sort_map[i]];
1132 :
1133 19 : r = cell_data_compare(d, *a, dd, *b);
1134 19 : if (r != 0)
1135 17 : return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1136 : }
1137 :
1138 : /* Order identical lines by the order there were originally added in */
1139 0 : return CMP(*a, *b);
1140 : }
1141 :
1142 212 : static const char *table_data_format(Table *t, TableData *d) {
1143 212 : assert(d);
1144 :
1145 212 : if (d->formatted)
1146 59 : return d->formatted;
1147 :
1148 153 : switch (d->type) {
1149 8 : case TABLE_EMPTY:
1150 8 : return strempty(t->empty_string);
1151 :
1152 92 : case TABLE_STRING:
1153 92 : if (d->uppercase) {
1154 : char *p, *q;
1155 :
1156 10 : d->formatted = new(char, strlen(d->string) + 1);
1157 10 : if (!d->formatted)
1158 0 : return NULL;
1159 :
1160 54 : for (p = d->string, q = d->formatted; *p; p++, q++)
1161 44 : *q = (char) toupper((unsigned char) *p);
1162 10 : *q = 0;
1163 :
1164 10 : return d->formatted;
1165 : }
1166 :
1167 82 : return d->string;
1168 :
1169 50 : case TABLE_BOOLEAN:
1170 50 : return yes_no(d->boolean);
1171 :
1172 0 : case TABLE_TIMESTAMP:
1173 : case TABLE_TIMESTAMP_UTC:
1174 : case TABLE_TIMESTAMP_RELATIVE: {
1175 0 : _cleanup_free_ char *p;
1176 : char *ret;
1177 :
1178 0 : p = new(char, FORMAT_TIMESTAMP_MAX);
1179 0 : if (!p)
1180 0 : return NULL;
1181 :
1182 0 : if (d->type == TABLE_TIMESTAMP)
1183 0 : ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1184 0 : else if (d->type == TABLE_TIMESTAMP_UTC)
1185 0 : ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1186 : else
1187 0 : ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1188 0 : if (!ret)
1189 0 : return "n/a";
1190 :
1191 0 : d->formatted = TAKE_PTR(p);
1192 0 : break;
1193 : }
1194 :
1195 1 : case TABLE_TIMESPAN:
1196 : case TABLE_TIMESPAN_MSEC: {
1197 1 : _cleanup_free_ char *p;
1198 :
1199 1 : p = new(char, FORMAT_TIMESPAN_MAX);
1200 1 : if (!p)
1201 0 : return NULL;
1202 :
1203 1 : if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1204 1 : d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1205 0 : return "n/a";
1206 :
1207 1 : d->formatted = TAKE_PTR(p);
1208 1 : break;
1209 : }
1210 :
1211 2 : case TABLE_SIZE: {
1212 2 : _cleanup_free_ char *p;
1213 :
1214 2 : p = new(char, FORMAT_BYTES_MAX);
1215 2 : if (!p)
1216 0 : return NULL;
1217 :
1218 2 : if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1219 0 : return "n/a";
1220 :
1221 2 : d->formatted = TAKE_PTR(p);
1222 2 : break;
1223 : }
1224 :
1225 0 : case TABLE_BPS: {
1226 0 : _cleanup_free_ char *p;
1227 : size_t n;
1228 :
1229 0 : p = new(char, FORMAT_BYTES_MAX+2);
1230 0 : if (!p)
1231 0 : return NULL;
1232 :
1233 0 : if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
1234 0 : return "n/a";
1235 :
1236 0 : n = strlen(p);
1237 0 : strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1238 :
1239 0 : d->formatted = TAKE_PTR(p);
1240 0 : break;
1241 : }
1242 :
1243 0 : case TABLE_INT: {
1244 0 : _cleanup_free_ char *p;
1245 :
1246 0 : p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1247 0 : if (!p)
1248 0 : return NULL;
1249 :
1250 0 : sprintf(p, "%i", d->int_val);
1251 0 : d->formatted = TAKE_PTR(p);
1252 0 : break;
1253 : }
1254 :
1255 0 : case TABLE_INT8: {
1256 0 : _cleanup_free_ char *p;
1257 :
1258 0 : p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
1259 0 : if (!p)
1260 0 : return NULL;
1261 :
1262 0 : sprintf(p, "%" PRIi8, d->int8);
1263 0 : d->formatted = TAKE_PTR(p);
1264 0 : break;
1265 : }
1266 :
1267 0 : case TABLE_INT16: {
1268 0 : _cleanup_free_ char *p;
1269 :
1270 0 : p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
1271 0 : if (!p)
1272 0 : return NULL;
1273 :
1274 0 : sprintf(p, "%" PRIi16, d->int16);
1275 0 : d->formatted = TAKE_PTR(p);
1276 0 : break;
1277 : }
1278 :
1279 0 : case TABLE_INT32: {
1280 0 : _cleanup_free_ char *p;
1281 :
1282 0 : p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1283 0 : if (!p)
1284 0 : return NULL;
1285 :
1286 0 : sprintf(p, "%" PRIi32, d->int32);
1287 0 : d->formatted = TAKE_PTR(p);
1288 0 : break;
1289 : }
1290 :
1291 0 : case TABLE_INT64: {
1292 0 : _cleanup_free_ char *p;
1293 :
1294 0 : p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1295 0 : if (!p)
1296 0 : return NULL;
1297 :
1298 0 : sprintf(p, "%" PRIi64, d->int64);
1299 0 : d->formatted = TAKE_PTR(p);
1300 0 : break;
1301 : }
1302 :
1303 0 : case TABLE_UINT: {
1304 0 : _cleanup_free_ char *p;
1305 :
1306 0 : p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1307 0 : if (!p)
1308 0 : return NULL;
1309 :
1310 0 : sprintf(p, "%u", d->uint_val);
1311 0 : d->formatted = TAKE_PTR(p);
1312 0 : break;
1313 : }
1314 :
1315 0 : case TABLE_UINT8: {
1316 0 : _cleanup_free_ char *p;
1317 :
1318 0 : p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
1319 0 : if (!p)
1320 0 : return NULL;
1321 :
1322 0 : sprintf(p, "%" PRIu8, d->uint8);
1323 0 : d->formatted = TAKE_PTR(p);
1324 0 : break;
1325 : }
1326 :
1327 0 : case TABLE_UINT16: {
1328 0 : _cleanup_free_ char *p;
1329 :
1330 0 : p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
1331 0 : if (!p)
1332 0 : return NULL;
1333 :
1334 0 : sprintf(p, "%" PRIu16, d->uint16);
1335 0 : d->formatted = TAKE_PTR(p);
1336 0 : break;
1337 : }
1338 :
1339 0 : case TABLE_UINT32: {
1340 0 : _cleanup_free_ char *p;
1341 :
1342 0 : p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1343 0 : if (!p)
1344 0 : return NULL;
1345 :
1346 0 : sprintf(p, "%" PRIu32, d->uint32);
1347 0 : d->formatted = TAKE_PTR(p);
1348 0 : break;
1349 : }
1350 :
1351 0 : case TABLE_UINT64: {
1352 0 : _cleanup_free_ char *p;
1353 :
1354 0 : p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1355 0 : if (!p)
1356 0 : return NULL;
1357 :
1358 0 : sprintf(p, "%" PRIu64, d->uint64);
1359 0 : d->formatted = TAKE_PTR(p);
1360 0 : break;
1361 : }
1362 :
1363 0 : case TABLE_PERCENT: {
1364 0 : _cleanup_free_ char *p;
1365 :
1366 0 : p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1367 0 : if (!p)
1368 0 : return NULL;
1369 :
1370 0 : sprintf(p, "%i%%" , d->percent);
1371 0 : d->formatted = TAKE_PTR(p);
1372 0 : break;
1373 : }
1374 :
1375 0 : case TABLE_IFINDEX: {
1376 0 : _cleanup_free_ char *p = NULL;
1377 : char name[IF_NAMESIZE + 1];
1378 :
1379 0 : if (format_ifname(d->ifindex, name)) {
1380 0 : p = strdup(name);
1381 0 : if (!p)
1382 0 : return NULL;
1383 : } else {
1384 0 : if (asprintf(&p, "%i" , d->ifindex) < 0)
1385 0 : return NULL;
1386 : }
1387 :
1388 0 : d->formatted = TAKE_PTR(p);
1389 0 : break;
1390 : }
1391 :
1392 0 : case TABLE_IN_ADDR:
1393 : case TABLE_IN6_ADDR: {
1394 0 : _cleanup_free_ char *p = NULL;
1395 :
1396 0 : if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
1397 0 : &d->address, &p) < 0)
1398 0 : return NULL;
1399 :
1400 0 : d->formatted = TAKE_PTR(p);
1401 0 : break;
1402 : }
1403 :
1404 0 : default:
1405 0 : assert_not_reached("Unexpected type?");
1406 : }
1407 :
1408 3 : return d->formatted;
1409 : }
1410 :
1411 106 : static int table_data_requested_width(Table *table, TableData *d, size_t *ret) {
1412 : const char *t;
1413 : size_t l;
1414 :
1415 106 : t = table_data_format(table, d);
1416 106 : if (!t)
1417 0 : return -ENOMEM;
1418 :
1419 106 : l = utf8_console_width(t);
1420 106 : if (l == (size_t) -1)
1421 0 : return -EINVAL;
1422 :
1423 106 : if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
1424 0 : l = d->maximum_width;
1425 :
1426 106 : if (l < d->minimum_width)
1427 4 : l = d->minimum_width;
1428 :
1429 106 : *ret = l;
1430 106 : return 0;
1431 : }
1432 :
1433 56 : static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1434 56 : size_t w = 0, space, lspace, old_length, clickable_length;
1435 56 : _cleanup_free_ char *clickable = NULL;
1436 : const char *p;
1437 : char *ret;
1438 : size_t i;
1439 : int r;
1440 :
1441 : /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1442 :
1443 56 : assert(str);
1444 56 : assert(percent <= 100);
1445 :
1446 56 : old_length = strlen(str);
1447 :
1448 56 : if (url) {
1449 0 : r = terminal_urlify(url, str, &clickable);
1450 0 : if (r < 0)
1451 0 : return NULL;
1452 :
1453 0 : clickable_length = strlen(clickable);
1454 : } else
1455 56 : clickable_length = old_length;
1456 :
1457 : /* Determine current width on screen */
1458 56 : p = str;
1459 233 : while (p < str + old_length) {
1460 : char32_t c;
1461 :
1462 177 : if (utf8_encoded_to_unichar(p, &c) < 0) {
1463 0 : p++, w++; /* count invalid chars as 1 */
1464 0 : continue;
1465 : }
1466 :
1467 177 : p = utf8_next_char(p);
1468 177 : w += unichar_iswide(c) ? 2 : 1;
1469 : }
1470 :
1471 : /* Already wider than the target, if so, don't do anything */
1472 56 : if (w >= new_length)
1473 0 : return clickable ? TAKE_PTR(clickable) : strdup(str);
1474 :
1475 : /* How much spaces shall we add? An how much on the left side? */
1476 56 : space = new_length - w;
1477 56 : lspace = space * percent / 100U;
1478 :
1479 56 : ret = new(char, space + clickable_length + 1);
1480 56 : if (!ret)
1481 0 : return NULL;
1482 :
1483 111 : for (i = 0; i < lspace; i++)
1484 55 : ret[i] = ' ';
1485 56 : memcpy(ret + lspace, clickable ?: str, clickable_length);
1486 306 : for (i = lspace + clickable_length; i < space + clickable_length; i++)
1487 250 : ret[i] = ' ';
1488 :
1489 56 : ret[space + clickable_length] = 0;
1490 56 : return ret;
1491 : }
1492 :
1493 106 : static const char* table_data_color(TableData *d) {
1494 106 : assert(d);
1495 :
1496 106 : if (d->color)
1497 0 : return d->color;
1498 :
1499 : /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1500 106 : if (d->type == TABLE_EMPTY)
1501 4 : return ansi_grey();
1502 :
1503 102 : return NULL;
1504 : }
1505 :
1506 9 : int table_print(Table *t, FILE *f) {
1507 : size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1508 : i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1509 : *width;
1510 9 : _cleanup_free_ size_t *sorted = NULL;
1511 : uint64_t *column_weight, weight_sum;
1512 : int r;
1513 :
1514 9 : assert(t);
1515 :
1516 9 : if (!f)
1517 0 : f = stdout;
1518 :
1519 : /* Ensure we have no incomplete rows */
1520 9 : assert(t->n_cells % t->n_columns == 0);
1521 :
1522 9 : n_rows = t->n_cells / t->n_columns;
1523 9 : assert(n_rows > 0); /* at least the header row must be complete */
1524 :
1525 9 : if (t->sort_map) {
1526 : /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1527 :
1528 3 : sorted = new(size_t, n_rows);
1529 3 : if (!sorted)
1530 0 : return -ENOMEM;
1531 :
1532 18 : for (i = 0; i < n_rows; i++)
1533 15 : sorted[i] = i * t->n_columns;
1534 :
1535 3 : typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1536 : }
1537 :
1538 9 : if (t->display_map)
1539 1 : display_columns = t->n_display_map;
1540 : else
1541 8 : display_columns = t->n_columns;
1542 :
1543 9 : assert(display_columns > 0);
1544 :
1545 9 : minimum_width = newa(size_t, display_columns);
1546 9 : maximum_width = newa(size_t, display_columns);
1547 9 : requested_width = newa(size_t, display_columns);
1548 9 : width = newa(size_t, display_columns);
1549 9 : column_weight = newa0(uint64_t, display_columns);
1550 :
1551 41 : for (j = 0; j < display_columns; j++) {
1552 32 : minimum_width[j] = 1;
1553 32 : maximum_width[j] = (size_t) -1;
1554 32 : requested_width[j] = (size_t) -1;
1555 : }
1556 :
1557 : /* First pass: determine column sizes */
1558 39 : for (i = t->header ? 0 : 1; i < n_rows; i++) {
1559 : TableData **row;
1560 :
1561 : /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1562 : * hence we don't care for sorted[] during the first pass. */
1563 30 : row = t->data + i * t->n_columns;
1564 :
1565 136 : for (j = 0; j < display_columns; j++) {
1566 : TableData *d;
1567 : size_t req;
1568 :
1569 106 : assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1570 :
1571 106 : r = table_data_requested_width(t, d, &req);
1572 106 : if (r < 0)
1573 0 : return r;
1574 :
1575 : /* Determine the biggest width that any cell in this column would like to have */
1576 106 : if (requested_width[j] == (size_t) -1 ||
1577 74 : requested_width[j] < req)
1578 50 : requested_width[j] = req;
1579 :
1580 : /* Determine the minimum width any cell in this column needs */
1581 106 : if (minimum_width[j] < d->minimum_width)
1582 0 : minimum_width[j] = d->minimum_width;
1583 :
1584 : /* Determine the maximum width any cell in this column needs */
1585 106 : if (d->maximum_width != (size_t) -1 &&
1586 0 : (maximum_width[j] == (size_t) -1 ||
1587 0 : maximum_width[j] > d->maximum_width))
1588 0 : maximum_width[j] = d->maximum_width;
1589 :
1590 : /* Determine the full columns weight */
1591 106 : column_weight[j] += d->weight;
1592 : }
1593 : }
1594 :
1595 : /* One space between each column */
1596 9 : table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1597 :
1598 : /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1599 9 : weight_sum = 0;
1600 41 : for (j = 0; j < display_columns; j++) {
1601 32 : weight_sum += column_weight[j];
1602 :
1603 32 : table_minimum_width += minimum_width[j];
1604 :
1605 32 : if (maximum_width[j] == (size_t) -1)
1606 32 : table_maximum_width = (size_t) -1;
1607 : else
1608 0 : table_maximum_width += maximum_width[j];
1609 :
1610 32 : table_requested_width += requested_width[j];
1611 : }
1612 :
1613 : /* Calculate effective table width */
1614 9 : if (t->width == (size_t) -1)
1615 4 : table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1616 : else
1617 5 : table_effective_width = t->width;
1618 :
1619 9 : if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1620 0 : table_effective_width = table_maximum_width;
1621 :
1622 9 : if (table_effective_width < table_minimum_width)
1623 1 : table_effective_width = table_minimum_width;
1624 :
1625 9 : if (table_effective_width >= table_requested_width) {
1626 : size_t extra;
1627 :
1628 : /* We have extra room, let's distribute it among columns according to their weights. We first provide
1629 : * each column with what it asked for and the distribute the rest. */
1630 :
1631 4 : extra = table_effective_width - table_requested_width;
1632 :
1633 16 : for (j = 0; j < display_columns; j++) {
1634 : size_t delta;
1635 :
1636 12 : if (weight_sum == 0)
1637 0 : width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1638 : else
1639 12 : width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1640 :
1641 12 : if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1642 0 : width[j] = maximum_width[j];
1643 :
1644 12 : if (width[j] < minimum_width[j])
1645 0 : width[j] = minimum_width[j];
1646 :
1647 12 : assert(width[j] >= requested_width[j]);
1648 12 : delta = width[j] - requested_width[j];
1649 :
1650 : /* Subtract what we just added from the rest */
1651 12 : if (extra > delta)
1652 2 : extra -= delta;
1653 : else
1654 10 : extra = 0;
1655 :
1656 12 : assert(weight_sum >= column_weight[j]);
1657 12 : weight_sum -= column_weight[j];
1658 : }
1659 :
1660 : } else {
1661 : /* We need to compress the table, columns can't get what they asked for. We first provide each column
1662 : * with the minimum they need, and then distribute anything left. */
1663 5 : bool finalize = false;
1664 : size_t extra;
1665 :
1666 5 : extra = table_effective_width - table_minimum_width;
1667 :
1668 25 : for (j = 0; j < display_columns; j++)
1669 20 : width[j] = (size_t) -1;
1670 :
1671 12 : for (;;) {
1672 17 : bool restart = false;
1673 :
1674 66 : for (j = 0; j < display_columns; j++) {
1675 : size_t delta, w;
1676 :
1677 : /* Did this column already get something assigned? If so, let's skip to the next */
1678 56 : if (width[j] != (size_t) -1)
1679 21 : continue;
1680 :
1681 35 : if (weight_sum == 0)
1682 0 : w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1683 : else
1684 35 : w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1685 :
1686 35 : if (w >= requested_width[j]) {
1687 : /* Never give more than requested. If we hit a column like this, there's more
1688 : * space to allocate to other columns which means we need to restart the
1689 : * iteration. However, if we hit a column like this, let's assign it the space
1690 : * it wanted for good early.*/
1691 :
1692 8 : w = requested_width[j];
1693 8 : restart = true;
1694 :
1695 27 : } else if (!finalize)
1696 15 : continue;
1697 :
1698 20 : width[j] = w;
1699 :
1700 20 : assert(w >= minimum_width[j]);
1701 20 : delta = w - minimum_width[j];
1702 :
1703 20 : assert(delta <= extra);
1704 20 : extra -= delta;
1705 :
1706 20 : assert(weight_sum >= column_weight[j]);
1707 20 : weight_sum -= column_weight[j];
1708 :
1709 20 : if (restart && !finalize)
1710 7 : break;
1711 : }
1712 :
1713 17 : if (finalize)
1714 5 : break;
1715 :
1716 12 : if (!restart)
1717 5 : finalize = true;
1718 : }
1719 : }
1720 :
1721 : /* Second pass: show output */
1722 39 : for (i = t->header ? 0 : 1; i < n_rows; i++) {
1723 : TableData **row;
1724 :
1725 30 : if (sorted)
1726 13 : row = t->data + sorted[i];
1727 : else
1728 17 : row = t->data + i * t->n_columns;
1729 :
1730 136 : for (j = 0; j < display_columns; j++) {
1731 106 : _cleanup_free_ char *buffer = NULL;
1732 : const char *field;
1733 : TableData *d;
1734 : size_t l;
1735 :
1736 106 : assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1737 :
1738 106 : field = table_data_format(t, d);
1739 106 : if (!field)
1740 0 : return -ENOMEM;
1741 :
1742 106 : l = utf8_console_width(field);
1743 106 : if (l > width[j]) {
1744 : /* Field is wider than allocated space. Let's ellipsize */
1745 :
1746 24 : buffer = ellipsize(field, width[j], d->ellipsize_percent);
1747 24 : if (!buffer)
1748 0 : return -ENOMEM;
1749 :
1750 24 : field = buffer;
1751 :
1752 82 : } else if (l < width[j]) {
1753 : /* Field is shorter than allocated space. Let's align with spaces */
1754 :
1755 56 : buffer = align_string_mem(field, d->url, width[j], d->align_percent);
1756 56 : if (!buffer)
1757 0 : return -ENOMEM;
1758 :
1759 56 : field = buffer;
1760 : }
1761 :
1762 106 : if (l >= width[j] && d->url) {
1763 0 : _cleanup_free_ char *clickable = NULL;
1764 :
1765 0 : r = terminal_urlify(d->url, field, &clickable);
1766 0 : if (r < 0)
1767 0 : return r;
1768 :
1769 0 : free_and_replace(buffer, clickable);
1770 0 : field = buffer;
1771 : }
1772 :
1773 106 : if (row == t->data) /* underline header line fully, including the column separator */
1774 24 : fputs(ansi_underline(), f);
1775 :
1776 106 : if (j > 0)
1777 76 : fputc(' ', f); /* column separator */
1778 :
1779 106 : if (table_data_color(d) && colors_enabled()) {
1780 0 : if (row == t->data) /* first undo header underliner */
1781 0 : fputs(ANSI_NORMAL, f);
1782 :
1783 0 : fputs(table_data_color(d), f);
1784 : }
1785 :
1786 106 : fputs(field, f);
1787 :
1788 106 : if (colors_enabled() && (table_data_color(d) || row == t->data))
1789 0 : fputs(ANSI_NORMAL, f);
1790 : }
1791 :
1792 30 : fputc('\n', f);
1793 : }
1794 :
1795 9 : return fflush_and_check(f);
1796 : }
1797 :
1798 9 : int table_format(Table *t, char **ret) {
1799 9 : _cleanup_fclose_ FILE *f = NULL;
1800 9 : char *buf = NULL;
1801 9 : size_t sz = 0;
1802 : int r;
1803 :
1804 9 : f = open_memstream_unlocked(&buf, &sz);
1805 9 : if (!f)
1806 0 : return -ENOMEM;
1807 :
1808 9 : r = table_print(t, f);
1809 9 : if (r < 0)
1810 0 : return r;
1811 :
1812 9 : f = safe_fclose(f);
1813 :
1814 9 : *ret = buf;
1815 :
1816 9 : return 0;
1817 : }
1818 :
1819 0 : size_t table_get_rows(Table *t) {
1820 0 : if (!t)
1821 0 : return 0;
1822 :
1823 0 : assert(t->n_columns > 0);
1824 0 : return t->n_cells / t->n_columns;
1825 : }
1826 :
1827 0 : size_t table_get_columns(Table *t) {
1828 0 : if (!t)
1829 0 : return 0;
1830 :
1831 0 : assert(t->n_columns > 0);
1832 0 : return t->n_columns;
1833 : }
1834 :
1835 0 : int table_set_reverse(Table *t, size_t column, bool b) {
1836 0 : assert(t);
1837 0 : assert(column < t->n_columns);
1838 :
1839 0 : if (!t->reverse_map) {
1840 0 : if (!b)
1841 0 : return 0;
1842 :
1843 0 : t->reverse_map = new0(bool, t->n_columns);
1844 0 : if (!t->reverse_map)
1845 0 : return -ENOMEM;
1846 : }
1847 :
1848 0 : t->reverse_map[column] = b;
1849 0 : return 0;
1850 : }
1851 :
1852 0 : TableCell *table_get_cell(Table *t, size_t row, size_t column) {
1853 : size_t i;
1854 :
1855 0 : assert(t);
1856 :
1857 0 : if (column >= t->n_columns)
1858 0 : return NULL;
1859 :
1860 0 : i = row * t->n_columns + column;
1861 0 : if (i >= t->n_cells)
1862 0 : return NULL;
1863 :
1864 0 : return TABLE_INDEX_TO_CELL(i);
1865 : }
1866 :
1867 0 : const void *table_get(Table *t, TableCell *cell) {
1868 : TableData *d;
1869 :
1870 0 : assert(t);
1871 :
1872 0 : d = table_get_data(t, cell);
1873 0 : if (!d)
1874 0 : return NULL;
1875 :
1876 0 : return d->data;
1877 : }
1878 :
1879 0 : const void* table_get_at(Table *t, size_t row, size_t column) {
1880 : TableCell *cell;
1881 :
1882 0 : cell = table_get_cell(t, row, column);
1883 0 : if (!cell)
1884 0 : return NULL;
1885 :
1886 0 : return table_get(t, cell);
1887 : }
1888 :
1889 0 : static int table_data_to_json(TableData *d, JsonVariant **ret) {
1890 :
1891 0 : switch (d->type) {
1892 :
1893 0 : case TABLE_EMPTY:
1894 0 : return json_variant_new_null(ret);
1895 :
1896 0 : case TABLE_STRING:
1897 0 : return json_variant_new_string(ret, d->string);
1898 :
1899 0 : case TABLE_BOOLEAN:
1900 0 : return json_variant_new_boolean(ret, d->boolean);
1901 :
1902 0 : case TABLE_TIMESTAMP:
1903 : case TABLE_TIMESTAMP_UTC:
1904 : case TABLE_TIMESTAMP_RELATIVE:
1905 0 : if (d->timestamp == USEC_INFINITY)
1906 0 : return json_variant_new_null(ret);
1907 :
1908 0 : return json_variant_new_unsigned(ret, d->timestamp);
1909 :
1910 0 : case TABLE_TIMESPAN:
1911 : case TABLE_TIMESPAN_MSEC:
1912 0 : if (d->timespan == USEC_INFINITY)
1913 0 : return json_variant_new_null(ret);
1914 :
1915 0 : return json_variant_new_unsigned(ret, d->timespan);
1916 :
1917 0 : case TABLE_SIZE:
1918 : case TABLE_BPS:
1919 0 : if (d->size == (size_t) -1)
1920 0 : return json_variant_new_null(ret);
1921 :
1922 0 : return json_variant_new_unsigned(ret, d->size);
1923 :
1924 0 : case TABLE_INT:
1925 0 : return json_variant_new_integer(ret, d->int_val);
1926 :
1927 0 : case TABLE_INT8:
1928 0 : return json_variant_new_integer(ret, d->int8);
1929 :
1930 0 : case TABLE_INT16:
1931 0 : return json_variant_new_integer(ret, d->int16);
1932 :
1933 0 : case TABLE_INT32:
1934 0 : return json_variant_new_integer(ret, d->int32);
1935 :
1936 0 : case TABLE_INT64:
1937 0 : return json_variant_new_integer(ret, d->int64);
1938 :
1939 0 : case TABLE_UINT:
1940 0 : return json_variant_new_unsigned(ret, d->uint_val);
1941 :
1942 0 : case TABLE_UINT8:
1943 0 : return json_variant_new_unsigned(ret, d->uint8);
1944 :
1945 0 : case TABLE_UINT16:
1946 0 : return json_variant_new_unsigned(ret, d->uint16);
1947 :
1948 0 : case TABLE_UINT32:
1949 0 : return json_variant_new_unsigned(ret, d->uint32);
1950 :
1951 0 : case TABLE_UINT64:
1952 0 : return json_variant_new_unsigned(ret, d->uint64);
1953 :
1954 0 : case TABLE_PERCENT:
1955 0 : return json_variant_new_integer(ret, d->percent);
1956 :
1957 0 : case TABLE_IFINDEX:
1958 0 : return json_variant_new_integer(ret, d->ifindex);
1959 :
1960 0 : case TABLE_IN_ADDR:
1961 0 : return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
1962 :
1963 0 : case TABLE_IN6_ADDR:
1964 0 : return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
1965 :
1966 0 : default:
1967 0 : return -EINVAL;
1968 : }
1969 : }
1970 :
1971 0 : int table_to_json(Table *t, JsonVariant **ret) {
1972 0 : JsonVariant **rows = NULL, **elements = NULL;
1973 0 : _cleanup_free_ size_t *sorted = NULL;
1974 : size_t n_rows, i, j, display_columns;
1975 : int r;
1976 :
1977 0 : assert(t);
1978 :
1979 : /* Ensure we have no incomplete rows */
1980 0 : assert(t->n_cells % t->n_columns == 0);
1981 :
1982 0 : n_rows = t->n_cells / t->n_columns;
1983 0 : assert(n_rows > 0); /* at least the header row must be complete */
1984 :
1985 0 : if (t->sort_map) {
1986 : /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1987 :
1988 0 : sorted = new(size_t, n_rows);
1989 0 : if (!sorted) {
1990 0 : r = -ENOMEM;
1991 0 : goto finish;
1992 : }
1993 :
1994 0 : for (i = 0; i < n_rows; i++)
1995 0 : sorted[i] = i * t->n_columns;
1996 :
1997 0 : typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1998 : }
1999 :
2000 0 : if (t->display_map)
2001 0 : display_columns = t->n_display_map;
2002 : else
2003 0 : display_columns = t->n_columns;
2004 0 : assert(display_columns > 0);
2005 :
2006 0 : elements = new0(JsonVariant*, display_columns * 2);
2007 0 : if (!elements) {
2008 0 : r = -ENOMEM;
2009 0 : goto finish;
2010 : }
2011 :
2012 0 : for (j = 0; j < display_columns; j++) {
2013 : TableData *d;
2014 :
2015 0 : assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
2016 :
2017 0 : r = table_data_to_json(d, elements + j*2);
2018 0 : if (r < 0)
2019 0 : goto finish;
2020 : }
2021 :
2022 0 : rows = new0(JsonVariant*, n_rows-1);
2023 0 : if (!rows) {
2024 0 : r = -ENOMEM;
2025 0 : goto finish;
2026 : }
2027 :
2028 0 : for (i = 1; i < n_rows; i++) {
2029 : TableData **row;
2030 :
2031 0 : if (sorted)
2032 0 : row = t->data + sorted[i];
2033 : else
2034 0 : row = t->data + i * t->n_columns;
2035 :
2036 0 : for (j = 0; j < display_columns; j++) {
2037 : TableData *d;
2038 : size_t k;
2039 :
2040 0 : assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2041 :
2042 0 : k = j*2+1;
2043 0 : elements[k] = json_variant_unref(elements[k]);
2044 :
2045 0 : r = table_data_to_json(d, elements + k);
2046 0 : if (r < 0)
2047 0 : goto finish;
2048 : }
2049 :
2050 0 : r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2051 0 : if (r < 0)
2052 0 : goto finish;
2053 : }
2054 :
2055 0 : r = json_variant_new_array(ret, rows, n_rows - 1);
2056 :
2057 0 : finish:
2058 0 : if (rows) {
2059 0 : json_variant_unref_many(rows, n_rows-1);
2060 0 : free(rows);
2061 : }
2062 :
2063 0 : if (elements) {
2064 0 : json_variant_unref_many(elements, display_columns*2);
2065 0 : free(elements);
2066 : }
2067 :
2068 0 : return r;
2069 : }
2070 :
2071 0 : int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2072 0 : _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2073 : int r;
2074 :
2075 0 : assert(t);
2076 :
2077 0 : if (!f)
2078 0 : f = stdout;
2079 :
2080 0 : r = table_to_json(t, &v);
2081 0 : if (r < 0)
2082 0 : return r;
2083 :
2084 0 : json_variant_dump(v, flags, f, NULL);
2085 :
2086 0 : return fflush_and_check(f);
2087 : }
|