]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
headers: remove unneeded includes from util.h
[thirdparty/systemd.git] / src / shared / format-table.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <ctype.h>
4 #include <stdio_ext.h>
5
6 #include "alloc-util.h"
7 #include "fd-util.h"
8 #include "fileio.h"
9 #include "format-table.h"
10 #include "gunicode.h"
11 #include "memory-util.h"
12 #include "pager.h"
13 #include "parse-util.h"
14 #include "pretty-print.h"
15 #include "sort-util.h"
16 #include "string-util.h"
17 #include "terminal-util.h"
18 #include "time-util.h"
19 #include "utf8.h"
20 #include "util.h"
21
22 #define DEFAULT_WEIGHT 100
23
24 /*
25 A few notes on implementation details:
26
27 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
28 table. It can be easily converted to an index number and back.
29
30 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
31 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
32 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
33 outside only sees Table and TableCell.
34
35 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
36 previous one.
37
38 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
39 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
40 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
41 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
42
43 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
44 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
45 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
46 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
47 instead.
48
49 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
50 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
51 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
52 */
53
54 typedef struct TableData {
55 unsigned n_ref;
56 TableDataType type;
57
58 size_t minimum_width; /* minimum width for the column */
59 size_t maximum_width; /* maximum width for the column */
60 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
61 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
62 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
63
64 bool uppercase; /* Uppercase string on display */
65
66 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 */
67 char *url; /* A URL to use for a clickable hyperlink */
68 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
69
70 union {
71 uint8_t data[0]; /* data is generic array */
72 bool boolean;
73 usec_t timestamp;
74 usec_t timespan;
75 uint64_t size;
76 char string[0];
77 uint32_t uint32;
78 uint64_t uint64;
79 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
80 /* … add more here as we start supporting more cell data types … */
81 };
82 } TableData;
83
84 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
85 size_t i;
86
87 assert(cell);
88
89 i = PTR_TO_SIZE(cell);
90 assert(i > 0);
91
92 return i-1;
93 }
94
95 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
96 assert(index != (size_t) -1);
97 return SIZE_TO_PTR(index + 1);
98 }
99
100 struct Table {
101 size_t n_columns;
102 size_t n_cells;
103
104 bool header; /* Whether to show the header row? */
105 size_t width; /* If != (size_t) -1 the width to format this table in */
106
107 TableData **data;
108 size_t n_allocated;
109
110 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 */
111 size_t n_display_map;
112
113 size_t *sort_map; /* The columns to order rows by, in order of preference. */
114 size_t n_sort_map;
115
116 bool *reverse_map;
117 };
118
119 Table *table_new_raw(size_t n_columns) {
120 _cleanup_(table_unrefp) Table *t = NULL;
121
122 assert(n_columns > 0);
123
124 t = new(Table, 1);
125 if (!t)
126 return NULL;
127
128 *t = (struct Table) {
129 .n_columns = n_columns,
130 .header = true,
131 .width = (size_t) -1,
132 };
133
134 return TAKE_PTR(t);
135 }
136
137 Table *table_new_internal(const char *first_header, ...) {
138 _cleanup_(table_unrefp) Table *t = NULL;
139 size_t n_columns = 1;
140 const char *h;
141 va_list ap;
142 int r;
143
144 assert(first_header);
145
146 va_start(ap, first_header);
147 for (;;) {
148 h = va_arg(ap, const char*);
149 if (!h)
150 break;
151
152 n_columns++;
153 }
154 va_end(ap);
155
156 t = table_new_raw(n_columns);
157 if (!t)
158 return NULL;
159
160 va_start(ap, first_header);
161 for (h = first_header; h; h = va_arg(ap, const char*)) {
162 TableCell *cell;
163
164 r = table_add_cell(t, &cell, TABLE_STRING, h);
165 if (r < 0) {
166 va_end(ap);
167 return NULL;
168 }
169
170 /* Make the table header uppercase */
171 r = table_set_uppercase(t, cell, true);
172 if (r < 0) {
173 va_end(ap);
174 return NULL;
175 }
176 }
177 va_end(ap);
178
179 assert(t->n_columns == t->n_cells);
180 return TAKE_PTR(t);
181 }
182
183 static TableData *table_data_free(TableData *d) {
184 assert(d);
185
186 free(d->formatted);
187 free(d->url);
188
189 return mfree(d);
190 }
191
192 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
193 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
194
195 Table *table_unref(Table *t) {
196 size_t i;
197
198 if (!t)
199 return NULL;
200
201 for (i = 0; i < t->n_cells; i++)
202 table_data_unref(t->data[i]);
203
204 free(t->data);
205 free(t->display_map);
206 free(t->sort_map);
207 free(t->reverse_map);
208
209 return mfree(t);
210 }
211
212 static size_t table_data_size(TableDataType type, const void *data) {
213
214 switch (type) {
215
216 case TABLE_EMPTY:
217 return 0;
218
219 case TABLE_STRING:
220 return strlen(data) + 1;
221
222 case TABLE_BOOLEAN:
223 return sizeof(bool);
224
225 case TABLE_TIMESTAMP:
226 case TABLE_TIMESPAN:
227 return sizeof(usec_t);
228
229 case TABLE_SIZE:
230 case TABLE_UINT64:
231 return sizeof(uint64_t);
232
233 case TABLE_UINT32:
234 return sizeof(uint32_t);
235
236 case TABLE_PERCENT:
237 return sizeof(int);
238
239 default:
240 assert_not_reached("Uh? Unexpected cell type");
241 }
242 }
243
244 static bool table_data_matches(
245 TableData *d,
246 TableDataType type,
247 const void *data,
248 size_t minimum_width,
249 size_t maximum_width,
250 unsigned weight,
251 unsigned align_percent,
252 unsigned ellipsize_percent) {
253
254 size_t k, l;
255 assert(d);
256
257 if (d->type != type)
258 return false;
259
260 if (d->minimum_width != minimum_width)
261 return false;
262
263 if (d->maximum_width != maximum_width)
264 return false;
265
266 if (d->weight != weight)
267 return false;
268
269 if (d->align_percent != align_percent)
270 return false;
271
272 if (d->ellipsize_percent != ellipsize_percent)
273 return false;
274
275 /* If a color/url/uppercase flag is set, refuse to merge */
276 if (d->color)
277 return false;
278 if (d->url)
279 return false;
280 if (d->uppercase)
281 return false;
282
283 k = table_data_size(type, data);
284 l = table_data_size(d->type, d->data);
285
286 if (k != l)
287 return false;
288
289 return memcmp_safe(data, d->data, l) == 0;
290 }
291
292 static TableData *table_data_new(
293 TableDataType type,
294 const void *data,
295 size_t minimum_width,
296 size_t maximum_width,
297 unsigned weight,
298 unsigned align_percent,
299 unsigned ellipsize_percent) {
300
301 size_t data_size;
302 TableData *d;
303
304 data_size = table_data_size(type, data);
305
306 d = malloc0(offsetof(TableData, data) + data_size);
307 if (!d)
308 return NULL;
309
310 d->n_ref = 1;
311 d->type = type;
312 d->minimum_width = minimum_width;
313 d->maximum_width = maximum_width;
314 d->weight = weight;
315 d->align_percent = align_percent;
316 d->ellipsize_percent = ellipsize_percent;
317 memcpy_safe(d->data, data, data_size);
318
319 return d;
320 }
321
322 int table_add_cell_full(
323 Table *t,
324 TableCell **ret_cell,
325 TableDataType type,
326 const void *data,
327 size_t minimum_width,
328 size_t maximum_width,
329 unsigned weight,
330 unsigned align_percent,
331 unsigned ellipsize_percent) {
332
333 _cleanup_(table_data_unrefp) TableData *d = NULL;
334 TableData *p;
335
336 assert(t);
337 assert(type >= 0);
338 assert(type < _TABLE_DATA_TYPE_MAX);
339
340 /* Determine the cell adjacent to the current one, but one row up */
341 if (t->n_cells >= t->n_columns)
342 assert_se(p = t->data[t->n_cells - t->n_columns]);
343 else
344 p = NULL;
345
346 /* If formatting parameters are left unspecified, copy from the previous row */
347 if (minimum_width == (size_t) -1)
348 minimum_width = p ? p->minimum_width : 1;
349
350 if (weight == (unsigned) -1)
351 weight = p ? p->weight : DEFAULT_WEIGHT;
352
353 if (align_percent == (unsigned) -1)
354 align_percent = p ? p->align_percent : 0;
355
356 if (ellipsize_percent == (unsigned) -1)
357 ellipsize_percent = p ? p->ellipsize_percent : 100;
358
359 assert(align_percent <= 100);
360 assert(ellipsize_percent <= 100);
361
362 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
363 * formatting. Let's see if we can reuse the cell data and ref it once more. */
364
365 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
366 d = table_data_ref(p);
367 else {
368 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
369 if (!d)
370 return -ENOMEM;
371 }
372
373 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
374 return -ENOMEM;
375
376 if (ret_cell)
377 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
378
379 t->data[t->n_cells++] = TAKE_PTR(d);
380
381 return 0;
382 }
383
384 int table_dup_cell(Table *t, TableCell *cell) {
385 size_t i;
386
387 assert(t);
388
389 /* Add the data of the specified cell a second time as a new cell to the end. */
390
391 i = TABLE_CELL_TO_INDEX(cell);
392 if (i >= t->n_cells)
393 return -ENXIO;
394
395 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
396 return -ENOMEM;
397
398 t->data[t->n_cells++] = table_data_ref(t->data[i]);
399 return 0;
400 }
401
402 static int table_dedup_cell(Table *t, TableCell *cell) {
403 _cleanup_free_ char *curl = NULL;
404 TableData *nd, *od;
405 size_t i;
406
407 assert(t);
408
409 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
410 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
411
412 i = TABLE_CELL_TO_INDEX(cell);
413 if (i >= t->n_cells)
414 return -ENXIO;
415
416 assert_se(od = t->data[i]);
417 if (od->n_ref == 1)
418 return 0;
419
420 assert(od->n_ref > 1);
421
422 if (od->url) {
423 curl = strdup(od->url);
424 if (!curl)
425 return -ENOMEM;
426 }
427
428 nd = table_data_new(
429 od->type,
430 od->data,
431 od->minimum_width,
432 od->maximum_width,
433 od->weight,
434 od->align_percent,
435 od->ellipsize_percent);
436 if (!nd)
437 return -ENOMEM;
438
439 nd->color = od->color;
440 nd->url = TAKE_PTR(curl);
441 nd->uppercase = od->uppercase;
442
443 table_data_unref(od);
444 t->data[i] = nd;
445
446 assert(nd->n_ref == 1);
447
448 return 1;
449 }
450
451 static TableData *table_get_data(Table *t, TableCell *cell) {
452 size_t i;
453
454 assert(t);
455 assert(cell);
456
457 /* Get the data object of the specified cell, or NULL if it doesn't exist */
458
459 i = TABLE_CELL_TO_INDEX(cell);
460 if (i >= t->n_cells)
461 return NULL;
462
463 assert(t->data[i]);
464 assert(t->data[i]->n_ref > 0);
465
466 return t->data[i];
467 }
468
469 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
470 int r;
471
472 assert(t);
473 assert(cell);
474
475 if (minimum_width == (size_t) -1)
476 minimum_width = 1;
477
478 r = table_dedup_cell(t, cell);
479 if (r < 0)
480 return r;
481
482 table_get_data(t, cell)->minimum_width = minimum_width;
483 return 0;
484 }
485
486 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
487 int r;
488
489 assert(t);
490 assert(cell);
491
492 r = table_dedup_cell(t, cell);
493 if (r < 0)
494 return r;
495
496 table_get_data(t, cell)->maximum_width = maximum_width;
497 return 0;
498 }
499
500 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
501 int r;
502
503 assert(t);
504 assert(cell);
505
506 if (weight == (unsigned) -1)
507 weight = DEFAULT_WEIGHT;
508
509 r = table_dedup_cell(t, cell);
510 if (r < 0)
511 return r;
512
513 table_get_data(t, cell)->weight = weight;
514 return 0;
515 }
516
517 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
518 int r;
519
520 assert(t);
521 assert(cell);
522
523 if (percent == (unsigned) -1)
524 percent = 0;
525
526 assert(percent <= 100);
527
528 r = table_dedup_cell(t, cell);
529 if (r < 0)
530 return r;
531
532 table_get_data(t, cell)->align_percent = percent;
533 return 0;
534 }
535
536 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
537 int r;
538
539 assert(t);
540 assert(cell);
541
542 if (percent == (unsigned) -1)
543 percent = 100;
544
545 assert(percent <= 100);
546
547 r = table_dedup_cell(t, cell);
548 if (r < 0)
549 return r;
550
551 table_get_data(t, cell)->ellipsize_percent = percent;
552 return 0;
553 }
554
555 int table_set_color(Table *t, TableCell *cell, const char *color) {
556 int r;
557
558 assert(t);
559 assert(cell);
560
561 r = table_dedup_cell(t, cell);
562 if (r < 0)
563 return r;
564
565 table_get_data(t, cell)->color = empty_to_null(color);
566 return 0;
567 }
568
569 int table_set_url(Table *t, TableCell *cell, const char *url) {
570 _cleanup_free_ char *copy = NULL;
571 int r;
572
573 assert(t);
574 assert(cell);
575
576 if (url) {
577 copy = strdup(url);
578 if (!copy)
579 return -ENOMEM;
580 }
581
582 r = table_dedup_cell(t, cell);
583 if (r < 0)
584 return r;
585
586 return free_and_replace(table_get_data(t, cell)->url, copy);
587 }
588
589 int table_set_uppercase(Table *t, TableCell *cell, bool b) {
590 TableData *d;
591 int r;
592
593 assert(t);
594 assert(cell);
595
596 r = table_dedup_cell(t, cell);
597 if (r < 0)
598 return r;
599
600 assert_se(d = table_get_data(t, cell));
601
602 if (d->uppercase == b)
603 return 0;
604
605 d->formatted = mfree(d->formatted);
606 d->uppercase = b;
607 return 1;
608 }
609
610 int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
611 _cleanup_free_ char *curl = NULL;
612 TableData *nd, *od;
613 size_t i;
614
615 assert(t);
616 assert(cell);
617
618 i = TABLE_CELL_TO_INDEX(cell);
619 if (i >= t->n_cells)
620 return -ENXIO;
621
622 assert_se(od = t->data[i]);
623
624 if (od->url) {
625 curl = strdup(od->url);
626 if (!curl)
627 return -ENOMEM;
628 }
629
630 nd = table_data_new(
631 type,
632 data,
633 od->minimum_width,
634 od->maximum_width,
635 od->weight,
636 od->align_percent,
637 od->ellipsize_percent);
638 if (!nd)
639 return -ENOMEM;
640
641 nd->color = od->color;
642 nd->url = TAKE_PTR(curl);
643 nd->uppercase = od->uppercase;
644
645 table_data_unref(od);
646 t->data[i] = nd;
647
648 return 0;
649 }
650
651 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
652 TableDataType type;
653 va_list ap;
654 int r;
655
656 assert(t);
657 assert(first_type >= 0);
658 assert(first_type < _TABLE_DATA_TYPE_MAX);
659
660 type = first_type;
661
662 va_start(ap, first_type);
663 for (;;) {
664 const void *data;
665 union {
666 uint64_t size;
667 usec_t usec;
668 uint32_t uint32;
669 uint64_t uint64;
670 int percent;
671 bool b;
672 } buffer;
673
674 switch (type) {
675
676 case TABLE_EMPTY:
677 data = NULL;
678 break;
679
680 case TABLE_STRING:
681 data = va_arg(ap, const char *);
682 break;
683
684 case TABLE_BOOLEAN:
685 buffer.b = va_arg(ap, int);
686 data = &buffer.b;
687 break;
688
689 case TABLE_TIMESTAMP:
690 case TABLE_TIMESPAN:
691 buffer.usec = va_arg(ap, usec_t);
692 data = &buffer.usec;
693 break;
694
695 case TABLE_SIZE:
696 buffer.size = va_arg(ap, uint64_t);
697 data = &buffer.size;
698 break;
699
700 case TABLE_UINT32:
701 buffer.uint32 = va_arg(ap, uint32_t);
702 data = &buffer.uint32;
703 break;
704
705 case TABLE_UINT64:
706 buffer.uint64 = va_arg(ap, uint64_t);
707 data = &buffer.uint64;
708 break;
709
710 case TABLE_PERCENT:
711 buffer.percent = va_arg(ap, int);
712 data = &buffer.percent;
713 break;
714
715 case _TABLE_DATA_TYPE_MAX:
716 /* Used as end marker */
717 va_end(ap);
718 return 0;
719
720 default:
721 assert_not_reached("Uh? Unexpected data type.");
722 }
723
724 r = table_add_cell(t, NULL, type, data);
725 if (r < 0) {
726 va_end(ap);
727 return r;
728 }
729
730 type = va_arg(ap, TableDataType);
731 }
732 }
733
734 void table_set_header(Table *t, bool b) {
735 assert(t);
736
737 t->header = b;
738 }
739
740 void table_set_width(Table *t, size_t width) {
741 assert(t);
742
743 t->width = width;
744 }
745
746 int table_set_display(Table *t, size_t first_column, ...) {
747 size_t allocated, column;
748 va_list ap;
749
750 assert(t);
751
752 allocated = t->n_display_map;
753 column = first_column;
754
755 va_start(ap, first_column);
756 for (;;) {
757 assert(column < t->n_columns);
758
759 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
760 va_end(ap);
761 return -ENOMEM;
762 }
763
764 t->display_map[t->n_display_map++] = column;
765
766 column = va_arg(ap, size_t);
767 if (column == (size_t) -1)
768 break;
769
770 }
771 va_end(ap);
772
773 return 0;
774 }
775
776 int table_set_sort(Table *t, size_t first_column, ...) {
777 size_t allocated, column;
778 va_list ap;
779
780 assert(t);
781
782 allocated = t->n_sort_map;
783 column = first_column;
784
785 va_start(ap, first_column);
786 for (;;) {
787 assert(column < t->n_columns);
788
789 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
790 va_end(ap);
791 return -ENOMEM;
792 }
793
794 t->sort_map[t->n_sort_map++] = column;
795
796 column = va_arg(ap, size_t);
797 if (column == (size_t) -1)
798 break;
799 }
800 va_end(ap);
801
802 return 0;
803 }
804
805 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
806 assert(a);
807 assert(b);
808
809 if (a->type == b->type) {
810
811 /* We only define ordering for cells of the same data type. If cells with different data types are
812 * compared we follow the order the cells were originally added in */
813
814 switch (a->type) {
815
816 case TABLE_STRING:
817 return strcmp(a->string, b->string);
818
819 case TABLE_BOOLEAN:
820 if (!a->boolean && b->boolean)
821 return -1;
822 if (a->boolean && !b->boolean)
823 return 1;
824 return 0;
825
826 case TABLE_TIMESTAMP:
827 return CMP(a->timestamp, b->timestamp);
828
829 case TABLE_TIMESPAN:
830 return CMP(a->timespan, b->timespan);
831
832 case TABLE_SIZE:
833 return CMP(a->size, b->size);
834
835 case TABLE_UINT32:
836 return CMP(a->uint32, b->uint32);
837
838 case TABLE_UINT64:
839 return CMP(a->uint64, b->uint64);
840
841 case TABLE_PERCENT:
842 return CMP(a->percent, b->percent);
843
844 default:
845 ;
846 }
847 }
848
849 /* Generic fallback using the orginal order in which the cells where added. */
850 return CMP(index_a, index_b);
851 }
852
853 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
854 size_t i;
855 int r;
856
857 assert(t);
858 assert(t->sort_map);
859
860 /* Make sure the header stays at the beginning */
861 if (*a < t->n_columns && *b < t->n_columns)
862 return 0;
863 if (*a < t->n_columns)
864 return -1;
865 if (*b < t->n_columns)
866 return 1;
867
868 /* Order other lines by the sorting map */
869 for (i = 0; i < t->n_sort_map; i++) {
870 TableData *d, *dd;
871
872 d = t->data[*a + t->sort_map[i]];
873 dd = t->data[*b + t->sort_map[i]];
874
875 r = cell_data_compare(d, *a, dd, *b);
876 if (r != 0)
877 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
878 }
879
880 /* Order identical lines by the order there were originally added in */
881 return CMP(*a, *b);
882 }
883
884 static const char *table_data_format(TableData *d) {
885 assert(d);
886
887 if (d->formatted)
888 return d->formatted;
889
890 switch (d->type) {
891 case TABLE_EMPTY:
892 return "";
893
894 case TABLE_STRING:
895 if (d->uppercase) {
896 char *p, *q;
897
898 d->formatted = new(char, strlen(d->string) + 1);
899 if (!d->formatted)
900 return NULL;
901
902 for (p = d->string, q = d->formatted; *p; p++, q++)
903 *q = (char) toupper((unsigned char) *p);
904 *q = 0;
905
906 return d->formatted;
907 }
908
909 return d->string;
910
911 case TABLE_BOOLEAN:
912 return yes_no(d->boolean);
913
914 case TABLE_TIMESTAMP: {
915 _cleanup_free_ char *p;
916
917 p = new(char, FORMAT_TIMESTAMP_MAX);
918 if (!p)
919 return NULL;
920
921 if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
922 return "n/a";
923
924 d->formatted = TAKE_PTR(p);
925 break;
926 }
927
928 case TABLE_TIMESPAN: {
929 _cleanup_free_ char *p;
930
931 p = new(char, FORMAT_TIMESPAN_MAX);
932 if (!p)
933 return NULL;
934
935 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, 0))
936 return "n/a";
937
938 d->formatted = TAKE_PTR(p);
939 break;
940 }
941
942 case TABLE_SIZE: {
943 _cleanup_free_ char *p;
944
945 p = new(char, FORMAT_BYTES_MAX);
946 if (!p)
947 return NULL;
948
949 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
950 return "n/a";
951
952 d->formatted = TAKE_PTR(p);
953 break;
954 }
955
956 case TABLE_UINT32: {
957 _cleanup_free_ char *p;
958
959 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
960 if (!p)
961 return NULL;
962
963 sprintf(p, "%" PRIu32, d->uint32);
964 d->formatted = TAKE_PTR(p);
965 break;
966 }
967
968 case TABLE_UINT64: {
969 _cleanup_free_ char *p;
970
971 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
972 if (!p)
973 return NULL;
974
975 sprintf(p, "%" PRIu64, d->uint64);
976 d->formatted = TAKE_PTR(p);
977 break;
978 }
979
980 case TABLE_PERCENT: {
981 _cleanup_free_ char *p;
982
983 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
984 if (!p)
985 return NULL;
986
987 sprintf(p, "%i%%" , d->percent);
988 d->formatted = TAKE_PTR(p);
989 break;
990 }
991
992 default:
993 assert_not_reached("Unexpected type?");
994 }
995
996 return d->formatted;
997 }
998
999 static int table_data_requested_width(TableData *d, size_t *ret) {
1000 const char *t;
1001 size_t l;
1002
1003 t = table_data_format(d);
1004 if (!t)
1005 return -ENOMEM;
1006
1007 l = utf8_console_width(t);
1008 if (l == (size_t) -1)
1009 return -EINVAL;
1010
1011 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
1012 l = d->maximum_width;
1013
1014 if (l < d->minimum_width)
1015 l = d->minimum_width;
1016
1017 *ret = l;
1018 return 0;
1019 }
1020
1021 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1022 size_t w = 0, space, lspace, old_length, clickable_length;
1023 _cleanup_free_ char *clickable = NULL;
1024 const char *p;
1025 char *ret;
1026 size_t i;
1027 int r;
1028
1029 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1030
1031 assert(str);
1032 assert(percent <= 100);
1033
1034 old_length = strlen(str);
1035
1036 if (url) {
1037 r = terminal_urlify(url, str, &clickable);
1038 if (r < 0)
1039 return NULL;
1040
1041 clickable_length = strlen(clickable);
1042 } else
1043 clickable_length = old_length;
1044
1045 /* Determine current width on screen */
1046 p = str;
1047 while (p < str + old_length) {
1048 char32_t c;
1049
1050 if (utf8_encoded_to_unichar(p, &c) < 0) {
1051 p++, w++; /* count invalid chars as 1 */
1052 continue;
1053 }
1054
1055 p = utf8_next_char(p);
1056 w += unichar_iswide(c) ? 2 : 1;
1057 }
1058
1059 /* Already wider than the target, if so, don't do anything */
1060 if (w >= new_length)
1061 return clickable ? TAKE_PTR(clickable) : strdup(str);
1062
1063 /* How much spaces shall we add? An how much on the left side? */
1064 space = new_length - w;
1065 lspace = space * percent / 100U;
1066
1067 ret = new(char, space + clickable_length + 1);
1068 if (!ret)
1069 return NULL;
1070
1071 for (i = 0; i < lspace; i++)
1072 ret[i] = ' ';
1073 memcpy(ret + lspace, clickable ?: str, clickable_length);
1074 for (i = lspace + clickable_length; i < space + clickable_length; i++)
1075 ret[i] = ' ';
1076
1077 ret[space + clickable_length] = 0;
1078 return ret;
1079 }
1080
1081 int table_print(Table *t, FILE *f) {
1082 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1083 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1084 *width;
1085 _cleanup_free_ size_t *sorted = NULL;
1086 uint64_t *column_weight, weight_sum;
1087 int r;
1088
1089 assert(t);
1090
1091 if (!f)
1092 f = stdout;
1093
1094 /* Ensure we have no incomplete rows */
1095 assert(t->n_cells % t->n_columns == 0);
1096
1097 n_rows = t->n_cells / t->n_columns;
1098 assert(n_rows > 0); /* at least the header row must be complete */
1099
1100 if (t->sort_map) {
1101 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
1102
1103 sorted = new(size_t, n_rows);
1104 if (!sorted)
1105 return -ENOMEM;
1106
1107 for (i = 0; i < n_rows; i++)
1108 sorted[i] = i * t->n_columns;
1109
1110 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1111 }
1112
1113 if (t->display_map)
1114 display_columns = t->n_display_map;
1115 else
1116 display_columns = t->n_columns;
1117
1118 assert(display_columns > 0);
1119
1120 minimum_width = newa(size_t, display_columns);
1121 maximum_width = newa(size_t, display_columns);
1122 requested_width = newa(size_t, display_columns);
1123 width = newa(size_t, display_columns);
1124 column_weight = newa0(uint64_t, display_columns);
1125
1126 for (j = 0; j < display_columns; j++) {
1127 minimum_width[j] = 1;
1128 maximum_width[j] = (size_t) -1;
1129 requested_width[j] = (size_t) -1;
1130 }
1131
1132 /* First pass: determine column sizes */
1133 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1134 TableData **row;
1135
1136 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
1137 * hence we don't care for sorted[] during the first pass. */
1138 row = t->data + i * t->n_columns;
1139
1140 for (j = 0; j < display_columns; j++) {
1141 TableData *d;
1142 size_t req;
1143
1144 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1145
1146 r = table_data_requested_width(d, &req);
1147 if (r < 0)
1148 return r;
1149
1150 /* Determine the biggest width that any cell in this column would like to have */
1151 if (requested_width[j] == (size_t) -1 ||
1152 requested_width[j] < req)
1153 requested_width[j] = req;
1154
1155 /* Determine the minimum width any cell in this column needs */
1156 if (minimum_width[j] < d->minimum_width)
1157 minimum_width[j] = d->minimum_width;
1158
1159 /* Determine the maximum width any cell in this column needs */
1160 if (d->maximum_width != (size_t) -1 &&
1161 (maximum_width[j] == (size_t) -1 ||
1162 maximum_width[j] > d->maximum_width))
1163 maximum_width[j] = d->maximum_width;
1164
1165 /* Determine the full columns weight */
1166 column_weight[j] += d->weight;
1167 }
1168 }
1169
1170 /* One space between each column */
1171 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1172
1173 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1174 weight_sum = 0;
1175 for (j = 0; j < display_columns; j++) {
1176 weight_sum += column_weight[j];
1177
1178 table_minimum_width += minimum_width[j];
1179
1180 if (maximum_width[j] == (size_t) -1)
1181 table_maximum_width = (size_t) -1;
1182 else
1183 table_maximum_width += maximum_width[j];
1184
1185 table_requested_width += requested_width[j];
1186 }
1187
1188 /* Calculate effective table width */
1189 if (t->width == (size_t) -1)
1190 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1191 else
1192 table_effective_width = t->width;
1193
1194 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1195 table_effective_width = table_maximum_width;
1196
1197 if (table_effective_width < table_minimum_width)
1198 table_effective_width = table_minimum_width;
1199
1200 if (table_effective_width >= table_requested_width) {
1201 size_t extra;
1202
1203 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1204 * each column with what it asked for and the distribute the rest. */
1205
1206 extra = table_effective_width - table_requested_width;
1207
1208 for (j = 0; j < display_columns; j++) {
1209 size_t delta;
1210
1211 if (weight_sum == 0)
1212 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1213 else
1214 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1215
1216 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1217 width[j] = maximum_width[j];
1218
1219 if (width[j] < minimum_width[j])
1220 width[j] = minimum_width[j];
1221
1222 assert(width[j] >= requested_width[j]);
1223 delta = width[j] - requested_width[j];
1224
1225 /* Subtract what we just added from the rest */
1226 if (extra > delta)
1227 extra -= delta;
1228 else
1229 extra = 0;
1230
1231 assert(weight_sum >= column_weight[j]);
1232 weight_sum -= column_weight[j];
1233 }
1234
1235 } else {
1236 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1237 * with the minimum they need, and then distribute anything left. */
1238 bool finalize = false;
1239 size_t extra;
1240
1241 extra = table_effective_width - table_minimum_width;
1242
1243 for (j = 0; j < display_columns; j++)
1244 width[j] = (size_t) -1;
1245
1246 for (;;) {
1247 bool restart = false;
1248
1249 for (j = 0; j < display_columns; j++) {
1250 size_t delta, w;
1251
1252 /* Did this column already get something assigned? If so, let's skip to the next */
1253 if (width[j] != (size_t) -1)
1254 continue;
1255
1256 if (weight_sum == 0)
1257 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1258 else
1259 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1260
1261 if (w >= requested_width[j]) {
1262 /* Never give more than requested. If we hit a column like this, there's more
1263 * space to allocate to other columns which means we need to restart the
1264 * iteration. However, if we hit a column like this, let's assign it the space
1265 * it wanted for good early.*/
1266
1267 w = requested_width[j];
1268 restart = true;
1269
1270 } else if (!finalize)
1271 continue;
1272
1273 width[j] = w;
1274
1275 assert(w >= minimum_width[j]);
1276 delta = w - minimum_width[j];
1277
1278 assert(delta <= extra);
1279 extra -= delta;
1280
1281 assert(weight_sum >= column_weight[j]);
1282 weight_sum -= column_weight[j];
1283
1284 if (restart && !finalize)
1285 break;
1286 }
1287
1288 if (finalize)
1289 break;
1290
1291 if (!restart)
1292 finalize = true;
1293 }
1294 }
1295
1296 /* Second pass: show output */
1297 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1298 TableData **row;
1299
1300 if (sorted)
1301 row = t->data + sorted[i];
1302 else
1303 row = t->data + i * t->n_columns;
1304
1305 for (j = 0; j < display_columns; j++) {
1306 _cleanup_free_ char *buffer = NULL;
1307 const char *field;
1308 TableData *d;
1309 size_t l;
1310
1311 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1312
1313 field = table_data_format(d);
1314 if (!field)
1315 return -ENOMEM;
1316
1317 l = utf8_console_width(field);
1318 if (l > width[j]) {
1319 /* Field is wider than allocated space. Let's ellipsize */
1320
1321 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1322 if (!buffer)
1323 return -ENOMEM;
1324
1325 field = buffer;
1326
1327 } else if (l < width[j]) {
1328 /* Field is shorter than allocated space. Let's align with spaces */
1329
1330 buffer = align_string_mem(field, d->url, width[j], d->align_percent);
1331 if (!buffer)
1332 return -ENOMEM;
1333
1334 field = buffer;
1335 }
1336
1337 if (l >= width[j] && d->url) {
1338 _cleanup_free_ char *clickable = NULL;
1339
1340 r = terminal_urlify(d->url, field, &clickable);
1341 if (r < 0)
1342 return r;
1343
1344 free_and_replace(buffer, clickable);
1345 field = buffer;
1346 }
1347
1348 if (row == t->data) /* underline header line fully, including the column separator */
1349 fputs(ansi_underline(), f);
1350
1351 if (j > 0)
1352 fputc(' ', f); /* column separator */
1353
1354 if (d->color && colors_enabled()) {
1355 if (row == t->data) /* first undo header underliner */
1356 fputs(ANSI_NORMAL, f);
1357
1358 fputs(d->color, f);
1359 }
1360
1361 fputs(field, f);
1362
1363 if (colors_enabled() && (d->color || row == t->data))
1364 fputs(ANSI_NORMAL, f);
1365 }
1366
1367 fputc('\n', f);
1368 }
1369
1370 return fflush_and_check(f);
1371 }
1372
1373 int table_format(Table *t, char **ret) {
1374 _cleanup_fclose_ FILE *f = NULL;
1375 char *buf = NULL;
1376 size_t sz = 0;
1377 int r;
1378
1379 f = open_memstream(&buf, &sz);
1380 if (!f)
1381 return -ENOMEM;
1382
1383 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
1384
1385 r = table_print(t, f);
1386 if (r < 0)
1387 return r;
1388
1389 f = safe_fclose(f);
1390
1391 *ret = buf;
1392
1393 return 0;
1394 }
1395
1396 size_t table_get_rows(Table *t) {
1397 if (!t)
1398 return 0;
1399
1400 assert(t->n_columns > 0);
1401 return t->n_cells / t->n_columns;
1402 }
1403
1404 size_t table_get_columns(Table *t) {
1405 if (!t)
1406 return 0;
1407
1408 assert(t->n_columns > 0);
1409 return t->n_columns;
1410 }
1411
1412 int table_set_reverse(Table *t, size_t column, bool b) {
1413 assert(t);
1414 assert(column < t->n_columns);
1415
1416 if (!t->reverse_map) {
1417 if (!b)
1418 return 0;
1419
1420 t->reverse_map = new0(bool, t->n_columns);
1421 if (!t->reverse_map)
1422 return -ENOMEM;
1423 }
1424
1425 t->reverse_map[column] = b;
1426 return 0;
1427 }
1428
1429 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
1430 size_t i;
1431
1432 assert(t);
1433
1434 if (column >= t->n_columns)
1435 return NULL;
1436
1437 i = row * t->n_columns + column;
1438 if (i >= t->n_cells)
1439 return NULL;
1440
1441 return TABLE_INDEX_TO_CELL(i);
1442 }
1443
1444 const void *table_get(Table *t, TableCell *cell) {
1445 TableData *d;
1446
1447 assert(t);
1448
1449 d = table_get_data(t, cell);
1450 if (!d)
1451 return NULL;
1452
1453 return d->data;
1454 }
1455
1456 const void* table_get_at(Table *t, size_t row, size_t column) {
1457 TableCell *cell;
1458
1459 cell = table_get_cell(t, row, column);
1460 if (!cell)
1461 return NULL;
1462
1463 return table_get(t, cell);
1464 }
1465
1466 static int table_data_to_json(TableData *d, JsonVariant **ret) {
1467
1468 switch (d->type) {
1469
1470 case TABLE_EMPTY:
1471 return json_variant_new_null(ret);
1472
1473 case TABLE_STRING:
1474 return json_variant_new_string(ret, d->string);
1475
1476 case TABLE_BOOLEAN:
1477 return json_variant_new_boolean(ret, d->boolean);
1478
1479 case TABLE_TIMESTAMP:
1480 if (d->timestamp == USEC_INFINITY)
1481 return json_variant_new_null(ret);
1482
1483 return json_variant_new_unsigned(ret, d->timestamp);
1484
1485 case TABLE_TIMESPAN:
1486 if (d->timespan == USEC_INFINITY)
1487 return json_variant_new_null(ret);
1488
1489 return json_variant_new_unsigned(ret, d->timespan);
1490
1491 case TABLE_SIZE:
1492 if (d->size == (size_t) -1)
1493 return json_variant_new_null(ret);
1494
1495 return json_variant_new_unsigned(ret, d->size);
1496
1497 case TABLE_UINT32:
1498 return json_variant_new_unsigned(ret, d->uint32);
1499
1500 case TABLE_UINT64:
1501 return json_variant_new_unsigned(ret, d->uint64);
1502
1503 case TABLE_PERCENT:
1504 return json_variant_new_integer(ret, d->percent);
1505
1506 default:
1507 return -EINVAL;
1508 }
1509 }
1510
1511 int table_to_json(Table *t, JsonVariant **ret) {
1512 JsonVariant **rows = NULL, **elements = NULL;
1513 _cleanup_free_ size_t *sorted = NULL;
1514 size_t n_rows, i, j, display_columns;
1515 int r;
1516
1517 assert(t);
1518
1519 /* Ensure we have no incomplete rows */
1520 assert(t->n_cells % t->n_columns == 0);
1521
1522 n_rows = t->n_cells / t->n_columns;
1523 assert(n_rows > 0); /* at least the header row must be complete */
1524
1525 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 sorted = new(size_t, n_rows);
1529 if (!sorted) {
1530 r = -ENOMEM;
1531 goto finish;
1532 }
1533
1534 for (i = 0; i < n_rows; i++)
1535 sorted[i] = i * t->n_columns;
1536
1537 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
1538 }
1539
1540 if (t->display_map)
1541 display_columns = t->n_display_map;
1542 else
1543 display_columns = t->n_columns;
1544 assert(display_columns > 0);
1545
1546 elements = new0(JsonVariant*, display_columns * 2);
1547 if (!elements) {
1548 r = -ENOMEM;
1549 goto finish;
1550 }
1551
1552 for (j = 0; j < display_columns; j++) {
1553 TableData *d;
1554
1555 assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
1556
1557 r = table_data_to_json(d, elements + j*2);
1558 if (r < 0)
1559 goto finish;
1560 }
1561
1562 rows = new0(JsonVariant*, n_rows-1);
1563 if (!rows) {
1564 r = -ENOMEM;
1565 goto finish;
1566 }
1567
1568 for (i = 1; i < n_rows; i++) {
1569 TableData **row;
1570
1571 if (sorted)
1572 row = t->data + sorted[i];
1573 else
1574 row = t->data + i * t->n_columns;
1575
1576 for (j = 0; j < display_columns; j++) {
1577 TableData *d;
1578 size_t k;
1579
1580 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1581
1582 k = j*2+1;
1583 elements[k] = json_variant_unref(elements[k]);
1584
1585 r = table_data_to_json(d, elements + k);
1586 if (r < 0)
1587 goto finish;
1588 }
1589
1590 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1591 if (r < 0)
1592 goto finish;
1593 }
1594
1595 r = json_variant_new_array(ret, rows, n_rows - 1);
1596
1597 finish:
1598 if (rows) {
1599 json_variant_unref_many(rows, n_rows-1);
1600 free(rows);
1601 }
1602
1603 if (elements) {
1604 json_variant_unref_many(elements, display_columns*2);
1605 free(elements);
1606 }
1607
1608 return r;
1609 }
1610
1611 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
1612 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
1613 int r;
1614
1615 assert(t);
1616
1617 if (!f)
1618 f = stdout;
1619
1620 r = table_to_json(t, &v);
1621 if (r < 0)
1622 return r;
1623
1624 json_variant_dump(v, flags, f, NULL);
1625
1626 return fflush_and_check(f);
1627 }