Branch data 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 : 96 : static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
98 : : size_t i;
99 : :
100 [ - + ]: 96 : assert(cell);
101 : :
102 : 96 : i = PTR_TO_SIZE(cell);
103 [ - + ]: 96 : assert(i > 0);
104 : :
105 : 96 : return i-1;
106 : : }
107 : :
108 : 120 : static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
109 [ - + ]: 120 : assert(index != (size_t) -1);
110 : 120 : 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 : 8 : Table *table_new_raw(size_t n_columns) {
135 : 8 : _cleanup_(table_unrefp) Table *t = NULL;
136 : :
137 [ - + ]: 8 : assert(n_columns > 0);
138 : :
139 : 8 : t = new(Table, 1);
140 [ - + ]: 8 : if (!t)
141 : 0 : return NULL;
142 : :
143 : 8 : *t = (struct Table) {
144 : : .n_columns = n_columns,
145 : : .header = true,
146 : : .width = (size_t) -1,
147 : : };
148 : :
149 : 8 : return TAKE_PTR(t);
150 : : }
151 : :
152 : 8 : Table *table_new_internal(const char *first_header, ...) {
153 : 8 : _cleanup_(table_unrefp) Table *t = NULL;
154 : 8 : size_t n_columns = 1;
155 : : const char *h;
156 : : va_list ap;
157 : : int r;
158 : :
159 [ - + ]: 8 : assert(first_header);
160 : :
161 : 8 : va_start(ap, first_header);
162 : : for (;;) {
163 : 36 : h = va_arg(ap, const char*);
164 [ + + ]: 36 : if (!h)
165 : 8 : break;
166 : :
167 : 28 : n_columns++;
168 : : }
169 : 8 : va_end(ap);
170 : :
171 : 8 : t = table_new_raw(n_columns);
172 [ - + ]: 8 : if (!t)
173 : 0 : return NULL;
174 : :
175 : 8 : va_start(ap, first_header);
176 [ + + ]: 44 : for (h = first_header; h; h = va_arg(ap, const char*)) {
177 : : TableCell *cell;
178 : :
179 : 36 : r = table_add_cell(t, &cell, TABLE_STRING, h);
180 [ - + ]: 36 : if (r < 0) {
181 : 0 : va_end(ap);
182 : 0 : return NULL;
183 : : }
184 : :
185 : : /* Make the table header uppercase */
186 : 36 : r = table_set_uppercase(t, cell, true);
187 [ - + ]: 36 : if (r < 0) {
188 : 0 : va_end(ap);
189 : 0 : return NULL;
190 : : }
191 : : }
192 : 8 : va_end(ap);
193 : :
194 [ - + ]: 8 : assert(t->n_columns == t->n_cells);
195 : 8 : return TAKE_PTR(t);
196 : : }
197 : :
198 : 116 : static TableData *table_data_free(TableData *d) {
199 [ - + ]: 116 : assert(d);
200 : :
201 : 116 : free(d->formatted);
202 : 116 : free(d->url);
203 : :
204 : 116 : return mfree(d);
205 : : }
206 : :
207 [ - + - + : 132 : DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
+ + ]
208 [ - + ]: 120 : DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
209 : :
210 : 8 : Table *table_unref(Table *t) {
211 : : size_t i;
212 : :
213 [ - + ]: 8 : if (!t)
214 : 0 : return NULL;
215 : :
216 [ + + ]: 128 : for (i = 0; i < t->n_cells; i++)
217 : 120 : table_data_unref(t->data[i]);
218 : :
219 : 8 : free(t->data);
220 : 8 : free(t->display_map);
221 : 8 : free(t->sort_map);
222 : 8 : free(t->reverse_map);
223 : 8 : free(t->empty_string);
224 : :
225 : 8 : return mfree(t);
226 : : }
227 : :
228 : 180 : static size_t table_data_size(TableDataType type, const void *data) {
229 : :
230 [ + + + + : 180 : switch (type) {
+ - - - -
- - - ]
231 : :
232 : 4 : case TABLE_EMPTY:
233 : 4 : return 0;
234 : :
235 : 120 : case TABLE_STRING:
236 : 120 : return strlen(data) + 1;
237 : :
238 : 44 : case TABLE_BOOLEAN:
239 : 44 : return sizeof(bool);
240 : :
241 : 4 : case TABLE_TIMESTAMP:
242 : : case TABLE_TIMESTAMP_UTC:
243 : : case TABLE_TIMESTAMP_RELATIVE:
244 : : case TABLE_TIMESPAN:
245 : : case TABLE_TIMESPAN_MSEC:
246 : 4 : return sizeof(usec_t);
247 : :
248 : 8 : case TABLE_SIZE:
249 : : case TABLE_INT64:
250 : : case TABLE_UINT64:
251 : : case TABLE_BPS:
252 : 8 : 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 : 84 : 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 [ - + ]: 84 : assert(d);
295 : :
296 [ + + ]: 84 : if (d->type != type)
297 : 24 : return false;
298 : :
299 [ - + ]: 60 : if (d->minimum_width != minimum_width)
300 : 0 : return false;
301 : :
302 [ - + ]: 60 : if (d->maximum_width != maximum_width)
303 : 0 : return false;
304 : :
305 [ - + ]: 60 : if (d->weight != weight)
306 : 0 : return false;
307 : :
308 [ - + ]: 60 : if (d->align_percent != align_percent)
309 : 0 : return false;
310 : :
311 [ - + ]: 60 : if (d->ellipsize_percent != ellipsize_percent)
312 : 0 : return false;
313 : :
314 : : /* If a color/url/uppercase flag is set, refuse to merge */
315 [ - + ]: 60 : if (d->color)
316 : 0 : return false;
317 [ - + ]: 60 : if (d->url)
318 : 0 : return false;
319 [ + + ]: 60 : if (d->uppercase)
320 : 28 : return false;
321 : :
322 : 32 : k = table_data_size(type, data);
323 : 32 : l = table_data_size(d->type, d->data);
324 : :
325 [ + + ]: 32 : if (k != l)
326 : 8 : return false;
327 : :
328 : 24 : return memcmp_safe(data, d->data, l) == 0;
329 : : }
330 : :
331 : 116 : 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 : 116 : data_size = table_data_size(type, data);
344 : :
345 : 116 : d = malloc0(offsetof(TableData, data) + data_size);
346 [ - + ]: 116 : if (!d)
347 : 0 : return NULL;
348 : :
349 : 116 : d->n_ref = 1;
350 : 116 : d->type = type;
351 : 116 : d->minimum_width = minimum_width;
352 : 116 : d->maximum_width = maximum_width;
353 : 116 : d->weight = weight;
354 : 116 : d->align_percent = align_percent;
355 : 116 : d->ellipsize_percent = ellipsize_percent;
356 : 116 : memcpy_safe(d->data, data, data_size);
357 : :
358 : 116 : return d;
359 : : }
360 : :
361 : 120 : 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 : 120 : _cleanup_(table_data_unrefp) TableData *d = NULL;
373 : : TableData *p;
374 : :
375 [ - + ]: 120 : assert(t);
376 [ - + ]: 120 : assert(type >= 0);
377 [ - + ]: 120 : assert(type < _TABLE_DATA_TYPE_MAX);
378 : :
379 : : /* Special rule: patch NULL data fields to the empty field */
380 [ + + ]: 120 : if (!data)
381 : 4 : type = TABLE_EMPTY;
382 : :
383 : : /* Determine the cell adjacent to the current one, but one row up */
384 [ + + ]: 120 : if (t->n_cells >= t->n_columns)
385 [ - + ]: 84 : assert_se(p = t->data[t->n_cells - t->n_columns]);
386 : : else
387 : 36 : p = NULL;
388 : :
389 : : /* If formatting parameters are left unspecified, copy from the previous row */
390 [ + - ]: 120 : if (minimum_width == (size_t) -1)
391 [ + + ]: 120 : minimum_width = p ? p->minimum_width : 1;
392 : :
393 [ + - ]: 120 : if (weight == (unsigned) -1)
394 [ + + ]: 120 : weight = p ? p->weight : DEFAULT_WEIGHT;
395 : :
396 [ + - ]: 120 : if (align_percent == (unsigned) -1)
397 [ + + ]: 120 : align_percent = p ? p->align_percent : 0;
398 : :
399 [ + - ]: 120 : if (ellipsize_percent == (unsigned) -1)
400 [ + + ]: 120 : ellipsize_percent = p ? p->ellipsize_percent : 100;
401 : :
402 [ - + ]: 120 : assert(align_percent <= 100);
403 [ - + ]: 120 : 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 [ + + + + ]: 120 : if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
409 : 8 : d = table_data_ref(p);
410 : : else {
411 : 112 : d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
412 [ - + ]: 112 : if (!d)
413 : 0 : return -ENOMEM;
414 : : }
415 : :
416 [ - + ]: 120 : if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
417 : 0 : return -ENOMEM;
418 : :
419 [ + - ]: 120 : if (ret_cell)
420 : 120 : *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
421 : :
422 : 120 : t->data[t->n_cells++] = TAKE_PTR(d);
423 : :
424 : 120 : 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 : 48 : static int table_dedup_cell(Table *t, TableCell *cell) {
481 : 48 : _cleanup_free_ char *curl = NULL;
482 : : TableData *nd, *od;
483 : : size_t i;
484 : :
485 [ - + ]: 48 : 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 : 48 : i = TABLE_CELL_TO_INDEX(cell);
491 [ - + ]: 48 : if (i >= t->n_cells)
492 : 0 : return -ENXIO;
493 : :
494 [ - + ]: 48 : assert_se(od = t->data[i]);
495 [ + + ]: 48 : if (od->n_ref == 1)
496 : 44 : return 0;
497 : :
498 [ - + ]: 4 : assert(od->n_ref > 1);
499 : :
500 [ - + ]: 4 : if (od->url) {
501 : 0 : curl = strdup(od->url);
502 [ # # ]: 0 : if (!curl)
503 : 0 : return -ENOMEM;
504 : : }
505 : :
506 : 8 : nd = table_data_new(
507 : : od->type,
508 : 4 : od->data,
509 : : od->minimum_width,
510 : : od->maximum_width,
511 : : od->weight,
512 : : od->align_percent,
513 : : od->ellipsize_percent);
514 [ - + ]: 4 : if (!nd)
515 : 0 : return -ENOMEM;
516 : :
517 : 4 : nd->color = od->color;
518 : 4 : nd->url = TAKE_PTR(curl);
519 : 4 : nd->uppercase = od->uppercase;
520 : :
521 : 4 : table_data_unref(od);
522 : 4 : t->data[i] = nd;
523 : :
524 [ - + ]: 4 : assert(nd->n_ref == 1);
525 : :
526 : 4 : return 1;
527 : : }
528 : :
529 : 48 : static TableData *table_get_data(Table *t, TableCell *cell) {
530 : : size_t i;
531 : :
532 [ - + ]: 48 : assert(t);
533 [ - + ]: 48 : assert(cell);
534 : :
535 : : /* Get the data object of the specified cell, or NULL if it doesn't exist */
536 : :
537 : 48 : i = TABLE_CELL_TO_INDEX(cell);
538 [ - + ]: 48 : if (i >= t->n_cells)
539 : 0 : return NULL;
540 : :
541 [ - + ]: 48 : assert(t->data[i]);
542 [ - + ]: 48 : assert(t->data[i]->n_ref > 0);
543 : :
544 : 48 : 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 : 8 : int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
596 : : int r;
597 : :
598 [ - + ]: 8 : assert(t);
599 [ - + ]: 8 : assert(cell);
600 : :
601 [ - + ]: 8 : if (percent == (unsigned) -1)
602 : 0 : percent = 0;
603 : :
604 [ - + ]: 8 : assert(percent <= 100);
605 : :
606 : 8 : r = table_dedup_cell(t, cell);
607 [ - + ]: 8 : if (r < 0)
608 : 0 : return r;
609 : :
610 : 8 : table_get_data(t, cell)->align_percent = percent;
611 : 8 : 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 : 40 : int table_set_uppercase(Table *t, TableCell *cell, bool b) {
668 : : TableData *d;
669 : : int r;
670 : :
671 [ - + ]: 40 : assert(t);
672 [ - + ]: 40 : assert(cell);
673 : :
674 : 40 : r = table_dedup_cell(t, cell);
675 [ - + ]: 40 : if (r < 0)
676 : 0 : return r;
677 : :
678 [ - + ]: 40 : assert_se(d = table_get_data(t, cell));
679 : :
680 [ - + ]: 40 : if (d->uppercase == b)
681 : 0 : return 0;
682 : :
683 : 40 : d->formatted = mfree(d->formatted);
684 : 40 : d->uppercase = b;
685 : 40 : 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 : 24 : int table_add_many_internal(Table *t, TableDataType first_type, ...) {
730 : : TableDataType type;
731 : : va_list ap;
732 : 24 : TableCell *last_cell = NULL;
733 : : int r;
734 : :
735 [ - + ]: 24 : assert(t);
736 [ - + ]: 24 : assert(first_type >= 0);
737 [ - + ]: 24 : assert(first_type < _TABLE_DATA_TYPE_MAX);
738 : :
739 : 24 : type = first_type;
740 : :
741 : 24 : va_start(ap, first_type);
742 : 88 : 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 [ + + + + : 112 : switch (type) {
+ - - - -
- - - - -
- - - - -
- - - - -
- - + +
- ]
764 : :
765 : 4 : case TABLE_EMPTY:
766 : 4 : data = NULL;
767 : 4 : break;
768 : :
769 : 48 : case TABLE_STRING:
770 : 48 : data = va_arg(ap, const char *);
771 : 48 : break;
772 : :
773 : 20 : case TABLE_BOOLEAN:
774 : 20 : buffer.b = va_arg(ap, int);
775 : 20 : data = &buffer.b;
776 : 20 : break;
777 : :
778 : 4 : case TABLE_TIMESTAMP:
779 : : case TABLE_TIMESTAMP_UTC:
780 : : case TABLE_TIMESTAMP_RELATIVE:
781 : : case TABLE_TIMESPAN:
782 : : case TABLE_TIMESPAN_MSEC:
783 : 4 : buffer.usec = va_arg(ap, usec_t);
784 : 4 : data = &buffer.usec;
785 : 4 : break;
786 : :
787 : 8 : case TABLE_SIZE:
788 : : case TABLE_BPS:
789 : 8 : buffer.size = va_arg(ap, uint64_t);
790 : 8 : data = &buffer.size;
791 : 8 : 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 : 4 : case TABLE_SET_UPPERCASE: {
923 : 4 : int u = va_arg(ap, int);
924 : 4 : r = table_set_uppercase(t, last_cell, u);
925 : 4 : break;
926 : : }
927 : :
928 : 24 : case _TABLE_DATA_TYPE_MAX:
929 : : /* Used as end marker */
930 : 24 : va_end(ap);
931 : 24 : return 0;
932 : :
933 : 0 : default:
934 : 0 : assert_not_reached("Uh? Unexpected data type.");
935 : : }
936 : :
937 [ + + ]: 88 : if (type < _TABLE_DATA_TYPE_MAX)
938 : 84 : r = table_add_cell(t, &last_cell, type, data);
939 : :
940 [ - + ]: 88 : if (r < 0) {
941 : 0 : va_end(ap);
942 : 0 : return r;
943 : : }
944 : :
945 : 88 : type = va_arg(ap, TableDataType);
946 : : }
947 : : }
948 : :
949 : 4 : void table_set_header(Table *t, bool b) {
950 [ - + ]: 4 : assert(t);
951 : :
952 : 4 : t->header = b;
953 : 4 : }
954 : :
955 : 24 : void table_set_width(Table *t, size_t width) {
956 [ - + ]: 24 : assert(t);
957 : :
958 : 24 : t->width = width;
959 : 24 : }
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 : 4 : int table_set_display(Table *t, size_t first_column, ...) {
968 : : size_t allocated, column;
969 : : va_list ap;
970 : :
971 [ - + ]: 4 : assert(t);
972 : :
973 : 4 : allocated = t->n_display_map;
974 : 4 : column = first_column;
975 : :
976 : 4 : va_start(ap, first_column);
977 : : for (;;) {
978 [ - + ]: 20 : assert(column < t->n_columns);
979 : :
980 [ - + ]: 20 : 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 : 20 : t->display_map[t->n_display_map++] = column;
986 : :
987 : 20 : column = va_arg(ap, size_t);
988 [ + + ]: 20 : if (column == (size_t) -1)
989 : 4 : break;
990 : :
991 : : }
992 : 4 : va_end(ap);
993 : :
994 : 4 : return 0;
995 : : }
996 : :
997 : 4 : int table_set_sort(Table *t, size_t first_column, ...) {
998 : : size_t allocated, column;
999 : : va_list ap;
1000 : :
1001 [ - + ]: 4 : assert(t);
1002 : :
1003 : 4 : allocated = t->n_sort_map;
1004 : 4 : column = first_column;
1005 : :
1006 : 4 : va_start(ap, first_column);
1007 : : for (;;) {
1008 [ - + ]: 8 : assert(column < t->n_columns);
1009 : :
1010 [ - + ]: 8 : 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 : 8 : t->sort_map[t->n_sort_map++] = column;
1016 : :
1017 : 8 : column = va_arg(ap, size_t);
1018 [ + + ]: 8 : if (column == (size_t) -1)
1019 : 4 : break;
1020 : : }
1021 : 4 : va_end(ap);
1022 : :
1023 : 4 : return 0;
1024 : : }
1025 : :
1026 : 76 : static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1027 [ - + ]: 76 : assert(a);
1028 [ - + ]: 76 : assert(b);
1029 : :
1030 [ + + ]: 76 : 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 [ + + - - : 52 : switch (a->type) {
- - - - -
- - - - -
- - - - -
- ]
1036 : :
1037 : 44 : case TABLE_STRING:
1038 : 44 : return strcmp(a->string, b->string);
1039 : :
1040 : 8 : case TABLE_BOOLEAN:
1041 [ - + # # ]: 8 : if (!a->boolean && b->boolean)
1042 : 0 : return -1;
1043 [ + - + - ]: 8 : if (a->boolean && !b->boolean)
1044 : 8 : 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 : 24 : default:
1103 : : ;
1104 : : }
1105 : : }
1106 : :
1107 : : /* Generic fallback using the original order in which the cells where added. */
1108 [ - + ]: 24 : return CMP(index_a, index_b);
1109 : : }
1110 : :
1111 : 88 : static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1112 : : size_t i;
1113 : : int r;
1114 : :
1115 [ - + ]: 88 : assert(t);
1116 [ - + ]: 88 : assert(t->sort_map);
1117 : :
1118 : : /* Make sure the header stays at the beginning */
1119 [ + + - + ]: 88 : if (*a < t->n_columns && *b < t->n_columns)
1120 : 0 : return 0;
1121 [ + + ]: 88 : if (*a < t->n_columns)
1122 : 20 : return -1;
1123 [ - + ]: 68 : if (*b < t->n_columns)
1124 : 0 : return 1;
1125 : :
1126 : : /* Order other lines by the sorting map */
1127 [ + - ]: 76 : for (i = 0; i < t->n_sort_map; i++) {
1128 : : TableData *d, *dd;
1129 : :
1130 : 76 : d = t->data[*a + t->sort_map[i]];
1131 : 76 : dd = t->data[*b + t->sort_map[i]];
1132 : :
1133 : 76 : r = cell_data_compare(d, *a, dd, *b);
1134 [ + + ]: 76 : if (r != 0)
1135 [ - + # # ]: 68 : 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 : 848 : static const char *table_data_format(Table *t, TableData *d) {
1143 [ - + ]: 848 : assert(d);
1144 : :
1145 [ + + ]: 848 : if (d->formatted)
1146 : 236 : return d->formatted;
1147 : :
1148 [ + + + - : 612 : switch (d->type) {
+ + - - -
- - - - -
- - - - -
- - ]
1149 : 32 : case TABLE_EMPTY:
1150 : 32 : return strempty(t->empty_string);
1151 : :
1152 : 368 : case TABLE_STRING:
1153 [ + + ]: 368 : if (d->uppercase) {
1154 : : char *p, *q;
1155 : :
1156 : 40 : d->formatted = new(char, strlen(d->string) + 1);
1157 [ - + ]: 40 : if (!d->formatted)
1158 : 0 : return NULL;
1159 : :
1160 [ + + ]: 216 : for (p = d->string, q = d->formatted; *p; p++, q++)
1161 : 176 : *q = (char) toupper((unsigned char) *p);
1162 : 40 : *q = 0;
1163 : :
1164 : 40 : return d->formatted;
1165 : : }
1166 : :
1167 : 328 : return d->string;
1168 : :
1169 : 200 : case TABLE_BOOLEAN:
1170 : 200 : 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 : 4 : case TABLE_TIMESPAN:
1196 : : case TABLE_TIMESPAN_MSEC: {
1197 [ - + ]: 4 : _cleanup_free_ char *p;
1198 : :
1199 : 4 : p = new(char, FORMAT_TIMESPAN_MAX);
1200 [ - + ]: 4 : if (!p)
1201 : 0 : return NULL;
1202 : :
1203 [ - + ]: 4 : if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1204 [ + - ]: 4 : d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1205 : 0 : return "n/a";
1206 : :
1207 : 4 : d->formatted = TAKE_PTR(p);
1208 : 4 : break;
1209 : : }
1210 : :
1211 : 8 : case TABLE_SIZE: {
1212 [ - + ]: 8 : _cleanup_free_ char *p;
1213 : :
1214 : 8 : p = new(char, FORMAT_BYTES_MAX);
1215 [ - + ]: 8 : if (!p)
1216 : 0 : return NULL;
1217 : :
1218 [ - + ]: 8 : if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1219 : 0 : return "n/a";
1220 : :
1221 : 8 : d->formatted = TAKE_PTR(p);
1222 : 8 : 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 : 12 : return d->formatted;
1409 : : }
1410 : :
1411 : 424 : static int table_data_requested_width(Table *table, TableData *d, size_t *ret) {
1412 : : const char *t;
1413 : : size_t l;
1414 : :
1415 : 424 : t = table_data_format(table, d);
1416 [ - + ]: 424 : if (!t)
1417 : 0 : return -ENOMEM;
1418 : :
1419 : 424 : l = utf8_console_width(t);
1420 [ - + ]: 424 : if (l == (size_t) -1)
1421 : 0 : return -EINVAL;
1422 : :
1423 [ - + # # ]: 424 : if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
1424 : 0 : l = d->maximum_width;
1425 : :
1426 [ + + ]: 424 : if (l < d->minimum_width)
1427 : 16 : l = d->minimum_width;
1428 : :
1429 : 424 : *ret = l;
1430 : 424 : return 0;
1431 : : }
1432 : :
1433 : 224 : static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1434 : 224 : size_t w = 0, space, lspace, old_length, clickable_length;
1435 : 224 : _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 [ - + ]: 224 : assert(str);
1444 [ - + ]: 224 : assert(percent <= 100);
1445 : :
1446 : 224 : old_length = strlen(str);
1447 : :
1448 [ - + ]: 224 : 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 : 224 : clickable_length = old_length;
1456 : :
1457 : : /* Determine current width on screen */
1458 : 224 : p = str;
1459 [ + + ]: 932 : while (p < str + old_length) {
1460 : : char32_t c;
1461 : :
1462 [ - + ]: 708 : if (utf8_encoded_to_unichar(p, &c) < 0) {
1463 : 0 : p++, w++; /* count invalid chars as 1 */
1464 : 0 : continue;
1465 : : }
1466 : :
1467 : 708 : p = utf8_next_char(p);
1468 [ - + ]: 708 : w += unichar_iswide(c) ? 2 : 1;
1469 : : }
1470 : :
1471 : : /* Already wider than the target, if so, don't do anything */
1472 [ - + ]: 224 : 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 : 224 : space = new_length - w;
1477 : 224 : lspace = space * percent / 100U;
1478 : :
1479 : 224 : ret = new(char, space + clickable_length + 1);
1480 [ - + ]: 224 : if (!ret)
1481 : 0 : return NULL;
1482 : :
1483 [ + + ]: 444 : for (i = 0; i < lspace; i++)
1484 : 220 : ret[i] = ' ';
1485 [ - + ]: 224 : memcpy(ret + lspace, clickable ?: str, clickable_length);
1486 [ + + ]: 1224 : for (i = lspace + clickable_length; i < space + clickable_length; i++)
1487 : 1000 : ret[i] = ' ';
1488 : :
1489 : 224 : ret[space + clickable_length] = 0;
1490 : 224 : return ret;
1491 : : }
1492 : :
1493 : 424 : static const char* table_data_color(TableData *d) {
1494 [ - + ]: 424 : assert(d);
1495 : :
1496 [ - + ]: 424 : 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 [ + + ]: 424 : if (d->type == TABLE_EMPTY)
1501 : 16 : return ansi_grey();
1502 : :
1503 : 408 : return NULL;
1504 : : }
1505 : :
1506 : 36 : 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 : 36 : _cleanup_free_ size_t *sorted = NULL;
1511 : : uint64_t *column_weight, weight_sum;
1512 : : int r;
1513 : :
1514 [ - + ]: 36 : assert(t);
1515 : :
1516 [ - + ]: 36 : if (!f)
1517 : 0 : f = stdout;
1518 : :
1519 : : /* Ensure we have no incomplete rows */
1520 [ - + ]: 36 : assert(t->n_cells % t->n_columns == 0);
1521 : :
1522 : 36 : n_rows = t->n_cells / t->n_columns;
1523 [ - + ]: 36 : assert(n_rows > 0); /* at least the header row must be complete */
1524 : :
1525 [ + + ]: 36 : 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 : 12 : sorted = new(size_t, n_rows);
1529 [ - + ]: 12 : if (!sorted)
1530 : 0 : return -ENOMEM;
1531 : :
1532 [ + + ]: 72 : for (i = 0; i < n_rows; i++)
1533 : 60 : sorted[i] = i * t->n_columns;
1534 : :
1535 : 12 : typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1536 : : }
1537 : :
1538 [ + + ]: 36 : if (t->display_map)
1539 : 4 : display_columns = t->n_display_map;
1540 : : else
1541 : 32 : display_columns = t->n_columns;
1542 : :
1543 [ - + ]: 36 : assert(display_columns > 0);
1544 : :
1545 [ - + - + ]: 36 : minimum_width = newa(size_t, display_columns);
1546 [ - + - + ]: 36 : maximum_width = newa(size_t, display_columns);
1547 [ - + - + ]: 36 : requested_width = newa(size_t, display_columns);
1548 [ - + - + ]: 36 : width = newa(size_t, display_columns);
1549 [ - + - + : 36 : column_weight = newa0(uint64_t, display_columns);
- + ]
1550 : :
1551 [ + + ]: 164 : for (j = 0; j < display_columns; j++) {
1552 : 128 : minimum_width[j] = 1;
1553 : 128 : maximum_width[j] = (size_t) -1;
1554 : 128 : requested_width[j] = (size_t) -1;
1555 : : }
1556 : :
1557 : : /* First pass: determine column sizes */
1558 [ + + ]: 156 : 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 : 120 : row = t->data + i * t->n_columns;
1564 : :
1565 [ + + ]: 544 : for (j = 0; j < display_columns; j++) {
1566 : : TableData *d;
1567 : : size_t req;
1568 : :
1569 [ + + - + ]: 424 : assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1570 : :
1571 : 424 : r = table_data_requested_width(t, d, &req);
1572 [ - + ]: 424 : if (r < 0)
1573 : 0 : return r;
1574 : :
1575 : : /* Determine the biggest width that any cell in this column would like to have */
1576 [ + + ]: 424 : if (requested_width[j] == (size_t) -1 ||
1577 [ + + ]: 296 : requested_width[j] < req)
1578 : 200 : requested_width[j] = req;
1579 : :
1580 : : /* Determine the minimum width any cell in this column needs */
1581 [ - + ]: 424 : 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 [ - + ]: 424 : 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 : 424 : column_weight[j] += d->weight;
1592 : : }
1593 : : }
1594 : :
1595 : : /* One space between each column */
1596 : 36 : 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 : 36 : weight_sum = 0;
1600 [ + + ]: 164 : for (j = 0; j < display_columns; j++) {
1601 : 128 : weight_sum += column_weight[j];
1602 : :
1603 : 128 : table_minimum_width += minimum_width[j];
1604 : :
1605 [ + - ]: 128 : if (maximum_width[j] == (size_t) -1)
1606 : 128 : table_maximum_width = (size_t) -1;
1607 : : else
1608 : 0 : table_maximum_width += maximum_width[j];
1609 : :
1610 : 128 : table_requested_width += requested_width[j];
1611 : : }
1612 : :
1613 : : /* Calculate effective table width */
1614 [ + + ]: 36 : if (t->width == (size_t) -1)
1615 [ + - ]: 16 : table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1616 : : else
1617 : 20 : table_effective_width = t->width;
1618 : :
1619 [ - + # # ]: 36 : if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1620 : 0 : table_effective_width = table_maximum_width;
1621 : :
1622 [ + + ]: 36 : if (table_effective_width < table_minimum_width)
1623 : 4 : table_effective_width = table_minimum_width;
1624 : :
1625 [ + + ]: 36 : 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 : 16 : extra = table_effective_width - table_requested_width;
1632 : :
1633 [ + + ]: 64 : for (j = 0; j < display_columns; j++) {
1634 : : size_t delta;
1635 : :
1636 [ - + ]: 48 : if (weight_sum == 0)
1637 : 0 : width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1638 : : else
1639 : 48 : width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1640 : :
1641 [ - + # # ]: 48 : if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1642 : 0 : width[j] = maximum_width[j];
1643 : :
1644 [ - + ]: 48 : if (width[j] < minimum_width[j])
1645 : 0 : width[j] = minimum_width[j];
1646 : :
1647 [ - + ]: 48 : assert(width[j] >= requested_width[j]);
1648 : 48 : delta = width[j] - requested_width[j];
1649 : :
1650 : : /* Subtract what we just added from the rest */
1651 [ + + ]: 48 : if (extra > delta)
1652 : 8 : extra -= delta;
1653 : : else
1654 : 40 : extra = 0;
1655 : :
1656 [ - + ]: 48 : assert(weight_sum >= column_weight[j]);
1657 : 48 : 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 : 20 : bool finalize = false;
1664 : : size_t extra;
1665 : :
1666 : 20 : extra = table_effective_width - table_minimum_width;
1667 : :
1668 [ + + ]: 100 : for (j = 0; j < display_columns; j++)
1669 : 80 : width[j] = (size_t) -1;
1670 : :
1671 : 48 : for (;;) {
1672 : 68 : bool restart = false;
1673 : :
1674 [ + + ]: 264 : 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 [ + + ]: 224 : if (width[j] != (size_t) -1)
1679 : 84 : continue;
1680 : :
1681 [ - + ]: 140 : if (weight_sum == 0)
1682 : 0 : w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1683 : : else
1684 : 140 : w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1685 : :
1686 [ + + ]: 140 : 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 : 32 : w = requested_width[j];
1693 : 32 : restart = true;
1694 : :
1695 [ + + ]: 108 : } else if (!finalize)
1696 : 60 : continue;
1697 : :
1698 : 80 : width[j] = w;
1699 : :
1700 [ - + ]: 80 : assert(w >= minimum_width[j]);
1701 : 80 : delta = w - minimum_width[j];
1702 : :
1703 [ - + ]: 80 : assert(delta <= extra);
1704 : 80 : extra -= delta;
1705 : :
1706 [ - + ]: 80 : assert(weight_sum >= column_weight[j]);
1707 : 80 : weight_sum -= column_weight[j];
1708 : :
1709 [ + + + + ]: 80 : if (restart && !finalize)
1710 : 28 : break;
1711 : : }
1712 : :
1713 [ + + ]: 68 : if (finalize)
1714 : 20 : break;
1715 : :
1716 [ + + ]: 48 : if (!restart)
1717 : 20 : finalize = true;
1718 : : }
1719 : : }
1720 : :
1721 : : /* Second pass: show output */
1722 [ + + ]: 156 : for (i = t->header ? 0 : 1; i < n_rows; i++) {
1723 : : TableData **row;
1724 : :
1725 [ + + ]: 120 : if (sorted)
1726 : 52 : row = t->data + sorted[i];
1727 : : else
1728 : 68 : row = t->data + i * t->n_columns;
1729 : :
1730 [ + + ]: 544 : for (j = 0; j < display_columns; j++) {
1731 [ + - ]: 424 : _cleanup_free_ char *buffer = NULL;
1732 : : const char *field;
1733 : : TableData *d;
1734 : : size_t l;
1735 : :
1736 [ + + - + ]: 424 : assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1737 : :
1738 : 424 : field = table_data_format(t, d);
1739 [ - + ]: 424 : if (!field)
1740 : 0 : return -ENOMEM;
1741 : :
1742 : 424 : l = utf8_console_width(field);
1743 [ + + ]: 424 : if (l > width[j]) {
1744 : : /* Field is wider than allocated space. Let's ellipsize */
1745 : :
1746 : 96 : buffer = ellipsize(field, width[j], d->ellipsize_percent);
1747 [ - + ]: 96 : if (!buffer)
1748 : 0 : return -ENOMEM;
1749 : :
1750 : 96 : field = buffer;
1751 : :
1752 [ + + ]: 328 : } else if (l < width[j]) {
1753 : : /* Field is shorter than allocated space. Let's align with spaces */
1754 : :
1755 : 224 : buffer = align_string_mem(field, d->url, width[j], d->align_percent);
1756 [ - + ]: 224 : if (!buffer)
1757 : 0 : return -ENOMEM;
1758 : :
1759 : 224 : field = buffer;
1760 : : }
1761 : :
1762 [ + + - + ]: 424 : 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 [ + + ]: 424 : if (row == t->data) /* underline header line fully, including the column separator */
1774 : 96 : fputs(ansi_underline(), f);
1775 : :
1776 [ + + ]: 424 : if (j > 0)
1777 : 304 : fputc(' ', f); /* column separator */
1778 : :
1779 [ + + - + ]: 424 : 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 : 424 : fputs(field, f);
1787 : :
1788 [ - + # # : 424 : if (colors_enabled() && (table_data_color(d) || row == t->data))
# # ]
1789 : 0 : fputs(ANSI_NORMAL, f);
1790 : : }
1791 : :
1792 : 120 : fputc('\n', f);
1793 : : }
1794 : :
1795 : 36 : return fflush_and_check(f);
1796 : : }
1797 : :
1798 : 36 : int table_format(Table *t, char **ret) {
1799 : 36 : _cleanup_fclose_ FILE *f = NULL;
1800 : 36 : char *buf = NULL;
1801 : 36 : size_t sz = 0;
1802 : : int r;
1803 : :
1804 : 36 : f = open_memstream_unlocked(&buf, &sz);
1805 [ - + ]: 36 : if (!f)
1806 : 0 : return -ENOMEM;
1807 : :
1808 : 36 : r = table_print(t, f);
1809 [ - + ]: 36 : if (r < 0)
1810 : 0 : return r;
1811 : :
1812 : 36 : f = safe_fclose(f);
1813 : :
1814 : 36 : *ret = buf;
1815 : :
1816 : 36 : 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 : : }
|