]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/format-table.c
Merge pull request #9950 from yuwata/macro-ref-unref
[thirdparty/systemd.git] / src / basic / 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 unsigned i;
77
78 assert(cell);
79
80 i = PTR_TO_UINT(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 UINT_TO_PTR((unsigned) (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 if (a->timestamp < b->timestamp)
692 return -1;
693 if (a->timestamp > b->timestamp)
694 return 1;
695 return 0;
696
697 case TABLE_TIMESPAN:
698 if (a->timespan < b->timespan)
699 return -1;
700 if (a->timespan > b->timespan)
701 return 1;
702 return 0;
703
704 case TABLE_SIZE:
705 if (a->size < b->size)
706 return -1;
707 if (a->size > b->size)
708 return 1;
709 return 0;
710
711 case TABLE_UINT32:
712 if (a->uint32 < b->uint32)
713 return -1;
714 if (a->uint32 > b->uint32)
715 return 1;
716 return 0;
717
718 default:
719 ;
720 }
721 }
722
723 /* Generic fallback using the orginal order in which the cells where added. */
724 if (index_a < index_b)
725 return -1;
726 if (index_a > index_b)
727 return 1;
728
729 return 0;
730 }
731
732 static int table_data_compare(const void *x, const void *y, void *userdata) {
733 const size_t *a = x, *b = y;
734 Table *t = userdata;
735 size_t i;
736 int r;
737
738 assert(t);
739 assert(t->sort_map);
740
741 /* Make sure the header stays at the beginning */
742 if (*a < t->n_columns && *b < t->n_columns)
743 return 0;
744 if (*a < t->n_columns)
745 return -1;
746 if (*b < t->n_columns)
747 return 1;
748
749 /* Order other lines by the sorting map */
750 for (i = 0; i < t->n_sort_map; i++) {
751 TableData *d, *dd;
752
753 d = t->data[*a + t->sort_map[i]];
754 dd = t->data[*b + t->sort_map[i]];
755
756 r = cell_data_compare(d, *a, dd, *b);
757 if (r != 0)
758 return r;
759 }
760
761 /* Order identical lines by the order there were originally added in */
762 if (*a < *b)
763 return -1;
764 if (*a > *b)
765 return 1;
766
767 return 0;
768 }
769
770 static const char *table_data_format(TableData *d) {
771 assert(d);
772
773 if (d->formatted)
774 return d->formatted;
775
776 switch (d->type) {
777 case TABLE_EMPTY:
778 return "";
779
780 case TABLE_STRING:
781 return d->string;
782
783 case TABLE_BOOLEAN:
784 return yes_no(d->boolean);
785
786 case TABLE_TIMESTAMP: {
787 _cleanup_free_ char *p;
788
789 p = new(char, FORMAT_TIMESTAMP_MAX);
790 if (!p)
791 return NULL;
792
793 if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
794 return "n/a";
795
796 d->formatted = TAKE_PTR(p);
797 break;
798 }
799
800 case TABLE_TIMESPAN: {
801 _cleanup_free_ char *p;
802
803 p = new(char, FORMAT_TIMESPAN_MAX);
804 if (!p)
805 return NULL;
806
807 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
808 return "n/a";
809
810 d->formatted = TAKE_PTR(p);
811 break;
812 }
813
814 case TABLE_SIZE: {
815 _cleanup_free_ char *p;
816
817 p = new(char, FORMAT_BYTES_MAX);
818 if (!p)
819 return NULL;
820
821 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
822 return "n/a";
823
824 d->formatted = TAKE_PTR(p);
825 break;
826 }
827
828 case TABLE_UINT32: {
829 _cleanup_free_ char *p;
830
831 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
832 if (!p)
833 return NULL;
834
835 sprintf(p, "%" PRIu32, d->uint32);
836 d->formatted = TAKE_PTR(p);
837 break;
838 }
839
840 default:
841 assert_not_reached("Unexpected type?");
842 }
843
844 return d->formatted;
845 }
846
847 static int table_data_requested_width(TableData *d, size_t *ret) {
848 const char *t;
849 size_t l;
850
851 t = table_data_format(d);
852 if (!t)
853 return -ENOMEM;
854
855 l = utf8_console_width(t);
856 if (l == (size_t) -1)
857 return -EINVAL;
858
859 if (d->maximum_width != (size_t) -1 && l > d->maximum_width)
860 l = d->maximum_width;
861
862 if (l < d->minimum_width)
863 l = d->minimum_width;
864
865 *ret = l;
866 return 0;
867 }
868
869 static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
870 size_t w = 0, space, lspace, old_length;
871 const char *p;
872 char *ret;
873 size_t i;
874
875 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
876
877 assert(str);
878 assert(percent <= 100);
879
880 old_length = strlen(str);
881
882 /* Determine current width on screen */
883 p = str;
884 while (p < str + old_length) {
885 char32_t c;
886
887 if (utf8_encoded_to_unichar(p, &c) < 0) {
888 p++, w++; /* count invalid chars as 1 */
889 continue;
890 }
891
892 p = utf8_next_char(p);
893 w += unichar_iswide(c) ? 2 : 1;
894 }
895
896 /* Already wider than the target, if so, don't do anything */
897 if (w >= new_length)
898 return strndup(str, old_length);
899
900 /* How much spaces shall we add? An how much on the left side? */
901 space = new_length - w;
902 lspace = space * percent / 100U;
903
904 ret = new(char, space + old_length + 1);
905 if (!ret)
906 return NULL;
907
908 for (i = 0; i < lspace; i++)
909 ret[i] = ' ';
910 memcpy(ret + lspace, str, old_length);
911 for (i = lspace + old_length; i < space + old_length; i++)
912 ret[i] = ' ';
913
914 ret[space + old_length] = 0;
915 return ret;
916 }
917
918 int table_print(Table *t, FILE *f) {
919 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
920 i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
921 *width;
922 _cleanup_free_ size_t *sorted = NULL;
923 uint64_t *column_weight, weight_sum;
924 int r;
925
926 assert(t);
927
928 if (!f)
929 f = stdout;
930
931 /* Ensure we have no incomplete rows */
932 assert(t->n_cells % t->n_columns == 0);
933
934 n_rows = t->n_cells / t->n_columns;
935 assert(n_rows > 0); /* at least the header row must be complete */
936
937 if (t->sort_map) {
938 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
939
940 sorted = new(size_t, n_rows);
941 if (!sorted)
942 return -ENOMEM;
943
944 for (i = 0; i < n_rows; i++)
945 sorted[i] = i * t->n_columns;
946
947 qsort_r_safe(sorted, n_rows, sizeof(size_t), table_data_compare, t);
948 }
949
950 if (t->display_map)
951 display_columns = t->n_display_map;
952 else
953 display_columns = t->n_columns;
954
955 assert(display_columns > 0);
956
957 minimum_width = newa(size_t, display_columns);
958 maximum_width = newa(size_t, display_columns);
959 requested_width = newa(size_t, display_columns);
960 width = newa(size_t, display_columns);
961 column_weight = newa0(uint64_t, display_columns);
962
963 for (j = 0; j < display_columns; j++) {
964 minimum_width[j] = 1;
965 maximum_width[j] = (size_t) -1;
966 requested_width[j] = (size_t) -1;
967 }
968
969 /* First pass: determine column sizes */
970 for (i = t->header ? 0 : 1; i < n_rows; i++) {
971 TableData **row;
972
973 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
974 * hence we don't care for sorted[] during the first pass. */
975 row = t->data + i * t->n_columns;
976
977 for (j = 0; j < display_columns; j++) {
978 TableData *d;
979 size_t req;
980
981 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
982
983 r = table_data_requested_width(d, &req);
984 if (r < 0)
985 return r;
986
987 /* Determine the biggest width that any cell in this column would like to have */
988 if (requested_width[j] == (size_t) -1 ||
989 requested_width[j] < req)
990 requested_width[j] = req;
991
992 /* Determine the minimum width any cell in this column needs */
993 if (minimum_width[j] < d->minimum_width)
994 minimum_width[j] = d->minimum_width;
995
996 /* Determine the maximum width any cell in this column needs */
997 if (d->maximum_width != (size_t) -1 &&
998 (maximum_width[j] == (size_t) -1 ||
999 maximum_width[j] > d->maximum_width))
1000 maximum_width[j] = d->maximum_width;
1001
1002 /* Determine the full columns weight */
1003 column_weight[j] += d->weight;
1004 }
1005 }
1006
1007 /* One space between each column */
1008 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
1009
1010 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
1011 weight_sum = 0;
1012 for (j = 0; j < display_columns; j++) {
1013 weight_sum += column_weight[j];
1014
1015 table_minimum_width += minimum_width[j];
1016
1017 if (maximum_width[j] == (size_t) -1)
1018 table_maximum_width = (size_t) -1;
1019 else
1020 table_maximum_width += maximum_width[j];
1021
1022 table_requested_width += requested_width[j];
1023 }
1024
1025 /* Calculate effective table width */
1026 if (t->width == (size_t) -1)
1027 table_effective_width = pager_have() ? table_requested_width : MIN(table_requested_width, columns());
1028 else
1029 table_effective_width = t->width;
1030
1031 if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
1032 table_effective_width = table_maximum_width;
1033
1034 if (table_effective_width < table_minimum_width)
1035 table_effective_width = table_minimum_width;
1036
1037 if (table_effective_width >= table_requested_width) {
1038 size_t extra;
1039
1040 /* We have extra room, let's distribute it among columns according to their weights. We first provide
1041 * each column with what it asked for and the distribute the rest. */
1042
1043 extra = table_effective_width - table_requested_width;
1044
1045 for (j = 0; j < display_columns; j++) {
1046 size_t delta;
1047
1048 if (weight_sum == 0)
1049 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
1050 else
1051 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
1052
1053 if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
1054 width[j] = maximum_width[j];
1055
1056 if (width[j] < minimum_width[j])
1057 width[j] = minimum_width[j];
1058
1059 assert(width[j] >= requested_width[j]);
1060 delta = width[j] - requested_width[j];
1061
1062 /* Subtract what we just added from the rest */
1063 if (extra > delta)
1064 extra -= delta;
1065 else
1066 extra = 0;
1067
1068 assert(weight_sum >= column_weight[j]);
1069 weight_sum -= column_weight[j];
1070 }
1071
1072 } else {
1073 /* We need to compress the table, columns can't get what they asked for. We first provide each column
1074 * with the minimum they need, and then distribute anything left. */
1075 bool finalize = false;
1076 size_t extra;
1077
1078 extra = table_effective_width - table_minimum_width;
1079
1080 for (j = 0; j < display_columns; j++)
1081 width[j] = (size_t) -1;
1082
1083 for (;;) {
1084 bool restart = false;
1085
1086 for (j = 0; j < display_columns; j++) {
1087 size_t delta, w;
1088
1089 /* Did this column already get something assigned? If so, let's skip to the next */
1090 if (width[j] != (size_t) -1)
1091 continue;
1092
1093 if (weight_sum == 0)
1094 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
1095 else
1096 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
1097
1098 if (w >= requested_width[j]) {
1099 /* Never give more than requested. If we hit a column like this, there's more
1100 * space to allocate to other columns which means we need to restart the
1101 * iteration. However, if we hit a column like this, let's assign it the space
1102 * it wanted for good early.*/
1103
1104 w = requested_width[j];
1105 restart = true;
1106
1107 } else if (!finalize)
1108 continue;
1109
1110 width[j] = w;
1111
1112 assert(w >= minimum_width[j]);
1113 delta = w - minimum_width[j];
1114
1115 assert(delta <= extra);
1116 extra -= delta;
1117
1118 assert(weight_sum >= column_weight[j]);
1119 weight_sum -= column_weight[j];
1120
1121 if (restart && !finalize)
1122 break;
1123 }
1124
1125 if (finalize)
1126 break;
1127
1128 if (!restart)
1129 finalize = true;
1130 }
1131 }
1132
1133 /* Second pass: show output */
1134 for (i = t->header ? 0 : 1; i < n_rows; i++) {
1135 TableData **row;
1136
1137 if (sorted)
1138 row = t->data + sorted[i];
1139 else
1140 row = t->data + i * t->n_columns;
1141
1142 for (j = 0; j < display_columns; j++) {
1143 _cleanup_free_ char *buffer = NULL;
1144 const char *field;
1145 TableData *d;
1146 size_t l;
1147
1148 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
1149
1150 field = table_data_format(d);
1151 if (!field)
1152 return -ENOMEM;
1153
1154 l = utf8_console_width(field);
1155 if (l > width[j]) {
1156 /* Field is wider than allocated space. Let's ellipsize */
1157
1158 buffer = ellipsize(field, width[j], d->ellipsize_percent);
1159 if (!buffer)
1160 return -ENOMEM;
1161
1162 field = buffer;
1163
1164 } else if (l < width[j]) {
1165 /* Field is shorter than allocated space. Let's align with spaces */
1166
1167 buffer = align_string_mem(field, width[j], d->align_percent);
1168 if (!buffer)
1169 return -ENOMEM;
1170
1171 field = buffer;
1172 }
1173
1174 if (j > 0)
1175 fputc(' ', f); /* column separator */
1176
1177 if (d->color)
1178 fputs(d->color, f);
1179
1180 fputs(field, f);
1181
1182 if (d->color)
1183 fputs(ansi_normal(), f);
1184 }
1185
1186 fputc('\n', f);
1187 }
1188
1189 return fflush_and_check(f);
1190 }
1191
1192 int table_format(Table *t, char **ret) {
1193 _cleanup_fclose_ FILE *f = NULL;
1194 char *buf = NULL;
1195 size_t sz = 0;
1196 int r;
1197
1198 f = open_memstream(&buf, &sz);
1199 if (!f)
1200 return -ENOMEM;
1201
1202 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
1203
1204 r = table_print(t, f);
1205 if (r < 0)
1206 return r;
1207
1208 f = safe_fclose(f);
1209
1210 *ret = buf;
1211
1212 return 0;
1213 }
1214
1215 size_t table_get_rows(Table *t) {
1216 if (!t)
1217 return 0;
1218
1219 assert(t->n_columns > 0);
1220 return t->n_cells / t->n_columns;
1221 }
1222
1223 size_t table_get_columns(Table *t) {
1224 if (!t)
1225 return 0;
1226
1227 assert(t->n_columns > 0);
1228 return t->n_columns;
1229 }