1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <ctype.h>
4 #include <net/if.h>
5 #include <unistd.h>
6 
7 #include "sd-id128.h"
8 
9 #include "alloc-util.h"
10 #include "fd-util.h"
11 #include "fileio.h"
12 #include "format-table.h"
13 #include "format-util.h"
14 #include "fs-util.h"
15 #include "glyph-util.h"
16 #include "gunicode.h"
17 #include "id128-util.h"
18 #include "in-addr-util.h"
19 #include "memory-util.h"
20 #include "pager.h"
21 #include "parse-util.h"
22 #include "path-util.h"
23 #include "pretty-print.h"
24 #include "process-util.h"
25 #include "signal-util.h"
26 #include "sort-util.h"
27 #include "string-util.h"
28 #include "strxcpyx.h"
29 #include "terminal-util.h"
30 #include "time-util.h"
31 #include "user-util.h"
32 #include "utf8.h"
33 #include "util.h"
34 
35 #define DEFAULT_WEIGHT 100
36 
37 /*
38    A few notes on implementation details:
39 
40  - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
41    table. It can be easily converted to an index number and back.
42 
43  - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
44    'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
45    ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
46    outside only sees Table and TableCell.
47 
48  - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
49    previous one.
50 
51  - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
52    derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
53    that. The first row is always the header row. If header display is turned off we simply skip outputting the first
54    row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
55 
56  - Note because there's no row and no column object some properties that might be appropriate as row/column properties
57    are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
58    add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
59    cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
60    instead.
61 
62  - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
63    from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
64    this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
65 */
66 
67 typedef struct TableData {
68         unsigned n_ref;
69         TableDataType type;
70 
71         size_t minimum_width;       /* minimum width for the column */
72         size_t maximum_width;       /* maximum width for the column */
73         size_t formatted_for_width; /* the width we tried to format for */
74         unsigned weight;            /* the horizontal weight for this column, in case the table is expanded/compressed */
75         unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
76         unsigned align_percent;     /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
77 
78         bool uppercase;             /* Uppercase string on display */
79 
80         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 */
81         const char *rgap_color;     /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
82         char *url;                  /* A URL to use for a clickable hyperlink */
83         char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
84 
85         union {
86                 uint8_t data[0];    /* data is generic array */
87                 bool boolean;
88                 usec_t timestamp;
89                 usec_t timespan;
90                 uint64_t size;
91                 char string[0];
92                 char **strv;
93                 int int_val;
94                 int8_t int8;
95                 int16_t int16;
96                 int32_t int32;
97                 int64_t int64;
98                 unsigned uint_val;
99                 uint8_t uint8;
100                 uint16_t uint16;
101                 uint32_t uint32;
102                 uint64_t uint64;
103                 int percent;        /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
104                 int ifindex;
105                 union in_addr_union address;
106                 sd_id128_t id128;
107                 uid_t uid;
108                 gid_t gid;
109                 pid_t pid;
110                 mode_t mode;
111                 /* … add more here as we start supporting more cell data types … */
112         };
113 } TableData;
114 
TABLE_CELL_TO_INDEX(TableCell * cell)115 static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
116         size_t i;
117 
118         assert(cell);
119 
120         i = PTR_TO_SIZE(cell);
121         assert(i > 0);
122 
123         return i-1;
124 }
125 
TABLE_INDEX_TO_CELL(size_t index)126 static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
127         assert(index != SIZE_MAX);
128         return SIZE_TO_PTR(index + 1);
129 }
130 
131 struct Table {
132         size_t n_columns;
133         size_t n_cells;
134 
135         bool header;   /* Whether to show the header row? */
136         size_t width;  /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
137                         * width or less wide, but not wider. Otherwise the width to format this table in. */
138         size_t cell_height_max; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If SIZE_MAX then no limit is set, the default. == 0 is not allowed.) */
139 
140         TableData **data;
141 
142         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 */
143         size_t n_display_map;
144 
145         size_t *sort_map;     /* The columns to order rows by, in order of preference. */
146         size_t n_sort_map;
147 
148         char **json_fields;
149         size_t n_json_fields;
150 
151         bool *reverse_map;
152 
153         char *empty_string;
154 };
155 
table_new_raw(size_t n_columns)156 Table *table_new_raw(size_t n_columns) {
157         _cleanup_(table_unrefp) Table *t = NULL;
158 
159         assert(n_columns > 0);
160 
161         t = new(Table, 1);
162         if (!t)
163                 return NULL;
164 
165         *t = (struct Table) {
166                 .n_columns = n_columns,
167                 .header = true,
168                 .width = SIZE_MAX,
169                 .cell_height_max = SIZE_MAX,
170         };
171 
172         return TAKE_PTR(t);
173 }
174 
table_new_internal(const char * first_header,...)175 Table *table_new_internal(const char *first_header, ...) {
176         _cleanup_(table_unrefp) Table *t = NULL;
177         size_t n_columns = 1;
178         va_list ap;
179         int r;
180 
181         assert(first_header);
182 
183         va_start(ap, first_header);
184         for (;;) {
185                 if (!va_arg(ap, const char*))
186                         break;
187 
188                 n_columns++;
189         }
190         va_end(ap);
191 
192         t = table_new_raw(n_columns);
193         if (!t)
194                 return NULL;
195 
196         va_start(ap, first_header);
197         for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
198                 TableCell *cell;
199 
200                 r = table_add_cell(t, &cell, TABLE_STRING, h);
201                 if (r < 0) {
202                         va_end(ap);
203                         return NULL;
204                 }
205 
206                 /* Make the table header uppercase */
207                 r = table_set_uppercase(t, cell, true);
208                 if (r < 0) {
209                         va_end(ap);
210                         return NULL;
211                 }
212         }
213         va_end(ap);
214 
215         assert(t->n_columns == t->n_cells);
216         return TAKE_PTR(t);
217 }
218 
table_data_free(TableData * d)219 static TableData *table_data_free(TableData *d) {
220         assert(d);
221 
222         free(d->formatted);
223         free(d->url);
224 
225         if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
226                 strv_free(d->strv);
227 
228         return mfree(d);
229 }
230 
231 DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
232 DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
233 
table_unref(Table * t)234 Table *table_unref(Table *t) {
235         if (!t)
236                 return NULL;
237 
238         for (size_t i = 0; i < t->n_cells; i++)
239                 table_data_unref(t->data[i]);
240 
241         free(t->data);
242         free(t->display_map);
243         free(t->sort_map);
244         free(t->reverse_map);
245         free(t->empty_string);
246 
247         for (size_t i = 0; i < t->n_json_fields; i++)
248                 free(t->json_fields[i]);
249 
250         free(t->json_fields);
251 
252         return mfree(t);
253 }
254 
table_data_size(TableDataType type,const void * data)255 static size_t table_data_size(TableDataType type, const void *data) {
256 
257         switch (type) {
258 
259         case TABLE_EMPTY:
260                 return 0;
261 
262         case TABLE_STRING:
263         case TABLE_PATH:
264                 return strlen(data) + 1;
265 
266         case TABLE_STRV:
267         case TABLE_STRV_WRAPPED:
268                 return sizeof(char **);
269 
270         case TABLE_BOOLEAN_CHECKMARK:
271         case TABLE_BOOLEAN:
272                 return sizeof(bool);
273 
274         case TABLE_TIMESTAMP:
275         case TABLE_TIMESTAMP_UTC:
276         case TABLE_TIMESTAMP_RELATIVE:
277         case TABLE_TIMESPAN:
278         case TABLE_TIMESPAN_MSEC:
279                 return sizeof(usec_t);
280 
281         case TABLE_SIZE:
282         case TABLE_INT64:
283         case TABLE_UINT64:
284         case TABLE_UINT64_HEX:
285         case TABLE_BPS:
286                 return sizeof(uint64_t);
287 
288         case TABLE_INT32:
289         case TABLE_UINT32:
290                 return sizeof(uint32_t);
291 
292         case TABLE_INT16:
293         case TABLE_UINT16:
294                 return sizeof(uint16_t);
295 
296         case TABLE_INT8:
297         case TABLE_UINT8:
298                 return sizeof(uint8_t);
299 
300         case TABLE_INT:
301         case TABLE_UINT:
302         case TABLE_PERCENT:
303         case TABLE_IFINDEX:
304         case TABLE_SIGNAL:
305                 return sizeof(int);
306 
307         case TABLE_IN_ADDR:
308                 return sizeof(struct in_addr);
309 
310         case TABLE_IN6_ADDR:
311                 return sizeof(struct in6_addr);
312 
313         case TABLE_UUID:
314         case TABLE_ID128:
315                 return sizeof(sd_id128_t);
316 
317         case TABLE_UID:
318                 return sizeof(uid_t);
319         case TABLE_GID:
320                 return sizeof(gid_t);
321         case TABLE_PID:
322                 return sizeof(pid_t);
323 
324         case TABLE_MODE:
325                 return sizeof(mode_t);
326 
327         default:
328                 assert_not_reached();
329         }
330 }
331 
table_data_matches(TableData * d,TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)332 static bool table_data_matches(
333                 TableData *d,
334                 TableDataType type,
335                 const void *data,
336                 size_t minimum_width,
337                 size_t maximum_width,
338                 unsigned weight,
339                 unsigned align_percent,
340                 unsigned ellipsize_percent) {
341 
342         size_t k, l;
343         assert(d);
344 
345         if (d->type != type)
346                 return false;
347 
348         if (d->minimum_width != minimum_width)
349                 return false;
350 
351         if (d->maximum_width != maximum_width)
352                 return false;
353 
354         if (d->weight != weight)
355                 return false;
356 
357         if (d->align_percent != align_percent)
358                 return false;
359 
360         if (d->ellipsize_percent != ellipsize_percent)
361                 return false;
362 
363         /* If a color/url/uppercase flag is set, refuse to merge */
364         if (d->color || d->rgap_color)
365                 return false;
366         if (d->url)
367                 return false;
368         if (d->uppercase)
369                 return false;
370 
371         k = table_data_size(type, data);
372         l = table_data_size(d->type, d->data);
373         if (k != l)
374                 return false;
375 
376         return memcmp_safe(data, d->data, l) == 0;
377 }
378 
table_data_new(TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)379 static TableData *table_data_new(
380                 TableDataType type,
381                 const void *data,
382                 size_t minimum_width,
383                 size_t maximum_width,
384                 unsigned weight,
385                 unsigned align_percent,
386                 unsigned ellipsize_percent) {
387 
388         _cleanup_free_ TableData *d = NULL;
389         size_t data_size;
390 
391         data_size = table_data_size(type, data);
392 
393         d = malloc0(offsetof(TableData, data) + data_size);
394         if (!d)
395                 return NULL;
396 
397         d->n_ref = 1;
398         d->type = type;
399         d->minimum_width = minimum_width;
400         d->maximum_width = maximum_width;
401         d->weight = weight;
402         d->align_percent = align_percent;
403         d->ellipsize_percent = ellipsize_percent;
404 
405         if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
406                 d->strv = strv_copy(data);
407                 if (!d->strv)
408                         return NULL;
409         } else
410                 memcpy_safe(d->data, data, data_size);
411 
412         return TAKE_PTR(d);
413 }
414 
table_add_cell_full(Table * t,TableCell ** ret_cell,TableDataType type,const void * data,size_t minimum_width,size_t maximum_width,unsigned weight,unsigned align_percent,unsigned ellipsize_percent)415 int table_add_cell_full(
416                 Table *t,
417                 TableCell **ret_cell,
418                 TableDataType type,
419                 const void *data,
420                 size_t minimum_width,
421                 size_t maximum_width,
422                 unsigned weight,
423                 unsigned align_percent,
424                 unsigned ellipsize_percent) {
425 
426         _cleanup_(table_data_unrefp) TableData *d = NULL;
427         TableData *p;
428 
429         assert(t);
430         assert(type >= 0);
431         assert(type < _TABLE_DATA_TYPE_MAX);
432 
433         /* Special rule: patch NULL data fields to the empty field */
434         if (!data)
435                 type = TABLE_EMPTY;
436 
437         /* Determine the cell adjacent to the current one, but one row up */
438         if (t->n_cells >= t->n_columns)
439                 assert_se(p = t->data[t->n_cells - t->n_columns]);
440         else
441                 p = NULL;
442 
443         /* If formatting parameters are left unspecified, copy from the previous row */
444         if (minimum_width == SIZE_MAX)
445                 minimum_width = p ? p->minimum_width : 1;
446 
447         if (weight == UINT_MAX)
448                 weight = p ? p->weight : DEFAULT_WEIGHT;
449 
450         if (align_percent == UINT_MAX)
451                 align_percent = p ? p->align_percent : 0;
452 
453         if (ellipsize_percent == UINT_MAX)
454                 ellipsize_percent = p ? p->ellipsize_percent : 100;
455 
456         assert(align_percent <= 100);
457         assert(ellipsize_percent <= 100);
458 
459         /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
460          * formatting. Let's see if we can reuse the cell data and ref it once more. */
461 
462         if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent))
463                 d = table_data_ref(p);
464         else {
465                 d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent);
466                 if (!d)
467                         return -ENOMEM;
468         }
469 
470         if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
471                 return -ENOMEM;
472 
473         if (ret_cell)
474                 *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
475 
476         t->data[t->n_cells++] = TAKE_PTR(d);
477 
478         return 0;
479 }
480 
table_add_cell_stringf(Table * t,TableCell ** ret_cell,const char * format,...)481 int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
482         _cleanup_free_ char *buffer = NULL;
483         va_list ap;
484         int r;
485 
486         va_start(ap, format);
487         r = vasprintf(&buffer, format, ap);
488         va_end(ap);
489         if (r < 0)
490                 return -ENOMEM;
491 
492         return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
493 }
494 
table_fill_empty(Table * t,size_t until_column)495 int table_fill_empty(Table *t, size_t until_column) {
496         int r;
497 
498         assert(t);
499 
500         /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
501          * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
502 
503         if (until_column >= t->n_columns)
504                 return -EINVAL;
505 
506         do {
507                 r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
508                 if (r < 0)
509                         return r;
510 
511         } while ((t->n_cells % t->n_columns) != until_column);
512 
513         return 0;
514 }
515 
table_dup_cell(Table * t,TableCell * cell)516 int table_dup_cell(Table *t, TableCell *cell) {
517         size_t i;
518 
519         assert(t);
520 
521         /* Add the data of the specified cell a second time as a new cell to the end. */
522 
523         i = TABLE_CELL_TO_INDEX(cell);
524         if (i >= t->n_cells)
525                 return -ENXIO;
526 
527         if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
528                 return -ENOMEM;
529 
530         t->data[t->n_cells++] = table_data_ref(t->data[i]);
531         return 0;
532 }
533 
table_dedup_cell(Table * t,TableCell * cell)534 static int table_dedup_cell(Table *t, TableCell *cell) {
535         _cleanup_free_ char *curl = NULL;
536         TableData *nd, *od;
537         size_t i;
538 
539         assert(t);
540 
541         /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
542          * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
543 
544         i = TABLE_CELL_TO_INDEX(cell);
545         if (i >= t->n_cells)
546                 return -ENXIO;
547 
548         assert_se(od = t->data[i]);
549         if (od->n_ref == 1)
550                 return 0;
551 
552         assert(od->n_ref > 1);
553 
554         if (od->url) {
555                 curl = strdup(od->url);
556                 if (!curl)
557                         return -ENOMEM;
558         }
559 
560         nd = table_data_new(
561                         od->type,
562                         od->data,
563                         od->minimum_width,
564                         od->maximum_width,
565                         od->weight,
566                         od->align_percent,
567                         od->ellipsize_percent);
568         if (!nd)
569                 return -ENOMEM;
570 
571         nd->color = od->color;
572         nd->rgap_color = od->rgap_color;
573         nd->url = TAKE_PTR(curl);
574         nd->uppercase = od->uppercase;
575 
576         table_data_unref(od);
577         t->data[i] = nd;
578 
579         assert(nd->n_ref == 1);
580 
581         return 1;
582 }
583 
table_get_data(Table * t,TableCell * cell)584 static TableData *table_get_data(Table *t, TableCell *cell) {
585         size_t i;
586 
587         assert(t);
588         assert(cell);
589 
590         /* Get the data object of the specified cell, or NULL if it doesn't exist */
591 
592         i = TABLE_CELL_TO_INDEX(cell);
593         if (i >= t->n_cells)
594                 return NULL;
595 
596         assert(t->data[i]);
597         assert(t->data[i]->n_ref > 0);
598 
599         return t->data[i];
600 }
601 
table_set_minimum_width(Table * t,TableCell * cell,size_t minimum_width)602 int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
603         int r;
604 
605         assert(t);
606         assert(cell);
607 
608         if (minimum_width == SIZE_MAX)
609                 minimum_width = 1;
610 
611         r = table_dedup_cell(t, cell);
612         if (r < 0)
613                 return r;
614 
615         table_get_data(t, cell)->minimum_width = minimum_width;
616         return 0;
617 }
618 
table_set_maximum_width(Table * t,TableCell * cell,size_t maximum_width)619 int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
620         int r;
621 
622         assert(t);
623         assert(cell);
624 
625         r = table_dedup_cell(t, cell);
626         if (r < 0)
627                 return r;
628 
629         table_get_data(t, cell)->maximum_width = maximum_width;
630         return 0;
631 }
632 
table_set_weight(Table * t,TableCell * cell,unsigned weight)633 int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
634         int r;
635 
636         assert(t);
637         assert(cell);
638 
639         if (weight == UINT_MAX)
640                 weight = DEFAULT_WEIGHT;
641 
642         r = table_dedup_cell(t, cell);
643         if (r < 0)
644                 return r;
645 
646         table_get_data(t, cell)->weight = weight;
647         return 0;
648 }
649 
table_set_align_percent(Table * t,TableCell * cell,unsigned percent)650 int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
651         int r;
652 
653         assert(t);
654         assert(cell);
655 
656         if (percent == UINT_MAX)
657                 percent = 0;
658 
659         assert(percent <= 100);
660 
661         r = table_dedup_cell(t, cell);
662         if (r < 0)
663                 return r;
664 
665         table_get_data(t, cell)->align_percent = percent;
666         return 0;
667 }
668 
table_set_ellipsize_percent(Table * t,TableCell * cell,unsigned percent)669 int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
670         int r;
671 
672         assert(t);
673         assert(cell);
674 
675         if (percent == UINT_MAX)
676                 percent = 100;
677 
678         assert(percent <= 100);
679 
680         r = table_dedup_cell(t, cell);
681         if (r < 0)
682                 return r;
683 
684         table_get_data(t, cell)->ellipsize_percent = percent;
685         return 0;
686 }
687 
table_set_color(Table * t,TableCell * cell,const char * color)688 int table_set_color(Table *t, TableCell *cell, const char *color) {
689         int r;
690 
691         assert(t);
692         assert(cell);
693 
694         r = table_dedup_cell(t, cell);
695         if (r < 0)
696                 return r;
697 
698         table_get_data(t, cell)->color = empty_to_null(color);
699         return 0;
700 }
701 
table_set_rgap_color(Table * t,TableCell * cell,const char * color)702 int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
703         int r;
704 
705         assert(t);
706         assert(cell);
707 
708         r = table_dedup_cell(t, cell);
709         if (r < 0)
710                 return r;
711 
712         table_get_data(t, cell)->rgap_color = empty_to_null(color);
713         return 0;
714 }
715 
table_set_url(Table * t,TableCell * cell,const char * url)716 int table_set_url(Table *t, TableCell *cell, const char *url) {
717         _cleanup_free_ char *copy = NULL;
718         int r;
719 
720         assert(t);
721         assert(cell);
722 
723         if (url) {
724                 copy = strdup(url);
725                 if (!copy)
726                         return -ENOMEM;
727         }
728 
729         r = table_dedup_cell(t, cell);
730         if (r < 0)
731                 return r;
732 
733         return free_and_replace(table_get_data(t, cell)->url, copy);
734 }
735 
table_set_uppercase(Table * t,TableCell * cell,bool b)736 int table_set_uppercase(Table *t, TableCell *cell, bool b) {
737         TableData *d;
738         int r;
739 
740         assert(t);
741         assert(cell);
742 
743         r = table_dedup_cell(t, cell);
744         if (r < 0)
745                 return r;
746 
747         assert_se(d = table_get_data(t, cell));
748 
749         if (d->uppercase == b)
750                 return 0;
751 
752         d->formatted = mfree(d->formatted);
753         d->uppercase = b;
754         return 1;
755 }
756 
table_update(Table * t,TableCell * cell,TableDataType type,const void * data)757 int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
758         _cleanup_free_ char *curl = NULL;
759         TableData *nd, *od;
760         size_t i;
761 
762         assert(t);
763         assert(cell);
764 
765         i = TABLE_CELL_TO_INDEX(cell);
766         if (i >= t->n_cells)
767                 return -ENXIO;
768 
769         assert_se(od = t->data[i]);
770 
771         if (od->url) {
772                 curl = strdup(od->url);
773                 if (!curl)
774                         return -ENOMEM;
775         }
776 
777         nd = table_data_new(
778                         type,
779                         data,
780                         od->minimum_width,
781                         od->maximum_width,
782                         od->weight,
783                         od->align_percent,
784                         od->ellipsize_percent);
785         if (!nd)
786                 return -ENOMEM;
787 
788         nd->color = od->color;
789         nd->rgap_color = od->rgap_color;
790         nd->url = TAKE_PTR(curl);
791         nd->uppercase = od->uppercase;
792 
793         table_data_unref(od);
794         t->data[i] = nd;
795 
796         return 0;
797 }
798 
table_add_many_internal(Table * t,TableDataType first_type,...)799 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
800         TableCell *last_cell = NULL;
801         va_list ap;
802         int r;
803 
804         assert(t);
805         assert(first_type >= 0);
806         assert(first_type < _TABLE_DATA_TYPE_MAX);
807 
808         va_start(ap, first_type);
809 
810         for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
811                 const void *data;
812                 union {
813                         uint64_t size;
814                         usec_t usec;
815                         int int_val;
816                         int8_t int8;
817                         int16_t int16;
818                         int32_t int32;
819                         int64_t int64;
820                         unsigned uint_val;
821                         uint8_t uint8;
822                         uint16_t uint16;
823                         uint32_t uint32;
824                         uint64_t uint64;
825                         int percent;
826                         int ifindex;
827                         bool b;
828                         union in_addr_union address;
829                         sd_id128_t id128;
830                         uid_t uid;
831                         gid_t gid;
832                         pid_t pid;
833                         mode_t mode;
834                 } buffer;
835 
836                 switch (type) {
837 
838                 case TABLE_EMPTY:
839                         data = NULL;
840                         break;
841 
842                 case TABLE_STRING:
843                 case TABLE_PATH:
844                         data = va_arg(ap, const char *);
845                         break;
846 
847                 case TABLE_STRV:
848                 case TABLE_STRV_WRAPPED:
849                         data = va_arg(ap, char * const *);
850                         break;
851 
852                 case TABLE_BOOLEAN_CHECKMARK:
853                 case TABLE_BOOLEAN:
854                         buffer.b = va_arg(ap, int);
855                         data = &buffer.b;
856                         break;
857 
858                 case TABLE_TIMESTAMP:
859                 case TABLE_TIMESTAMP_UTC:
860                 case TABLE_TIMESTAMP_RELATIVE:
861                 case TABLE_TIMESPAN:
862                 case TABLE_TIMESPAN_MSEC:
863                         buffer.usec = va_arg(ap, usec_t);
864                         data = &buffer.usec;
865                         break;
866 
867                 case TABLE_SIZE:
868                 case TABLE_BPS:
869                         buffer.size = va_arg(ap, uint64_t);
870                         data = &buffer.size;
871                         break;
872 
873                 case TABLE_INT:
874                 case TABLE_SIGNAL:
875                         buffer.int_val = va_arg(ap, int);
876                         data = &buffer.int_val;
877                         break;
878 
879                 case TABLE_INT8: {
880                         int x = va_arg(ap, int);
881                         assert(x >= INT8_MIN && x <= INT8_MAX);
882 
883                         buffer.int8 = x;
884                         data = &buffer.int8;
885                         break;
886                 }
887 
888                 case TABLE_INT16: {
889                         int x = va_arg(ap, int);
890                         assert(x >= INT16_MIN && x <= INT16_MAX);
891 
892                         buffer.int16 = x;
893                         data = &buffer.int16;
894                         break;
895                 }
896 
897                 case TABLE_INT32:
898                         buffer.int32 = va_arg(ap, int32_t);
899                         data = &buffer.int32;
900                         break;
901 
902                 case TABLE_INT64:
903                         buffer.int64 = va_arg(ap, int64_t);
904                         data = &buffer.int64;
905                         break;
906 
907                 case TABLE_UINT:
908                         buffer.uint_val = va_arg(ap, unsigned);
909                         data = &buffer.uint_val;
910                         break;
911 
912                 case TABLE_UINT8: {
913                         unsigned x = va_arg(ap, unsigned);
914                         assert(x <= UINT8_MAX);
915 
916                         buffer.uint8 = x;
917                         data = &buffer.uint8;
918                         break;
919                 }
920 
921                 case TABLE_UINT16: {
922                         unsigned x = va_arg(ap, unsigned);
923                         assert(x <= UINT16_MAX);
924 
925                         buffer.uint16 = x;
926                         data = &buffer.uint16;
927                         break;
928                 }
929 
930                 case TABLE_UINT32:
931                         buffer.uint32 = va_arg(ap, uint32_t);
932                         data = &buffer.uint32;
933                         break;
934 
935                 case TABLE_UINT64:
936                 case TABLE_UINT64_HEX:
937                         buffer.uint64 = va_arg(ap, uint64_t);
938                         data = &buffer.uint64;
939                         break;
940 
941                 case TABLE_PERCENT:
942                         buffer.percent = va_arg(ap, int);
943                         data = &buffer.percent;
944                         break;
945 
946                 case TABLE_IFINDEX:
947                         buffer.ifindex = va_arg(ap, int);
948                         data = &buffer.ifindex;
949                         break;
950 
951                 case TABLE_IN_ADDR:
952                         buffer.address = *va_arg(ap, union in_addr_union *);
953                         data = &buffer.address.in;
954                         break;
955 
956                 case TABLE_IN6_ADDR:
957                         buffer.address = *va_arg(ap, union in_addr_union *);
958                         data = &buffer.address.in6;
959                         break;
960 
961                 case TABLE_UUID:
962                 case TABLE_ID128:
963                         buffer.id128 = va_arg(ap, sd_id128_t);
964                         data = &buffer.id128;
965                         break;
966 
967                 case TABLE_UID:
968                         buffer.uid = va_arg(ap, uid_t);
969                         data = &buffer.uid;
970                         break;
971 
972                 case TABLE_GID:
973                         buffer.gid = va_arg(ap, gid_t);
974                         data = &buffer.gid;
975                         break;
976 
977                 case TABLE_PID:
978                         buffer.pid = va_arg(ap, pid_t);
979                         data = &buffer.pid;
980                         break;
981 
982                 case TABLE_MODE:
983                         buffer.mode = va_arg(ap, mode_t);
984                         data = &buffer.mode;
985                         break;
986 
987                 case TABLE_SET_MINIMUM_WIDTH: {
988                         size_t w = va_arg(ap, size_t);
989 
990                         r = table_set_minimum_width(t, last_cell, w);
991                         goto check;
992                 }
993 
994                 case TABLE_SET_MAXIMUM_WIDTH: {
995                         size_t w = va_arg(ap, size_t);
996                         r = table_set_maximum_width(t, last_cell, w);
997                         goto check;
998                 }
999 
1000                 case TABLE_SET_WEIGHT: {
1001                         unsigned w = va_arg(ap, unsigned);
1002                         r = table_set_weight(t, last_cell, w);
1003                         goto check;
1004                 }
1005 
1006                 case TABLE_SET_ALIGN_PERCENT: {
1007                         unsigned p = va_arg(ap, unsigned);
1008                         r = table_set_align_percent(t, last_cell, p);
1009                         goto check;
1010                 }
1011 
1012                 case TABLE_SET_ELLIPSIZE_PERCENT: {
1013                         unsigned p = va_arg(ap, unsigned);
1014                         r = table_set_ellipsize_percent(t, last_cell, p);
1015                         goto check;
1016                 }
1017 
1018                 case TABLE_SET_COLOR: {
1019                         const char *c = va_arg(ap, const char*);
1020                         r = table_set_color(t, last_cell, c);
1021                         goto check;
1022                 }
1023 
1024                 case TABLE_SET_RGAP_COLOR: {
1025                         const char *c = va_arg(ap, const char*);
1026                         r = table_set_rgap_color(t, last_cell, c);
1027                         goto check;
1028                 }
1029 
1030                 case TABLE_SET_BOTH_COLORS: {
1031                         const char *c = va_arg(ap, const char*);
1032 
1033                         r = table_set_color(t, last_cell, c);
1034                         if (r < 0) {
1035                                 va_end(ap);
1036                                 return r;
1037                         }
1038 
1039                         r = table_set_rgap_color(t, last_cell, c);
1040                         goto check;
1041                 }
1042 
1043                 case TABLE_SET_URL: {
1044                         const char *u = va_arg(ap, const char*);
1045                         r = table_set_url(t, last_cell, u);
1046                         goto check;
1047                 }
1048 
1049                 case TABLE_SET_UPPERCASE: {
1050                         int u = va_arg(ap, int);
1051                         r = table_set_uppercase(t, last_cell, u);
1052                         goto check;
1053                 }
1054 
1055                 case _TABLE_DATA_TYPE_MAX:
1056                         /* Used as end marker */
1057                         va_end(ap);
1058                         return 0;
1059 
1060                 default:
1061                         assert_not_reached();
1062                 }
1063 
1064                 r = table_add_cell(t, &last_cell, type, data);
1065         check:
1066                 if (r < 0) {
1067                         va_end(ap);
1068                         return r;
1069                 }
1070         }
1071 }
1072 
table_set_header(Table * t,bool b)1073 void table_set_header(Table *t, bool b) {
1074         assert(t);
1075 
1076         t->header = b;
1077 }
1078 
table_set_width(Table * t,size_t width)1079 void table_set_width(Table *t, size_t width) {
1080         assert(t);
1081 
1082         t->width = width;
1083 }
1084 
table_set_cell_height_max(Table * t,size_t height)1085 void table_set_cell_height_max(Table *t, size_t height) {
1086         assert(t);
1087         assert(height >= 1 || height == SIZE_MAX);
1088 
1089         t->cell_height_max = height;
1090 }
1091 
table_set_empty_string(Table * t,const char * empty)1092 int table_set_empty_string(Table *t, const char *empty) {
1093         assert(t);
1094 
1095         return free_and_strdup(&t->empty_string, empty);
1096 }
1097 
table_set_display_all(Table * t)1098 static int table_set_display_all(Table *t) {
1099         size_t *d;
1100 
1101         assert(t);
1102 
1103         /* Initialize the display map to the identity */
1104 
1105         d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
1106         if (!d)
1107                 return -ENOMEM;
1108 
1109         for (size_t i = 0; i < t->n_columns; i++)
1110                 d[i] = i;
1111 
1112         t->display_map = d;
1113         t->n_display_map = t->n_columns;
1114 
1115         return 0;
1116 }
1117 
table_set_display_internal(Table * t,size_t first_column,...)1118 int table_set_display_internal(Table *t, size_t first_column, ...) {
1119         size_t column;
1120         va_list ap;
1121 
1122         assert(t);
1123 
1124         column = first_column;
1125 
1126         va_start(ap, first_column);
1127         for (;;) {
1128                 assert(column < t->n_columns);
1129 
1130                 if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
1131                         va_end(ap);
1132                         return -ENOMEM;
1133                 }
1134 
1135                 t->display_map[t->n_display_map++] = column;
1136 
1137                 column = va_arg(ap, size_t);
1138                 if (column == SIZE_MAX)
1139                         break;
1140 
1141         }
1142         va_end(ap);
1143 
1144         return 0;
1145 }
1146 
table_set_sort_internal(Table * t,size_t first_column,...)1147 int table_set_sort_internal(Table *t, size_t first_column, ...) {
1148         size_t column;
1149         va_list ap;
1150 
1151         assert(t);
1152 
1153         column = first_column;
1154 
1155         va_start(ap, first_column);
1156         for (;;) {
1157                 assert(column < t->n_columns);
1158 
1159                 if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
1160                         va_end(ap);
1161                         return -ENOMEM;
1162                 }
1163 
1164                 t->sort_map[t->n_sort_map++] = column;
1165 
1166                 column = va_arg(ap, size_t);
1167                 if (column == SIZE_MAX)
1168                         break;
1169         }
1170         va_end(ap);
1171 
1172         return 0;
1173 }
1174 
table_hide_column_from_display_internal(Table * t,...)1175 int table_hide_column_from_display_internal(Table *t, ...) {
1176         size_t cur = 0;
1177         int r;
1178 
1179         assert(t);
1180 
1181         /* If the display map is empty, initialize it with all available columns */
1182         if (!t->display_map) {
1183                 r = table_set_display_all(t);
1184                 if (r < 0)
1185                         return r;
1186         }
1187 
1188         for (size_t i = 0; i < t->n_display_map; i++) {
1189                 bool listed = false;
1190                 va_list ap;
1191 
1192                 va_start(ap, t);
1193                 for (;;) {
1194                         size_t column;
1195 
1196                         column = va_arg(ap, size_t);
1197                         if (column == SIZE_MAX)
1198                                 break;
1199                         if (column == t->display_map[i]) {
1200                                 listed = true;
1201                                 break;
1202                         }
1203                 }
1204                 va_end(ap);
1205 
1206                 if (listed)
1207                         continue;
1208 
1209                 t->display_map[cur++] = t->display_map[i];
1210         }
1211 
1212         t->n_display_map = cur;
1213 
1214         return 0;
1215 }
1216 
cell_data_compare(TableData * a,size_t index_a,TableData * b,size_t index_b)1217 static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1218         assert(a);
1219         assert(b);
1220 
1221         if (a->type == b->type) {
1222 
1223                 /* We only define ordering for cells of the same data type. If cells with different data types are
1224                  * compared we follow the order the cells were originally added in */
1225 
1226                 switch (a->type) {
1227 
1228                 case TABLE_STRING:
1229                         return strcmp(a->string, b->string);
1230 
1231                 case TABLE_PATH:
1232                         return path_compare(a->string, b->string);
1233 
1234                 case TABLE_STRV:
1235                 case TABLE_STRV_WRAPPED:
1236                         return strv_compare(a->strv, b->strv);
1237 
1238                 case TABLE_BOOLEAN:
1239                         if (!a->boolean && b->boolean)
1240                                 return -1;
1241                         if (a->boolean && !b->boolean)
1242                                 return 1;
1243                         return 0;
1244 
1245                 case TABLE_TIMESTAMP:
1246                 case TABLE_TIMESTAMP_UTC:
1247                 case TABLE_TIMESTAMP_RELATIVE:
1248                         return CMP(a->timestamp, b->timestamp);
1249 
1250                 case TABLE_TIMESPAN:
1251                 case TABLE_TIMESPAN_MSEC:
1252                         return CMP(a->timespan, b->timespan);
1253 
1254                 case TABLE_SIZE:
1255                 case TABLE_BPS:
1256                         return CMP(a->size, b->size);
1257 
1258                 case TABLE_INT:
1259                 case TABLE_SIGNAL:
1260                         return CMP(a->int_val, b->int_val);
1261 
1262                 case TABLE_INT8:
1263                         return CMP(a->int8, b->int8);
1264 
1265                 case TABLE_INT16:
1266                         return CMP(a->int16, b->int16);
1267 
1268                 case TABLE_INT32:
1269                         return CMP(a->int32, b->int32);
1270 
1271                 case TABLE_INT64:
1272                         return CMP(a->int64, b->int64);
1273 
1274                 case TABLE_UINT:
1275                         return CMP(a->uint_val, b->uint_val);
1276 
1277                 case TABLE_UINT8:
1278                         return CMP(a->uint8, b->uint8);
1279 
1280                 case TABLE_UINT16:
1281                         return CMP(a->uint16, b->uint16);
1282 
1283                 case TABLE_UINT32:
1284                         return CMP(a->uint32, b->uint32);
1285 
1286                 case TABLE_UINT64:
1287                 case TABLE_UINT64_HEX:
1288                         return CMP(a->uint64, b->uint64);
1289 
1290                 case TABLE_PERCENT:
1291                         return CMP(a->percent, b->percent);
1292 
1293                 case TABLE_IFINDEX:
1294                         return CMP(a->ifindex, b->ifindex);
1295 
1296                 case TABLE_IN_ADDR:
1297                         return CMP(a->address.in.s_addr, b->address.in.s_addr);
1298 
1299                 case TABLE_IN6_ADDR:
1300                         return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
1301 
1302                 case TABLE_UUID:
1303                 case TABLE_ID128:
1304                         return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
1305 
1306                 case TABLE_UID:
1307                         return CMP(a->uid, b->uid);
1308 
1309                 case TABLE_GID:
1310                         return CMP(a->gid, b->gid);
1311 
1312                 case TABLE_PID:
1313                         return CMP(a->pid, b->pid);
1314 
1315                 case TABLE_MODE:
1316                         return CMP(a->mode, b->mode);
1317 
1318                 default:
1319                         ;
1320                 }
1321         }
1322 
1323         /* Generic fallback using the original order in which the cells where added. */
1324         return CMP(index_a, index_b);
1325 }
1326 
table_data_compare(const size_t * a,const size_t * b,Table * t)1327 static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
1328         int r;
1329 
1330         assert(t);
1331         assert(t->sort_map);
1332 
1333         /* Make sure the header stays at the beginning */
1334         if (*a < t->n_columns && *b < t->n_columns)
1335                 return 0;
1336         if (*a < t->n_columns)
1337                 return -1;
1338         if (*b < t->n_columns)
1339                 return 1;
1340 
1341         /* Order other lines by the sorting map */
1342         for (size_t i = 0; i < t->n_sort_map; i++) {
1343                 TableData *d, *dd;
1344 
1345                 d = t->data[*a + t->sort_map[i]];
1346                 dd = t->data[*b + t->sort_map[i]];
1347 
1348                 r = cell_data_compare(d, *a, dd, *b);
1349                 if (r != 0)
1350                         return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
1351         }
1352 
1353         /* Order identical lines by the order there were originally added in */
1354         return CMP(*a, *b);
1355 }
1356 
format_strv_width(char ** strv,size_t column_width)1357 static char* format_strv_width(char **strv, size_t column_width) {
1358         _cleanup_free_ char *buf = NULL; /* buf must be freed after f */
1359         _cleanup_fclose_ FILE *f = NULL;
1360         size_t sz = 0;
1361 
1362         f = open_memstream_unlocked(&buf, &sz);
1363         if (!f)
1364                 return NULL;
1365 
1366         size_t position = 0;
1367         STRV_FOREACH(p, strv) {
1368                 size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1369                                                           * If that happens, we'll just print one item per line. */
1370 
1371                 if (position == 0) {
1372                         fputs(*p, f);
1373                         position = our_len;
1374                 } else if (size_add(size_add(position, 1), our_len) <= column_width) {
1375                         fprintf(f, " %s", *p);
1376                         position = size_add(size_add(position, 1), our_len);
1377                 } else {
1378                         fprintf(f, "\n%s", *p);
1379                         position = our_len;
1380                 }
1381         }
1382 
1383         if (fflush_and_check(f) < 0)
1384                 return NULL;
1385 
1386         f = safe_fclose(f);
1387         return TAKE_PTR(buf);
1388 }
1389 
table_data_format(Table * t,TableData * d,bool avoid_uppercasing,size_t column_width,bool * have_soft)1390 static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
1391         assert(d);
1392 
1393         if (d->formatted &&
1394             /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1395             (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
1396                 return d->formatted;
1397 
1398         switch (d->type) {
1399         case TABLE_EMPTY:
1400                 return strempty(t->empty_string);
1401 
1402         case TABLE_STRING:
1403         case TABLE_PATH:
1404                 if (d->uppercase && !avoid_uppercasing) {
1405                         d->formatted = new(char, strlen(d->string) + 1);
1406                         if (!d->formatted)
1407                                 return NULL;
1408 
1409                         char *q = d->formatted;
1410                         for (char *p = d->string; *p; p++, q++)
1411                                 *q = (char) toupper((unsigned char) *p);
1412                         *q = 0;
1413 
1414                         return d->formatted;
1415                 }
1416 
1417                 return d->string;
1418 
1419         case TABLE_STRV:
1420                 if (strv_isempty(d->strv))
1421                         return strempty(t->empty_string);
1422 
1423                 d->formatted = strv_join(d->strv, "\n");
1424                 if (!d->formatted)
1425                         return NULL;
1426                 break;
1427 
1428         case TABLE_STRV_WRAPPED: {
1429                 if (strv_isempty(d->strv))
1430                         return strempty(t->empty_string);
1431 
1432                 char *buf = format_strv_width(d->strv, column_width);
1433                 if (!buf)
1434                         return NULL;
1435 
1436                 free_and_replace(d->formatted, buf);
1437                 d->formatted_for_width = column_width;
1438                 if (have_soft)
1439                         *have_soft = true;
1440 
1441                 break;
1442         }
1443 
1444         case TABLE_BOOLEAN:
1445                 return yes_no(d->boolean);
1446 
1447         case TABLE_BOOLEAN_CHECKMARK:
1448                 return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
1449 
1450         case TABLE_TIMESTAMP:
1451         case TABLE_TIMESTAMP_UTC:
1452         case TABLE_TIMESTAMP_RELATIVE: {
1453                 _cleanup_free_ char *p = NULL;
1454                 char *ret;
1455 
1456                 p = new(char, d->type == TABLE_TIMESTAMP_RELATIVE ? FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1457                 if (!p)
1458                         return NULL;
1459 
1460                 if (d->type == TABLE_TIMESTAMP)
1461                         ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1462                 else if (d->type == TABLE_TIMESTAMP_UTC)
1463                         ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
1464                 else
1465                         ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
1466                 if (!ret)
1467                         return "n/a";
1468 
1469                 d->formatted = TAKE_PTR(p);
1470                 break;
1471         }
1472 
1473         case TABLE_TIMESPAN:
1474         case TABLE_TIMESPAN_MSEC: {
1475                 _cleanup_free_ char *p = NULL;
1476 
1477                 p = new(char, FORMAT_TIMESPAN_MAX);
1478                 if (!p)
1479                         return NULL;
1480 
1481                 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1482                                      d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
1483                         return "n/a";
1484 
1485                 d->formatted = TAKE_PTR(p);
1486                 break;
1487         }
1488 
1489         case TABLE_SIZE: {
1490                 _cleanup_free_ char *p = NULL;
1491 
1492                 p = new(char, FORMAT_BYTES_MAX);
1493                 if (!p)
1494                         return NULL;
1495 
1496                 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
1497                         return "n/a";
1498 
1499                 d->formatted = TAKE_PTR(p);
1500                 break;
1501         }
1502 
1503         case TABLE_BPS: {
1504                 _cleanup_free_ char *p = NULL;
1505                 size_t n;
1506 
1507                 p = new(char, FORMAT_BYTES_MAX+2);
1508                 if (!p)
1509                         return NULL;
1510 
1511                 if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
1512                         return "n/a";
1513 
1514                 n = strlen(p);
1515                 strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1516 
1517                 d->formatted = TAKE_PTR(p);
1518                 break;
1519         }
1520 
1521         case TABLE_INT: {
1522                 _cleanup_free_ char *p = NULL;
1523 
1524                 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1525                 if (!p)
1526                         return NULL;
1527 
1528                 sprintf(p, "%i", d->int_val);
1529                 d->formatted = TAKE_PTR(p);
1530                 break;
1531         }
1532 
1533         case TABLE_INT8: {
1534                 _cleanup_free_ char *p = NULL;
1535 
1536                 p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
1537                 if (!p)
1538                         return NULL;
1539 
1540                 sprintf(p, "%" PRIi8, d->int8);
1541                 d->formatted = TAKE_PTR(p);
1542                 break;
1543         }
1544 
1545         case TABLE_INT16: {
1546                 _cleanup_free_ char *p = NULL;
1547 
1548                 p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
1549                 if (!p)
1550                         return NULL;
1551 
1552                 sprintf(p, "%" PRIi16, d->int16);
1553                 d->formatted = TAKE_PTR(p);
1554                 break;
1555         }
1556 
1557         case TABLE_INT32: {
1558                 _cleanup_free_ char *p = NULL;
1559 
1560                 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1561                 if (!p)
1562                         return NULL;
1563 
1564                 sprintf(p, "%" PRIi32, d->int32);
1565                 d->formatted = TAKE_PTR(p);
1566                 break;
1567         }
1568 
1569         case TABLE_INT64: {
1570                 _cleanup_free_ char *p = NULL;
1571 
1572                 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1573                 if (!p)
1574                         return NULL;
1575 
1576                 sprintf(p, "%" PRIi64, d->int64);
1577                 d->formatted = TAKE_PTR(p);
1578                 break;
1579         }
1580 
1581         case TABLE_UINT: {
1582                 _cleanup_free_ char *p = NULL;
1583 
1584                 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1585                 if (!p)
1586                         return NULL;
1587 
1588                 sprintf(p, "%u", d->uint_val);
1589                 d->formatted = TAKE_PTR(p);
1590                 break;
1591         }
1592 
1593         case TABLE_UINT8: {
1594                 _cleanup_free_ char *p = NULL;
1595 
1596                 p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
1597                 if (!p)
1598                         return NULL;
1599 
1600                 sprintf(p, "%" PRIu8, d->uint8);
1601                 d->formatted = TAKE_PTR(p);
1602                 break;
1603         }
1604 
1605         case TABLE_UINT16: {
1606                 _cleanup_free_ char *p = NULL;
1607 
1608                 p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
1609                 if (!p)
1610                         return NULL;
1611 
1612                 sprintf(p, "%" PRIu16, d->uint16);
1613                 d->formatted = TAKE_PTR(p);
1614                 break;
1615         }
1616 
1617         case TABLE_UINT32: {
1618                 _cleanup_free_ char *p = NULL;
1619 
1620                 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1621                 if (!p)
1622                         return NULL;
1623 
1624                 sprintf(p, "%" PRIu32, d->uint32);
1625                 d->formatted = TAKE_PTR(p);
1626                 break;
1627         }
1628 
1629         case TABLE_UINT64: {
1630                 _cleanup_free_ char *p = NULL;
1631 
1632                 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1633                 if (!p)
1634                         return NULL;
1635 
1636                 sprintf(p, "%" PRIu64, d->uint64);
1637                 d->formatted = TAKE_PTR(p);
1638                 break;
1639         }
1640 
1641         case TABLE_UINT64_HEX: {
1642                 _cleanup_free_ char *p = NULL;
1643 
1644                 p = new(char, 16 + 1);
1645                 if (!p)
1646                         return NULL;
1647 
1648                 sprintf(p, "%" PRIx64, d->uint64);
1649                 d->formatted = TAKE_PTR(p);
1650                 break;
1651         }
1652 
1653         case TABLE_PERCENT: {
1654                 _cleanup_free_ char *p = NULL;
1655 
1656                 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1657                 if (!p)
1658                         return NULL;
1659 
1660                 sprintf(p, "%i%%" , d->percent);
1661                 d->formatted = TAKE_PTR(p);
1662                 break;
1663         }
1664 
1665         case TABLE_IFINDEX: {
1666                 _cleanup_free_ char *p = NULL;
1667 
1668                 if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
1669                         return NULL;
1670 
1671                 d->formatted = TAKE_PTR(p);
1672                 break;
1673         }
1674 
1675         case TABLE_IN_ADDR:
1676         case TABLE_IN6_ADDR: {
1677                 _cleanup_free_ char *p = NULL;
1678 
1679                 if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
1680                                       &d->address, &p) < 0)
1681                         return NULL;
1682 
1683                 d->formatted = TAKE_PTR(p);
1684                 break;
1685         }
1686 
1687         case TABLE_ID128: {
1688                 char *p;
1689 
1690                 p = new(char, SD_ID128_STRING_MAX);
1691                 if (!p)
1692                         return NULL;
1693 
1694                 d->formatted = sd_id128_to_string(d->id128, p);
1695                 break;
1696         }
1697 
1698         case TABLE_UUID: {
1699                 char *p;
1700 
1701                 p = new(char, SD_ID128_UUID_STRING_MAX);
1702                 if (!p)
1703                         return NULL;
1704 
1705                 d->formatted = sd_id128_to_uuid_string(d->id128, p);
1706                 break;
1707         }
1708 
1709         case TABLE_UID: {
1710                 char *p;
1711 
1712                 if (!uid_is_valid(d->uid))
1713                         return "n/a";
1714 
1715                 p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1716                 if (!p)
1717                         return NULL;
1718                 sprintf(p, UID_FMT, d->uid);
1719 
1720                 d->formatted = p;
1721                 break;
1722         }
1723 
1724         case TABLE_GID: {
1725                 char *p;
1726 
1727                 if (!gid_is_valid(d->gid))
1728                         return "n/a";
1729 
1730                 p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1731                 if (!p)
1732                         return NULL;
1733                 sprintf(p, GID_FMT, d->gid);
1734 
1735                 d->formatted = p;
1736                 break;
1737         }
1738 
1739         case TABLE_PID: {
1740                 char *p;
1741 
1742                 if (!pid_is_valid(d->pid))
1743                         return "n/a";
1744 
1745                 p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
1746                 if (!p)
1747                         return NULL;
1748                 sprintf(p, PID_FMT, d->pid);
1749 
1750                 d->formatted = p;
1751                 break;
1752         }
1753 
1754         case TABLE_SIGNAL: {
1755                 const char *suffix;
1756                 char *p;
1757 
1758                 suffix = signal_to_string(d->int_val);
1759                 if (!suffix)
1760                         return "n/a";
1761 
1762                 p = strjoin("SIG", suffix);
1763                 if (!p)
1764                         return NULL;
1765 
1766                 d->formatted = p;
1767                 break;
1768         }
1769 
1770         case TABLE_MODE: {
1771                 char *p;
1772 
1773                 if (d->mode == MODE_INVALID)
1774                         return "n/a";
1775 
1776                 p = new(char, 4 + 1);
1777                 if (!p)
1778                         return NULL;
1779 
1780                 sprintf(p, "%04o", d->mode & 07777);
1781                 d->formatted = p;
1782                 break;
1783         }
1784 
1785         default:
1786                 assert_not_reached();
1787         }
1788 
1789         return d->formatted;
1790 }
1791 
console_width_height(const char * s,size_t * ret_width,size_t * ret_height)1792 static int console_width_height(
1793                 const char *s,
1794                 size_t *ret_width,
1795                 size_t *ret_height) {
1796 
1797         size_t max_width = 0, height = 0;
1798         const char *p;
1799 
1800         assert(s);
1801 
1802         /* Determine the width and height in console character cells the specified string needs. */
1803 
1804         do {
1805                 size_t k;
1806 
1807                 p = strchr(s, '\n');
1808                 if (p) {
1809                         _cleanup_free_ char *c = NULL;
1810 
1811                         c = strndup(s, p - s);
1812                         if (!c)
1813                                 return -ENOMEM;
1814 
1815                         k = utf8_console_width(c);
1816                         s = p + 1;
1817                 } else {
1818                         k = utf8_console_width(s);
1819                         s = NULL;
1820                 }
1821                 if (k == SIZE_MAX)
1822                         return -EINVAL;
1823                 if (k > max_width)
1824                         max_width = k;
1825 
1826                 height++;
1827         } while (!isempty(s));
1828 
1829         if (ret_width)
1830                 *ret_width = max_width;
1831 
1832         if (ret_height)
1833                 *ret_height = height;
1834 
1835         return 0;
1836 }
1837 
table_data_requested_width_height(Table * table,TableData * d,size_t available_width,size_t * ret_width,size_t * ret_height,bool * have_soft)1838 static int table_data_requested_width_height(
1839                 Table *table,
1840                 TableData *d,
1841                 size_t available_width,
1842                 size_t *ret_width,
1843                 size_t *ret_height,
1844                 bool *have_soft) {
1845 
1846         _cleanup_free_ char *truncated = NULL;
1847         bool truncation_applied = false;
1848         size_t width, height;
1849         const char *t;
1850         int r;
1851         bool soft = false;
1852 
1853         t = table_data_format(table, d, false, available_width, &soft);
1854         if (!t)
1855                 return -ENOMEM;
1856 
1857         if (table->cell_height_max != SIZE_MAX) {
1858                 r = string_truncate_lines(t, table->cell_height_max, &truncated);
1859                 if (r < 0)
1860                         return r;
1861                 if (r > 0)
1862                         truncation_applied = true;
1863 
1864                 t = truncated;
1865         }
1866 
1867         r = console_width_height(t, &width, &height);
1868         if (r < 0)
1869                 return r;
1870 
1871         if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
1872                 width = d->maximum_width;
1873 
1874         if (width < d->minimum_width)
1875                 width = d->minimum_width;
1876 
1877         if (ret_width)
1878                 *ret_width = width;
1879         if (ret_height)
1880                 *ret_height = height;
1881         if (have_soft && soft)
1882                 *have_soft = true;
1883 
1884         return truncation_applied;
1885 }
1886 
align_string_mem(const char * str,const char * url,size_t new_length,unsigned percent)1887 static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1888         size_t w = 0, space, lspace, old_length, clickable_length;
1889         _cleanup_free_ char *clickable = NULL;
1890         const char *p;
1891         char *ret;
1892         int r;
1893 
1894         /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1895 
1896         assert(str);
1897         assert(percent <= 100);
1898 
1899         old_length = strlen(str);
1900 
1901         if (url) {
1902                 r = terminal_urlify(url, str, &clickable);
1903                 if (r < 0)
1904                         return NULL;
1905 
1906                 clickable_length = strlen(clickable);
1907         } else
1908                 clickable_length = old_length;
1909 
1910         /* Determine current width on screen */
1911         p = str;
1912         while (p < str + old_length) {
1913                 char32_t c;
1914 
1915                 if (utf8_encoded_to_unichar(p, &c) < 0) {
1916                         p++, w++; /* count invalid chars as 1 */
1917                         continue;
1918                 }
1919 
1920                 p = utf8_next_char(p);
1921                 w += unichar_iswide(c) ? 2 : 1;
1922         }
1923 
1924         /* Already wider than the target, if so, don't do anything */
1925         if (w >= new_length)
1926                 return clickable ? TAKE_PTR(clickable) : strdup(str);
1927 
1928         /* How much spaces shall we add? An how much on the left side? */
1929         space = new_length - w;
1930         lspace = space * percent / 100U;
1931 
1932         ret = new(char, space + clickable_length + 1);
1933         if (!ret)
1934                 return NULL;
1935 
1936         for (size_t i = 0; i < lspace; i++)
1937                 ret[i] = ' ';
1938         memcpy(ret + lspace, clickable ?: str, clickable_length);
1939         for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
1940                 ret[i] = ' ';
1941 
1942         ret[space + clickable_length] = 0;
1943         return ret;
1944 }
1945 
table_data_isempty(TableData * d)1946 static bool table_data_isempty(TableData *d) {
1947         assert(d);
1948 
1949         if (d->type == TABLE_EMPTY)
1950                 return true;
1951 
1952         /* Let's also consider an empty strv as truly empty. */
1953         if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
1954                 return strv_isempty(d->strv);
1955 
1956         /* Note that an empty string we do not consider empty here! */
1957         return false;
1958 }
1959 
table_data_color(TableData * d)1960 static const char* table_data_color(TableData *d) {
1961         assert(d);
1962 
1963         if (d->color)
1964                 return d->color;
1965 
1966         /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
1967         if (table_data_isempty(d))
1968                 return ansi_grey();
1969 
1970         return NULL;
1971 }
1972 
table_data_rgap_color(TableData * d)1973 static const char* table_data_rgap_color(TableData *d) {
1974         assert(d);
1975 
1976         if (d->rgap_color)
1977                 return d->rgap_color;
1978 
1979         return NULL;
1980 }
1981 
table_print(Table * t,FILE * f)1982 int table_print(Table *t, FILE *f) {
1983         size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
1984                 table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
1985                 *width = NULL;
1986         _cleanup_free_ size_t *sorted = NULL;
1987         uint64_t *column_weight, weight_sum;
1988         int r;
1989 
1990         assert(t);
1991 
1992         if (!f)
1993                 f = stdout;
1994 
1995         /* Ensure we have no incomplete rows */
1996         assert(t->n_cells % t->n_columns == 0);
1997 
1998         n_rows = t->n_cells / t->n_columns;
1999         assert(n_rows > 0); /* at least the header row must be complete */
2000 
2001         if (t->sort_map) {
2002                 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2003 
2004                 sorted = new(size_t, n_rows);
2005                 if (!sorted)
2006                         return -ENOMEM;
2007 
2008                 for (size_t i = 0; i < n_rows; i++)
2009                         sorted[i] = i * t->n_columns;
2010 
2011                 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2012         }
2013 
2014         if (t->display_map)
2015                 display_columns = t->n_display_map;
2016         else
2017                 display_columns = t->n_columns;
2018 
2019         assert(display_columns > 0);
2020 
2021         minimum_width = newa(size_t, display_columns);
2022         maximum_width = newa(size_t, display_columns);
2023         requested_width = newa(size_t, display_columns);
2024         column_weight = newa0(uint64_t, display_columns);
2025 
2026         for (size_t j = 0; j < display_columns; j++) {
2027                 minimum_width[j] = 1;
2028                 maximum_width[j] = SIZE_MAX;
2029         }
2030 
2031         for (unsigned pass = 0; pass < 2; pass++) {
2032                 /* First pass: determine column sizes */
2033 
2034                 for (size_t j = 0; j < display_columns; j++)
2035                         requested_width[j] = SIZE_MAX;
2036 
2037                 bool any_soft = false;
2038 
2039                 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2040                         TableData **row;
2041 
2042                         /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2043                          * hence we don't care for sorted[] during the first pass. */
2044                         row = t->data + i * t->n_columns;
2045 
2046                         for (size_t j = 0; j < display_columns; j++) {
2047                                 TableData *d;
2048                                 size_t req_width, req_height;
2049 
2050                                 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2051 
2052                                 r = table_data_requested_width_height(t, d,
2053                                                                       width ? width[j] : SIZE_MAX,
2054                                                                       &req_width, &req_height, &any_soft);
2055                                 if (r < 0)
2056                                         return r;
2057                                 if (r > 0) { /* Truncated because too many lines? */
2058                                         _cleanup_free_ char *last = NULL;
2059                                         const char *field;
2060 
2061                                         /* If we are going to show only the first few lines of a cell that has
2062                                          * multiple make sure that we have enough space horizontally to show an
2063                                          * ellipsis. Hence, let's figure out the last line, and account for its
2064                                          * length plus ellipsis. */
2065 
2066                                         field = table_data_format(t, d, false,
2067                                                                   width ? width[j] : SIZE_MAX,
2068                                                                   &any_soft);
2069                                         if (!field)
2070                                                 return -ENOMEM;
2071 
2072                                         assert_se(t->cell_height_max > 0);
2073                                         r = string_extract_line(field, t->cell_height_max-1, &last);
2074                                         if (r < 0)
2075                                                 return r;
2076 
2077                                         req_width = MAX(req_width,
2078                                                         utf8_console_width(last) +
2079                                                         utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
2080                                 }
2081 
2082                                 /* Determine the biggest width that any cell in this column would like to have */
2083                                 if (requested_width[j] == SIZE_MAX ||
2084                                     requested_width[j] < req_width)
2085                                         requested_width[j] = req_width;
2086 
2087                                 /* Determine the minimum width any cell in this column needs */
2088                                 if (minimum_width[j] < d->minimum_width)
2089                                         minimum_width[j] = d->minimum_width;
2090 
2091                                 /* Determine the maximum width any cell in this column needs */
2092                                 if (d->maximum_width != SIZE_MAX &&
2093                                     (maximum_width[j] == SIZE_MAX ||
2094                                      maximum_width[j] > d->maximum_width))
2095                                         maximum_width[j] = d->maximum_width;
2096 
2097                                 /* Determine the full columns weight */
2098                                 column_weight[j] += d->weight;
2099                         }
2100                 }
2101 
2102                 /* One space between each column */
2103                 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
2104 
2105                 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2106                 weight_sum = 0;
2107                 for (size_t j = 0; j < display_columns; j++) {
2108                         weight_sum += column_weight[j];
2109 
2110                         table_minimum_width += minimum_width[j];
2111 
2112                         if (maximum_width[j] == SIZE_MAX)
2113                                 table_maximum_width = SIZE_MAX;
2114                         else
2115                                 table_maximum_width += maximum_width[j];
2116 
2117                         table_requested_width += requested_width[j];
2118                 }
2119 
2120                 /* Calculate effective table width */
2121                 if (t->width != 0 && t->width != SIZE_MAX)
2122                         table_effective_width = t->width;
2123                 else if (t->width == 0 ||
2124                          ((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
2125                         table_effective_width = table_requested_width;
2126                 else
2127                         table_effective_width = MIN(table_requested_width, columns());
2128 
2129                 if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
2130                         table_effective_width = table_maximum_width;
2131 
2132                 if (table_effective_width < table_minimum_width)
2133                         table_effective_width = table_minimum_width;
2134 
2135                 if (!width)
2136                         width = newa(size_t, display_columns);
2137 
2138                 if (table_effective_width >= table_requested_width) {
2139                         size_t extra;
2140 
2141                         /* We have extra room, let's distribute it among columns according to their weights. We first provide
2142                          * each column with what it asked for and the distribute the rest.  */
2143 
2144                         extra = table_effective_width - table_requested_width;
2145 
2146                         for (size_t j = 0; j < display_columns; j++) {
2147                                 size_t delta;
2148 
2149                                 if (weight_sum == 0)
2150                                         width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
2151                                 else
2152                                         width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
2153 
2154                                 if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
2155                                         width[j] = maximum_width[j];
2156 
2157                                 if (width[j] < minimum_width[j])
2158                                         width[j] = minimum_width[j];
2159 
2160                                 delta = LESS_BY(width[j], requested_width[j]);
2161 
2162                                 /* Subtract what we just added from the rest */
2163                                 if (extra > delta)
2164                                         extra -= delta;
2165                                 else
2166                                         extra = 0;
2167 
2168                                 assert(weight_sum >= column_weight[j]);
2169                                 weight_sum -= column_weight[j];
2170                         }
2171 
2172                         break; /* Every column should be happy, no need to repeat calculations. */
2173                 } else {
2174                         /* We need to compress the table, columns can't get what they asked for. We first provide each column
2175                          * with the minimum they need, and then distribute anything left. */
2176                         bool finalize = false;
2177                         size_t extra;
2178 
2179                         extra = table_effective_width - table_minimum_width;
2180 
2181                         for (size_t j = 0; j < display_columns; j++)
2182                                 width[j] = SIZE_MAX;
2183 
2184                         for (;;) {
2185                                 bool restart = false;
2186 
2187                                 for (size_t j = 0; j < display_columns; j++) {
2188                                         size_t delta, w;
2189 
2190                                         /* Did this column already get something assigned? If so, let's skip to the next */
2191                                         if (width[j] != SIZE_MAX)
2192                                                 continue;
2193 
2194                                         if (weight_sum == 0)
2195                                                 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
2196                                         else
2197                                                 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
2198 
2199                                         if (w >= requested_width[j]) {
2200                                                 /* Never give more than requested. If we hit a column like this, there's more
2201                                                  * space to allocate to other columns which means we need to restart the
2202                                                  * iteration. However, if we hit a column like this, let's assign it the space
2203                                                  * it wanted for good early. */
2204 
2205                                                 w = requested_width[j];
2206                                                 restart = true;
2207 
2208                                         } else if (!finalize)
2209                                                 continue;
2210 
2211                                         width[j] = w;
2212 
2213                                         assert(w >= minimum_width[j]);
2214                                         delta = w - minimum_width[j];
2215 
2216                                         assert(delta <= extra);
2217                                         extra -= delta;
2218 
2219                                         assert(weight_sum >= column_weight[j]);
2220                                         weight_sum -= column_weight[j];
2221 
2222                                         if (restart && !finalize)
2223                                                 break;
2224                                 }
2225 
2226                                 if (finalize)
2227                                         break;
2228 
2229                                 if (!restart)
2230                                         finalize = true;
2231                         }
2232 
2233                         if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
2234                                         * let's try to reformat them with the new widths. Otherwise, let's
2235                                         * move on. */
2236                                 break;
2237                 }
2238         }
2239 
2240         /* Second pass: show output */
2241         for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2242                 size_t n_subline = 0;
2243                 bool more_sublines;
2244                 TableData **row;
2245 
2246                 if (sorted)
2247                         row = t->data + sorted[i];
2248                 else
2249                         row = t->data + i * t->n_columns;
2250 
2251                 do {
2252                         const char *gap_color = NULL;
2253                         more_sublines = false;
2254 
2255                         for (size_t j = 0; j < display_columns; j++) {
2256                                 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
2257                                 bool lines_truncated = false;
2258                                 const char *field, *color = NULL;
2259                                 TableData *d;
2260                                 size_t l;
2261 
2262                                 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2263 
2264                                 field = table_data_format(t, d, false, width[j], NULL);
2265                                 if (!field)
2266                                         return -ENOMEM;
2267 
2268                                 r = string_extract_line(field, n_subline, &extracted);
2269                                 if (r < 0)
2270                                         return r;
2271                                 if (r > 0) {
2272                                         /* There are more lines to come */
2273                                         if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
2274                                                 more_sublines = true; /* There are more lines to come */
2275                                         else
2276                                                 lines_truncated = true;
2277                                 }
2278                                 if (extracted)
2279                                         field = extracted;
2280 
2281                                 l = utf8_console_width(field);
2282                                 if (l > width[j]) {
2283                                         /* Field is wider than allocated space. Let's ellipsize */
2284 
2285                                         buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2286                                                            lines_truncated ? 100 : d->ellipsize_percent);
2287                                         if (!buffer)
2288                                                 return -ENOMEM;
2289 
2290                                         field = buffer;
2291                                 } else {
2292                                         if (lines_truncated) {
2293                                                 _cleanup_free_ char *padded = NULL;
2294 
2295                                                 /* We truncated more lines of this cell, let's add an
2296                                                  * ellipsis. We first append it, but that might make our
2297                                                  * string grow above what we have space for, hence ellipsize
2298                                                  * right after. This will truncate the ellipsis and add a new
2299                                                  * one. */
2300 
2301                                                 padded = strjoin(field, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
2302                                                 if (!padded)
2303                                                         return -ENOMEM;
2304 
2305                                                 buffer = ellipsize(padded, width[j], 100);
2306                                                 if (!buffer)
2307                                                         return -ENOMEM;
2308 
2309                                                 field = buffer;
2310                                                 l = utf8_console_width(field);
2311                                         }
2312 
2313                                         if (l < width[j]) {
2314                                                 _cleanup_free_ char *aligned = NULL;
2315                                                 /* Field is shorter than allocated space. Let's align with spaces */
2316 
2317                                                 aligned = align_string_mem(field, d->url, width[j], d->align_percent);
2318                                                 if (!aligned)
2319                                                         return -ENOMEM;
2320 
2321                                                 /* Drop trailing white spaces of last column when no cosmetics is set. */
2322                                                 if (j == display_columns - 1 &&
2323                                                     (!colors_enabled() || (!table_data_color(d) && row != t->data)) &&
2324                                                     (!urlify_enabled() || !d->url))
2325                                                         delete_trailing_chars(aligned, NULL);
2326 
2327                                                 free_and_replace(buffer, aligned);
2328                                                 field = buffer;
2329                                         }
2330                                 }
2331 
2332                                 if (l >= width[j] && d->url) {
2333                                         _cleanup_free_ char *clickable = NULL;
2334 
2335                                         r = terminal_urlify(d->url, field, &clickable);
2336                                         if (r < 0)
2337                                                 return r;
2338 
2339                                         free_and_replace(buffer, clickable);
2340                                         field = buffer;
2341                                 }
2342 
2343                                 if (colors_enabled()) {
2344                                         if (gap_color)
2345                                                 fputs(gap_color, f);
2346                                         else if (row == t->data) /* underline header line fully, including the column separator */
2347                                                 fputs(ansi_underline(), f);
2348                                 }
2349 
2350                                 if (j > 0)
2351                                         fputc(' ', f); /* column separator left of cell */
2352 
2353                                 if (colors_enabled()) {
2354                                         color = table_data_color(d);
2355 
2356                                         /* Undo gap color */
2357                                         if (gap_color || (color && row == t->data))
2358                                                 fputs(ANSI_NORMAL, f);
2359 
2360                                         if (color)
2361                                                 fputs(color, f);
2362                                         else if (gap_color && row == t->data) /* underline header line cell */
2363                                                 fputs(ansi_underline(), f);
2364                                 }
2365 
2366                                 fputs(field, f);
2367 
2368                                 if (colors_enabled() && (color || row == t->data))
2369                                         fputs(ANSI_NORMAL, f);
2370 
2371                                 gap_color = table_data_rgap_color(d);
2372                         }
2373 
2374                         fputc('\n', f);
2375                         n_subline ++;
2376                 } while (more_sublines);
2377         }
2378 
2379         return fflush_and_check(f);
2380 }
2381 
table_format(Table * t,char ** ret)2382 int table_format(Table *t, char **ret) {
2383         _cleanup_free_ char *buf = NULL;
2384         _cleanup_fclose_ FILE *f = NULL;
2385         size_t sz = 0;
2386         int r;
2387 
2388         f = open_memstream_unlocked(&buf, &sz);
2389         if (!f)
2390                 return -ENOMEM;
2391 
2392         r = table_print(t, f);
2393         if (r < 0)
2394                 return r;
2395 
2396         f = safe_fclose(f);
2397 
2398         *ret = TAKE_PTR(buf);
2399 
2400         return 0;
2401 }
2402 
table_get_rows(Table * t)2403 size_t table_get_rows(Table *t) {
2404         if (!t)
2405                 return 0;
2406 
2407         assert(t->n_columns > 0);
2408         return t->n_cells / t->n_columns;
2409 }
2410 
table_get_columns(Table * t)2411 size_t table_get_columns(Table *t) {
2412         if (!t)
2413                 return 0;
2414 
2415         assert(t->n_columns > 0);
2416         return t->n_columns;
2417 }
2418 
table_set_reverse(Table * t,size_t column,bool b)2419 int table_set_reverse(Table *t, size_t column, bool b) {
2420         assert(t);
2421         assert(column < t->n_columns);
2422 
2423         if (!t->reverse_map) {
2424                 if (!b)
2425                         return 0;
2426 
2427                 t->reverse_map = new0(bool, t->n_columns);
2428                 if (!t->reverse_map)
2429                         return -ENOMEM;
2430         }
2431 
2432         t->reverse_map[column] = b;
2433         return 0;
2434 }
2435 
table_get_cell(Table * t,size_t row,size_t column)2436 TableCell *table_get_cell(Table *t, size_t row, size_t column) {
2437         size_t i;
2438 
2439         assert(t);
2440 
2441         if (column >= t->n_columns)
2442                 return NULL;
2443 
2444         i = row * t->n_columns + column;
2445         if (i >= t->n_cells)
2446                 return NULL;
2447 
2448         return TABLE_INDEX_TO_CELL(i);
2449 }
2450 
table_get(Table * t,TableCell * cell)2451 const void *table_get(Table *t, TableCell *cell) {
2452         TableData *d;
2453 
2454         assert(t);
2455 
2456         d = table_get_data(t, cell);
2457         if (!d)
2458                 return NULL;
2459 
2460         return d->data;
2461 }
2462 
table_get_at(Table * t,size_t row,size_t column)2463 const void* table_get_at(Table *t, size_t row, size_t column) {
2464         TableCell *cell;
2465 
2466         cell = table_get_cell(t, row, column);
2467         if (!cell)
2468                 return NULL;
2469 
2470         return table_get(t, cell);
2471 }
2472 
table_data_to_json(TableData * d,JsonVariant ** ret)2473 static int table_data_to_json(TableData *d, JsonVariant **ret) {
2474 
2475         switch (d->type) {
2476 
2477         case TABLE_EMPTY:
2478                 return json_variant_new_null(ret);
2479 
2480         case TABLE_STRING:
2481         case TABLE_PATH:
2482                 return json_variant_new_string(ret, d->string);
2483 
2484         case TABLE_STRV:
2485         case TABLE_STRV_WRAPPED:
2486                 return json_variant_new_array_strv(ret, d->strv);
2487 
2488         case TABLE_BOOLEAN_CHECKMARK:
2489         case TABLE_BOOLEAN:
2490                 return json_variant_new_boolean(ret, d->boolean);
2491 
2492         case TABLE_TIMESTAMP:
2493         case TABLE_TIMESTAMP_UTC:
2494         case TABLE_TIMESTAMP_RELATIVE:
2495                 if (d->timestamp == USEC_INFINITY)
2496                         return json_variant_new_null(ret);
2497 
2498                 return json_variant_new_unsigned(ret, d->timestamp);
2499 
2500         case TABLE_TIMESPAN:
2501         case TABLE_TIMESPAN_MSEC:
2502                 if (d->timespan == USEC_INFINITY)
2503                         return json_variant_new_null(ret);
2504 
2505                 return json_variant_new_unsigned(ret, d->timespan);
2506 
2507         case TABLE_SIZE:
2508         case TABLE_BPS:
2509                 if (d->size == UINT64_MAX)
2510                         return json_variant_new_null(ret);
2511 
2512                 return json_variant_new_unsigned(ret, d->size);
2513 
2514         case TABLE_INT:
2515                 return json_variant_new_integer(ret, d->int_val);
2516 
2517         case TABLE_INT8:
2518                 return json_variant_new_integer(ret, d->int8);
2519 
2520         case TABLE_INT16:
2521                 return json_variant_new_integer(ret, d->int16);
2522 
2523         case TABLE_INT32:
2524                 return json_variant_new_integer(ret, d->int32);
2525 
2526         case TABLE_INT64:
2527                 return json_variant_new_integer(ret, d->int64);
2528 
2529         case TABLE_UINT:
2530                 return json_variant_new_unsigned(ret, d->uint_val);
2531 
2532         case TABLE_UINT8:
2533                 return json_variant_new_unsigned(ret, d->uint8);
2534 
2535         case TABLE_UINT16:
2536                 return json_variant_new_unsigned(ret, d->uint16);
2537 
2538         case TABLE_UINT32:
2539                 return json_variant_new_unsigned(ret, d->uint32);
2540 
2541         case TABLE_UINT64:
2542         case TABLE_UINT64_HEX:
2543                 return json_variant_new_unsigned(ret, d->uint64);
2544 
2545         case TABLE_PERCENT:
2546                 return json_variant_new_integer(ret, d->percent);
2547 
2548         case TABLE_IFINDEX:
2549                 if (d->ifindex <= 0)
2550                         return json_variant_new_null(ret);
2551 
2552                 return json_variant_new_integer(ret, d->ifindex);
2553 
2554         case TABLE_IN_ADDR:
2555                 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
2556 
2557         case TABLE_IN6_ADDR:
2558                 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
2559 
2560         case TABLE_ID128:
2561                 return json_variant_new_string(ret, SD_ID128_TO_STRING(d->id128));
2562 
2563         case TABLE_UUID:
2564                 return json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(d->id128));
2565 
2566         case TABLE_UID:
2567                 if (!uid_is_valid(d->uid))
2568                         return json_variant_new_null(ret);
2569 
2570                 return json_variant_new_integer(ret, d->uid);
2571 
2572         case TABLE_GID:
2573                 if (!gid_is_valid(d->gid))
2574                         return json_variant_new_null(ret);
2575 
2576                 return json_variant_new_integer(ret, d->gid);
2577 
2578         case TABLE_PID:
2579                 if (!pid_is_valid(d->pid))
2580                         return json_variant_new_null(ret);
2581 
2582                 return json_variant_new_integer(ret, d->pid);
2583 
2584         case TABLE_SIGNAL:
2585                 if (!SIGNAL_VALID(d->int_val))
2586                         return json_variant_new_null(ret);
2587 
2588                 return json_variant_new_integer(ret, d->int_val);
2589 
2590         case TABLE_MODE:
2591                 if (d->mode == MODE_INVALID)
2592                         return json_variant_new_null(ret);
2593 
2594                 return json_variant_new_unsigned(ret, d->mode);
2595 
2596         default:
2597                 return -EINVAL;
2598         }
2599 }
2600 
string_to_json_field_name(const char * f)2601 static char* string_to_json_field_name(const char *f) {
2602         /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2603          * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2604          * underscores and leave everything as is. */
2605 
2606         char *c = strdup(f);
2607         if (!c)
2608                 return NULL;
2609 
2610         for (char *x = c; *x; x++)
2611                 if (isspace(*x))
2612                         *x = '_';
2613 
2614         return c;
2615 }
2616 
table_get_json_field_name(Table * t,size_t column)2617 static const char *table_get_json_field_name(Table *t, size_t column) {
2618         assert(t);
2619 
2620         return column < t->n_json_fields ? t->json_fields[column] : NULL;
2621 }
2622 
table_to_json(Table * t,JsonVariant ** ret)2623 int table_to_json(Table *t, JsonVariant **ret) {
2624         JsonVariant **rows = NULL, **elements = NULL;
2625         _cleanup_free_ size_t *sorted = NULL;
2626         size_t n_rows, display_columns;
2627         int r;
2628 
2629         assert(t);
2630 
2631         /* Ensure we have no incomplete rows */
2632         assert(t->n_cells % t->n_columns == 0);
2633 
2634         n_rows = t->n_cells / t->n_columns;
2635         assert(n_rows > 0); /* at least the header row must be complete */
2636 
2637         if (t->sort_map) {
2638                 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2639 
2640                 sorted = new(size_t, n_rows);
2641                 if (!sorted) {
2642                         r = -ENOMEM;
2643                         goto finish;
2644                 }
2645 
2646                 for (size_t i = 0; i < n_rows; i++)
2647                         sorted[i] = i * t->n_columns;
2648 
2649                 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2650         }
2651 
2652         if (t->display_map)
2653                 display_columns = t->n_display_map;
2654         else
2655                 display_columns = t->n_columns;
2656         assert(display_columns > 0);
2657 
2658         elements = new0(JsonVariant*, display_columns * 2);
2659         if (!elements) {
2660                 r = -ENOMEM;
2661                 goto finish;
2662         }
2663 
2664         for (size_t j = 0; j < display_columns; j++) {
2665                 _cleanup_free_ char *mangled = NULL;
2666                 const char *n;
2667                 size_t c;
2668 
2669                 c = t->display_map ? t->display_map[j] : j;
2670 
2671                 /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
2672                 n = table_get_json_field_name(t, c);
2673                 if (!n) {
2674                         const char *formatted;
2675                         TableData *d;
2676 
2677                         assert_se(d = t->data[c]);
2678 
2679                         /* Field names must be strings, hence format whatever we got here as a string first */
2680                         formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
2681                         if (!formatted) {
2682                                 r = -ENOMEM;
2683                                 goto finish;
2684                         }
2685 
2686                         /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2687                         mangled = string_to_json_field_name(formatted);
2688                         if (!mangled) {
2689                                 r = -ENOMEM;
2690                                 goto finish;
2691                         }
2692                         n = mangled;
2693                 }
2694 
2695                 r = json_variant_new_string(elements + j*2, n);
2696                 if (r < 0)
2697                         goto finish;
2698         }
2699 
2700         rows = new0(JsonVariant*, n_rows-1);
2701         if (!rows) {
2702                 r = -ENOMEM;
2703                 goto finish;
2704         }
2705 
2706         for (size_t i = 1; i < n_rows; i++) {
2707                 TableData **row;
2708 
2709                 if (sorted)
2710                         row = t->data + sorted[i];
2711                 else
2712                         row = t->data + i * t->n_columns;
2713 
2714                 for (size_t j = 0; j < display_columns; j++) {
2715                         TableData *d;
2716                         size_t k;
2717 
2718                         assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2719 
2720                         k = j*2+1;
2721                         elements[k] = json_variant_unref(elements[k]);
2722 
2723                         r = table_data_to_json(d, elements + k);
2724                         if (r < 0)
2725                                 goto finish;
2726                 }
2727 
2728                 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2729                 if (r < 0)
2730                         goto finish;
2731         }
2732 
2733         r = json_variant_new_array(ret, rows, n_rows - 1);
2734 
2735 finish:
2736         if (rows) {
2737                 json_variant_unref_many(rows, n_rows-1);
2738                 free(rows);
2739         }
2740 
2741         if (elements) {
2742                 json_variant_unref_many(elements, display_columns*2);
2743                 free(elements);
2744         }
2745 
2746         return r;
2747 }
2748 
table_print_json(Table * t,FILE * f,JsonFormatFlags flags)2749 int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2750         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2751         int r;
2752 
2753         assert(t);
2754 
2755         if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */
2756                 return table_print(t, f);
2757 
2758         if (!f)
2759                 f = stdout;
2760 
2761         r = table_to_json(t, &v);
2762         if (r < 0)
2763                 return r;
2764 
2765         json_variant_dump(v, flags, f, NULL);
2766 
2767         return fflush_and_check(f);
2768 }
2769 
table_print_with_pager(Table * t,JsonFormatFlags json_format_flags,PagerFlags pager_flags,bool show_header)2770 int table_print_with_pager(
2771                 Table *t,
2772                 JsonFormatFlags json_format_flags,
2773                 PagerFlags pager_flags,
2774                 bool show_header) {
2775 
2776         bool saved_header;
2777         int r;
2778 
2779         assert(t);
2780 
2781         /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
2782          * the table header and logs about any error. */
2783 
2784         if (json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
2785                 pager_open(pager_flags);
2786 
2787         saved_header = t->header;
2788         t->header = show_header;
2789         r = table_print_json(t, stdout, json_format_flags);
2790         t->header = saved_header;
2791         if (r < 0)
2792                 return table_log_print_error(r);
2793 
2794         return 0;
2795 }
2796 
table_set_json_field_name(Table * t,size_t column,const char * name)2797 int table_set_json_field_name(Table *t, size_t column, const char *name) {
2798         int r;
2799 
2800         assert(t);
2801 
2802         if (name) {
2803                 size_t m;
2804 
2805                 m = MAX(column + 1, t->n_json_fields);
2806                 if (!GREEDY_REALLOC0(t->json_fields, m))
2807                         return -ENOMEM;
2808 
2809                 r = free_and_strdup(t->json_fields + column, name);
2810                 if (r < 0)
2811                         return r;
2812 
2813                 t->n_json_fields = m;
2814                 return r;
2815         } else {
2816                 if (column >= t->n_json_fields)
2817                         return 0;
2818 
2819                 t->json_fields[column] = mfree(t->json_fields[column]);
2820                 return 1;
2821         }
2822 }
2823