]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/format-table.c
Merge pull request #10850 from poettering/log-setup
[thirdparty/systemd.git] / src / shared / format-table.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <stdio_ext.h>
4
5 #include "alloc-util.h"
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "format-table.h"
9 #include "gunicode.h"
10 #include "pager.h"
11 #include "parse-util.h"
12 #include "string-util.h"
13 #include "terminal-util.h"
14 #include "time-util.h"
15 #include "utf8.h"
16 #include "util.h"
17
18 #define DEFAULT_WEIGHT 100
19
20 /*
21 A few notes on implementation details:
22
23 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
24 table. It can be easily converted to an index number and back.
25
26 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
27 'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
28 ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
29 outside only sees Table and TableCell.
30
31 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
32 previous one.
33
34 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
35 derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
36 that. The first row is always the header row. If header display is turned off we simply skip outputting the first
37 row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
38
39 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
40 are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
41 add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
42 cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
43 instead.
44
45 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
46 from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
47 this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
48 */
49
50 typedef struct TableData {
51 unsigned n_ref;
52 TableDataType type;
53
54 size_t minimum_width; /* minimum width for the column */
55 size_t maximum_width; /* maximum width for the column */
56 unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
57 unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
58 unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
59
60 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 */
61 char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
62
63 union {
64 uint8_t data[0]; /* data is generic array */
65 bool boolean;
66 usec_t timestamp;
67 usec_t timespan;
68 uint64_t size;
69 char string[0];
70 uint32_t uint32;
71 /* … add more here as we start supporting more cell data types … */
72 };
73 } TableData;
74
75 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
76 size_t i;
77
78 assert(cell);
79
80 i = PTR_TO_SIZE(cell);
81 assert(i > 0);
82
83 return i-1;
84 }
85
86 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
87 assert(index != (size_t) -1);
88 return SIZE_TO_PTR(index + 1);
89 }
90
91 struct Table {
92 size_t n_columns;
93 size_t n_cells;
94
95 bool header; /* Whether to show the header row? */
96 size_t width; /* If != (size_t) -1 the width to format this table in */
97
98 TableData **data;
99 size_t n_allocated;
100
101 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 */
102 size_t n_display_map;
103
104 size_t *sort_map; /* The columns to order rows by, in order of preference. */
105 size_t n_sort_map;
106 };
107
108 Table *table_new_raw(size_t n_columns) {
109 _cleanup_(table_unrefp) Table *t = NULL;
110
111 assert(n_columns > 0);
112
113 t = new(Table, 1);
114 if (!t)
115 return NULL;
116
117 *t = (struct Table) {
118 .n_columns = n_columns,
119 .header = true,
120 .width = (size_t) -1,
121 };
122
123 return TAKE_PTR(t);
124 }
125
126 Table *table_new_internal(const char *first_header, ...) {
127 _cleanup_(table_unrefp) Table *t = NULL;
128 size_t n_columns = 1;
129 va_list ap;
130 int r;
131
132 assert(first_header);
133
134 va_start(ap, first_header);
135 for (;;) {
136 const char *h;
137
138 h = va_arg(ap, const char*);
139 if (!h)
140 break;
141
142 n_columns++;
143 }
144 va_end(ap);
145
146 t = table_new_raw(n_columns);
147 if (!t)
148 return NULL;
149
150 r = table_add_cell(t, NULL, TABLE_STRING, first_header);
151 if (r < 0)
152 return NULL;
153
154 va_start(ap, first_header);
155 for (;;) {
156 const char *h;
157
158 h = va_arg(ap, const char*);
159 if (!h)
160 break;
161
162 r = table_add_cell(t, NULL, TABLE_STRING, h);
163 if (r < 0) {
164 va_end(ap);
165 return NULL;
166 }
167 }
168 va_end(ap);
169
170 assert(t->n_columns == t->n_cells);
171 return TAKE_PTR(t);
172 }
173
174 static TableData *table_data_free(TableData *d) {
175 assert(d);
176
177 free(d->formatted);
178 return mfree(d);
179 }
180
181 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
182 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
183
184 Table *table_unref(Table *t) {
185 size_t i;
186
187 if (!t)
188 return NULL;
189
190 for (i = 0; i < t->n_cells; i++)
191 table_data_unref(t->data[i]);
192
193 free(t->data);
194 free(t->display_map);
195 free(t->sort_map);
196
197 return mfree(t);
198 }
199
200 static size_t table_data_size(TableDataType type, const void *data) {
201
202 switch (type) {
203
204 case TABLE_EMPTY:
205 return 0;
206
207 case TABLE_STRING:
208 return strlen(data) + 1;
209
210 case TABLE_BOOLEAN:
211 return sizeof(bool);
212
213 case TABLE_TIMESTAMP:
214 case TABLE_TIMESPAN:
215 return sizeof(usec_t);
216
217 case TABLE_SIZE:
218 return sizeof(uint64_t);
219
220 case TABLE_UINT32:
221 return sizeof(uint32_t);
222
223 default:
224 assert_not_reached("Uh? Unexpected cell type");
225 }
226 }
227
228 static bool table_data_matches(
229 TableData *d,
230 TableDataType type,
231 const void *data,
232 size_t minimum_width,
233 size_t maximum_width,
234 unsigned weight,
235 unsigned align_percent,
236 unsigned ellipsize_percent) {
237
238 size_t k, l;
239 assert(d);
240
241 if (d->type != type)
242 return false;
243
244 if (d->minimum_width != minimum_width)
245 return false;
246
247 if (d->maximum_width != maximum_width)
248 return false;
249
250 if (d->weight != weight)
251 return false;
252
253 if (d->align_percent != align_percent)
254 return false;
255
256 if (d->ellipsize_percent != ellipsize_percent)
257 return false;
258
259 k = table_data_size(type, data);
260 l = table_data_size(d->type, d->data);
261
262 if (k != l)
263 return false;
264
265 return memcmp(data, d->data, l) == 0;
266 }
267
268 static TableData *table_data_new(
269 TableDataType type,
270 const void *data,
271 size_t minimum_width,
272 size_t maximum_width,
273 unsigned weight,
274 unsigned align_percent,
275 unsigned ellipsize_percent) {
276
277 size_t data_size;
278 TableData *d;
279
280 data_size = table_data_size(type, data);
281
282 d = malloc0(offsetof(TableData, data) + data_size);
283 if (!d)
284 return NULL;
285
286 d->n_ref = 1;
287 d->type = type;
288 d->minimum_width = minimum_width;
289 d->maximum_width = maximum_width;
290 d->weight = weight;
291 d->align_percent = align_percent;
292 d->ellipsize_percent = ellipsize_percent;
293 memcpy_safe(d->data, data, data_size);
294
295 return d;
296 }
297
298 int table_add_cell_full(
299 Table *t,
300 TableCell **ret_cell,
301 TableDataType type,
302 const void *data,
303 size_t minimum_width,
304 size_t maximum_width,
305 unsigned weight,
306 unsigned align_percent,
307 unsigned ellipsize_percent) {
308
309 _cleanup_(table_data_unrefp) TableData *d = NULL;
310 TableData *p;
311
312 assert(t);
313 assert(type >= 0);
314 assert(type < _TABLE_DATA_TYPE_MAX);
315
316 /* Determine the cell adjacent to the current one, but one row up */
317 if (t->n_cells >= t->n_columns)
318 assert_se(p = t->data[t->n_cells - t->n_columns]);
319 else
320 p = NULL;
321
322 /* If formatting parameters are left unspecified, copy from the previous row */
323 if (minimum_width == (size_t) -1)
324 minimum_width = p ? p->minimum_width : 1;
325
326 if (weight == (unsigned) -1)
327 weight = p ? p->weight : DEFAULT_WEIGHT;
328
329 if (align_percent == (unsigned) -1)
330 align_percent = p ? p->align_percent : 0;
331
332 if (ellipsize_percent == (unsigned) -1)
333 ellipsize_percent = p ? p->ellipsize_percent : 100;
334
335 assert(align_percent <= 100);
336 assert(ellipsize_percent <= 100);
337
338 /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
339 * formatting. Let's see if we can reuse the cell data and ref it once more. */
340
341 if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
342 d = table_data_ref(p);
343 else {
344 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
345 if (!d)
346 return -ENOMEM;
347 }
348
349 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
350 return -ENOMEM;
351
352 if (ret_cell)
353 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
354
355 t->data[t->n_cells++] = TAKE_PTR(d);
356
357 return 0;
358 }
359
360 int table_dup_cell(Table *t, TableCell *cell) {
361 size_t i;
362
363 assert(t);
364
365 /* Add the data of the specified cell a second time as a new cell to the end. */
366
367 i = TABLE_CELL_TO_INDEX(cell);
368 if (i >= t->n_cells)
369 return -ENXIO;
370
371 if (!GREEDY_REALLOC(t->data, t->n_allocated, MAX(t->n_cells + 1, t->n_columns)))
372 return -ENOMEM;
373
374 t->data[t->n_cells++] = table_data_ref(t->data[i]);
375 return 0;
376 }
377
378 static int table_dedup_cell(Table *t, TableCell *cell) {
379 TableData *nd, *od;
380 size_t i;
381
382 assert(t);
383
384 /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
385 * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
386
387 i = TABLE_CELL_TO_INDEX(cell);
388 if (i >= t->n_cells)
389 return -ENXIO;
390
391 assert_se(od = t->data[i]);
392 if (od->n_ref == 1)
393 return 0;
394
395 assert(od->n_ref > 1);
396
397 nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
398 if (!nd)
399 return -ENOMEM;
400
401 table_data_unref(od);
402 t->data[i] = nd;
403
404 assert(nd->n_ref == 1);
405
406 return 1;
407 }
408
409 static TableData *table_get_data(Table *t, TableCell *cell) {
410 size_t i;
411
412 assert(t);
413 assert(cell);
414
415 /* Get the data object of the specified cell, or NULL if it doesn't exist */
416
417 i = TABLE_CELL_TO_INDEX(cell);
418 if (i >= t->n_cells)
419 return NULL;
420
421 assert(t->data[i]);
422 assert(t->data[i]->n_ref > 0);
423
424 return t->data[i];
425 }
426
427 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
428 int r;
429
430 assert(t);
431 assert(cell);
432
433 if (minimum_width == (size_t) -1)
434 minimum_width = 1;
435
436 r = table_dedup_cell(t, cell);
437 if (r < 0)
438 return r;
439
440 table_get_data(t, cell)->minimum_width = minimum_width;
441 return 0;
442 }
443
444 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
445 int r;
446
447 assert(t);
448 assert(cell);
449
450 r = table_dedup_cell(t, cell);
451 if (r < 0)
452 return r;
453
454 table_get_data(t, cell)->maximum_width = maximum_width;
455 return 0;
456 }
457
458 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
459 int r;
460
461 assert(t);
462 assert(cell);
463
464 if (weight == (unsigned) -1)
465 weight = DEFAULT_WEIGHT;
466
467 r = table_dedup_cell(t, cell);
468 if (r < 0)
469 return r;
470
471 table_get_data(t, cell)->weight = weight;
472 return 0;
473 }
474
475 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
476 int r;
477
478 assert(t);
479 assert(cell);
480
481 if (percent == (unsigned) -1)
482 percent = 0;
483
484 assert(percent <= 100);
485
486 r = table_dedup_cell(t, cell);
487 if (r < 0)
488 return r;
489
490 table_get_data(t, cell)->align_percent = percent;
491 return 0;
492 }
493
494 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
495 int r;
496
497 assert(t);
498 assert(cell);
499
500 if (percent == (unsigned) -1)
501 percent = 100;
502
503 assert(percent <= 100);
504
505 r = table_dedup_cell(t, cell);
506 if (r < 0)
507 return r;
508
509 table_get_data(t, cell)->ellipsize_percent = percent;
510 return 0;
511 }
512
513 int table_set_color(Table *t, TableCell *cell, const char *color) {
514 int r;
515
516 assert(t);
517 assert(cell);
518
519 r = table_dedup_cell(t, cell);
520 if (r < 0)
521 return r;
522
523 table_get_data(t, cell)->color = empty_to_null(color);
524 return 0;
525 }
526
527 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
528 TableDataType type;
529 va_list ap;
530 int r;
531
532 assert(t);
533 assert(first_type >= 0);
534 assert(first_type < _TABLE_DATA_TYPE_MAX);
535
536 type = first_type;
537
538 va_start(ap, first_type);
539 for (;;) {
540 const void *data;
541 union {
542 uint64_t size;
543 usec_t usec;
544 uint32_t uint32;
545 bool b;
546 } buffer;
547
548 switch (type) {
549
550 case TABLE_EMPTY:
551 data = NULL;
552 break;
553
554 case TABLE_STRING:
555 data = va_arg(ap, const char *);
556 break;
557
558 case TABLE_BOOLEAN:
559 buffer.b = va_arg(ap, int);
560 data = &buffer.b;
561 break;
562
563 case TABLE_TIMESTAMP:
564 case TABLE_TIMESPAN:
565 buffer.usec = va_arg(ap, usec_t);
566 data = &buffer.usec;
567 break;
568
569 case TABLE_SIZE:
570 buffer.size = va_arg(ap, uint64_t);
571 data = &buffer.size;
572 break;
573
574 case TABLE_UINT32:
575 buffer.uint32 = va_arg(ap, uint32_t);
576 data = &buffer.uint32;
577 break;
578
579 case _TABLE_DATA_TYPE_MAX:
580 /* Used as end marker */
581 va_end(ap);
582 return 0;
583
584 default:
585 assert_not_reached("Uh? Unexpected data type.");
586 }
587
588 r = table_add_cell(t, NULL, type, data);
589 if (r < 0) {
590 va_end(ap);
591 return r;
592 }
593
594 type = va_arg(ap, TableDataType);
595 }
596 }
597
598 void table_set_header(Table *t, bool b) {
599 assert(t);
600
601 t->header = b;
602 }
603
604 void table_set_width(Table *t, size_t width) {
605 assert(t);
606
607 t->width = width;
608 }
609
610 int table_set_display(Table *t, size_t first_column, ...) {
611 size_t allocated, column;
612 va_list ap;
613
614 assert(t);
615
616 allocated = t->n_display_map;
617 column = first_column;
618
619 va_start(ap, first_column);
620 for (;;) {
621 assert(column < t->n_columns);
622
623 if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, t->n_display_map+1))) {
624 va_end(ap);
625 return -ENOMEM;
626 }
627
628 t->display_map[t->n_display_map++] = column;
629
630 column = va_arg(ap, size_t);
631 if (column == (size_t) -1)
632 break;
633
634 }
635 va_end(ap);
636
637 return 0;
638 }
639
640 int table_set_sort(Table *t, size_t first_column, ...) {
641 size_t allocated, column;
642 va_list ap;
643
644 assert(t);
645
646 allocated = t->n_sort_map;
647 column = first_column;
648
649 va_start(ap, first_column);
650 for (;;) {
651 assert(column < t->n_columns);
652
653 if (!GREEDY_REALLOC(t->sort_map, allocated, MAX(t->n_columns, t->n_sort_map+1))) {
654 va_end(ap);
655 return -ENOMEM;
656 }
657
658 t->sort_map[t->n_sort_map++] = column;
659
660 column = va_arg(ap, size_t);
661 if (column == (size_t) -1)
662 break;
663 }
664 va_end(ap);
665
666 return 0;
667 }
668
669 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
670 assert(a);
671 assert(b);
672
673 if (a->type == b->type) {
674
675 /* We only define ordering for cells of the same data type. If cells with different data types are
676 * compared we follow the order the cells were originally added in */
677
678 switch (a->type) {
679
680 case TABLE_STRING:
681 return strcmp(a->string, b->string);
682
683 case TABLE_BOOLEAN:
684 if (!a->boolean && b->boolean)
685 return -1;
686 if (a->boolean && !b->boolean)
687 return 1;
688 return 0;
689
690 case TABLE_TIMESTAMP:
691 return CMP(a->timestamp, b->timestamp);
692
693 case TABLE_TIMESPAN:
694 return CMP(a->timespan, b->timespan);
695
696 case TABLE_SIZE:
697 return CMP(a->size, b->size);
698
699 case TABLE_UINT32:
700 return CMP(a->uint32, b->uint32);
701
702 default:
703 ;
704 }
705 }
706
707 /* Generic fallback using the orginal order in which the cells where added. */
708 return CMP(index_a, index_b);
709 }
710
711 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
712 size_t i;
713 int r;
714
715 assert(t);
716 assert(t->sort_map);
717
718 /* Make sure the header stays at the beginning */
719 if (*a < t->n_columns && *b < t->n_columns)
720 return 0;
721 if (*a < t->n_columns)
722 return -1;
723 if (*b < t->n_columns)
724 return 1;
725
726 /* Order other lines by the sorting map */
727 for (i = 0; i < t->n_sort_map; i++) {
728 TableData *d, *dd;
729
730 d = t->data[*a + t->sort_map[i]];
731 dd = t->data[*b + t->sort_map[i]];
732
733 r = cell_data_compare(d, *a, dd, *b);
734 if (r != 0)
735 return r;
736 }
737
738 /* Order identical lines by the order there were originally added in */
739 return CMP(*a, *b);
740 }
741
742 static const char *table_data_format(TableData *d) {
743 assert(d);
744
745 if (d->formatted)
746 return d->formatted;
747
748 switch (d->type) {
749 case TABLE_EMPTY:
750 return "";
751
752 case TABLE_STRING:
753 return d->string;
754
755 case TABLE_BOOLEAN:
756 return yes_no(d->boolean);
757
758 case TABLE_TIMESTAMP: {
759 _cleanup_free_ char *p;
760
761 p = new(char, FORMAT_TIMESTAMP_MAX);
762 if (!p)
763 return NULL;
764
765 if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
766 return "n/a";
767
768 d->formatted = TAKE_PTR(p);
769 break;
770 }
771
772 case TABLE_TIMESPAN: {
773 _cleanup_free_ char *p;
774
775 p = new(char, FORMAT_TIMESPAN_MAX);
776 if (!p)
777 return NULL;
778
779 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
780 return "n/a";
781
782 d->formatted = TAKE_PTR(p);
783 break;
784 }
785
786 case TABLE_SIZE: {
787 _cleanup_free_ char *p;
788
789 p = new(char, FORMAT_BYTES_MAX);
790 if (!p)
791 return NULL;
792
793 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
794 return "n/a";
795
796 d->formatted = TAKE_PTR(p);
797 break;
798 }
799
800 case TABLE_UINT32: {
801 _cleanup_free_ char *p;
802
803 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
804 if (!p)
805 return NULL;
806
807 sprintf(p, "%" PRIu32, d->uint32);
808 d->formatted = TAKE_PTR(p);
809 break;
810 }
811
812 default:
813 assert_not_reached("Unexpected type?");
814 }
815
816 return d->formatted;
817 }
818
819 static int table_data_requested_width(TableData *d, size_t *ret) {
820 const char *t;
821 size_t l;
822
823 t = table_data_format(d);
824 if (!t)
825 return -ENOMEM;
826
827 l = utf8_console_width(t);
828 if (l == (size_t) -1)
829 return -EINVAL;
830
831 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
832 l = d->maximum_width;
833
834 if (l < d->minimum_width)
835 l = d->minimum_width;
836
837 *ret = l;
838 return 0;
839 }
840
841 static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
842 size_t w = 0, space, lspace, old_length;
843 const char *p;
844 char *ret;
845 size_t i;
846
847 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
848
849 assert(str);
850 assert(percent <= 100);
851
852 old_length = strlen(str);
853
854 /* Determine current width on screen */
855 p = str;
856 while (p < str + old_length) {
857 char32_t c;
858
859 if (utf8_encoded_to_unichar(p, &c) < 0) {
860 p++, w++; /* count invalid chars as 1 */
861 continue;
862 }
863
864 p = utf8_next_char(p);
865 w += unichar_iswide(c) ? 2 : 1;
866 }
867
868 /* Already wider than the target, if so, don't do anything */
869 if (w >= new_length)
870 return strndup(str, old_length);
871
872 /* How much spaces shall we add? An how much on the left side? */
873 space = new_length - w;
874 lspace = space * percent / 100U;
875
876 ret = new(char, space + old_length + 1);
877 if (!ret)
878 return NULL;
879
880 for (i = 0; i < lspace; i++)
881 ret[i] = ' ';
882 memcpy(ret + lspace, str, old_length);
883 for (i = lspace + old_length; i < space + old_length; i++)
884 ret[i] = ' ';
885
886 ret[space + old_length] = 0;
887 return ret;
888 }
889
890 int table_print(Table *t, FILE *f) {
891 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
892 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
893 *width;
894 _cleanup_free_ size_t *sorted = NULL;
895 uint64_t *column_weight, weight_sum;
896 int r;
897
898 assert(t);
899
900 if (!f)
901 f = stdout;
902
903 /* Ensure we have no incomplete rows */
904 assert(t->n_cells % t->n_columns == 0);
905
906 n_rows = t->n_cells / t->n_columns;
907 assert(n_rows > 0); /* at least the header row must be complete */
908
909 if (t->sort_map) {
910 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
911
912 sorted = new(size_t, n_rows);
913 if (!sorted)
914 return -ENOMEM;
915
916 for (i = 0; i < n_rows; i++)
917 sorted[i] = i * t->n_columns;
918
919 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
920 }
921
922 if (t->display_map)
923 display_columns = t->n_display_map;
924 else
925 display_columns = t->n_columns;
926
927 assert(display_columns > 0);
928
929 minimum_width = newa(size_t, display_columns);
930 maximum_width = newa(size_t, display_columns);
931 requested_width = newa(size_t, display_columns);
932 width = newa(size_t, display_columns);
933 column_weight = newa0(uint64_t, display_columns);
934
935 for (j = 0; j < display_columns; j++) {
936 minimum_width[j] = 1;
937 maximum_width[j] = (size_t) -1;
938 requested_width[j] = (size_t) -1;
939 }
940
941 /* First pass: determine column sizes */
942 for (i = t->header ? 0 : 1; i < n_rows; i++) {
943 TableData **row;
944
945 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
946 * hence we don't care for sorted[] during the first pass. */
947 row = t->data + i * t->n_columns;
948
949 for (j = 0; j < display_columns; j++) {
950 TableData *d;
951 size_t req;
952
953 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
954
955 r = table_data_requested_width(d, &req);
956 if (r < 0)
957 return r;
958
959 /* Determine the biggest width that any cell in this column would like to have */
960 if (requested_width[j] == (size_t) -1 ||
961 requested_width[j] < req)
962 requested_width[j] = req;
963
964 /* Determine the minimum width any cell in this column needs */
965 if (minimum_width[j] < d->minimum_width)
966 minimum_width[j] = d->minimum_width;
967
968 /* Determine the maximum width any cell in this column needs */
969 if (d->maximum_width != (size_t) -1 &&
970 (maximum_width[j] == (size_t) -1 ||
971 maximum_width[j] > d->maximum_width))
972 maximum_width[j] = d->maximum_width;
973
974 /* Determine the full columns weight */
975 column_weight[j] += d->weight;
976 }
977 }
978
979 /* One space between each column */
980 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
981
982 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
983 weight_sum = 0;
984 for (j = 0; j < display_columns; j++) {
985 weight_sum += column_weight[j];
986
987 table_minimum_width += minimum_width[j];
988
989 if (maximum_width[j] == (size_t) -1)
990 table_maximum_width = (size_t) -1;
991 else
992 table_maximum_width += maximum_width[j];
993
994 table_requested_width += requested_width[j];
995 }
996
997 /* Calculate effective table width */
998 if (t->width == (size_t) -1)
999 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1000 else
1001 table_effective_width = t->width;
1002
1003 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1004 table_effective_width = table_maximum_width;
1005
1006 if (table_effective_width < table_minimum_width)
1007 table_effective_width = table_minimum_width;
1008
1009 if (table_effective_width >= table_requested_width) {
1010 size_t extra;
1011
1012 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1013 * each column with what it asked for and the distribute the rest. */
1014
1015 extra = table_effective_width - table_requested_width;
1016
1017 for (j = 0; j < display_columns; j++) {
1018 size_t delta;
1019
1020 if (weight_sum == 0)
1021 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1022 else
1023 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1024
1025 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1026 width[j] = maximum_width[j];
1027
1028 if (width[j] < minimum_width[j])
1029 width[j] = minimum_width[j];
1030
1031 assert(width[j] >= requested_width[j]);
1032 delta = width[j] - requested_width[j];
1033
1034 /* Subtract what we just added from the rest */
1035 if (extra > delta)
1036 extra -= delta;
1037 else
1038 extra = 0;
1039
1040 assert(weight_sum >= column_weight[j]);
1041 weight_sum -= column_weight[j];
1042 }
1043
1044 } else {
1045 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1046 * with the minimum they need, and then distribute anything left. */
1047 bool finalize = false;
1048 size_t extra;
1049
1050 extra = table_effective_width - table_minimum_width;
1051
1052 for (j = 0; j < display_columns; j++)
1053 width[j] = (size_t) -1;
1054
1055 for (;;) {
1056 bool restart = false;
1057
1058 for (j = 0; j < display_columns; j++) {
1059 size_t delta, w;
1060
1061 /* Did this column already get something assigned? If so, let's skip to the next */
1062 if (width[j] != (size_t) -1)
1063 continue;
1064
1065 if (weight_sum == 0)
1066 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1067 else
1068 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1069
1070 if (w >= requested_width[j]) {
1071 /* Never give more than requested. If we hit a column like this, there's more
1072 * space to allocate to other columns which means we need to restart the
1073 * iteration. However, if we hit a column like this, let's assign it the space
1074 * it wanted for good early.*/
1075
1076 w = requested_width[j];
1077 restart = true;
1078
1079 } else if (!finalize)
1080 continue;
1081
1082 width[j] = w;
1083
1084 assert(w >= minimum_width[j]);
1085 delta = w - minimum_width[j];
1086
1087 assert(delta <= extra);
1088 extra -= delta;
1089
1090 assert(weight_sum >= column_weight[j]);
1091 weight_sum -= column_weight[j];
1092
1093 if (restart && !finalize)
1094 break;
1095 }
1096
1097 if (finalize)
1098 break;
1099
1100 if (!restart)
1101 finalize = true;
1102 }
1103 }
1104
1105 /* Second pass: show output */
1106 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1107 TableData **row;
1108
1109 if (sorted)
1110 row = t->data + sorted[i];
1111 else
1112 row = t->data + i * t->n_columns;
1113
1114 for (j = 0; j < display_columns; j++) {
1115 _cleanup_free_ char *buffer = NULL;
1116 const char *field;
1117 TableData *d;
1118 size_t l;
1119
1120 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1121
1122 field = table_data_format(d);
1123 if (!field)
1124 return -ENOMEM;
1125
1126 l = utf8_console_width(field);
1127 if (l > width[j]) {
1128 /* Field is wider than allocated space. Let's ellipsize */
1129
1130 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1131 if (!buffer)
1132 return -ENOMEM;
1133
1134 field = buffer;
1135
1136 } else if (l < width[j]) {
1137 /* Field is shorter than allocated space. Let's align with spaces */
1138
1139 buffer = align_string_mem(field, width[j], d->align_percent);
1140 if (!buffer)
1141 return -ENOMEM;
1142
1143 field = buffer;
1144 }
1145
1146 if (j > 0)
1147 fputc(' ', f); /* column separator */
1148
1149 if (d->color)
1150 fputs(d->color, f);
1151
1152 fputs(field, f);
1153
1154 if (d->color)
1155 fputs(ansi_normal(), f);
1156 }
1157
1158 fputc('\n', f);
1159 }
1160
1161 return fflush_and_check(f);
1162 }
1163
1164 int table_format(Table *t, char **ret) {
1165 _cleanup_fclose_ FILE *f = NULL;
1166 char *buf = NULL;
1167 size_t sz = 0;
1168 int r;
1169
1170 f = open_memstream(&buf, &sz);
1171 if (!f)
1172 return -ENOMEM;
1173
1174 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
1175
1176 r = table_print(t, f);
1177 if (r < 0)
1178 return r;
1179
1180 f = safe_fclose(f);
1181
1182 *ret = buf;
1183
1184 return 0;
1185 }
1186
1187 size_t table_get_rows(Table *t) {
1188 if (!t)
1189 return 0;
1190
1191 assert(t->n_columns > 0);
1192 return t->n_cells / t->n_columns;
1193 }
1194
1195 size_t table_get_columns(Table *t) {
1196 if (!t)
1197 return 0;
1198
1199 assert(t->n_columns > 0);
1200 return t->n_columns;
1201 }