]> git.proxmox.com Git - systemd.git/blame - src/shared/format-table.c
bump version to 252.11-pve1
[systemd.git] / src / shared / format-table.c
CommitLineData
a032b68d 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
b012e921 2
6e866b33 3#include <ctype.h>
f2dec872 4#include <net/if.h>
e1f67bc7 5#include <unistd.h>
b012e921 6
46cdbd49
BR
7#include "sd-id128.h"
8
b012e921
MB
9#include "alloc-util.h"
10#include "fd-util.h"
11#include "fileio.h"
12#include "format-table.h"
f2dec872 13#include "format-util.h"
ea0999c9
MB
14#include "fs-util.h"
15#include "glyph-util.h"
b012e921 16#include "gunicode.h"
46cdbd49 17#include "id128-util.h"
f2dec872 18#include "in-addr-util.h"
bb4f798a 19#include "memory-util.h"
b012e921
MB
20#include "pager.h"
21#include "parse-util.h"
46cdbd49 22#include "path-util.h"
6e866b33 23#include "pretty-print.h"
3a6ce677
BR
24#include "process-util.h"
25#include "signal-util.h"
bb4f798a 26#include "sort-util.h"
b012e921 27#include "string-util.h"
f2dec872 28#include "strxcpyx.h"
b012e921
MB
29#include "terminal-util.h"
30#include "time-util.h"
3a6ce677 31#include "user-util.h"
b012e921
MB
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
6e866b33 56 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
b012e921
MB
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
67typedef 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 */
a032b68d 73 size_t formatted_for_width; /* the width we tried to format for */
b012e921
MB
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
6e866b33
MB
78 bool uppercase; /* Uppercase string on display */
79
b012e921 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 */
a10f5d05 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 */
6e866b33 82 char *url; /* A URL to use for a clickable hyperlink */
b012e921
MB
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];
46cdbd49 92 char **strv;
f2dec872
BR
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;
b012e921 101 uint32_t uint32;
6e866b33
MB
102 uint64_t uint64;
103 int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
f2dec872
BR
104 int ifindex;
105 union in_addr_union address;
46cdbd49 106 sd_id128_t id128;
3a6ce677
BR
107 uid_t uid;
108 gid_t gid;
109 pid_t pid;
ea0999c9 110 mode_t mode;
b012e921
MB
111 /* … add more here as we start supporting more cell data types … */
112 };
113} TableData;
114
115static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
6e866b33 116 size_t i;
b012e921
MB
117
118 assert(cell);
119
6e866b33 120 i = PTR_TO_SIZE(cell);
b012e921
MB
121 assert(i > 0);
122
123 return i-1;
124}
125
126static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
3a6ce677 127 assert(index != SIZE_MAX);
6e866b33 128 return SIZE_TO_PTR(index + 1);
b012e921
MB
129}
130
131struct Table {
132 size_t n_columns;
133 size_t n_cells;
134
135 bool header; /* Whether to show the header row? */
086111aa
LB
136 TableErsatz ersatz; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */
137
3a6ce677 138 size_t width; /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
46cdbd49 139 * width or less wide, but not wider. Otherwise the width to format this table in. */
3a6ce677 140 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.) */
b012e921
MB
141
142 TableData **data;
b012e921
MB
143
144 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 */
145 size_t n_display_map;
146
147 size_t *sort_map; /* The columns to order rows by, in order of preference. */
148 size_t n_sort_map;
6e866b33 149
ea0999c9
MB
150 char **json_fields;
151 size_t n_json_fields;
152
6e866b33 153 bool *reverse_map;
b012e921
MB
154};
155
156Table *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,
3a6ce677
BR
168 .width = SIZE_MAX,
169 .cell_height_max = SIZE_MAX,
086111aa 170 .ersatz = TABLE_ERSATZ_EMPTY,
b012e921
MB
171 };
172
173 return TAKE_PTR(t);
174}
175
176Table *table_new_internal(const char *first_header, ...) {
177 _cleanup_(table_unrefp) Table *t = NULL;
178 size_t n_columns = 1;
179 va_list ap;
180 int r;
181
182 assert(first_header);
183
184 va_start(ap, first_header);
185 for (;;) {
a032b68d 186 if (!va_arg(ap, const char*))
b012e921
MB
187 break;
188
189 n_columns++;
190 }
191 va_end(ap);
192
193 t = table_new_raw(n_columns);
194 if (!t)
195 return NULL;
196
b012e921 197 va_start(ap, first_header);
a032b68d 198 for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
6e866b33 199 TableCell *cell;
b012e921 200
6e866b33
MB
201 r = table_add_cell(t, &cell, TABLE_STRING, h);
202 if (r < 0) {
203 va_end(ap);
204 return NULL;
205 }
b012e921 206
6e866b33
MB
207 /* Make the table header uppercase */
208 r = table_set_uppercase(t, cell, true);
b012e921
MB
209 if (r < 0) {
210 va_end(ap);
211 return NULL;
212 }
213 }
214 va_end(ap);
215
216 assert(t->n_columns == t->n_cells);
217 return TAKE_PTR(t);
218}
219
6e866b33
MB
220static TableData *table_data_free(TableData *d) {
221 assert(d);
b012e921
MB
222
223 free(d->formatted);
6e866b33
MB
224 free(d->url);
225
a032b68d 226 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
46cdbd49
BR
227 strv_free(d->strv);
228
b012e921
MB
229 return mfree(d);
230}
231
6e866b33 232DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
b012e921
MB
233DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
234
b012e921 235Table *table_unref(Table *t) {
b012e921
MB
236 if (!t)
237 return NULL;
238
a032b68d 239 for (size_t i = 0; i < t->n_cells; i++)
b012e921
MB
240 table_data_unref(t->data[i]);
241
242 free(t->data);
243 free(t->display_map);
244 free(t->sort_map);
6e866b33 245 free(t->reverse_map);
b012e921 246
ea0999c9
MB
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
b012e921
MB
252 return mfree(t);
253}
254
255static 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:
46cdbd49 263 case TABLE_PATH:
b012e921
MB
264 return strlen(data) + 1;
265
46cdbd49 266 case TABLE_STRV:
a032b68d 267 case TABLE_STRV_WRAPPED:
46cdbd49
BR
268 return sizeof(char **);
269
ea0999c9 270 case TABLE_BOOLEAN_CHECKMARK:
b012e921
MB
271 case TABLE_BOOLEAN:
272 return sizeof(bool);
273
274 case TABLE_TIMESTAMP:
f2dec872
BR
275 case TABLE_TIMESTAMP_UTC:
276 case TABLE_TIMESTAMP_RELATIVE:
b012e921 277 case TABLE_TIMESPAN:
f2dec872 278 case TABLE_TIMESPAN_MSEC:
b012e921
MB
279 return sizeof(usec_t);
280
281 case TABLE_SIZE:
f2dec872 282 case TABLE_INT64:
6e866b33 283 case TABLE_UINT64:
ea0999c9 284 case TABLE_UINT64_HEX:
f2dec872 285 case TABLE_BPS:
b012e921
MB
286 return sizeof(uint64_t);
287
f2dec872 288 case TABLE_INT32:
b012e921
MB
289 case TABLE_UINT32:
290 return sizeof(uint32_t);
291
f2dec872
BR
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:
6e866b33 302 case TABLE_PERCENT:
f2dec872 303 case TABLE_IFINDEX:
3a6ce677 304 case TABLE_SIGNAL:
6e866b33
MB
305 return sizeof(int);
306
f2dec872
BR
307 case TABLE_IN_ADDR:
308 return sizeof(struct in_addr);
309
310 case TABLE_IN6_ADDR:
311 return sizeof(struct in6_addr);
312
46cdbd49
BR
313 case TABLE_UUID:
314 case TABLE_ID128:
315 return sizeof(sd_id128_t);
316
3a6ce677
BR
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
ea0999c9
MB
324 case TABLE_MODE:
325 return sizeof(mode_t);
326
b012e921 327 default:
ea0999c9 328 assert_not_reached();
b012e921
MB
329 }
330}
331
332static 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
6e866b33 363 /* If a color/url/uppercase flag is set, refuse to merge */
a10f5d05 364 if (d->color || d->rgap_color)
6e866b33
MB
365 return false;
366 if (d->url)
367 return false;
368 if (d->uppercase)
369 return false;
370
b012e921
MB
371 k = table_data_size(type, data);
372 l = table_data_size(d->type, d->data);
b012e921
MB
373 if (k != l)
374 return false;
375
6e866b33 376 return memcmp_safe(data, d->data, l) == 0;
b012e921
MB
377}
378
379static 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
46cdbd49 388 _cleanup_free_ TableData *d = NULL;
b012e921 389 size_t data_size;
b012e921
MB
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;
b012e921 404
a032b68d 405 if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
46cdbd49
BR
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);
b012e921
MB
413}
414
415int 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
f2dec872
BR
433 /* Special rule: patch NULL data fields to the empty field */
434 if (!data)
435 type = TABLE_EMPTY;
436
b012e921
MB
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 */
3a6ce677 444 if (minimum_width == SIZE_MAX)
b012e921
MB
445 minimum_width = p ? p->minimum_width : 1;
446
3a6ce677 447 if (weight == UINT_MAX)
b012e921
MB
448 weight = p ? p->weight : DEFAULT_WEIGHT;
449
3a6ce677 450 if (align_percent == UINT_MAX)
b012e921
MB
451 align_percent = p ? p->align_percent : 0;
452
3a6ce677 453 if (ellipsize_percent == UINT_MAX)
b012e921
MB
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
8b3d4ff0 470 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
b012e921
MB
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
f2dec872
BR
481int 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
495int 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
b012e921
MB
516int 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
8b3d4ff0 527 if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
b012e921
MB
528 return -ENOMEM;
529
530 t->data[t->n_cells++] = table_data_ref(t->data[i]);
531 return 0;
532}
533
534static int table_dedup_cell(Table *t, TableCell *cell) {
6e866b33 535 _cleanup_free_ char *curl = NULL;
b012e921
MB
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
6e866b33
MB
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);
b012e921
MB
568 if (!nd)
569 return -ENOMEM;
570
6e866b33 571 nd->color = od->color;
a10f5d05 572 nd->rgap_color = od->rgap_color;
6e866b33
MB
573 nd->url = TAKE_PTR(curl);
574 nd->uppercase = od->uppercase;
575
b012e921
MB
576 table_data_unref(od);
577 t->data[i] = nd;
578
579 assert(nd->n_ref == 1);
580
581 return 1;
582}
583
584static 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
602int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
603 int r;
604
605 assert(t);
606 assert(cell);
607
3a6ce677 608 if (minimum_width == SIZE_MAX)
b012e921
MB
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
619int 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
633int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
634 int r;
635
636 assert(t);
637 assert(cell);
638
3a6ce677 639 if (weight == UINT_MAX)
b012e921
MB
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
650int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
651 int r;
652
653 assert(t);
654 assert(cell);
655
3a6ce677 656 if (percent == UINT_MAX)
b012e921
MB
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
669int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
670 int r;
671
672 assert(t);
673 assert(cell);
674
3a6ce677 675 if (percent == UINT_MAX)
b012e921
MB
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
688int 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
a10f5d05
MB
702int 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
6e866b33
MB
716int 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
736int 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
757int 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;
a10f5d05 789 nd->rgap_color = od->rgap_color;
6e866b33
MB
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
b012e921 799int table_add_many_internal(Table *t, TableDataType first_type, ...) {
f2dec872 800 TableCell *last_cell = NULL;
8b3d4ff0 801 va_list ap;
b012e921
MB
802 int r;
803
804 assert(t);
805 assert(first_type >= 0);
806 assert(first_type < _TABLE_DATA_TYPE_MAX);
807
b012e921 808 va_start(ap, first_type);
8b3d4ff0
MB
809
810 for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
b012e921
MB
811 const void *data;
812 union {
813 uint64_t size;
814 usec_t usec;
f2dec872
BR
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;
b012e921 823 uint32_t uint32;
6e866b33
MB
824 uint64_t uint64;
825 int percent;
f2dec872 826 int ifindex;
b012e921 827 bool b;
f2dec872 828 union in_addr_union address;
46cdbd49 829 sd_id128_t id128;
3a6ce677
BR
830 uid_t uid;
831 gid_t gid;
832 pid_t pid;
ea0999c9 833 mode_t mode;
b012e921
MB
834 } buffer;
835
836 switch (type) {
837
838 case TABLE_EMPTY:
839 data = NULL;
840 break;
841
842 case TABLE_STRING:
46cdbd49 843 case TABLE_PATH:
b012e921
MB
844 data = va_arg(ap, const char *);
845 break;
846
46cdbd49 847 case TABLE_STRV:
a032b68d 848 case TABLE_STRV_WRAPPED:
46cdbd49
BR
849 data = va_arg(ap, char * const *);
850 break;
851
ea0999c9 852 case TABLE_BOOLEAN_CHECKMARK:
b012e921
MB
853 case TABLE_BOOLEAN:
854 buffer.b = va_arg(ap, int);
855 data = &buffer.b;
856 break;
857
858 case TABLE_TIMESTAMP:
f2dec872
BR
859 case TABLE_TIMESTAMP_UTC:
860 case TABLE_TIMESTAMP_RELATIVE:
b012e921 861 case TABLE_TIMESPAN:
f2dec872 862 case TABLE_TIMESPAN_MSEC:
b012e921
MB
863 buffer.usec = va_arg(ap, usec_t);
864 data = &buffer.usec;
865 break;
866
867 case TABLE_SIZE:
f2dec872 868 case TABLE_BPS:
b012e921
MB
869 buffer.size = va_arg(ap, uint64_t);
870 data = &buffer.size;
871 break;
872
f2dec872 873 case TABLE_INT:
3a6ce677 874 case TABLE_SIGNAL:
f2dec872
BR
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
b012e921
MB
930 case TABLE_UINT32:
931 buffer.uint32 = va_arg(ap, uint32_t);
932 data = &buffer.uint32;
933 break;
934
6e866b33 935 case TABLE_UINT64:
ea0999c9 936 case TABLE_UINT64_HEX:
6e866b33
MB
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
f2dec872
BR
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
46cdbd49
BR
961 case TABLE_UUID:
962 case TABLE_ID128:
963 buffer.id128 = va_arg(ap, sd_id128_t);
964 data = &buffer.id128;
965 break;
966
3a6ce677
BR
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
ea0999c9
MB
982 case TABLE_MODE:
983 buffer.mode = va_arg(ap, mode_t);
984 data = &buffer.mode;
985 break;
986
f2dec872
BR
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);
8b3d4ff0 991 goto check;
f2dec872
BR
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);
8b3d4ff0 997 goto check;
f2dec872
BR
998 }
999
1000 case TABLE_SET_WEIGHT: {
1001 unsigned w = va_arg(ap, unsigned);
1002 r = table_set_weight(t, last_cell, w);
8b3d4ff0 1003 goto check;
f2dec872
BR
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);
8b3d4ff0 1009 goto check;
f2dec872
BR
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);
8b3d4ff0 1015 goto check;
f2dec872
BR
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);
8b3d4ff0 1021 goto check;
f2dec872
BR
1022 }
1023
a10f5d05
MB
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);
8b3d4ff0 1027 goto check;
a10f5d05
MB
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);
8b3d4ff0 1040 goto check;
a10f5d05
MB
1041 }
1042
f2dec872
BR
1043 case TABLE_SET_URL: {
1044 const char *u = va_arg(ap, const char*);
1045 r = table_set_url(t, last_cell, u);
8b3d4ff0 1046 goto check;
f2dec872
BR
1047 }
1048
1049 case TABLE_SET_UPPERCASE: {
1050 int u = va_arg(ap, int);
1051 r = table_set_uppercase(t, last_cell, u);
8b3d4ff0 1052 goto check;
f2dec872
BR
1053 }
1054
b012e921
MB
1055 case _TABLE_DATA_TYPE_MAX:
1056 /* Used as end marker */
1057 va_end(ap);
1058 return 0;
1059
1060 default:
ea0999c9 1061 assert_not_reached();
b012e921
MB
1062 }
1063
8b3d4ff0
MB
1064 r = table_add_cell(t, &last_cell, type, data);
1065 check:
b012e921
MB
1066 if (r < 0) {
1067 va_end(ap);
1068 return r;
1069 }
b012e921
MB
1070 }
1071}
1072
1073void table_set_header(Table *t, bool b) {
1074 assert(t);
1075
1076 t->header = b;
1077}
1078
1079void table_set_width(Table *t, size_t width) {
1080 assert(t);
1081
1082 t->width = width;
1083}
1084
46cdbd49
BR
1085void table_set_cell_height_max(Table *t, size_t height) {
1086 assert(t);
3a6ce677 1087 assert(height >= 1 || height == SIZE_MAX);
46cdbd49
BR
1088
1089 t->cell_height_max = height;
1090}
1091
086111aa 1092void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
f2dec872 1093 assert(t);
086111aa
LB
1094 assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
1095
1096 t->ersatz = ersatz;
1097}
f2dec872 1098
086111aa
LB
1099static const char* table_ersatz_string(const Table *t) {
1100 switch (t->ersatz) {
1101 case TABLE_ERSATZ_EMPTY:
1102 return "";
1103 case TABLE_ERSATZ_DASH:
1104 return "-";
1105 case TABLE_ERSATZ_UNSET:
1106 return "(unset)";
1107 case TABLE_ERSATZ_NA:
1108 return "n/a";
1109 default:
1110 assert_not_reached();
1111 }
f2dec872
BR
1112}
1113
3a6ce677
BR
1114static int table_set_display_all(Table *t) {
1115 size_t *d;
1116
cb695f0e
MB
1117 assert(t);
1118
3a6ce677 1119 /* Initialize the display map to the identity */
cb695f0e 1120
3a6ce677
BR
1121 d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
1122 if (!d)
cb695f0e
MB
1123 return -ENOMEM;
1124
1125 for (size_t i = 0; i < t->n_columns; i++)
3a6ce677 1126 d[i] = i;
cb695f0e 1127
3a6ce677 1128 t->display_map = d;
cb695f0e
MB
1129 t->n_display_map = t->n_columns;
1130
1131 return 0;
1132}
1133
3a6ce677 1134int table_set_display_internal(Table *t, size_t first_column, ...) {
8b3d4ff0 1135 size_t column;
b012e921
MB
1136 va_list ap;
1137
1138 assert(t);
1139
b012e921
MB
1140 column = first_column;
1141
1142 va_start(ap, first_column);
1143 for (;;) {
1144 assert(column < t->n_columns);
1145
8b3d4ff0 1146 if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
b012e921
MB
1147 va_end(ap);
1148 return -ENOMEM;
1149 }
1150
1151 t->display_map[t->n_display_map++] = column;
1152
1153 column = va_arg(ap, size_t);
3a6ce677 1154 if (column == SIZE_MAX)
b012e921
MB
1155 break;
1156
1157 }
1158 va_end(ap);
1159
1160 return 0;
1161}
1162
3a6ce677 1163int table_set_sort_internal(Table *t, size_t first_column, ...) {
8b3d4ff0 1164 size_t column;
b012e921
MB
1165 va_list ap;
1166
1167 assert(t);
1168
b012e921
MB
1169 column = first_column;
1170
1171 va_start(ap, first_column);
1172 for (;;) {
1173 assert(column < t->n_columns);
1174
8b3d4ff0 1175 if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
b012e921
MB
1176 va_end(ap);
1177 return -ENOMEM;
1178 }
1179
1180 t->sort_map[t->n_sort_map++] = column;
1181
1182 column = va_arg(ap, size_t);
3a6ce677 1183 if (column == SIZE_MAX)
b012e921
MB
1184 break;
1185 }
1186 va_end(ap);
1187
1188 return 0;
1189}
1190
ea0999c9
MB
1191int table_hide_column_from_display_internal(Table *t, ...) {
1192 size_t cur = 0;
cb695f0e
MB
1193 int r;
1194
1195 assert(t);
cb695f0e
MB
1196
1197 /* If the display map is empty, initialize it with all available columns */
1198 if (!t->display_map) {
1199 r = table_set_display_all(t);
1200 if (r < 0)
1201 return r;
1202 }
1203
ea0999c9
MB
1204 for (size_t i = 0; i < t->n_display_map; i++) {
1205 bool listed = false;
1206 va_list ap;
1207
1208 va_start(ap, t);
1209 for (;;) {
1210 size_t column;
1211
1212 column = va_arg(ap, size_t);
1213 if (column == SIZE_MAX)
1214 break;
1215 if (column == t->display_map[i]) {
1216 listed = true;
1217 break;
1218 }
1219 }
1220 va_end(ap);
cb695f0e 1221
ea0999c9 1222 if (listed)
cb695f0e
MB
1223 continue;
1224
1225 t->display_map[cur++] = t->display_map[i];
1226 }
1227
1228 t->n_display_map = cur;
1229
1230 return 0;
1231}
1232
b012e921
MB
1233static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
1234 assert(a);
1235 assert(b);
1236
1237 if (a->type == b->type) {
1238
1239 /* We only define ordering for cells of the same data type. If cells with different data types are
1240 * compared we follow the order the cells were originally added in */
1241
1242 switch (a->type) {
1243
1244 case TABLE_STRING:
1245 return strcmp(a->string, b->string);
1246
46cdbd49
BR
1247 case TABLE_PATH:
1248 return path_compare(a->string, b->string);
1249
1250 case TABLE_STRV:
a032b68d 1251 case TABLE_STRV_WRAPPED:
46cdbd49
BR
1252 return strv_compare(a->strv, b->strv);
1253
b012e921
MB
1254 case TABLE_BOOLEAN:
1255 if (!a->boolean && b->boolean)
1256 return -1;
1257 if (a->boolean && !b->boolean)
1258 return 1;
1259 return 0;
1260
1261 case TABLE_TIMESTAMP:
f2dec872
BR
1262 case TABLE_TIMESTAMP_UTC:
1263 case TABLE_TIMESTAMP_RELATIVE:
6e866b33 1264 return CMP(a->timestamp, b->timestamp);
b012e921
MB
1265
1266 case TABLE_TIMESPAN:
f2dec872 1267 case TABLE_TIMESPAN_MSEC:
6e866b33 1268 return CMP(a->timespan, b->timespan);
b012e921
MB
1269
1270 case TABLE_SIZE:
f2dec872 1271 case TABLE_BPS:
6e866b33 1272 return CMP(a->size, b->size);
b012e921 1273
f2dec872 1274 case TABLE_INT:
3a6ce677 1275 case TABLE_SIGNAL:
f2dec872
BR
1276 return CMP(a->int_val, b->int_val);
1277
1278 case TABLE_INT8:
1279 return CMP(a->int8, b->int8);
1280
1281 case TABLE_INT16:
1282 return CMP(a->int16, b->int16);
1283
1284 case TABLE_INT32:
1285 return CMP(a->int32, b->int32);
1286
1287 case TABLE_INT64:
1288 return CMP(a->int64, b->int64);
1289
1290 case TABLE_UINT:
1291 return CMP(a->uint_val, b->uint_val);
1292
1293 case TABLE_UINT8:
1294 return CMP(a->uint8, b->uint8);
1295
1296 case TABLE_UINT16:
1297 return CMP(a->uint16, b->uint16);
1298
b012e921 1299 case TABLE_UINT32:
6e866b33
MB
1300 return CMP(a->uint32, b->uint32);
1301
1302 case TABLE_UINT64:
ea0999c9 1303 case TABLE_UINT64_HEX:
6e866b33
MB
1304 return CMP(a->uint64, b->uint64);
1305
1306 case TABLE_PERCENT:
1307 return CMP(a->percent, b->percent);
b012e921 1308
f2dec872
BR
1309 case TABLE_IFINDEX:
1310 return CMP(a->ifindex, b->ifindex);
1311
1312 case TABLE_IN_ADDR:
1313 return CMP(a->address.in.s_addr, b->address.in.s_addr);
1314
1315 case TABLE_IN6_ADDR:
1316 return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
1317
46cdbd49
BR
1318 case TABLE_UUID:
1319 case TABLE_ID128:
1320 return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
1321
3a6ce677
BR
1322 case TABLE_UID:
1323 return CMP(a->uid, b->uid);
1324
1325 case TABLE_GID:
1326 return CMP(a->gid, b->gid);
1327
1328 case TABLE_PID:
1329 return CMP(a->pid, b->pid);
1330
ea0999c9
MB
1331 case TABLE_MODE:
1332 return CMP(a->mode, b->mode);
1333
b012e921
MB
1334 default:
1335 ;
1336 }
1337 }
1338
f2dec872 1339 /* Generic fallback using the original order in which the cells where added. */
6e866b33 1340 return CMP(index_a, index_b);
b012e921
MB
1341}
1342
6e866b33 1343static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
b012e921
MB
1344 int r;
1345
1346 assert(t);
1347 assert(t->sort_map);
1348
1349 /* Make sure the header stays at the beginning */
1350 if (*a < t->n_columns && *b < t->n_columns)
1351 return 0;
1352 if (*a < t->n_columns)
1353 return -1;
1354 if (*b < t->n_columns)
1355 return 1;
1356
1357 /* Order other lines by the sorting map */
a032b68d 1358 for (size_t i = 0; i < t->n_sort_map; i++) {
b012e921
MB
1359 TableData *d, *dd;
1360
1361 d = t->data[*a + t->sort_map[i]];
1362 dd = t->data[*b + t->sort_map[i]];
1363
1364 r = cell_data_compare(d, *a, dd, *b);
1365 if (r != 0)
6e866b33 1366 return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
b012e921
MB
1367 }
1368
1369 /* Order identical lines by the order there were originally added in */
6e866b33 1370 return CMP(*a, *b);
b012e921
MB
1371}
1372
a032b68d 1373static char* format_strv_width(char **strv, size_t column_width) {
3a6ce677 1374 _cleanup_free_ char *buf = NULL; /* buf must be freed after f */
a032b68d
MB
1375 _cleanup_fclose_ FILE *f = NULL;
1376 size_t sz = 0;
a032b68d
MB
1377
1378 f = open_memstream_unlocked(&buf, &sz);
1379 if (!f)
1380 return NULL;
1381
1382 size_t position = 0;
a032b68d
MB
1383 STRV_FOREACH(p, strv) {
1384 size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1385 * If that happens, we'll just print one item per line. */
1386
1387 if (position == 0) {
1388 fputs(*p, f);
1389 position = our_len;
1390 } else if (size_add(size_add(position, 1), our_len) <= column_width) {
1391 fprintf(f, " %s", *p);
1392 position = size_add(size_add(position, 1), our_len);
1393 } else {
1394 fprintf(f, "\n%s", *p);
1395 position = our_len;
1396 }
1397 }
1398
1399 if (fflush_and_check(f) < 0)
1400 return NULL;
1401
1402 f = safe_fclose(f);
1403 return TAKE_PTR(buf);
1404}
1405
1406static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
b012e921
MB
1407 assert(d);
1408
a032b68d
MB
1409 if (d->formatted &&
1410 /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1411 (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
b012e921
MB
1412 return d->formatted;
1413
1414 switch (d->type) {
1415 case TABLE_EMPTY:
086111aa 1416 return table_ersatz_string(t);
b012e921
MB
1417
1418 case TABLE_STRING:
46cdbd49 1419 case TABLE_PATH:
a10f5d05 1420 if (d->uppercase && !avoid_uppercasing) {
6e866b33
MB
1421 d->formatted = new(char, strlen(d->string) + 1);
1422 if (!d->formatted)
1423 return NULL;
1424
a032b68d
MB
1425 char *q = d->formatted;
1426 for (char *p = d->string; *p; p++, q++)
6e866b33
MB
1427 *q = (char) toupper((unsigned char) *p);
1428 *q = 0;
1429
1430 return d->formatted;
1431 }
1432
b012e921
MB
1433 return d->string;
1434
a032b68d
MB
1435 case TABLE_STRV:
1436 if (strv_isempty(d->strv))
086111aa 1437 return table_ersatz_string(t);
46cdbd49 1438
a032b68d
MB
1439 d->formatted = strv_join(d->strv, "\n");
1440 if (!d->formatted)
1441 return NULL;
1442 break;
1443
1444 case TABLE_STRV_WRAPPED: {
a10f5d05 1445 if (strv_isempty(d->strv))
086111aa 1446 return table_ersatz_string(t);
a10f5d05 1447
a032b68d
MB
1448 char *buf = format_strv_width(d->strv, column_width);
1449 if (!buf)
46cdbd49
BR
1450 return NULL;
1451
a032b68d
MB
1452 free_and_replace(d->formatted, buf);
1453 d->formatted_for_width = column_width;
1454 if (have_soft)
1455 *have_soft = true;
1456
46cdbd49
BR
1457 break;
1458 }
1459
b012e921
MB
1460 case TABLE_BOOLEAN:
1461 return yes_no(d->boolean);
1462
ea0999c9
MB
1463 case TABLE_BOOLEAN_CHECKMARK:
1464 return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
1465
f2dec872
BR
1466 case TABLE_TIMESTAMP:
1467 case TABLE_TIMESTAMP_UTC:
1468 case TABLE_TIMESTAMP_RELATIVE: {
5b5a102a 1469 _cleanup_free_ char *p = NULL;
f2dec872 1470 char *ret;
b012e921 1471
0e1568bb 1472 p = new(char, d->type == TABLE_TIMESTAMP_RELATIVE ? FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
b012e921
MB
1473 if (!p)
1474 return NULL;
1475
f2dec872
BR
1476 if (d->type == TABLE_TIMESTAMP)
1477 ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1478 else if (d->type == TABLE_TIMESTAMP_UTC)
a032b68d 1479 ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
f2dec872 1480 else
0e1568bb 1481 ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
f2dec872 1482 if (!ret)
086111aa 1483 return "-";
b012e921
MB
1484
1485 d->formatted = TAKE_PTR(p);
1486 break;
1487 }
1488
f2dec872
BR
1489 case TABLE_TIMESPAN:
1490 case TABLE_TIMESPAN_MSEC: {
5b5a102a 1491 _cleanup_free_ char *p = NULL;
b012e921
MB
1492
1493 p = new(char, FORMAT_TIMESPAN_MAX);
1494 if (!p)
1495 return NULL;
1496
f2dec872
BR
1497 if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
1498 d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
086111aa 1499 return "-";
b012e921
MB
1500
1501 d->formatted = TAKE_PTR(p);
1502 break;
1503 }
1504
1505 case TABLE_SIZE: {
5b5a102a 1506 _cleanup_free_ char *p = NULL;
b012e921
MB
1507
1508 p = new(char, FORMAT_BYTES_MAX);
1509 if (!p)
1510 return NULL;
1511
1512 if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
086111aa 1513 return table_ersatz_string(t);
b012e921
MB
1514
1515 d->formatted = TAKE_PTR(p);
1516 break;
1517 }
1518
f2dec872 1519 case TABLE_BPS: {
5b5a102a 1520 _cleanup_free_ char *p = NULL;
f2dec872
BR
1521 size_t n;
1522
1523 p = new(char, FORMAT_BYTES_MAX+2);
1524 if (!p)
1525 return NULL;
1526
1527 if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
086111aa 1528 return table_ersatz_string(t);
f2dec872
BR
1529
1530 n = strlen(p);
1531 strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
1532
1533 d->formatted = TAKE_PTR(p);
1534 break;
1535 }
1536
1537 case TABLE_INT: {
5b5a102a 1538 _cleanup_free_ char *p = NULL;
f2dec872
BR
1539
1540 p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
1541 if (!p)
1542 return NULL;
1543
1544 sprintf(p, "%i", d->int_val);
1545 d->formatted = TAKE_PTR(p);
1546 break;
1547 }
1548
1549 case TABLE_INT8: {
5b5a102a 1550 _cleanup_free_ char *p = NULL;
f2dec872
BR
1551
1552 p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
1553 if (!p)
1554 return NULL;
1555
1556 sprintf(p, "%" PRIi8, d->int8);
1557 d->formatted = TAKE_PTR(p);
1558 break;
1559 }
1560
1561 case TABLE_INT16: {
5b5a102a 1562 _cleanup_free_ char *p = NULL;
f2dec872
BR
1563
1564 p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
1565 if (!p)
1566 return NULL;
1567
1568 sprintf(p, "%" PRIi16, d->int16);
1569 d->formatted = TAKE_PTR(p);
1570 break;
1571 }
1572
1573 case TABLE_INT32: {
5b5a102a 1574 _cleanup_free_ char *p = NULL;
f2dec872
BR
1575
1576 p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
1577 if (!p)
1578 return NULL;
1579
1580 sprintf(p, "%" PRIi32, d->int32);
1581 d->formatted = TAKE_PTR(p);
1582 break;
1583 }
1584
1585 case TABLE_INT64: {
5b5a102a 1586 _cleanup_free_ char *p = NULL;
f2dec872
BR
1587
1588 p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
1589 if (!p)
1590 return NULL;
1591
1592 sprintf(p, "%" PRIi64, d->int64);
1593 d->formatted = TAKE_PTR(p);
1594 break;
1595 }
1596
1597 case TABLE_UINT: {
5b5a102a 1598 _cleanup_free_ char *p = NULL;
f2dec872
BR
1599
1600 p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
1601 if (!p)
1602 return NULL;
1603
1604 sprintf(p, "%u", d->uint_val);
1605 d->formatted = TAKE_PTR(p);
1606 break;
1607 }
1608
1609 case TABLE_UINT8: {
5b5a102a 1610 _cleanup_free_ char *p = NULL;
f2dec872
BR
1611
1612 p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
1613 if (!p)
1614 return NULL;
1615
1616 sprintf(p, "%" PRIu8, d->uint8);
1617 d->formatted = TAKE_PTR(p);
1618 break;
1619 }
1620
1621 case TABLE_UINT16: {
5b5a102a 1622 _cleanup_free_ char *p = NULL;
f2dec872
BR
1623
1624 p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
1625 if (!p)
1626 return NULL;
1627
1628 sprintf(p, "%" PRIu16, d->uint16);
1629 d->formatted = TAKE_PTR(p);
1630 break;
1631 }
1632
b012e921 1633 case TABLE_UINT32: {
5b5a102a 1634 _cleanup_free_ char *p = NULL;
b012e921
MB
1635
1636 p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1637 if (!p)
1638 return NULL;
1639
1640 sprintf(p, "%" PRIu32, d->uint32);
1641 d->formatted = TAKE_PTR(p);
1642 break;
1643 }
1644
6e866b33 1645 case TABLE_UINT64: {
5b5a102a 1646 _cleanup_free_ char *p = NULL;
6e866b33
MB
1647
1648 p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
1649 if (!p)
1650 return NULL;
1651
1652 sprintf(p, "%" PRIu64, d->uint64);
1653 d->formatted = TAKE_PTR(p);
1654 break;
1655 }
1656
ea0999c9
MB
1657 case TABLE_UINT64_HEX: {
1658 _cleanup_free_ char *p = NULL;
1659
1660 p = new(char, 16 + 1);
1661 if (!p)
1662 return NULL;
1663
1664 sprintf(p, "%" PRIx64, d->uint64);
1665 d->formatted = TAKE_PTR(p);
1666 break;
1667 }
1668
6e866b33 1669 case TABLE_PERCENT: {
5b5a102a 1670 _cleanup_free_ char *p = NULL;
6e866b33
MB
1671
1672 p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
1673 if (!p)
1674 return NULL;
1675
1676 sprintf(p, "%i%%" , d->percent);
1677 d->formatted = TAKE_PTR(p);
1678 break;
1679 }
1680
f2dec872
BR
1681 case TABLE_IFINDEX: {
1682 _cleanup_free_ char *p = NULL;
f2dec872 1683
ea0999c9
MB
1684 if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
1685 return NULL;
f2dec872
BR
1686
1687 d->formatted = TAKE_PTR(p);
1688 break;
1689 }
1690
1691 case TABLE_IN_ADDR:
1692 case TABLE_IN6_ADDR: {
1693 _cleanup_free_ char *p = NULL;
1694
1695 if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
1696 &d->address, &p) < 0)
1697 return NULL;
1698
1699 d->formatted = TAKE_PTR(p);
1700 break;
1701 }
1702
46cdbd49
BR
1703 case TABLE_ID128: {
1704 char *p;
1705
1706 p = new(char, SD_ID128_STRING_MAX);
1707 if (!p)
1708 return NULL;
1709
1710 d->formatted = sd_id128_to_string(d->id128, p);
1711 break;
1712 }
1713
1714 case TABLE_UUID: {
1715 char *p;
1716
f5caa8fa 1717 p = new(char, SD_ID128_UUID_STRING_MAX);
46cdbd49
BR
1718 if (!p)
1719 return NULL;
1720
f5caa8fa 1721 d->formatted = sd_id128_to_uuid_string(d->id128, p);
46cdbd49
BR
1722 break;
1723 }
1724
3a6ce677 1725 case TABLE_UID: {
ea0999c9 1726 char *p;
3a6ce677
BR
1727
1728 if (!uid_is_valid(d->uid))
086111aa 1729 return table_ersatz_string(t);
3a6ce677
BR
1730
1731 p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1732 if (!p)
1733 return NULL;
3a6ce677 1734 sprintf(p, UID_FMT, d->uid);
ea0999c9
MB
1735
1736 d->formatted = p;
3a6ce677
BR
1737 break;
1738 }
1739
1740 case TABLE_GID: {
ea0999c9 1741 char *p;
3a6ce677
BR
1742
1743 if (!gid_is_valid(d->gid))
086111aa 1744 return table_ersatz_string(t);
3a6ce677
BR
1745
1746 p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1747 if (!p)
1748 return NULL;
3a6ce677 1749 sprintf(p, GID_FMT, d->gid);
ea0999c9
MB
1750
1751 d->formatted = p;
3a6ce677
BR
1752 break;
1753 }
1754
1755 case TABLE_PID: {
ea0999c9 1756 char *p;
3a6ce677
BR
1757
1758 if (!pid_is_valid(d->pid))
086111aa 1759 return table_ersatz_string(t);
3a6ce677
BR
1760
1761 p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
1762 if (!p)
1763 return NULL;
3a6ce677 1764 sprintf(p, PID_FMT, d->pid);
ea0999c9
MB
1765
1766 d->formatted = p;
3a6ce677
BR
1767 break;
1768 }
1769
1770 case TABLE_SIGNAL: {
3a6ce677 1771 const char *suffix;
ea0999c9 1772 char *p;
3a6ce677
BR
1773
1774 suffix = signal_to_string(d->int_val);
1775 if (!suffix)
086111aa 1776 return table_ersatz_string(t);
3a6ce677
BR
1777
1778 p = strjoin("SIG", suffix);
1779 if (!p)
1780 return NULL;
1781
ea0999c9
MB
1782 d->formatted = p;
1783 break;
1784 }
1785
1786 case TABLE_MODE: {
1787 char *p;
1788
1789 if (d->mode == MODE_INVALID)
086111aa 1790 return table_ersatz_string(t);
ea0999c9
MB
1791
1792 p = new(char, 4 + 1);
1793 if (!p)
1794 return NULL;
1795
1796 sprintf(p, "%04o", d->mode & 07777);
1797 d->formatted = p;
3a6ce677
BR
1798 break;
1799 }
1800
b012e921 1801 default:
ea0999c9 1802 assert_not_reached();
b012e921
MB
1803 }
1804
1805 return d->formatted;
1806}
1807
46cdbd49
BR
1808static int console_width_height(
1809 const char *s,
1810 size_t *ret_width,
1811 size_t *ret_height) {
1812
1813 size_t max_width = 0, height = 0;
1814 const char *p;
1815
1816 assert(s);
1817
1818 /* Determine the width and height in console character cells the specified string needs. */
1819
1820 do {
1821 size_t k;
1822
1823 p = strchr(s, '\n');
1824 if (p) {
1825 _cleanup_free_ char *c = NULL;
1826
1827 c = strndup(s, p - s);
1828 if (!c)
1829 return -ENOMEM;
1830
1831 k = utf8_console_width(c);
1832 s = p + 1;
1833 } else {
1834 k = utf8_console_width(s);
1835 s = NULL;
1836 }
3a6ce677 1837 if (k == SIZE_MAX)
46cdbd49
BR
1838 return -EINVAL;
1839 if (k > max_width)
1840 max_width = k;
1841
1842 height++;
1843 } while (!isempty(s));
1844
1845 if (ret_width)
1846 *ret_width = max_width;
1847
1848 if (ret_height)
1849 *ret_height = height;
1850
1851 return 0;
1852}
1853
1854static int table_data_requested_width_height(
1855 Table *table,
1856 TableData *d,
a032b68d 1857 size_t available_width,
46cdbd49 1858 size_t *ret_width,
a032b68d
MB
1859 size_t *ret_height,
1860 bool *have_soft) {
46cdbd49
BR
1861
1862 _cleanup_free_ char *truncated = NULL;
1863 bool truncation_applied = false;
1864 size_t width, height;
b012e921 1865 const char *t;
46cdbd49 1866 int r;
a032b68d 1867 bool soft = false;
b012e921 1868
a032b68d 1869 t = table_data_format(table, d, false, available_width, &soft);
b012e921
MB
1870 if (!t)
1871 return -ENOMEM;
1872
3a6ce677 1873 if (table->cell_height_max != SIZE_MAX) {
46cdbd49
BR
1874 r = string_truncate_lines(t, table->cell_height_max, &truncated);
1875 if (r < 0)
1876 return r;
1877 if (r > 0)
1878 truncation_applied = true;
b012e921 1879
46cdbd49
BR
1880 t = truncated;
1881 }
b012e921 1882
46cdbd49
BR
1883 r = console_width_height(t, &width, &height);
1884 if (r < 0)
1885 return r;
b012e921 1886
3a6ce677 1887 if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
46cdbd49
BR
1888 width = d->maximum_width;
1889
1890 if (width < d->minimum_width)
1891 width = d->minimum_width;
1892
1893 if (ret_width)
1894 *ret_width = width;
1895 if (ret_height)
1896 *ret_height = height;
a032b68d
MB
1897 if (have_soft && soft)
1898 *have_soft = true;
46cdbd49
BR
1899
1900 return truncation_applied;
b012e921
MB
1901}
1902
6e866b33
MB
1903static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
1904 size_t w = 0, space, lspace, old_length, clickable_length;
1905 _cleanup_free_ char *clickable = NULL;
b012e921
MB
1906 const char *p;
1907 char *ret;
6e866b33 1908 int r;
b012e921
MB
1909
1910 /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
1911
1912 assert(str);
1913 assert(percent <= 100);
1914
1915 old_length = strlen(str);
1916
6e866b33
MB
1917 if (url) {
1918 r = terminal_urlify(url, str, &clickable);
1919 if (r < 0)
1920 return NULL;
1921
1922 clickable_length = strlen(clickable);
1923 } else
1924 clickable_length = old_length;
1925
b012e921
MB
1926 /* Determine current width on screen */
1927 p = str;
1928 while (p < str + old_length) {
1929 char32_t c;
1930
1931 if (utf8_encoded_to_unichar(p, &c) < 0) {
1932 p++, w++; /* count invalid chars as 1 */
1933 continue;
1934 }
1935
1936 p = utf8_next_char(p);
1937 w += unichar_iswide(c) ? 2 : 1;
1938 }
1939
1940 /* Already wider than the target, if so, don't do anything */
1941 if (w >= new_length)
6e866b33 1942 return clickable ? TAKE_PTR(clickable) : strdup(str);
b012e921
MB
1943
1944 /* How much spaces shall we add? An how much on the left side? */
1945 space = new_length - w;
1946 lspace = space * percent / 100U;
1947
6e866b33 1948 ret = new(char, space + clickable_length + 1);
b012e921
MB
1949 if (!ret)
1950 return NULL;
1951
a032b68d 1952 for (size_t i = 0; i < lspace; i++)
b012e921 1953 ret[i] = ' ';
6e866b33 1954 memcpy(ret + lspace, clickable ?: str, clickable_length);
a032b68d 1955 for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
b012e921
MB
1956 ret[i] = ' ';
1957
6e866b33 1958 ret[space + clickable_length] = 0;
b012e921
MB
1959 return ret;
1960}
1961
a10f5d05
MB
1962static bool table_data_isempty(TableData *d) {
1963 assert(d);
1964
1965 if (d->type == TABLE_EMPTY)
1966 return true;
1967
1968 /* Let's also consider an empty strv as truly empty. */
a032b68d 1969 if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
a10f5d05
MB
1970 return strv_isempty(d->strv);
1971
1972 /* Note that an empty string we do not consider empty here! */
1973 return false;
1974}
1975
f2dec872
BR
1976static const char* table_data_color(TableData *d) {
1977 assert(d);
1978
1979 if (d->color)
1980 return d->color;
1981
1982 /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
a10f5d05 1983 if (table_data_isempty(d))
f2dec872
BR
1984 return ansi_grey();
1985
1986 return NULL;
1987}
1988
a10f5d05
MB
1989static const char* table_data_rgap_color(TableData *d) {
1990 assert(d);
1991
1992 if (d->rgap_color)
1993 return d->rgap_color;
1994
1995 return NULL;
1996}
1997
b012e921
MB
1998int table_print(Table *t, FILE *f) {
1999 size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
a032b68d
MB
2000 table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2001 *width = NULL;
b012e921
MB
2002 _cleanup_free_ size_t *sorted = NULL;
2003 uint64_t *column_weight, weight_sum;
2004 int r;
2005
2006 assert(t);
2007
2008 if (!f)
2009 f = stdout;
2010
2011 /* Ensure we have no incomplete rows */
2012 assert(t->n_cells % t->n_columns == 0);
2013
2014 n_rows = t->n_cells / t->n_columns;
2015 assert(n_rows > 0); /* at least the header row must be complete */
2016
2017 if (t->sort_map) {
2018 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2019
2020 sorted = new(size_t, n_rows);
2021 if (!sorted)
2022 return -ENOMEM;
2023
a032b68d 2024 for (size_t i = 0; i < n_rows; i++)
b012e921
MB
2025 sorted[i] = i * t->n_columns;
2026
6e866b33 2027 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
b012e921
MB
2028 }
2029
2030 if (t->display_map)
2031 display_columns = t->n_display_map;
2032 else
2033 display_columns = t->n_columns;
2034
2035 assert(display_columns > 0);
2036
2037 minimum_width = newa(size_t, display_columns);
2038 maximum_width = newa(size_t, display_columns);
2039 requested_width = newa(size_t, display_columns);
b012e921
MB
2040 column_weight = newa0(uint64_t, display_columns);
2041
a032b68d 2042 for (size_t j = 0; j < display_columns; j++) {
b012e921 2043 minimum_width[j] = 1;
3a6ce677 2044 maximum_width[j] = SIZE_MAX;
b012e921
MB
2045 }
2046
a032b68d
MB
2047 for (unsigned pass = 0; pass < 2; pass++) {
2048 /* First pass: determine column sizes */
b012e921 2049
a032b68d 2050 for (size_t j = 0; j < display_columns; j++)
3a6ce677 2051 requested_width[j] = SIZE_MAX;
b012e921 2052
a032b68d 2053 bool any_soft = false;
b012e921 2054
a032b68d
MB
2055 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
2056 TableData **row;
b012e921 2057
a032b68d
MB
2058 /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2059 * hence we don't care for sorted[] during the first pass. */
2060 row = t->data + i * t->n_columns;
46cdbd49 2061
a032b68d
MB
2062 for (size_t j = 0; j < display_columns; j++) {
2063 TableData *d;
2064 size_t req_width, req_height;
46cdbd49 2065
a032b68d 2066 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
46cdbd49 2067
a032b68d
MB
2068 r = table_data_requested_width_height(t, d,
2069 width ? width[j] : SIZE_MAX,
2070 &req_width, &req_height, &any_soft);
46cdbd49
BR
2071 if (r < 0)
2072 return r;
a032b68d
MB
2073 if (r > 0) { /* Truncated because too many lines? */
2074 _cleanup_free_ char *last = NULL;
2075 const char *field;
2076
2077 /* If we are going to show only the first few lines of a cell that has
2078 * multiple make sure that we have enough space horizontally to show an
2079 * ellipsis. Hence, let's figure out the last line, and account for its
2080 * length plus ellipsis. */
2081
2082 field = table_data_format(t, d, false,
2083 width ? width[j] : SIZE_MAX,
2084 &any_soft);
2085 if (!field)
2086 return -ENOMEM;
46cdbd49 2087
a032b68d
MB
2088 assert_se(t->cell_height_max > 0);
2089 r = string_extract_line(field, t->cell_height_max-1, &last);
2090 if (r < 0)
2091 return r;
b012e921 2092
a032b68d
MB
2093 req_width = MAX(req_width,
2094 utf8_console_width(last) +
2095 utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
2096 }
b012e921 2097
a032b68d 2098 /* Determine the biggest width that any cell in this column would like to have */
3a6ce677 2099 if (requested_width[j] == SIZE_MAX ||
a032b68d
MB
2100 requested_width[j] < req_width)
2101 requested_width[j] = req_width;
b012e921 2102
a032b68d
MB
2103 /* Determine the minimum width any cell in this column needs */
2104 if (minimum_width[j] < d->minimum_width)
2105 minimum_width[j] = d->minimum_width;
b012e921 2106
a032b68d 2107 /* Determine the maximum width any cell in this column needs */
3a6ce677
BR
2108 if (d->maximum_width != SIZE_MAX &&
2109 (maximum_width[j] == SIZE_MAX ||
a032b68d
MB
2110 maximum_width[j] > d->maximum_width))
2111 maximum_width[j] = d->maximum_width;
2112
2113 /* Determine the full columns weight */
2114 column_weight[j] += d->weight;
2115 }
b012e921 2116 }
b012e921 2117
a032b68d
MB
2118 /* One space between each column */
2119 table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
b012e921 2120
a032b68d
MB
2121 /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2122 weight_sum = 0;
2123 for (size_t j = 0; j < display_columns; j++) {
2124 weight_sum += column_weight[j];
b012e921 2125
a032b68d
MB
2126 table_minimum_width += minimum_width[j];
2127
3a6ce677
BR
2128 if (maximum_width[j] == SIZE_MAX)
2129 table_maximum_width = SIZE_MAX;
a032b68d
MB
2130 else
2131 table_maximum_width += maximum_width[j];
b012e921 2132
a032b68d
MB
2133 table_requested_width += requested_width[j];
2134 }
2135
2136 /* Calculate effective table width */
3a6ce677 2137 if (t->width != 0 && t->width != SIZE_MAX)
a032b68d
MB
2138 table_effective_width = t->width;
2139 else if (t->width == 0 ||
2140 ((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
2141 table_effective_width = table_requested_width;
b012e921 2142 else
a032b68d 2143 table_effective_width = MIN(table_requested_width, columns());
b012e921 2144
3a6ce677 2145 if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
a032b68d 2146 table_effective_width = table_maximum_width;
b012e921 2147
a032b68d
MB
2148 if (table_effective_width < table_minimum_width)
2149 table_effective_width = table_minimum_width;
b012e921 2150
a032b68d
MB
2151 if (!width)
2152 width = newa(size_t, display_columns);
b012e921 2153
a032b68d
MB
2154 if (table_effective_width >= table_requested_width) {
2155 size_t extra;
b012e921 2156
a032b68d
MB
2157 /* We have extra room, let's distribute it among columns according to their weights. We first provide
2158 * each column with what it asked for and the distribute the rest. */
b012e921 2159
a032b68d 2160 extra = table_effective_width - table_requested_width;
b012e921 2161
a032b68d
MB
2162 for (size_t j = 0; j < display_columns; j++) {
2163 size_t delta;
b012e921 2164
a032b68d
MB
2165 if (weight_sum == 0)
2166 width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
2167 else
2168 width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
b012e921 2169
3a6ce677 2170 if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
a032b68d 2171 width[j] = maximum_width[j];
b012e921 2172
a032b68d
MB
2173 if (width[j] < minimum_width[j])
2174 width[j] = minimum_width[j];
b012e921 2175
3a6ce677 2176 delta = LESS_BY(width[j], requested_width[j]);
b012e921 2177
a032b68d
MB
2178 /* Subtract what we just added from the rest */
2179 if (extra > delta)
2180 extra -= delta;
2181 else
2182 extra = 0;
b012e921 2183
a032b68d
MB
2184 assert(weight_sum >= column_weight[j]);
2185 weight_sum -= column_weight[j];
2186 }
b012e921 2187
a032b68d
MB
2188 break; /* Every column should be happy, no need to repeat calculations. */
2189 } else {
2190 /* We need to compress the table, columns can't get what they asked for. We first provide each column
2191 * with the minimum they need, and then distribute anything left. */
2192 bool finalize = false;
2193 size_t extra;
b012e921 2194
a032b68d 2195 extra = table_effective_width - table_minimum_width;
b012e921 2196
a032b68d 2197 for (size_t j = 0; j < display_columns; j++)
3a6ce677 2198 width[j] = SIZE_MAX;
b012e921 2199
a032b68d
MB
2200 for (;;) {
2201 bool restart = false;
b012e921 2202
a032b68d
MB
2203 for (size_t j = 0; j < display_columns; j++) {
2204 size_t delta, w;
b012e921 2205
a032b68d 2206 /* Did this column already get something assigned? If so, let's skip to the next */
3a6ce677 2207 if (width[j] != SIZE_MAX)
a032b68d 2208 continue;
b012e921 2209
a032b68d
MB
2210 if (weight_sum == 0)
2211 w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
2212 else
2213 w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
b012e921 2214
a032b68d
MB
2215 if (w >= requested_width[j]) {
2216 /* Never give more than requested. If we hit a column like this, there's more
2217 * space to allocate to other columns which means we need to restart the
2218 * iteration. However, if we hit a column like this, let's assign it the space
8b3d4ff0 2219 * it wanted for good early. */
b012e921 2220
a032b68d
MB
2221 w = requested_width[j];
2222 restart = true;
b012e921 2223
a032b68d
MB
2224 } else if (!finalize)
2225 continue;
b012e921 2226
a032b68d 2227 width[j] = w;
b012e921 2228
a032b68d
MB
2229 assert(w >= minimum_width[j]);
2230 delta = w - minimum_width[j];
b012e921 2231
a032b68d
MB
2232 assert(delta <= extra);
2233 extra -= delta;
b012e921 2234
a032b68d
MB
2235 assert(weight_sum >= column_weight[j]);
2236 weight_sum -= column_weight[j];
b012e921 2237
a032b68d
MB
2238 if (restart && !finalize)
2239 break;
2240 }
b012e921 2241
a032b68d 2242 if (finalize)
b012e921 2243 break;
a032b68d
MB
2244
2245 if (!restart)
2246 finalize = true;
b012e921
MB
2247 }
2248
a032b68d
MB
2249 if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
2250 * let's try to reformat them with the new widths. Otherwise, let's
2251 * move on. */
b012e921 2252 break;
b012e921
MB
2253 }
2254 }
2255
2256 /* Second pass: show output */
a032b68d 2257 for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
46cdbd49
BR
2258 size_t n_subline = 0;
2259 bool more_sublines;
b012e921
MB
2260 TableData **row;
2261
2262 if (sorted)
2263 row = t->data + sorted[i];
2264 else
2265 row = t->data + i * t->n_columns;
2266
46cdbd49 2267 do {
a10f5d05 2268 const char *gap_color = NULL;
46cdbd49 2269 more_sublines = false;
b012e921 2270
a032b68d 2271 for (size_t j = 0; j < display_columns; j++) {
46cdbd49
BR
2272 _cleanup_free_ char *buffer = NULL, *extracted = NULL;
2273 bool lines_truncated = false;
a10f5d05 2274 const char *field, *color = NULL;
46cdbd49
BR
2275 TableData *d;
2276 size_t l;
b012e921 2277
46cdbd49 2278 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
b012e921 2279
a032b68d 2280 field = table_data_format(t, d, false, width[j], NULL);
46cdbd49 2281 if (!field)
b012e921
MB
2282 return -ENOMEM;
2283
46cdbd49 2284 r = string_extract_line(field, n_subline, &extracted);
6e866b33
MB
2285 if (r < 0)
2286 return r;
46cdbd49
BR
2287 if (r > 0) {
2288 /* There are more lines to come */
3a6ce677 2289 if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
46cdbd49
BR
2290 more_sublines = true; /* There are more lines to come */
2291 else
2292 lines_truncated = true;
2293 }
2294 if (extracted)
2295 field = extracted;
2296
2297 l = utf8_console_width(field);
2298 if (l > width[j]) {
2299 /* Field is wider than allocated space. Let's ellipsize */
2300
2301 buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
2302 lines_truncated ? 100 : d->ellipsize_percent);
2303 if (!buffer)
2304 return -ENOMEM;
2305
2306 field = buffer;
2307 } else {
2308 if (lines_truncated) {
2309 _cleanup_free_ char *padded = NULL;
2310
2311 /* We truncated more lines of this cell, let's add an
a10f5d05 2312 * ellipsis. We first append it, but that might make our
46cdbd49
BR
2313 * string grow above what we have space for, hence ellipsize
2314 * right after. This will truncate the ellipsis and add a new
2315 * one. */
2316
2317 padded = strjoin(field, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
2318 if (!padded)
2319 return -ENOMEM;
2320
2321 buffer = ellipsize(padded, width[j], 100);
2322 if (!buffer)
2323 return -ENOMEM;
2324
2325 field = buffer;
2326 l = utf8_console_width(field);
2327 }
2328
2329 if (l < width[j]) {
2330 _cleanup_free_ char *aligned = NULL;
2331 /* Field is shorter than allocated space. Let's align with spaces */
2332
2333 aligned = align_string_mem(field, d->url, width[j], d->align_percent);
2334 if (!aligned)
2335 return -ENOMEM;
2336
3a6ce677
BR
2337 /* Drop trailing white spaces of last column when no cosmetics is set. */
2338 if (j == display_columns - 1 &&
2339 (!colors_enabled() || (!table_data_color(d) && row != t->data)) &&
2340 (!urlify_enabled() || !d->url))
2341 delete_trailing_chars(aligned, NULL);
2342
46cdbd49
BR
2343 free_and_replace(buffer, aligned);
2344 field = buffer;
2345 }
2346 }
2347
2348 if (l >= width[j] && d->url) {
2349 _cleanup_free_ char *clickable = NULL;
2350
2351 r = terminal_urlify(d->url, field, &clickable);
2352 if (r < 0)
2353 return r;
2354
2355 free_and_replace(buffer, clickable);
2356 field = buffer;
2357 }
2358
a10f5d05
MB
2359 if (colors_enabled()) {
2360 if (gap_color)
2361 fputs(gap_color, f);
2362 else if (row == t->data) /* underline header line fully, including the column separator */
2363 fputs(ansi_underline(), f);
2364 }
46cdbd49
BR
2365
2366 if (j > 0)
a10f5d05
MB
2367 fputc(' ', f); /* column separator left of cell */
2368
2369 if (colors_enabled()) {
2370 color = table_data_color(d);
46cdbd49 2371
a10f5d05
MB
2372 /* Undo gap color */
2373 if (gap_color || (color && row == t->data))
46cdbd49
BR
2374 fputs(ANSI_NORMAL, f);
2375
a10f5d05
MB
2376 if (color)
2377 fputs(color, f);
2378 else if (gap_color && row == t->data) /* underline header line cell */
2379 fputs(ansi_underline(), f);
46cdbd49
BR
2380 }
2381
2382 fputs(field, f);
2383
a10f5d05 2384 if (colors_enabled() && (color || row == t->data))
6e866b33 2385 fputs(ANSI_NORMAL, f);
a10f5d05
MB
2386
2387 gap_color = table_data_rgap_color(d);
6e866b33 2388 }
b012e921 2389
46cdbd49
BR
2390 fputc('\n', f);
2391 n_subline ++;
2392 } while (more_sublines);
b012e921
MB
2393 }
2394
2395 return fflush_and_check(f);
2396}
2397
2398int table_format(Table *t, char **ret) {
3a6ce677 2399 _cleanup_free_ char *buf = NULL;
b012e921 2400 _cleanup_fclose_ FILE *f = NULL;
b012e921
MB
2401 size_t sz = 0;
2402 int r;
2403
f2dec872 2404 f = open_memstream_unlocked(&buf, &sz);
b012e921
MB
2405 if (!f)
2406 return -ENOMEM;
2407
b012e921
MB
2408 r = table_print(t, f);
2409 if (r < 0)
2410 return r;
2411
2412 f = safe_fclose(f);
2413
3a6ce677 2414 *ret = TAKE_PTR(buf);
b012e921
MB
2415
2416 return 0;
2417}
2418
2419size_t table_get_rows(Table *t) {
2420 if (!t)
2421 return 0;
2422
2423 assert(t->n_columns > 0);
2424 return t->n_cells / t->n_columns;
2425}
2426
2427size_t table_get_columns(Table *t) {
2428 if (!t)
2429 return 0;
2430
2431 assert(t->n_columns > 0);
2432 return t->n_columns;
2433}
6e866b33
MB
2434
2435int table_set_reverse(Table *t, size_t column, bool b) {
2436 assert(t);
2437 assert(column < t->n_columns);
2438
2439 if (!t->reverse_map) {
2440 if (!b)
2441 return 0;
2442
2443 t->reverse_map = new0(bool, t->n_columns);
2444 if (!t->reverse_map)
2445 return -ENOMEM;
2446 }
2447
2448 t->reverse_map[column] = b;
2449 return 0;
2450}
2451
2452TableCell *table_get_cell(Table *t, size_t row, size_t column) {
2453 size_t i;
2454
2455 assert(t);
2456
2457 if (column >= t->n_columns)
2458 return NULL;
2459
2460 i = row * t->n_columns + column;
2461 if (i >= t->n_cells)
2462 return NULL;
2463
2464 return TABLE_INDEX_TO_CELL(i);
2465}
2466
2467const void *table_get(Table *t, TableCell *cell) {
2468 TableData *d;
2469
2470 assert(t);
2471
2472 d = table_get_data(t, cell);
2473 if (!d)
2474 return NULL;
2475
2476 return d->data;
2477}
2478
2479const void* table_get_at(Table *t, size_t row, size_t column) {
2480 TableCell *cell;
2481
2482 cell = table_get_cell(t, row, column);
2483 if (!cell)
2484 return NULL;
2485
2486 return table_get(t, cell);
2487}
2488
2489static int table_data_to_json(TableData *d, JsonVariant **ret) {
2490
2491 switch (d->type) {
2492
2493 case TABLE_EMPTY:
2494 return json_variant_new_null(ret);
2495
2496 case TABLE_STRING:
46cdbd49 2497 case TABLE_PATH:
6e866b33
MB
2498 return json_variant_new_string(ret, d->string);
2499
46cdbd49 2500 case TABLE_STRV:
a032b68d 2501 case TABLE_STRV_WRAPPED:
46cdbd49
BR
2502 return json_variant_new_array_strv(ret, d->strv);
2503
ea0999c9 2504 case TABLE_BOOLEAN_CHECKMARK:
6e866b33
MB
2505 case TABLE_BOOLEAN:
2506 return json_variant_new_boolean(ret, d->boolean);
2507
2508 case TABLE_TIMESTAMP:
f2dec872
BR
2509 case TABLE_TIMESTAMP_UTC:
2510 case TABLE_TIMESTAMP_RELATIVE:
6e866b33
MB
2511 if (d->timestamp == USEC_INFINITY)
2512 return json_variant_new_null(ret);
2513
2514 return json_variant_new_unsigned(ret, d->timestamp);
2515
2516 case TABLE_TIMESPAN:
f2dec872 2517 case TABLE_TIMESPAN_MSEC:
6e866b33
MB
2518 if (d->timespan == USEC_INFINITY)
2519 return json_variant_new_null(ret);
2520
2521 return json_variant_new_unsigned(ret, d->timespan);
2522
2523 case TABLE_SIZE:
f2dec872 2524 case TABLE_BPS:
3a6ce677 2525 if (d->size == UINT64_MAX)
6e866b33
MB
2526 return json_variant_new_null(ret);
2527
2528 return json_variant_new_unsigned(ret, d->size);
2529
f2dec872
BR
2530 case TABLE_INT:
2531 return json_variant_new_integer(ret, d->int_val);
2532
2533 case TABLE_INT8:
2534 return json_variant_new_integer(ret, d->int8);
2535
2536 case TABLE_INT16:
2537 return json_variant_new_integer(ret, d->int16);
2538
2539 case TABLE_INT32:
2540 return json_variant_new_integer(ret, d->int32);
2541
2542 case TABLE_INT64:
2543 return json_variant_new_integer(ret, d->int64);
2544
2545 case TABLE_UINT:
2546 return json_variant_new_unsigned(ret, d->uint_val);
2547
2548 case TABLE_UINT8:
2549 return json_variant_new_unsigned(ret, d->uint8);
2550
2551 case TABLE_UINT16:
2552 return json_variant_new_unsigned(ret, d->uint16);
2553
6e866b33
MB
2554 case TABLE_UINT32:
2555 return json_variant_new_unsigned(ret, d->uint32);
2556
2557 case TABLE_UINT64:
ea0999c9 2558 case TABLE_UINT64_HEX:
6e866b33
MB
2559 return json_variant_new_unsigned(ret, d->uint64);
2560
2561 case TABLE_PERCENT:
2562 return json_variant_new_integer(ret, d->percent);
2563
f2dec872 2564 case TABLE_IFINDEX:
3a6ce677
BR
2565 if (d->ifindex <= 0)
2566 return json_variant_new_null(ret);
2567
f2dec872
BR
2568 return json_variant_new_integer(ret, d->ifindex);
2569
2570 case TABLE_IN_ADDR:
2571 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
2572
2573 case TABLE_IN6_ADDR:
2574 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
2575
ea0999c9
MB
2576 case TABLE_ID128:
2577 return json_variant_new_string(ret, SD_ID128_TO_STRING(d->id128));
46cdbd49 2578
ea0999c9 2579 case TABLE_UUID:
f5caa8fa 2580 return json_variant_new_string(ret, SD_ID128_TO_UUID_STRING(d->id128));
46cdbd49 2581
3a6ce677
BR
2582 case TABLE_UID:
2583 if (!uid_is_valid(d->uid))
2584 return json_variant_new_null(ret);
2585
2586 return json_variant_new_integer(ret, d->uid);
2587
2588 case TABLE_GID:
2589 if (!gid_is_valid(d->gid))
2590 return json_variant_new_null(ret);
2591
2592 return json_variant_new_integer(ret, d->gid);
2593
2594 case TABLE_PID:
2595 if (!pid_is_valid(d->pid))
2596 return json_variant_new_null(ret);
2597
2598 return json_variant_new_integer(ret, d->pid);
2599
2600 case TABLE_SIGNAL:
2601 if (!SIGNAL_VALID(d->int_val))
2602 return json_variant_new_null(ret);
2603
2604 return json_variant_new_integer(ret, d->int_val);
2605
ea0999c9
MB
2606 case TABLE_MODE:
2607 if (d->mode == MODE_INVALID)
2608 return json_variant_new_null(ret);
2609
2610 return json_variant_new_unsigned(ret, d->mode);
2611
6e866b33
MB
2612 default:
2613 return -EINVAL;
2614 }
2615}
2616
a10f5d05 2617static char* string_to_json_field_name(const char *f) {
a10f5d05
MB
2618 /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2619 * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
2620 * underscores and leave everything as is. */
2621
a032b68d 2622 char *c = strdup(f);
a10f5d05
MB
2623 if (!c)
2624 return NULL;
2625
a032b68d 2626 for (char *x = c; *x; x++)
a10f5d05
MB
2627 if (isspace(*x))
2628 *x = '_';
2629
2630 return c;
2631}
2632
ea0999c9
MB
2633static const char *table_get_json_field_name(Table *t, size_t column) {
2634 assert(t);
2635
2636 return column < t->n_json_fields ? t->json_fields[column] : NULL;
2637}
2638
6e866b33
MB
2639int table_to_json(Table *t, JsonVariant **ret) {
2640 JsonVariant **rows = NULL, **elements = NULL;
2641 _cleanup_free_ size_t *sorted = NULL;
a032b68d 2642 size_t n_rows, display_columns;
6e866b33
MB
2643 int r;
2644
2645 assert(t);
2646
2647 /* Ensure we have no incomplete rows */
2648 assert(t->n_cells % t->n_columns == 0);
2649
2650 n_rows = t->n_cells / t->n_columns;
2651 assert(n_rows > 0); /* at least the header row must be complete */
2652
2653 if (t->sort_map) {
2654 /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2655
2656 sorted = new(size_t, n_rows);
2657 if (!sorted) {
2658 r = -ENOMEM;
2659 goto finish;
2660 }
2661
a032b68d 2662 for (size_t i = 0; i < n_rows; i++)
6e866b33
MB
2663 sorted[i] = i * t->n_columns;
2664
2665 typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
2666 }
2667
2668 if (t->display_map)
2669 display_columns = t->n_display_map;
2670 else
2671 display_columns = t->n_columns;
2672 assert(display_columns > 0);
2673
2674 elements = new0(JsonVariant*, display_columns * 2);
2675 if (!elements) {
2676 r = -ENOMEM;
2677 goto finish;
2678 }
2679
a032b68d 2680 for (size_t j = 0; j < display_columns; j++) {
a10f5d05 2681 _cleanup_free_ char *mangled = NULL;
ea0999c9
MB
2682 const char *n;
2683 size_t c;
6e866b33 2684
ea0999c9 2685 c = t->display_map ? t->display_map[j] : j;
6e866b33 2686
ea0999c9
MB
2687 /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
2688 n = table_get_json_field_name(t, c);
2689 if (!n) {
2690 const char *formatted;
2691 TableData *d;
a10f5d05 2692
ea0999c9
MB
2693 assert_se(d = t->data[c]);
2694
2695 /* Field names must be strings, hence format whatever we got here as a string first */
2696 formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
2697 if (!formatted) {
2698 r = -ENOMEM;
2699 goto finish;
2700 }
2701
2702 /* Arbitrary strings suck as field names, try to mangle them into something more suitable hence */
2703 mangled = string_to_json_field_name(formatted);
2704 if (!mangled) {
2705 r = -ENOMEM;
2706 goto finish;
2707 }
2708 n = mangled;
a10f5d05
MB
2709 }
2710
ea0999c9 2711 r = json_variant_new_string(elements + j*2, n);
6e866b33
MB
2712 if (r < 0)
2713 goto finish;
2714 }
2715
2716 rows = new0(JsonVariant*, n_rows-1);
2717 if (!rows) {
2718 r = -ENOMEM;
2719 goto finish;
2720 }
2721
a032b68d 2722 for (size_t i = 1; i < n_rows; i++) {
6e866b33
MB
2723 TableData **row;
2724
2725 if (sorted)
2726 row = t->data + sorted[i];
2727 else
2728 row = t->data + i * t->n_columns;
2729
a032b68d 2730 for (size_t j = 0; j < display_columns; j++) {
6e866b33
MB
2731 TableData *d;
2732 size_t k;
2733
2734 assert_se(d = row[t->display_map ? t->display_map[j] : j]);
2735
2736 k = j*2+1;
2737 elements[k] = json_variant_unref(elements[k]);
2738
2739 r = table_data_to_json(d, elements + k);
2740 if (r < 0)
2741 goto finish;
2742 }
2743
2744 r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
2745 if (r < 0)
2746 goto finish;
2747 }
2748
2749 r = json_variant_new_array(ret, rows, n_rows - 1);
2750
2751finish:
2752 if (rows) {
2753 json_variant_unref_many(rows, n_rows-1);
2754 free(rows);
2755 }
2756
2757 if (elements) {
2758 json_variant_unref_many(elements, display_columns*2);
2759 free(elements);
2760 }
2761
2762 return r;
2763}
2764
2765int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
2766 _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
2767 int r;
2768
2769 assert(t);
2770
3a6ce677
BR
2771 if (flags & JSON_FORMAT_OFF) /* If JSON output is turned off, use regular output */
2772 return table_print(t, f);
2773
6e866b33
MB
2774 if (!f)
2775 f = stdout;
2776
2777 r = table_to_json(t, &v);
2778 if (r < 0)
2779 return r;
2780
2781 json_variant_dump(v, flags, f, NULL);
2782
2783 return fflush_and_check(f);
2784}
3a6ce677
BR
2785
2786int table_print_with_pager(
2787 Table *t,
2788 JsonFormatFlags json_format_flags,
2789 PagerFlags pager_flags,
2790 bool show_header) {
2791
2792 bool saved_header;
2793 int r;
2794
2795 assert(t);
2796
67bbd050 2797 /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
3a6ce677
BR
2798 * the table header and logs about any error. */
2799
2800 if (json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
ea0999c9 2801 pager_open(pager_flags);
3a6ce677
BR
2802
2803 saved_header = t->header;
2804 t->header = show_header;
2805 r = table_print_json(t, stdout, json_format_flags);
2806 t->header = saved_header;
2807 if (r < 0)
2808 return table_log_print_error(r);
2809
2810 return 0;
2811}
ea0999c9
MB
2812
2813int table_set_json_field_name(Table *t, size_t column, const char *name) {
2814 int r;
2815
2816 assert(t);
2817
2818 if (name) {
2819 size_t m;
2820
2821 m = MAX(column + 1, t->n_json_fields);
2822 if (!GREEDY_REALLOC0(t->json_fields, m))
2823 return -ENOMEM;
2824
2825 r = free_and_strdup(t->json_fields + column, name);
2826 if (r < 0)
2827 return r;
2828
2829 t->n_json_fields = m;
2830 return r;
2831 } else {
2832 if (column >= t->n_json_fields)
2833 return 0;
2834
2835 t->json_fields[column] = mfree(t->json_fields[column]);
2836 return 1;
2837 }
2838}