• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 18859702686

27 Oct 2025 04:06PM UTC coverage: 72.245% (-0.008%) from 72.253%
18859702686

push

github

web-flow
machined: support image clone/rm operations unpriv, and make hidden images always read-only (#39408)

20 of 85 new or added lines in 6 files covered. (23.53%)

124 existing lines in 37 files now uncovered.

304824 of 421932 relevant lines covered (72.24%)

1100207.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.88
/src/shared/format-table.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <ctype.h>
4
#include <unistd.h>
5

6
#include "sd-id128.h"
7

8
#include "alloc-util.h"
9
#include "devnum-util.h"
10
#include "fileio.h"
11
#include "format-ifname.h"
12
#include "format-table.h"
13
#include "format-util.h"
14
#include "glyph-util.h"
15
#include "gunicode.h"
16
#include "in-addr-util.h"
17
#include "memory-util.h"
18
#include "memstream-util.h"
19
#include "pager.h"
20
#include "path-util.h"
21
#include "pretty-print.h"
22
#include "process-util.h"
23
#include "signal-util.h"
24
#include "sort-util.h"
25
#include "stat-util.h"
26
#include "string-util.h"
27
#include "strv.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

34
#define DEFAULT_WEIGHT 100
35

36
/*
37
   A few notes on implementation details:
38

39
 - TableCell is a 'fake' structure, it's just used as data type to pass references to specific cell positions in the
40
   table. It can be easily converted to an index number and back.
41

42
 - TableData is where the actual data is stored: it encapsulates the data and formatting for a specific cell. It's
43
   'pseudo-immutable' and ref-counted. When a cell's data's formatting is to be changed, we duplicate the object if the
44
   ref-counting is larger than 1. Note that TableData and its ref-counting is mostly not visible to the outside. The
45
   outside only sees Table and TableCell.
46

47
 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
48
   previous one.
49

50
 - There's no special concept of a "row" or "column" in the table, and no special concept of the "header" row. It's all
51
   derived from the cell index: we know how many cells are to be stored in a row, and can determine the rest from
52
   that. The first row is always the header row. If header display is turned off we simply skip outputting the first
53
   row. Also, when sorting rows we always leave the first row where it is, as the header shouldn't move.
54

55
 - Note because there's no row and no column object some properties that might be appropriate as row/column properties
56
   are exposed as cell properties instead. For example, the "weight" of a column (which is used to determine where to
57
   add/remove space preferable when expanding/compressing tables horizontally) is actually made the "weight" of a
58
   cell. Given that we usually need it per-column though we will calculate the average across every cell of the column
59
   instead.
60

61
 - To make things easy, when cells are added without any explicit configured formatting, then we'll copy the formatting
62
   from the same cell in the previous cell. This is particularly useful for the "weight" of the cell (see above), as
63
   this means setting the weight of the cells of the header row will nicely propagate to all cells in the other rows.
64
*/
65

66
typedef struct TableData {
67
        unsigned n_ref;
68
        TableDataType type;
69

70
        size_t minimum_width;       /* minimum width for the column */
71
        size_t maximum_width;       /* maximum width for the column */
72
        size_t formatted_for_width; /* the width we tried to format for */
73
        unsigned weight;            /* the horizontal weight for this column, in case the table is expanded/compressed */
74
        unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
75
        unsigned align_percent;     /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
76

77
        bool uppercase:1;           /* Uppercase string on display */
78
        bool underline:1;
79
        bool rgap_underline:1;
80

81
        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 */
82
        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 */
83
        char *url;                  /* A URL to use for a clickable hyperlink */
84
        char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
85

86
        union {
87
                uint8_t data[0];    /* data is generic array */
88
                bool boolean;
89
                usec_t timestamp;
90
                usec_t timespan;
91
                uint64_t size;
92
                char string[0];
93
                char **strv;
94
                int int_val;
95
                int8_t int8;
96
                int16_t int16;
97
                int32_t int32;
98
                int64_t int64;
99
                unsigned uint_val;
100
                uint8_t uint8;
101
                uint16_t uint16;
102
                uint32_t uint32;
103
                uint64_t uint64;
104
                int percent;        /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
105
                int ifindex;
106
                union in_addr_union address;
107
                sd_id128_t id128;
108
                uid_t uid;
109
                gid_t gid;
110
                pid_t pid;
111
                mode_t mode;
112
                dev_t devnum;
113
                /* … add more here as we start supporting more cell data types … */
114
        };
115
} TableData;
116

117
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
412,172✔
118
        size_t i;
412,172✔
119

120
        assert(cell);
412,172✔
121

122
        i = PTR_TO_SIZE(cell);
412,172✔
123
        assert(i > 0);
412,172✔
124

125
        return i-1;
412,172✔
126
}
127

128
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
182,982✔
129
        assert(index != SIZE_MAX);
182,982✔
130
        return SIZE_TO_PTR(index + 1);
182,982✔
131
}
132

133
struct Table {
134
        size_t n_columns;
135
        size_t n_cells;
136

137
        bool header;   /* Whether to show the header row? */
138
        bool vertical; /* Whether to field names are on the left rather than the first line */
139

140
        TableErsatz ersatz; /* What to show when we have an empty cell or an invalid value that cannot be rendered. */
141

142
        size_t width;  /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
143
                        * width or less wide, but not wider. Otherwise the width to format this table in. */
144
        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.) */
145

146
        TableData **data;
147

148
        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 */
149
        size_t n_display_map;
150

151
        size_t *sort_map;     /* The columns to order rows by, in order of preference. */
152
        size_t n_sort_map;
153

154
        char **json_fields;
155
        size_t n_json_fields;
156

157
        bool *reverse_map;
158
};
159

160
Table *table_new_raw(size_t n_columns) {
3,549✔
161
        _cleanup_(table_unrefp) Table *t = NULL;
3,549✔
162

163
        assert(n_columns > 0);
3,549✔
164

165
        t = new(Table, 1);
3,549✔
166
        if (!t)
3,549✔
167
                return NULL;
168

169
        *t = (Table) {
3,549✔
170
                .n_columns = n_columns,
171
                .header = true,
172
                .width = SIZE_MAX,
173
                .cell_height_max = SIZE_MAX,
174
                .ersatz = TABLE_ERSATZ_EMPTY,
175
        };
176

177
        return TAKE_PTR(t);
3,549✔
178
}
179

180
Table *table_new_internal(const char *first_header, ...) {
618✔
181
        _cleanup_(table_unrefp) Table *t = NULL;
618✔
182
        size_t n_columns = 1;
618✔
183
        va_list ap;
618✔
184
        int r;
618✔
185

186
        assert(first_header);
618✔
187

188
        va_start(ap, first_header);
618✔
189
        for (;;) {
9,040✔
190
                if (!va_arg(ap, const char*))
4,829✔
191
                        break;
192

193
                n_columns++;
4,211✔
194
        }
195
        va_end(ap);
618✔
196

197
        t = table_new_raw(n_columns);
618✔
198
        if (!t)
618✔
199
                return NULL;
200

201
        va_start(ap, first_header);
618✔
202
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,447✔
203
                TableCell *cell;
4,829✔
204

205
                r = table_add_cell(t, &cell, TABLE_HEADER, h);
4,829✔
206
                if (r < 0) {
4,829✔
207
                        va_end(ap);
×
208
                        return NULL;
×
209
                }
210
        }
211
        va_end(ap);
618✔
212

213
        assert(t->n_columns == t->n_cells);
618✔
214
        return TAKE_PTR(t);
618✔
215
}
216

217
Table *table_new_vertical(void) {
2,888✔
218
        _cleanup_(table_unrefp) Table *t = NULL;
2,888✔
219
        TableCell *cell;
2,888✔
220

221
        t = table_new_raw(2);
2,888✔
222
        if (!t)
2,888✔
223
                return NULL;
224

225
        t->vertical = true;
2,888✔
226
        t->header = false;
2,888✔
227

228
        if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
2,888✔
229
                return NULL;
230

231
        if (table_set_align_percent(t, cell, 100) < 0)
2,888✔
232
                return NULL;
233

234
        if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
2,888✔
235
                return NULL;
236

237
        if (table_set_align_percent(t, cell, 0) < 0)
2,888✔
238
                return NULL;
239

240
        return TAKE_PTR(t);
2,888✔
241
}
242

243
static TableData *table_data_free(TableData *d) {
179,566✔
244
        assert(d);
179,566✔
245

246
        free(d->formatted);
179,566✔
247
        free(d->url);
179,566✔
248

249
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
179,566✔
250
                strv_free(d->strv);
6,258✔
251

252
        return mfree(d);
179,566✔
253
}
254

255
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
318,816✔
256
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
194,405✔
257

258
Table *table_unref(Table *t) {
3,549✔
259
        if (!t)
3,549✔
260
                return NULL;
261

262
        for (size_t i = 0; i < t->n_cells; i++)
197,966✔
263
                table_data_unref(t->data[i]);
194,417✔
264

265
        free(t->data);
3,549✔
266
        free(t->display_map);
3,549✔
267
        free(t->sort_map);
3,549✔
268
        free(t->reverse_map);
3,549✔
269

270
        for (size_t i = 0; i < t->n_json_fields; i++)
5,921✔
271
                free(t->json_fields[i]);
2,372✔
272

273
        free(t->json_fields);
3,549✔
274

275
        return mfree(t);
3,549✔
276
}
277

278
static size_t table_data_size(TableDataType type, const void *data) {
485,952✔
279

280
        switch (type) {
485,952✔
281

282
        case TABLE_EMPTY:
283
                return 0;
284

285
        case TABLE_STRING:
402,366✔
286
        case TABLE_PATH:
287
        case TABLE_PATH_BASENAME:
288
        case TABLE_FIELD:
289
        case TABLE_HEADER:
290
        case TABLE_VERSION:
291
                return strlen(data) + 1;
402,366✔
292

293
        case TABLE_STRV:
12,788✔
294
        case TABLE_STRV_WRAPPED:
295
                return sizeof(char **);
12,788✔
296

297
        case TABLE_BOOLEAN_CHECKMARK:
9,593✔
298
        case TABLE_BOOLEAN:
299
                return sizeof(bool);
9,593✔
300

301
        case TABLE_TIMESTAMP:
6,222✔
302
        case TABLE_TIMESTAMP_UTC:
303
        case TABLE_TIMESTAMP_RELATIVE:
304
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
305
        case TABLE_TIMESTAMP_LEFT:
306
        case TABLE_TIMESTAMP_DATE:
307
        case TABLE_TIMESPAN:
308
        case TABLE_TIMESPAN_MSEC:
309
        case TABLE_TIMESPAN_DAY:
310
                return sizeof(usec_t);
6,222✔
311

312
        case TABLE_SIZE:
18,607✔
313
        case TABLE_INT64:
314
        case TABLE_UINT64:
315
        case TABLE_UINT64_HEX:
316
        case TABLE_BPS:
317
                return sizeof(uint64_t);
18,607✔
318

319
        case TABLE_INT32:
4,482✔
320
        case TABLE_UINT32:
321
        case TABLE_UINT32_HEX:
322
                return sizeof(uint32_t);
4,482✔
323

324
        case TABLE_INT16:
99✔
325
        case TABLE_UINT16:
326
                return sizeof(uint16_t);
99✔
327

328
        case TABLE_INT8:
54✔
329
        case TABLE_UINT8:
330
                return sizeof(uint8_t);
54✔
331

332
        case TABLE_INT:
4,216✔
333
        case TABLE_UINT:
334
        case TABLE_PERCENT:
335
        case TABLE_IFINDEX:
336
        case TABLE_SIGNAL:
337
                return sizeof(int);
4,216✔
338

339
        case TABLE_IN_ADDR:
289✔
340
                return sizeof(struct in_addr);
289✔
341

342
        case TABLE_IN6_ADDR:
38✔
343
                return sizeof(struct in6_addr);
38✔
344

345
        case TABLE_UUID:
5,542✔
346
        case TABLE_ID128:
347
                return sizeof(sd_id128_t);
5,542✔
348

349
        case TABLE_UID:
1,374✔
350
                return sizeof(uid_t);
1,374✔
351
        case TABLE_GID:
1,851✔
352
                return sizeof(gid_t);
1,851✔
353
        case TABLE_PID:
593✔
354
                return sizeof(pid_t);
593✔
355

356
        case TABLE_MODE:
178✔
357
        case TABLE_MODE_INODE_TYPE:
358
                return sizeof(mode_t);
178✔
359

360
        case TABLE_DEVNUM:
6✔
361
                return sizeof(dev_t);
6✔
362

363
        default:
×
364
                assert_not_reached();
×
365
        }
366
}
367

368
static bool table_data_matches(
183,378✔
369
                TableData *d,
370
                TableDataType type,
371
                const void *data,
372
                size_t minimum_width,
373
                size_t maximum_width,
374
                unsigned weight,
375
                unsigned align_percent,
376
                unsigned ellipsize_percent,
377
                bool uppercase) {
378

379
        size_t k, l;
183,378✔
380
        assert(d);
183,378✔
381

382
        if (d->type != type)
183,378✔
383
                return false;
384

385
        if (d->minimum_width != minimum_width)
158,140✔
386
                return false;
387

388
        if (d->maximum_width != maximum_width)
158,140✔
389
                return false;
390

391
        if (d->weight != weight)
156,783✔
392
                return false;
393

394
        if (d->align_percent != align_percent)
156,783✔
395
                return false;
396

397
        if (d->ellipsize_percent != ellipsize_percent)
156,783✔
398
                return false;
399

400
        if (d->uppercase != uppercase)
156,783✔
401
                return false;
402

403
        /* If a color/url is set, refuse to merge */
404
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
156,782✔
405
                return false;
406
        if (d->url)
154,774✔
407
                return false;
408

409
        k = table_data_size(type, data);
153,193✔
410
        l = table_data_size(d->type, d->data);
153,193✔
411
        if (k != l)
153,193✔
412
                return false;
413

414
        return memcmp_safe(data, d->data, l) == 0;
95,324✔
415
}
416

417
static TableData *table_data_new(
179,566✔
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
                bool uppercase) {
426

427
        _cleanup_free_ TableData *d = NULL;
179,566✔
428
        size_t data_size;
179,566✔
429

430
        data_size = table_data_size(type, data);
179,566✔
431

432
        d = malloc0(offsetof(TableData, data) + data_size);
179,566✔
433
        if (!d)
179,566✔
434
                return NULL;
435

436
        d->n_ref = 1;
179,566✔
437
        d->type = type;
179,566✔
438
        d->minimum_width = minimum_width;
179,566✔
439
        d->maximum_width = maximum_width;
179,566✔
440
        d->weight = weight;
179,566✔
441
        d->align_percent = align_percent;
179,566✔
442
        d->ellipsize_percent = ellipsize_percent;
179,566✔
443
        d->uppercase = uppercase;
179,566✔
444

445
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
179,566✔
446
                d->strv = strv_copy(data);
6,258✔
447
                if (!d->strv)
6,258✔
448
                        return NULL;
×
449
        } else
450
                memcpy_safe(d->data, data, data_size);
173,308✔
451

452
        return TAKE_PTR(d);
453
}
454

455
int table_add_cell_full(
194,405✔
456
                Table *t,
457
                TableCell **ret_cell,
458
                TableDataType type,
459
                const void *data,
460
                size_t minimum_width,
461
                size_t maximum_width,
462
                unsigned weight,
463
                unsigned align_percent,
464
                unsigned ellipsize_percent) {
465

466
        _cleanup_(table_data_unrefp) TableData *d = NULL;
194,405✔
467
        bool uppercase;
194,405✔
468
        TableData *p;
194,405✔
469

470
        assert(t);
194,405✔
471
        assert(type >= 0);
194,405✔
472
        assert(type < _TABLE_DATA_TYPE_MAX);
194,405✔
473

474
        /* Special rule: patch NULL data fields to the empty field */
475
        if (!data)
194,405✔
476
                type = TABLE_EMPTY;
9,303✔
477

478
        /* Determine the cell adjacent to the current one, but one row up */
479
        if (t->n_cells >= t->n_columns)
194,405✔
480
                assert_se(p = t->data[t->n_cells - t->n_columns]);
183,378✔
481
        else
482
                p = NULL;
483

484
        /* If formatting parameters are left unspecified, copy from the previous row */
485
        if (minimum_width == SIZE_MAX)
194,405✔
486
                minimum_width = p ? p->minimum_width : 1;
194,405✔
487

488
        if (weight == UINT_MAX)
194,405✔
489
                weight = p ? p->weight : DEFAULT_WEIGHT;
194,405✔
490

491
        if (align_percent == UINT_MAX)
194,405✔
492
                align_percent = p ? p->align_percent : 0;
194,405✔
493

494
        if (ellipsize_percent == UINT_MAX)
194,405✔
495
                ellipsize_percent = p ? p->ellipsize_percent : 100;
194,405✔
496

497
        assert(align_percent <= 100);
194,405✔
498
        assert(ellipsize_percent <= 100);
194,405✔
499

500
        uppercase = type == TABLE_HEADER;
194,405✔
501

502
        /* Small optimization: Pretty often adjacent cells in two subsequent lines have the same data and
503
         * formatting. Let's see if we can reuse the cell data and ref it once more. */
504

505
        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
194,405✔
506
                d = table_data_ref(p);
69,613✔
507
        else {
508
                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
124,792✔
509
                if (!d)
124,792✔
510
                        return -ENOMEM;
511
        }
512

513
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
194,405✔
514
                return -ENOMEM;
515

516
        if (ret_cell)
194,405✔
517
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
174,925✔
518

519
        t->data[t->n_cells++] = TAKE_PTR(d);
194,405✔
520

521
        return 0;
194,405✔
522
}
523

524
int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
7,870✔
525
        _cleanup_free_ char *buffer = NULL;
7,870✔
526
        va_list ap;
7,870✔
527
        int r;
7,870✔
528

529
        assert(t);
7,870✔
530
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
7,870✔
531

532
        va_start(ap, format);
7,870✔
533
        r = vasprintf(&buffer, format, ap);
7,870✔
534
        va_end(ap);
7,870✔
535
        if (r < 0)
7,870✔
536
                return -ENOMEM;
537

538
        return table_add_cell(t, ret_cell, dt, buffer);
7,870✔
539
}
540

541
int table_fill_empty(Table *t, size_t until_column) {
53✔
542
        int r;
53✔
543

544
        assert(t);
53✔
545

546
        /* Fill the rest of the current line with empty cells until we reach the specified column. Will add
547
         * at least one cell. Pass 0 in order to fill a line to the end or insert an empty line. */
548

549
        if (until_column >= t->n_columns)
53✔
550
                return -EINVAL;
551

552
        do {
53✔
553
                r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
53✔
554
                if (r < 0)
53✔
555
                        return r;
556

557
        } while ((t->n_cells % t->n_columns) != until_column);
53✔
558

559
        return 0;
560
}
561

562
int table_dup_cell(Table *t, TableCell *cell) {
12✔
563
        size_t i;
12✔
564

565
        assert(t);
12✔
566

567
        /* Add the data of the specified cell a second time as a new cell to the end. */
568

569
        i = TABLE_CELL_TO_INDEX(cell);
12✔
570
        if (i >= t->n_cells)
12✔
571
                return -ENXIO;
572

573
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
12✔
574
                return -ENOMEM;
575

576
        t->data[t->n_cells++] = table_data_ref(t->data[i]);
12✔
577
        return 0;
12✔
578
}
579

580
static int table_dedup_cell(Table *t, TableCell *cell) {
203,330✔
581
        _cleanup_free_ char *curl = NULL;
203,330✔
582
        TableData *nd, *od;
203,330✔
583
        size_t i;
203,330✔
584

585
        assert(t);
203,330✔
586

587
        /* Helper call that ensures the specified cell's data object has a ref count of 1, which we can use before
588
         * changing a cell's formatting without effecting every other cell's formatting that shares the same data */
589

590
        i = TABLE_CELL_TO_INDEX(cell);
203,330✔
591
        if (i >= t->n_cells)
203,330✔
592
                return -ENXIO;
593

594
        assert_se(od = t->data[i]);
203,330✔
595
        if (od->n_ref == 1)
203,330✔
596
                return 0;
597

598
        assert(od->n_ref > 1);
53,651✔
599

600
        if (od->url) {
53,651✔
601
                curl = strdup(od->url);
×
602
                if (!curl)
×
603
                        return -ENOMEM;
604
        }
605

606
        nd = table_data_new(
107,302✔
607
                        od->type,
608
                        od->data,
53,651✔
609
                        od->minimum_width,
610
                        od->maximum_width,
611
                        od->weight,
612
                        od->align_percent,
613
                        od->ellipsize_percent,
614
                        od->uppercase);
53,651✔
615
        if (!nd)
53,651✔
616
                return -ENOMEM;
617

618
        nd->color = od->color;
53,651✔
619
        nd->rgap_color = od->rgap_color;
53,651✔
620
        nd->underline = od->underline;
53,651✔
621
        nd->rgap_underline = od->rgap_underline;
53,651✔
622
        nd->url = TAKE_PTR(curl);
53,651✔
623

624
        table_data_unref(od);
53,651✔
625
        t->data[i] = nd;
53,651✔
626

627
        assert(nd->n_ref == 1);
53,651✔
628

629
        return 1;
630
}
631

632
static TableData *table_get_data(Table *t, TableCell *cell) {
207,704✔
633
        size_t i;
207,704✔
634

635
        assert(t);
207,704✔
636
        assert(cell);
207,704✔
637

638
        /* Get the data object of the specified cell, or NULL if it doesn't exist */
639

640
        i = TABLE_CELL_TO_INDEX(cell);
207,704✔
641
        if (i >= t->n_cells)
207,704✔
642
                return NULL;
643

644
        assert(t->data[i]);
207,704✔
645
        assert(t->data[i]->n_ref > 0);
207,704✔
646

647
        return t->data[i];
648
}
649

650
int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
1,450✔
651
        int r;
1,450✔
652

653
        assert(t);
1,450✔
654
        assert(cell);
1,450✔
655

656
        if (minimum_width == SIZE_MAX)
1,450✔
657
                minimum_width = 1;
×
658

659
        r = table_dedup_cell(t, cell);
1,450✔
660
        if (r < 0)
1,450✔
661
                return r;
662

663
        table_get_data(t, cell)->minimum_width = minimum_width;
1,450✔
664
        return 0;
1,450✔
665
}
666

667
int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
1,414✔
668
        int r;
1,414✔
669

670
        assert(t);
1,414✔
671
        assert(cell);
1,414✔
672

673
        r = table_dedup_cell(t, cell);
1,414✔
674
        if (r < 0)
1,414✔
675
                return r;
676

677
        table_get_data(t, cell)->maximum_width = maximum_width;
1,414✔
678
        return 0;
1,414✔
679
}
680

681
int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
5✔
682
        int r;
5✔
683

684
        assert(t);
5✔
685
        assert(cell);
5✔
686

687
        if (weight == UINT_MAX)
5✔
688
                weight = DEFAULT_WEIGHT;
×
689

690
        r = table_dedup_cell(t, cell);
5✔
691
        if (r < 0)
5✔
692
                return r;
693

694
        table_get_data(t, cell)->weight = weight;
5✔
695
        return 0;
5✔
696
}
697

698
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
13,216✔
699
        int r;
13,216✔
700

701
        assert(t);
13,216✔
702
        assert(cell);
13,216✔
703

704
        if (percent == UINT_MAX)
13,216✔
705
                percent = 0;
706

707
        assert(percent <= 100);
13,216✔
708

709
        r = table_dedup_cell(t, cell);
13,216✔
710
        if (r < 0)
13,216✔
711
                return r;
712

713
        table_get_data(t, cell)->align_percent = percent;
13,216✔
714
        return 0;
13,216✔
715
}
716

717
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
2,960✔
718
        int r;
2,960✔
719

720
        assert(t);
2,960✔
721
        assert(cell);
2,960✔
722

723
        if (percent == UINT_MAX)
2,960✔
724
                percent = 100;
725

726
        assert(percent <= 100);
2,960✔
727

728
        r = table_dedup_cell(t, cell);
2,960✔
729
        if (r < 0)
2,960✔
730
                return r;
731

732
        table_get_data(t, cell)->ellipsize_percent = percent;
2,960✔
733
        return 0;
2,960✔
734
}
735

736
int table_set_color(Table *t, TableCell *cell, const char *color) {
58,858✔
737
        int r;
58,858✔
738

739
        assert(t);
58,858✔
740
        assert(cell);
58,858✔
741

742
        r = table_dedup_cell(t, cell);
58,858✔
743
        if (r < 0)
58,858✔
744
                return r;
745

746
        table_get_data(t, cell)->color = empty_to_null(color);
73,460✔
747
        return 0;
58,858✔
748
}
749

750
int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
3,108✔
751
        int r;
3,108✔
752

753
        assert(t);
3,108✔
754
        assert(cell);
3,108✔
755

756
        r = table_dedup_cell(t, cell);
3,108✔
757
        if (r < 0)
3,108✔
758
                return r;
759

760
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,216✔
761
        return 0;
3,108✔
762
}
763

764
int table_set_underline(Table *t, TableCell *cell, bool b) {
60,333✔
765
        TableData *d;
60,333✔
766
        int r;
60,333✔
767

768
        assert(t);
60,333✔
769
        assert(cell);
60,333✔
770

771
        r = table_dedup_cell(t, cell);
60,333✔
772
        if (r < 0)
60,333✔
773
                return r;
774

775
        assert_se(d = table_get_data(t, cell));
60,333✔
776

777
        if (d->underline == b)
60,333✔
778
                return 0;
779

780
        d->underline = b;
322✔
781
        return 1;
322✔
782
}
783

784
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
60,333✔
785
        TableData *d;
60,333✔
786
        int r;
60,333✔
787

788
        assert(t);
60,333✔
789
        assert(cell);
60,333✔
790

791
        r = table_dedup_cell(t, cell);
60,333✔
792
        if (r < 0)
60,333✔
793
                return r;
794

795
        assert_se(d = table_get_data(t, cell));
60,333✔
796

797
        if (d->rgap_underline == b)
60,333✔
798
                return 0;
799

800
        d->rgap_underline = b;
322✔
801
        return 1;
322✔
802
}
803

804
int table_set_url(Table *t, TableCell *cell, const char *url) {
1,652✔
805
        _cleanup_free_ char *copy = NULL;
1,652✔
806
        int r;
1,652✔
807

808
        assert(t);
1,652✔
809
        assert(cell);
1,652✔
810

811
        if (url) {
1,652✔
812
                copy = strdup(url);
1,640✔
813
                if (!copy)
1,640✔
814
                        return -ENOMEM;
815
        }
816

817
        r = table_dedup_cell(t, cell);
1,652✔
818
        if (r < 0)
1,652✔
819
                return r;
820

821
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,652✔
822
}
823

824
int table_set_uppercase(Table *t, TableCell *cell, bool b) {
1✔
825
        TableData *d;
1✔
826
        int r;
1✔
827

828
        assert(t);
1✔
829
        assert(cell);
1✔
830

831
        r = table_dedup_cell(t, cell);
1✔
832
        if (r < 0)
1✔
833
                return r;
834

835
        assert_se(d = table_get_data(t, cell));
1✔
836

837
        if (d->uppercase == b)
1✔
838
                return 0;
839

840
        d->formatted = mfree(d->formatted);
1✔
841
        d->uppercase = b;
1✔
842
        return 1;
1✔
843
}
844

845
int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
1,123✔
846
        _cleanup_free_ char *curl = NULL;
1,123✔
847
        TableData *nd, *od;
1,123✔
848
        size_t i;
1,123✔
849

850
        assert(t);
1,123✔
851
        assert(cell);
1,123✔
852

853
        i = TABLE_CELL_TO_INDEX(cell);
1,123✔
854
        if (i >= t->n_cells)
1,123✔
855
                return -ENXIO;
856

857
        assert_se(od = t->data[i]);
1,123✔
858

859
        if (od->url) {
1,123✔
860
                curl = strdup(od->url);
×
861
                if (!curl)
×
862
                        return -ENOMEM;
863
        }
864

865
        nd = table_data_new(
2,246✔
866
                        type,
867
                        data,
868
                        od->minimum_width,
869
                        od->maximum_width,
870
                        od->weight,
871
                        od->align_percent,
872
                        od->ellipsize_percent,
873
                        od->uppercase);
1,123✔
874
        if (!nd)
1,123✔
875
                return -ENOMEM;
876

877
        nd->color = od->color;
1,123✔
878
        nd->rgap_color = od->rgap_color;
1,123✔
879
        nd->underline = od->underline;
1,123✔
880
        nd->rgap_underline = od->rgap_underline;
1,123✔
881
        nd->url = TAKE_PTR(curl);
1,123✔
882

883
        table_data_unref(od);
1,123✔
884
        t->data[i] = nd;
1,123✔
885

886
        return 0;
1,123✔
887
}
888

889
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
45,899✔
890
        TableCell *last_cell = NULL;
45,899✔
891
        va_list ap;
45,899✔
892
        int r;
45,899✔
893

894
        assert(t);
45,899✔
895
        assert(first_type >= 0);
45,899✔
896
        assert(first_type < _TABLE_DATA_TYPE_MAX);
45,899✔
897

898
        va_start(ap, first_type);
45,899✔
899

900
        for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
341,634✔
901
                const void *data;
341,634✔
902
                union {
341,634✔
903
                        uint64_t size;
904
                        usec_t usec;
905
                        int int_val;
906
                        int8_t int8;
907
                        int16_t int16;
908
                        int32_t int32;
909
                        int64_t int64;
910
                        unsigned uint_val;
911
                        uint8_t uint8;
912
                        uint16_t uint16;
913
                        uint32_t uint32;
914
                        uint64_t uint64;
915
                        int percent;
916
                        int ifindex;
917
                        bool b;
918
                        union in_addr_union address;
919
                        sd_id128_t id128;
920
                        uid_t uid;
921
                        gid_t gid;
922
                        pid_t pid;
923
                        mode_t mode;
924
                        dev_t devnum;
925
                } buffer;
926

927
                switch (type) {
341,634✔
928

929
                case TABLE_EMPTY:
930
                        data = NULL;
931
                        break;
932

933
                case TABLE_STRING:
130,071✔
934
                case TABLE_PATH:
935
                case TABLE_PATH_BASENAME:
936
                case TABLE_FIELD:
937
                case TABLE_HEADER:
938
                case TABLE_VERSION:
939
                        data = va_arg(ap, const char *);
130,071✔
940
                        break;
130,071✔
941

942
                case TABLE_STRV:
6,574✔
943
                case TABLE_STRV_WRAPPED:
944
                        data = va_arg(ap, char * const *);
6,574✔
945
                        break;
6,574✔
946

947
                case TABLE_BOOLEAN_CHECKMARK:
5,816✔
948
                case TABLE_BOOLEAN:
949
                        buffer.b = va_arg(ap, int);
5,816✔
950
                        data = &buffer.b;
5,816✔
951
                        break;
5,816✔
952

953
                case TABLE_TIMESTAMP:
3,489✔
954
                case TABLE_TIMESTAMP_UTC:
955
                case TABLE_TIMESTAMP_RELATIVE:
956
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
957
                case TABLE_TIMESTAMP_LEFT:
958
                case TABLE_TIMESTAMP_DATE:
959
                case TABLE_TIMESPAN:
960
                case TABLE_TIMESPAN_MSEC:
961
                case TABLE_TIMESPAN_DAY:
962
                        buffer.usec = va_arg(ap, usec_t);
3,489✔
963
                        data = &buffer.usec;
3,489✔
964
                        break;
3,489✔
965

966
                case TABLE_SIZE:
665✔
967
                case TABLE_BPS:
968
                        buffer.size = va_arg(ap, uint64_t);
665✔
969
                        data = &buffer.size;
665✔
970
                        break;
665✔
971

972
                case TABLE_INT:
1,250✔
973
                case TABLE_SIGNAL:
974
                        buffer.int_val = va_arg(ap, int);
1,250✔
975
                        data = &buffer.int_val;
1,250✔
976
                        break;
1,250✔
977

978
                case TABLE_INT8: {
3✔
979
                        int x = va_arg(ap, int);
3✔
980
                        assert(x >= INT8_MIN && x <= INT8_MAX);
3✔
981

982
                        buffer.int8 = x;
3✔
983
                        data = &buffer.int8;
3✔
984
                        break;
3✔
985
                }
986

987
                case TABLE_INT16: {
3✔
988
                        int x = va_arg(ap, int);
3✔
989
                        assert(x >= INT16_MIN && x <= INT16_MAX);
3✔
990

991
                        buffer.int16 = x;
3✔
992
                        data = &buffer.int16;
3✔
993
                        break;
3✔
994
                }
995

996
                case TABLE_INT32:
3✔
997
                        buffer.int32 = va_arg(ap, int32_t);
3✔
998
                        data = &buffer.int32;
3✔
999
                        break;
3✔
1000

1001
                case TABLE_INT64:
195✔
1002
                        buffer.int64 = va_arg(ap, int64_t);
195✔
1003
                        data = &buffer.int64;
195✔
1004
                        break;
195✔
1005

1006
                case TABLE_UINT:
98✔
1007
                        buffer.uint_val = va_arg(ap, unsigned);
98✔
1008
                        data = &buffer.uint_val;
98✔
1009
                        break;
98✔
1010

1011
                case TABLE_UINT8: {
43✔
1012
                        unsigned x = va_arg(ap, unsigned);
43✔
1013
                        assert(x <= UINT8_MAX);
43✔
1014

1015
                        buffer.uint8 = x;
43✔
1016
                        data = &buffer.uint8;
43✔
1017
                        break;
43✔
1018
                }
1019

1020
                case TABLE_UINT16: {
88✔
1021
                        unsigned x = va_arg(ap, unsigned);
88✔
1022
                        assert(x <= UINT16_MAX);
88✔
1023

1024
                        buffer.uint16 = x;
88✔
1025
                        data = &buffer.uint16;
88✔
1026
                        break;
88✔
1027
                }
1028

1029
                case TABLE_UINT32:
1,776✔
1030
                case TABLE_UINT32_HEX:
1031
                        buffer.uint32 = va_arg(ap, uint32_t);
1,776✔
1032
                        data = &buffer.uint32;
1,776✔
1033
                        break;
1,776✔
1034

1035
                case TABLE_UINT64:
6,322✔
1036
                case TABLE_UINT64_HEX:
1037
                        buffer.uint64 = va_arg(ap, uint64_t);
6,322✔
1038
                        data = &buffer.uint64;
6,322✔
1039
                        break;
6,322✔
1040

1041
                case TABLE_PERCENT:
2✔
1042
                        buffer.percent = va_arg(ap, int);
2✔
1043
                        data = &buffer.percent;
2✔
1044
                        break;
2✔
1045

1046
                case TABLE_IFINDEX:
62✔
1047
                        buffer.ifindex = va_arg(ap, int);
62✔
1048
                        data = &buffer.ifindex;
62✔
1049
                        break;
62✔
1050

1051
                case TABLE_IN_ADDR:
179✔
1052
                        buffer.address = *va_arg(ap, union in_addr_union *);
179✔
1053
                        data = &buffer.address.in;
179✔
1054
                        break;
179✔
1055

1056
                case TABLE_IN6_ADDR:
26✔
1057
                        buffer.address = *va_arg(ap, union in_addr_union *);
26✔
1058
                        data = &buffer.address.in6;
26✔
1059
                        break;
26✔
1060

1061
                case TABLE_UUID:
1,916✔
1062
                case TABLE_ID128:
1063
                        buffer.id128 = va_arg(ap, sd_id128_t);
1,916✔
1064
                        data = &buffer.id128;
1,916✔
1065
                        break;
1,916✔
1066

1067
                case TABLE_UID:
526✔
1068
                        buffer.uid = va_arg(ap, uid_t);
526✔
1069
                        data = &buffer.uid;
526✔
1070
                        break;
526✔
1071

1072
                case TABLE_GID:
661✔
1073
                        buffer.gid = va_arg(ap, gid_t);
661✔
1074
                        data = &buffer.gid;
661✔
1075
                        break;
661✔
1076

1077
                case TABLE_PID:
238✔
1078
                        buffer.pid = va_arg(ap, pid_t);
238✔
1079
                        data = &buffer.pid;
238✔
1080
                        break;
238✔
1081

1082
                case TABLE_MODE:
4✔
1083
                case TABLE_MODE_INODE_TYPE:
1084
                        buffer.mode = va_arg(ap, mode_t);
4✔
1085
                        data = &buffer.mode;
4✔
1086
                        break;
4✔
1087

1088
                case TABLE_DEVNUM:
6✔
1089
                        buffer.devnum = va_arg(ap, dev_t);
6✔
1090
                        data = &buffer.devnum;
6✔
1091
                        break;
6✔
1092

1093
                case TABLE_SET_MINIMUM_WIDTH: {
1,437✔
1094
                        size_t w = va_arg(ap, size_t);
1,437✔
1095

1096
                        r = table_set_minimum_width(t, last_cell, w);
1,437✔
1097
                        goto check;
1,437✔
1098
                }
1099

1100
                case TABLE_SET_MAXIMUM_WIDTH: {
1,412✔
1101
                        size_t w = va_arg(ap, size_t);
1,412✔
1102
                        r = table_set_maximum_width(t, last_cell, w);
1,412✔
1103
                        goto check;
1,412✔
1104
                }
1105

1106
                case TABLE_SET_WEIGHT: {
×
1107
                        unsigned w = va_arg(ap, unsigned);
×
1108
                        r = table_set_weight(t, last_cell, w);
×
1109
                        goto check;
×
1110
                }
1111

1112
                case TABLE_SET_ALIGN_PERCENT: {
6,308✔
1113
                        unsigned p = va_arg(ap, unsigned);
6,308✔
1114
                        r = table_set_align_percent(t, last_cell, p);
6,308✔
1115
                        goto check;
6,308✔
1116
                }
1117

1118
                case TABLE_SET_ELLIPSIZE_PERCENT: {
1,412✔
1119
                        unsigned p = va_arg(ap, unsigned);
1,412✔
1120
                        r = table_set_ellipsize_percent(t, last_cell, p);
1,412✔
1121
                        goto check;
1,412✔
1122
                }
1123

1124
                case TABLE_SET_COLOR: {
55,750✔
1125
                        const char *c = va_arg(ap, const char*);
55,750✔
1126
                        r = table_set_color(t, last_cell, c);
55,750✔
1127
                        goto check;
55,750✔
1128
                }
1129

1130
                case TABLE_SET_RGAP_COLOR: {
×
1131
                        const char *c = va_arg(ap, const char*);
×
1132
                        r = table_set_rgap_color(t, last_cell, c);
×
1133
                        goto check;
×
1134
                }
1135

1136
                case TABLE_SET_BOTH_COLORS: {
3,108✔
1137
                        const char *c = va_arg(ap, const char*);
3,108✔
1138

1139
                        r = table_set_color(t, last_cell, c);
3,108✔
1140
                        if (r < 0) {
3,108✔
1141
                                va_end(ap);
×
1142
                                return r;
×
1143
                        }
1144

1145
                        r = table_set_rgap_color(t, last_cell, c);
3,108✔
1146
                        goto check;
3,108✔
1147
                }
1148

1149
                case TABLE_SET_UNDERLINE: {
×
1150
                        int u = va_arg(ap, int);
×
1151
                        r = table_set_underline(t, last_cell, u);
×
1152
                        goto check;
×
1153
                }
1154

1155
                case TABLE_SET_RGAP_UNDERLINE: {
×
1156
                        int u = va_arg(ap, int);
×
1157
                        r = table_set_rgap_underline(t, last_cell, u);
×
1158
                        goto check;
×
1159
                }
1160

1161
                case TABLE_SET_BOTH_UNDERLINES: {
60,333✔
1162
                        int u = va_arg(ap, int);
60,333✔
1163

1164
                        r = table_set_underline(t, last_cell, u);
60,333✔
1165
                        if (r < 0) {
60,333✔
1166
                                va_end(ap);
×
1167
                                return r;
×
1168
                        }
1169

1170
                        r = table_set_rgap_underline(t, last_cell, u);
60,333✔
1171
                        goto check;
60,333✔
1172
                }
1173

1174
                case TABLE_SET_URL: {
1,652✔
1175
                        const char *u = va_arg(ap, const char*);
1,652✔
1176
                        r = table_set_url(t, last_cell, u);
1,652✔
1177
                        goto check;
1,652✔
1178
                }
1179

1180
                case TABLE_SET_UPPERCASE: {
1✔
1181
                        int u = va_arg(ap, int);
1✔
1182
                        r = table_set_uppercase(t, last_cell, u);
1✔
1183
                        goto check;
1✔
1184
                }
1185

1186
                case TABLE_SET_JSON_FIELD_NAME: {
2✔
1187
                        const char *n = va_arg(ap, const char*);
2✔
1188
                        size_t idx;
2✔
1189
                        if (t->vertical) {
2✔
1190
                                assert(TABLE_CELL_TO_INDEX(last_cell) >= t->n_columns);
1✔
1191
                                idx = TABLE_CELL_TO_INDEX(last_cell) / t->n_columns - 1;
1✔
1192
                        } else {
1193
                                idx = TABLE_CELL_TO_INDEX(last_cell);
1✔
1194
                                assert(idx < t->n_columns);
1✔
1195
                        }
1196
                        r = table_set_json_field_name(t, idx, n);
2✔
1197
                        goto check;
2✔
1198
                }
1199

1200
                case _TABLE_DATA_TYPE_MAX:
45,899✔
1201
                        /* Used as end marker */
1202
                        va_end(ap);
45,899✔
1203
                        return 0;
45,899✔
1204

1205
                default:
×
1206
                        assert_not_reached();
×
1207
                }
1208

1209
                r = table_add_cell(t, &last_cell, type, data);
164,320✔
1210
        check:
295,735✔
1211
                if (r < 0) {
295,735✔
1212
                        va_end(ap);
×
1213
                        return r;
×
1214
                }
1215
        }
1216
}
1217

1218
void table_set_header(Table *t, bool b) {
182✔
1219
        assert(t);
182✔
1220

1221
        t->header = b;
182✔
1222
}
182✔
1223

1224
void table_set_width(Table *t, size_t width) {
99✔
1225
        assert(t);
99✔
1226

1227
        t->width = width;
99✔
1228
}
99✔
1229

1230
void table_set_cell_height_max(Table *t, size_t height) {
28✔
1231
        assert(t);
28✔
1232
        assert(height >= 1 || height == SIZE_MAX);
28✔
1233

1234
        t->cell_height_max = height;
28✔
1235
}
28✔
1236

1237
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
615✔
1238
        assert(t);
615✔
1239
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
615✔
1240

1241
        t->ersatz = ersatz;
615✔
1242
}
615✔
1243

1244
static const char* table_ersatz_string(const Table *t) {
12,872✔
1245
        switch (t->ersatz) {
12,872✔
1246
        case TABLE_ERSATZ_EMPTY:
1247
                return "";
1248
        case TABLE_ERSATZ_DASH:
9,280✔
1249
                return "-";
9,280✔
1250
        case TABLE_ERSATZ_UNSET:
424✔
1251
                return "(unset)";
424✔
1252
        case TABLE_ERSATZ_NA:
×
1253
                return "n/a";
×
1254
        default:
×
1255
                assert_not_reached();
×
1256
        }
1257
}
1258

1259
static int table_set_display_all(Table *t) {
299✔
1260
        size_t *d;
299✔
1261

1262
        assert(t);
299✔
1263

1264
        /* Initialize the display map to the identity */
1265

1266
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
299✔
1267
        if (!d)
299✔
1268
                return -ENOMEM;
1269

1270
        for (size_t i = 0; i < t->n_columns; i++)
3,795✔
1271
                d[i] = i;
3,496✔
1272

1273
        t->display_map = d;
299✔
1274
        t->n_display_map = t->n_columns;
299✔
1275

1276
        return 0;
299✔
1277
}
1278

1279
int table_set_display_internal(Table *t, size_t first_column, ...) {
31✔
1280
        size_t column;
31✔
1281
        va_list ap;
31✔
1282

1283
        assert(t);
31✔
1284

1285
        column = first_column;
31✔
1286

1287
        va_start(ap, first_column);
31✔
1288
        for (;;) {
159✔
1289
                assert(column < t->n_columns);
159✔
1290

1291
                if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
159✔
1292
                        va_end(ap);
×
1293
                        return -ENOMEM;
×
1294
                }
1295

1296
                t->display_map[t->n_display_map++] = column;
159✔
1297

1298
                column = va_arg(ap, size_t);
159✔
1299
                if (column == SIZE_MAX)
159✔
1300
                        break;
1301

1302
        }
1303
        va_end(ap);
31✔
1304

1305
        return 0;
31✔
1306
}
1307

1308
int table_set_sort_internal(Table *t, size_t first_column, ...) {
224✔
1309
        size_t column;
224✔
1310
        va_list ap;
224✔
1311

1312
        assert(t);
224✔
1313

1314
        column = first_column;
224✔
1315

1316
        va_start(ap, first_column);
224✔
1317
        for (;;) {
296✔
1318
                assert(column < t->n_columns);
296✔
1319

1320
                if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
296✔
1321
                        va_end(ap);
×
1322
                        return -ENOMEM;
×
1323
                }
1324

1325
                t->sort_map[t->n_sort_map++] = column;
296✔
1326

1327
                column = va_arg(ap, size_t);
296✔
1328
                if (column == SIZE_MAX)
296✔
1329
                        break;
1330
        }
1331
        va_end(ap);
224✔
1332

1333
        return 0;
224✔
1334
}
1335

1336
int table_hide_column_from_display_internal(Table *t, ...) {
589✔
1337
        size_t cur = 0;
589✔
1338
        int r;
589✔
1339

1340
        assert(t);
589✔
1341

1342
        /* If the display map is empty, initialize it with all available columns */
1343
        if (!t->display_map) {
589✔
1344
                r = table_set_display_all(t);
299✔
1345
                if (r < 0)
299✔
1346
                        return r;
1347
        }
1348

1349
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
7,863✔
1350
                bool listed = false;
7,274✔
1351
                va_list ap;
7,274✔
1352

1353
                va_start(ap, t);
7,274✔
1354
                for (;;) {
14,081✔
1355
                        size_t column;
14,081✔
1356

1357
                        column = va_arg(ap, size_t);
14,081✔
1358
                        if (column == SIZE_MAX)
14,081✔
1359
                                break;
1360
                        if (column == *i) {
7,409✔
1361
                                listed = true;
1362
                                break;
1363
                        }
1364
                }
1365
                va_end(ap);
7,274✔
1366

1367
                if (listed)
7,274✔
1368
                        continue;
602✔
1369

1370
                t->display_map[cur++] = *i;
6,672✔
1371
        }
1372

1373
        t->n_display_map = cur;
589✔
1374

1375
        return 0;
589✔
1376
}
1377

1378
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
11,836✔
1379
        int r;
11,836✔
1380

1381
        assert(a);
11,836✔
1382
        assert(b);
11,836✔
1383

1384
        if (a->type == b->type) {
11,836✔
1385

1386
                /* We only define ordering for cells of the same data type. If cells with different data types are
1387
                 * compared we follow the order the cells were originally added in */
1388

1389
                switch (a->type) {
11,830✔
1390

1391
                case TABLE_STRING:
8,343✔
1392
                case TABLE_FIELD:
1393
                case TABLE_HEADER:
1394
                        return strcmp(a->string, b->string);
8,343✔
1395

1396
                case TABLE_PATH:
39✔
1397
                case TABLE_PATH_BASENAME:
1398
                        return path_compare(a->string, b->string);
39✔
1399

1400
                case TABLE_VERSION:
×
1401
                        return strverscmp_improved(a->string, b->string);
×
1402

1403
                case TABLE_STRV:
×
1404
                case TABLE_STRV_WRAPPED:
1405
                        return strv_compare(a->strv, b->strv);
×
1406

1407
                case TABLE_BOOLEAN:
2✔
1408
                        if (!a->boolean && b->boolean)
2✔
1409
                                return -1;
1410
                        if (a->boolean && !b->boolean)
2✔
1411
                                return 1;
1412
                        return 0;
×
1413

1414
                case TABLE_TIMESTAMP:
×
1415
                case TABLE_TIMESTAMP_UTC:
1416
                case TABLE_TIMESTAMP_RELATIVE:
1417
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1418
                case TABLE_TIMESTAMP_LEFT:
1419
                case TABLE_TIMESTAMP_DATE:
1420
                        return CMP(a->timestamp, b->timestamp);
×
1421

1422
                case TABLE_TIMESPAN:
750✔
1423
                case TABLE_TIMESPAN_MSEC:
1424
                case TABLE_TIMESPAN_DAY:
1425
                        return CMP(a->timespan, b->timespan);
750✔
1426

1427
                case TABLE_SIZE:
×
1428
                case TABLE_BPS:
1429
                        return CMP(a->size, b->size);
×
1430

1431
                case TABLE_INT:
16✔
1432
                case TABLE_SIGNAL:
1433
                        return CMP(a->int_val, b->int_val);
16✔
1434

1435
                case TABLE_INT8:
×
1436
                        return CMP(a->int8, b->int8);
×
1437

1438
                case TABLE_INT16:
×
1439
                        return CMP(a->int16, b->int16);
×
1440

1441
                case TABLE_INT32:
×
1442
                        return CMP(a->int32, b->int32);
×
1443

1444
                case TABLE_INT64:
188✔
1445
                        return CMP(a->int64, b->int64);
188✔
1446

1447
                case TABLE_UINT:
12✔
1448
                        return CMP(a->uint_val, b->uint_val);
12✔
1449

1450
                case TABLE_UINT8:
×
1451
                        return CMP(a->uint8, b->uint8);
×
1452

1453
                case TABLE_UINT16:
×
1454
                        return CMP(a->uint16, b->uint16);
×
1455

1456
                case TABLE_UINT32:
27✔
1457
                case TABLE_UINT32_HEX:
1458
                        return CMP(a->uint32, b->uint32);
27✔
1459

1460
                case TABLE_UINT64:
×
1461
                case TABLE_UINT64_HEX:
1462
                        return CMP(a->uint64, b->uint64);
×
1463

1464
                case TABLE_PERCENT:
×
1465
                        return CMP(a->percent, b->percent);
×
1466

1467
                case TABLE_IFINDEX:
×
1468
                        return CMP(a->ifindex, b->ifindex);
×
1469

1470
                case TABLE_IN_ADDR:
×
1471
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1472

1473
                case TABLE_IN6_ADDR:
1474
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1475

1476
                case TABLE_UUID:
×
1477
                case TABLE_ID128:
1478
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1479

1480
                case TABLE_UID:
1,154✔
1481
                        return CMP(a->uid, b->uid);
1,154✔
1482

1483
                case TABLE_GID:
1,299✔
1484
                        return CMP(a->gid, b->gid);
1,299✔
1485

1486
                case TABLE_PID:
×
1487
                        return CMP(a->pid, b->pid);
×
1488

1489
                case TABLE_MODE:
×
1490
                case TABLE_MODE_INODE_TYPE:
1491
                        return CMP(a->mode, b->mode);
×
1492

1493
                case TABLE_DEVNUM:
×
1494
                        r = CMP(major(a->devnum), major(b->devnum));
×
1495
                        if (r != 0)
×
1496
                                return r;
×
1497

1498
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1499

1500
                default:
6✔
1501
                        ;
6✔
1502
                }
1503
        }
1504

1505
        /* Generic fallback using the original order in which the cells where added. */
1506
        return CMP(index_a, index_b);
6✔
1507
}
1508

1509
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
12,049✔
1510
        int r;
12,049✔
1511

1512
        assert(t);
12,049✔
1513
        assert(t->sort_map);
12,049✔
1514

1515
        /* Make sure the header stays at the beginning */
1516
        if (*a < t->n_columns && *b < t->n_columns)
12,049✔
1517
                return 0;
1518
        if (*a < t->n_columns)
12,049✔
1519
                return -1;
1520
        if (*b < t->n_columns)
11,564✔
1521
                return 1;
1522

1523
        /* Order other lines by the sorting map */
1524
        for (size_t i = 0; i < t->n_sort_map; i++) {
11,837✔
1525
                TableData *d, *dd;
11,836✔
1526

1527
                d = t->data[*a + t->sort_map[i]];
11,836✔
1528
                dd = t->data[*b + t->sort_map[i]];
11,836✔
1529

1530
                r = cell_data_compare(d, *a, dd, *b);
11,836✔
1531
                if (r != 0)
11,836✔
1532
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
11,563✔
1533
        }
1534

1535
        /* Order identical lines by the order there were originally added in */
1536
        return CMP(*a, *b);
1✔
1537
}
1538

1539
static char* format_strv_width(char **strv, size_t column_width) {
168✔
1540
        _cleanup_(memstream_done) MemStream m = {};
168✔
1541
        FILE *f;
168✔
1542

1543
        f = memstream_init(&m);
168✔
1544
        if (!f)
168✔
1545
                return NULL;
1546

1547
        size_t position = 0;
1548
        STRV_FOREACH(p, strv) {
763✔
1549
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
595✔
1550
                                                          * If that happens, we'll just print one item per line. */
1551

1552
                if (position == 0) {
595✔
1553
                        fputs(*p, f);
168✔
1554
                        position = our_len;
1555
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
854✔
1556
                        fprintf(f, " %s", *p);
377✔
1557
                        position = size_add(size_add(position, 1), our_len);
972✔
1558
                } else {
1559
                        fprintf(f, "\n%s", *p);
50✔
1560
                        position = our_len;
1561
                }
1562
        }
1563

1564
        char *buf;
168✔
1565
        if (memstream_finalize(&m, &buf, NULL) < 0)
168✔
1566
                return NULL;
1567

1568
        return buf;
168✔
1569
}
1570

1571
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
335,515✔
1572
        assert(d);
335,515✔
1573

1574
        if (d->formatted &&
335,515✔
1575
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1576
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
62,580✔
1577
                return d->formatted;
1578

1579
        switch (d->type) {
273,057✔
1580
        case TABLE_EMPTY:
12,726✔
1581
                return table_ersatz_string(t);
12,726✔
1582

1583
        case TABLE_STRING:
232,240✔
1584
        case TABLE_PATH:
1585
        case TABLE_PATH_BASENAME:
1586
        case TABLE_FIELD:
1587
        case TABLE_HEADER:
1588
        case TABLE_VERSION: {
1589
                _cleanup_free_ char *bn = NULL;
232,240✔
1590
                const char *s;
232,240✔
1591

1592
                if (d->type == TABLE_PATH_BASENAME)
232,240✔
1593
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
348✔
1594
                else
1595
                        s = d->string;
231,892✔
1596

1597
                if (d->uppercase && !avoid_uppercasing) {
232,240✔
1598
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,315✔
1599
                        if (!d->formatted)
3,315✔
1600
                                return NULL;
1601

1602
                        char *q = d->formatted;
1603
                        for (const char *p = s; *p; p++)
23,393✔
1604
                                *(q++) = (char) toupper((unsigned char) *p);
20,078✔
1605

1606
                        if (d->type == TABLE_FIELD)
3,315✔
1607
                                *(q++) = ':';
×
1608

1609
                        *q = 0;
3,315✔
1610
                        return d->formatted;
3,315✔
1611
                } else if (d->type == TABLE_FIELD) {
228,925✔
1612
                        d->formatted = strjoin(s, ":");
32,605✔
1613
                        if (!d->formatted)
32,605✔
1614
                                return NULL;
1615

1616
                        return d->formatted;
32,605✔
1617
                }
1618

1619
                if (bn) {
196,320✔
1620
                        d->formatted = TAKE_PTR(bn);
346✔
1621
                        return d->formatted;
346✔
1622
                }
1623

1624
                return d->string;
195,974✔
1625
        }
1626

1627
        case TABLE_STRV:
6,209✔
1628
                if (strv_isempty(d->strv))
6,209✔
1629
                        return table_ersatz_string(t);
×
1630

1631
                d->formatted = strv_join(d->strv, "\n");
6,209✔
1632
                if (!d->formatted)
6,209✔
1633
                        return NULL;
1634
                break;
1635

1636
        case TABLE_STRV_WRAPPED: {
168✔
1637
                if (strv_isempty(d->strv))
168✔
1638
                        return table_ersatz_string(t);
×
1639

1640
                char *buf = format_strv_width(d->strv, column_width);
168✔
1641
                if (!buf)
168✔
1642
                        return NULL;
1643

1644
                free_and_replace(d->formatted, buf);
168✔
1645
                d->formatted_for_width = column_width;
168✔
1646
                if (have_soft)
168✔
1647
                        *have_soft = true;
113✔
1648

1649
                break;
16,422✔
1650
        }
1651

1652
        case TABLE_BOOLEAN:
4,517✔
1653
                return yes_no(d->boolean);
337,246✔
1654

1655
        case TABLE_BOOLEAN_CHECKMARK:
6,928✔
1656
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,672✔
1657

1658
        case TABLE_TIMESTAMP:
1,891✔
1659
        case TABLE_TIMESTAMP_UTC:
1660
        case TABLE_TIMESTAMP_RELATIVE:
1661
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1662
        case TABLE_TIMESTAMP_LEFT:
1663
        case TABLE_TIMESTAMP_DATE: {
1664
                _cleanup_free_ char *p = NULL;
76✔
1665
                char *ret;
1,891✔
1666

1667
                p = new(char,
1,891✔
1668
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1669
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1670
                if (!p)
1,891✔
1671
                        return NULL;
1672

1673
                if (d->type == TABLE_TIMESTAMP)
1,891✔
1674
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,207✔
1675
                else if (d->type == TABLE_TIMESTAMP_UTC)
684✔
1676
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1677
                else if (d->type == TABLE_TIMESTAMP_DATE)
684✔
1678
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
1✔
1679
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1680
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1681
                else
1682
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1683
                                                             d->timestamp, CLOCK_REALTIME,
1684
                                                             /* implicit_left = */ d->type == TABLE_TIMESTAMP_LEFT);
1685
                if (!ret)
1,891✔
1686
                        return "-";
1687

1688
                d->formatted = TAKE_PTR(p);
1,815✔
1689
                break;
1,815✔
1690
        }
1691

1692
        case TABLE_TIMESPAN:
1,453✔
1693
        case TABLE_TIMESPAN_MSEC:
1694
        case TABLE_TIMESPAN_DAY: {
1695
                _cleanup_free_ char *p = NULL;
×
1696

1697
                p = new(char, FORMAT_TIMESPAN_MAX);
1,453✔
1698
                if (!p)
1,453✔
1699
                        return NULL;
1700

1701
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,899✔
1702
                                     d->type == TABLE_TIMESPAN ? 0 :
1,446✔
1703
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1704
                        return "-";
1705

1706
                d->formatted = TAKE_PTR(p);
1,453✔
1707
                break;
1,453✔
1708
        }
1709

1710
        case TABLE_SIZE: {
258✔
1711
                _cleanup_free_ char *p = NULL;
144✔
1712

1713
                p = new(char, FORMAT_BYTES_MAX);
258✔
1714
                if (!p)
258✔
1715
                        return NULL;
1716

1717
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
258✔
1718
                        return table_ersatz_string(t);
144✔
1719

1720
                d->formatted = TAKE_PTR(p);
114✔
1721
                break;
114✔
1722
        }
1723

1724
        case TABLE_BPS: {
404✔
1725
                _cleanup_free_ char *p = NULL;
×
1726
                size_t n;
404✔
1727

1728
                p = new(char, FORMAT_BYTES_MAX+2);
404✔
1729
                if (!p)
404✔
1730
                        return NULL;
1731

1732
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
404✔
1733
                        return table_ersatz_string(t);
×
1734

1735
                n = strlen(p);
404✔
1736
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
404✔
1737

1738
                d->formatted = TAKE_PTR(p);
404✔
1739
                break;
404✔
1740
        }
1741

1742
        case TABLE_INT: {
259✔
1743
                _cleanup_free_ char *p = NULL;
×
1744

1745
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
734✔
1746
                if (!p)
259✔
1747
                        return NULL;
×
1748

1749
                sprintf(p, "%i", d->int_val);
259✔
1750
                d->formatted = TAKE_PTR(p);
259✔
1751
                break;
259✔
1752
        }
1753

1754
        case TABLE_INT8: {
3✔
1755
                _cleanup_free_ char *p = NULL;
×
1756

1757
                p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
10✔
1758
                if (!p)
3✔
1759
                        return NULL;
×
1760

1761
                sprintf(p, "%" PRIi8, d->int8);
3✔
1762
                d->formatted = TAKE_PTR(p);
3✔
1763
                break;
3✔
1764
        }
1765

1766
        case TABLE_INT16: {
3✔
1767
                _cleanup_free_ char *p = NULL;
×
1768

1769
                p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
14✔
1770
                if (!p)
3✔
1771
                        return NULL;
×
1772

1773
                sprintf(p, "%" PRIi16, d->int16);
3✔
1774
                d->formatted = TAKE_PTR(p);
3✔
1775
                break;
3✔
1776
        }
1777

1778
        case TABLE_INT32: {
3✔
1779
                _cleanup_free_ char *p = NULL;
×
1780

1781
                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
24✔
1782
                if (!p)
3✔
1783
                        return NULL;
×
1784

1785
                sprintf(p, "%" PRIi32, d->int32);
3✔
1786
                d->formatted = TAKE_PTR(p);
3✔
1787
                break;
3✔
1788
        }
1789

1790
        case TABLE_INT64: {
155✔
1791
                _cleanup_free_ char *p = NULL;
×
1792

1793
                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
319✔
1794
                if (!p)
155✔
1795
                        return NULL;
×
1796

1797
                sprintf(p, "%" PRIi64, d->int64);
155✔
1798
                d->formatted = TAKE_PTR(p);
155✔
1799
                break;
155✔
1800
        }
1801

1802
        case TABLE_UINT: {
284✔
1803
                _cleanup_free_ char *p = NULL;
×
1804

1805
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
407✔
1806
                if (!p)
284✔
1807
                        return NULL;
×
1808

1809
                sprintf(p, "%u", d->uint_val);
284✔
1810
                d->formatted = TAKE_PTR(p);
284✔
1811
                break;
284✔
1812
        }
1813

1814
        case TABLE_UINT8: {
43✔
1815
                _cleanup_free_ char *p = NULL;
×
1816

1817
                p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
46✔
1818
                if (!p)
43✔
1819
                        return NULL;
×
1820

1821
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1822
                d->formatted = TAKE_PTR(p);
43✔
1823
                break;
43✔
1824
        }
1825

1826
        case TABLE_UINT16: {
88✔
1827
                _cleanup_free_ char *p = NULL;
×
1828

1829
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
302✔
1830
                if (!p)
88✔
1831
                        return NULL;
×
1832

1833
                sprintf(p, "%" PRIu16, d->uint16);
88✔
1834
                d->formatted = TAKE_PTR(p);
88✔
1835
                break;
88✔
1836
        }
1837

1838
        case TABLE_UINT32: {
1,022✔
1839
                _cleanup_free_ char *p = NULL;
×
1840

1841
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,539✔
1842
                if (!p)
1,022✔
1843
                        return NULL;
×
1844

1845
                sprintf(p, "%" PRIu32, d->uint32);
1,022✔
1846
                d->formatted = TAKE_PTR(p);
1,022✔
1847
                break;
1,022✔
1848
        }
1849

1850
        case TABLE_UINT32_HEX: {
2✔
1851
                _cleanup_free_ char *p = NULL;
×
1852

1853
                p = new(char, 8 + 1);
2✔
1854
                if (!p)
2✔
1855
                        return NULL;
×
1856

1857
                sprintf(p, "%" PRIx32, d->uint32);
2✔
1858
                d->formatted = TAKE_PTR(p);
2✔
1859
                break;
2✔
1860
        }
1861

1862
        case TABLE_UINT64: {
1,166✔
1863
                _cleanup_free_ char *p = NULL;
×
1864

1865
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
5,572✔
1866
                if (!p)
1,166✔
1867
                        return NULL;
×
1868

1869
                sprintf(p, "%" PRIu64, d->uint64);
1,166✔
1870
                d->formatted = TAKE_PTR(p);
1,166✔
1871
                break;
1,166✔
1872
        }
1873

1874
        case TABLE_UINT64_HEX: {
14✔
1875
                _cleanup_free_ char *p = NULL;
×
1876

1877
                p = new(char, 16 + 1);
14✔
1878
                if (!p)
14✔
1879
                        return NULL;
×
1880

1881
                sprintf(p, "%" PRIx64, d->uint64);
14✔
1882
                d->formatted = TAKE_PTR(p);
14✔
1883
                break;
14✔
1884
        }
1885

1886
        case TABLE_PERCENT: {
2✔
1887
                _cleanup_free_ char *p = NULL;
×
1888

1889
                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
5✔
1890
                if (!p)
2✔
1891
                        return NULL;
×
1892

1893
                sprintf(p, "%i%%" , d->percent);
2✔
1894
                d->formatted = TAKE_PTR(p);
2✔
1895
                break;
2✔
1896
        }
1897

1898
        case TABLE_IFINDEX: {
219✔
1899
                _cleanup_free_ char *p = NULL;
×
1900

1901
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
219✔
1902
                        return NULL;
×
1903

1904
                d->formatted = TAKE_PTR(p);
219✔
1905
                break;
219✔
1906
        }
1907

1908
        case TABLE_IN_ADDR:
205✔
1909
        case TABLE_IN6_ADDR: {
1910
                _cleanup_free_ char *p = NULL;
×
1911

1912
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1913
                                      &d->address, &p) < 0)
205✔
1914
                        return NULL;
×
1915

1916
                d->formatted = TAKE_PTR(p);
205✔
1917
                break;
205✔
1918
        }
1919

1920
        case TABLE_ID128: {
1921
                char *p;
1,096✔
1922

1923
                p = new(char, SD_ID128_STRING_MAX);
1,096✔
1924
                if (!p)
1,096✔
1925
                        return NULL;
1926

1927
                d->formatted = sd_id128_to_string(d->id128, p);
1,096✔
1928
                break;
1,096✔
1929
        }
1930

1931
        case TABLE_UUID: {
1932
                char *p;
299✔
1933

1934
                p = new(char, SD_ID128_UUID_STRING_MAX);
299✔
1935
                if (!p)
299✔
1936
                        return NULL;
1937

1938
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
299✔
1939
                break;
299✔
1940
        }
1941

1942
        case TABLE_UID: {
418✔
1943
                char *p;
418✔
1944

1945
                if (!uid_is_valid(d->uid))
418✔
1946
                        return table_ersatz_string(t);
×
1947

1948
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,435✔
1949
                if (!p)
418✔
1950
                        return NULL;
1951
                sprintf(p, UID_FMT, d->uid);
418✔
1952

1953
                d->formatted = p;
418✔
1954
                break;
418✔
1955
        }
1956

1957
        case TABLE_GID: {
609✔
1958
                char *p;
609✔
1959

1960
                if (!gid_is_valid(d->gid))
609✔
1961
                        return table_ersatz_string(t);
×
1962

1963
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,790✔
1964
                if (!p)
609✔
1965
                        return NULL;
1966
                sprintf(p, GID_FMT, d->gid);
609✔
1967

1968
                d->formatted = p;
609✔
1969
                break;
609✔
1970
        }
1971

1972
        case TABLE_PID: {
221✔
1973
                char *p;
221✔
1974

1975
                if (!pid_is_valid(d->pid))
221✔
1976
                        return table_ersatz_string(t);
×
1977

1978
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
657✔
1979
                if (!p)
221✔
1980
                        return NULL;
1981
                sprintf(p, PID_FMT, d->pid);
221✔
1982

1983
                d->formatted = p;
221✔
1984
                break;
221✔
1985
        }
1986

1987
        case TABLE_SIGNAL: {
123✔
1988
                const char *suffix;
123✔
1989
                char *p;
123✔
1990

1991
                suffix = signal_to_string(d->int_val);
123✔
1992
                if (!suffix)
123✔
1993
                        return table_ersatz_string(t);
×
1994

1995
                p = strjoin("SIG", suffix);
123✔
1996
                if (!p)
123✔
1997
                        return NULL;
1998

1999
                d->formatted = p;
123✔
2000
                break;
123✔
2001
        }
2002

2003
        case TABLE_MODE: {
24✔
2004
                char *p;
24✔
2005

2006
                if (d->mode == MODE_INVALID)
24✔
2007
                        return table_ersatz_string(t);
×
2008

2009
                p = new(char, 4 + 1);
24✔
2010
                if (!p)
24✔
2011
                        return NULL;
2012

2013
                sprintf(p, "%04o", d->mode & 07777);
24✔
2014
                d->formatted = p;
24✔
2015
                break;
24✔
2016
        }
2017

2018
        case TABLE_MODE_INODE_TYPE:
2✔
2019

2020
                if (d->mode == MODE_INVALID)
2✔
2021
                        return table_ersatz_string(t);
×
2022

2023
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2024

2025
        case TABLE_DEVNUM:
3✔
2026
                if (devnum_is_zero(d->devnum))
3✔
2027
                        return table_ersatz_string(t);
2✔
2028

2029
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2030
                        return NULL;
2031

2032
                break;
2033

2034
        default:
×
2035
                assert_not_reached();
×
2036
        }
2037

2038
        return d->formatted;
16,422✔
2039
}
2040

2041
static int console_width_height(
164,585✔
2042
                const char *s,
2043
                size_t *ret_width,
2044
                size_t *ret_height) {
2045

2046
        size_t max_width = 0, height = 0;
164,585✔
2047
        const char *p;
164,585✔
2048

2049
        assert(s);
164,585✔
2050

2051
        /* Determine the width and height in console character cells the specified string needs. */
2052

2053
        do {
167,817✔
2054
                size_t k;
167,817✔
2055

2056
                p = strchr(s, '\n');
167,817✔
2057
                if (p) {
167,817✔
2058
                        _cleanup_free_ char *c = NULL;
3,235✔
2059

2060
                        c = strndup(s, p - s);
3,235✔
2061
                        if (!c)
3,235✔
2062
                                return -ENOMEM;
×
2063

2064
                        k = utf8_console_width(c);
3,235✔
2065
                        s = p + 1;
3,235✔
2066
                } else {
2067
                        k = utf8_console_width(s);
164,582✔
2068
                        s = NULL;
164,582✔
2069
                }
2070
                if (k == SIZE_MAX)
167,817✔
2071
                        return -EINVAL;
2072
                if (k > max_width)
167,817✔
2073
                        max_width = k;
163,578✔
2074

2075
                height++;
167,817✔
2076
        } while (!isempty(s));
171,052✔
2077

2078
        if (ret_width)
164,585✔
2079
                *ret_width = max_width;
164,585✔
2080

2081
        if (ret_height)
164,585✔
2082
                *ret_height = height;
164,585✔
2083

2084
        return 0;
2085
}
2086

2087
static int table_data_requested_width_height(
164,585✔
2088
                Table *table,
2089
                TableData *d,
2090
                size_t available_width,
2091
                size_t *ret_width,
2092
                size_t *ret_height,
2093
                bool *have_soft) {
2094

2095
        _cleanup_free_ char *truncated = NULL;
164,585✔
2096
        bool truncation_applied = false;
164,585✔
2097
        size_t width, height;
164,585✔
2098
        const char *t;
164,585✔
2099
        int r;
164,585✔
2100
        bool soft = false;
164,585✔
2101

2102
        t = table_data_format(table, d, false, available_width, &soft);
164,585✔
2103
        if (!t)
164,585✔
2104
                return -ENOMEM;
2105

2106
        if (table->cell_height_max != SIZE_MAX) {
164,585✔
2107
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
180✔
2108
                if (r < 0)
180✔
2109
                        return r;
2110
                if (r > 0)
180✔
2111
                        truncation_applied = true;
25✔
2112

2113
                t = truncated;
180✔
2114
        }
2115

2116
        r = console_width_height(t, &width, &height);
164,585✔
2117
        if (r < 0)
164,585✔
2118
                return r;
2119

2120
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
164,585✔
2121
                width = d->maximum_width;
×
2122

2123
        if (width < d->minimum_width)
164,585✔
2124
                width = d->minimum_width;
2,730✔
2125

2126
        if (ret_width)
164,585✔
2127
                *ret_width = width;
164,585✔
2128
        if (ret_height)
164,585✔
2129
                *ret_height = height;
164,585✔
2130
        if (have_soft && soft)
164,585✔
2131
                *have_soft = true;
113✔
2132

2133
        return truncation_applied;
164,585✔
2134
}
2135

2136
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
124,883✔
2137
        size_t w = 0, space, lspace, old_length, clickable_length;
124,883✔
2138
        _cleanup_free_ char *clickable = NULL;
124,883✔
2139
        const char *p;
124,883✔
2140
        char *ret;
124,883✔
2141
        int r;
124,883✔
2142

2143
        /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
2144

2145
        assert(str);
124,883✔
2146
        assert(percent <= 100);
124,883✔
2147

2148
        old_length = strlen(str);
124,883✔
2149

2150
        if (url) {
124,883✔
2151
                r = terminal_urlify(url, str, &clickable);
1,552✔
2152
                if (r < 0)
1,552✔
2153
                        return NULL;
2154

2155
                clickable_length = strlen(clickable);
1,552✔
2156
        } else
2157
                clickable_length = old_length;
2158

2159
        /* Determine current width on screen */
2160
        p = str;
124,883✔
2161
        while (p < str + old_length) {
1,886,276✔
2162
                char32_t c;
1,761,393✔
2163

2164
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,761,393✔
2165
                        p++, w++; /* count invalid chars as 1 */
×
2166
                        continue;
×
2167
                }
2168

2169
                p = utf8_next_char(p);
1,761,393✔
2170
                w += unichar_iswide(c) ? 2 : 1;
3,522,779✔
2171
        }
2172

2173
        /* Already wider than the target, if so, don't do anything */
2174
        if (w >= new_length)
124,883✔
2175
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2176

2177
        /* How much spaces shall we add? An how much on the left side? */
2178
        space = new_length - w;
124,883✔
2179
        lspace = space * percent / 100U;
124,883✔
2180

2181
        ret = new(char, space + clickable_length + 1);
124,883✔
2182
        if (!ret)
124,883✔
2183
                return NULL;
2184

2185
        for (size_t i = 0; i < lspace; i++)
745,131✔
2186
                ret[i] = ' ';
620,248✔
2187
        memcpy(ret + lspace, clickable ?: str, clickable_length);
124,883✔
2188
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,483,893✔
2189
                ret[i] = ' ';
3,359,010✔
2190

2191
        ret[space + clickable_length] = 0;
124,883✔
2192
        return ret;
124,883✔
2193
}
2194

2195
static bool table_data_isempty(const TableData *d) {
553✔
2196
        assert(d);
553✔
2197

2198
        if (d->type == TABLE_EMPTY)
553✔
2199
                return true;
2200

2201
        /* Let's also consider an empty strv as truly empty. */
2202
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
471✔
2203
                return strv_isempty(d->strv);
×
2204

2205
        /* Note that an empty string we do not consider empty here! */
2206
        return false;
2207
}
2208

2209
static const char* table_data_color(const TableData *d) {
765✔
2210
        assert(d);
765✔
2211

2212
        if (d->color)
765✔
2213
                return d->color;
2214

2215
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2216
        if (table_data_isempty(d))
553✔
2217
                return ansi_grey();
82✔
2218

2219
        if (d->type == TABLE_FIELD)
471✔
2220
                return ansi_bright_blue();
×
2221

2222
        return NULL;
2223
}
2224

2225
static const char* table_data_underline(const TableData *d) {
765✔
2226
        assert(d);
765✔
2227

2228
        if (d->underline)
765✔
2229
                return ansi_add_underline_grey();
×
2230

2231
        if (d->type == TABLE_HEADER)
765✔
2232
                return ansi_add_underline();
40✔
2233

2234
        return NULL;
2235
}
2236

2237
static const char* table_data_rgap_underline(const TableData *d) {
170,905✔
2238
        assert(d);
170,905✔
2239

2240
        if (d->rgap_underline)
170,905✔
2241
                return ansi_add_underline_grey();
300✔
2242

2243
        if (d->type == TABLE_HEADER)
170,605✔
2244
                return ansi_add_underline();
6,752✔
2245

2246
        return NULL;
2247
}
2248

2249
int table_print(Table *t, FILE *f) {
3,462✔
2250
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,462✔
2251
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2252
                *width = NULL;
3,462✔
2253
        _cleanup_free_ size_t *sorted = NULL;
3,462✔
2254
        uint64_t *column_weight, weight_sum;
3,462✔
2255
        int r;
3,462✔
2256

2257
        assert(t);
3,462✔
2258

2259
        if (!f)
3,462✔
2260
                f = stdout;
2,577✔
2261

2262
        /* Ensure we have no incomplete rows */
2263
        assert(t->n_cells % t->n_columns == 0);
3,462✔
2264

2265
        n_rows = t->n_cells / t->n_columns;
3,462✔
2266
        assert(n_rows > 0); /* at least the header row must be complete */
3,462✔
2267

2268
        if (t->sort_map) {
3,462✔
2269
                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2270

2271
                sorted = new(size_t, n_rows);
185✔
2272
                if (!sorted)
185✔
2273
                        return -ENOMEM;
2274

2275
                for (size_t i = 0; i < n_rows; i++)
3,232✔
2276
                        sorted[i] = i * t->n_columns;
3,047✔
2277

2278
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
185✔
2279
        }
2280

2281
        if (t->display_map)
3,462✔
2282
                display_columns = t->n_display_map;
287✔
2283
        else
2284
                display_columns = t->n_columns;
3,175✔
2285

2286
        assert(display_columns > 0);
3,462✔
2287

2288
        minimum_width = newa(size_t, display_columns);
3,462✔
2289
        maximum_width = newa(size_t, display_columns);
3,462✔
2290
        requested_width = newa(size_t, display_columns);
3,462✔
2291
        column_weight = newa0(uint64_t, display_columns);
3,462✔
2292

2293
        for (size_t j = 0; j < display_columns; j++) {
13,059✔
2294
                minimum_width[j] = 1;
9,597✔
2295
                maximum_width[j] = SIZE_MAX;
9,597✔
2296
        }
2297

2298
        for (unsigned pass = 0; pass < 2; pass++) {
3,479✔
2299
                /* First pass: determine column sizes */
2300

2301
                for (size_t j = 0; j < display_columns; j++)
13,110✔
2302
                        requested_width[j] = SIZE_MAX;
9,631✔
2303

2304
                bool any_soft = false;
3,479✔
2305

2306
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
53,447✔
2307
                        TableData **row;
49,968✔
2308

2309
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2310
                         * hence we don't care for sorted[] during the first pass. */
2311
                        row = t->data + i * t->n_columns;
49,968✔
2312

2313
                        for (size_t j = 0; j < display_columns; j++) {
214,553✔
2314
                                TableData *d;
164,585✔
2315
                                size_t req_width, req_height;
164,585✔
2316

2317
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
164,585✔
2318

2319
                                r = table_data_requested_width_height(t, d,
164,705✔
2320
                                                                      width ? width[j] : SIZE_MAX,
120✔
2321
                                                                      &req_width, &req_height, &any_soft);
2322
                                if (r < 0)
164,585✔
2323
                                        return r;
×
2324
                                if (r > 0) { /* Truncated because too many lines? */
164,585✔
2325
                                        _cleanup_free_ char *last = NULL;
25✔
2326
                                        const char *field;
25✔
2327

2328
                                        /* If we are going to show only the first few lines of a cell that has
2329
                                         * multiple make sure that we have enough space horizontally to show an
2330
                                         * ellipsis. Hence, let's figure out the last line, and account for its
2331
                                         * length plus ellipsis. */
2332

2333
                                        field = table_data_format(t, d, false,
28✔
2334
                                                                  width ? width[j] : SIZE_MAX,
3✔
2335
                                                                  &any_soft);
2336
                                        if (!field)
25✔
2337
                                                return -ENOMEM;
2338

2339
                                        assert_se(t->cell_height_max > 0);
25✔
2340
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2341
                                        if (r < 0)
25✔
2342
                                                return r;
2343

2344
                                        req_width = MAX(req_width,
25✔
2345
                                                        utf8_console_width(last) +
2346
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2347
                                }
2348

2349
                                /* Determine the biggest width that any cell in this column would like to have */
2350
                                if (requested_width[j] == SIZE_MAX ||
164,585✔
2351
                                    requested_width[j] < req_width)
154,986✔
2352
                                        requested_width[j] = req_width;
21,843✔
2353

2354
                                /* Determine the minimum width any cell in this column needs */
2355
                                if (minimum_width[j] < d->minimum_width)
164,585✔
2356
                                        minimum_width[j] = d->minimum_width;
38✔
2357

2358
                                /* Determine the maximum width any cell in this column needs */
2359
                                if (d->maximum_width != SIZE_MAX &&
164,585✔
2360
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2361
                                     maximum_width[j] > d->maximum_width))
2362
                                        maximum_width[j] = d->maximum_width;
20✔
2363

2364
                                /* Determine the full columns weight */
2365
                                column_weight[j] += d->weight;
164,585✔
2366
                        }
2367
                }
2368

2369
                /* One space between each column */
2370
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,479✔
2371

2372
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2373
                weight_sum = 0;
3,479✔
2374
                for (size_t j = 0; j < display_columns; j++) {
13,110✔
2375
                        weight_sum += column_weight[j];
9,631✔
2376

2377
                        table_minimum_width += minimum_width[j];
9,631✔
2378

2379
                        if (maximum_width[j] == SIZE_MAX)
9,631✔
2380
                                table_maximum_width = SIZE_MAX;
2381
                        else
2382
                                table_maximum_width += maximum_width[j];
20✔
2383

2384
                        table_requested_width += requested_width[j];
9,631✔
2385
                }
2386

2387
                /* Calculate effective table width */
2388
                if (t->width != 0 && t->width != SIZE_MAX)
3,479✔
2389
                        table_effective_width = t->width;
2390
                else if (t->width == 0 ||
3,472✔
2391
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,401✔
2392
                        table_effective_width = table_requested_width;
2393
                else
2394
                        table_effective_width = MIN(table_requested_width, columns());
35✔
2395

2396
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,479✔
2397
                        table_effective_width = table_maximum_width;
×
2398

2399
                if (table_effective_width < table_minimum_width)
3,479✔
2400
                        table_effective_width = table_minimum_width;
2✔
2401

2402
                if (!width)
3,479✔
2403
                        width = newa(size_t, display_columns);
3,462✔
2404

2405
                if (table_effective_width >= table_requested_width) {
3,479✔
2406
                        size_t extra;
3,458✔
2407

2408
                        /* We have extra room, let's distribute it among columns according to their weights. We first provide
2409
                         * each column with what it asked for and the distribute the rest.  */
2410

2411
                        extra = table_effective_width - table_requested_width;
3,458✔
2412

2413
                        for (size_t j = 0; j < display_columns; j++) {
13,037✔
2414
                                size_t delta;
9,579✔
2415

2416
                                if (weight_sum == 0)
9,579✔
2417
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2418
                                else
2419
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
9,547✔
2420

2421
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
9,579✔
2422
                                        width[j] = maximum_width[j];
2✔
2423

2424
                                if (width[j] < minimum_width[j])
9,579✔
2425
                                        width[j] = minimum_width[j];
×
2426

2427
                                delta = LESS_BY(width[j], requested_width[j]);
9,579✔
2428

2429
                                /* Subtract what we just added from the rest */
2430
                                if (extra > delta)
9,579✔
2431
                                        extra -= delta;
13✔
2432
                                else
2433
                                        extra = 0;
2434

2435
                                assert(weight_sum >= column_weight[j]);
9,579✔
2436
                                weight_sum -= column_weight[j];
9,579✔
2437
                        }
2438

2439
                        break; /* Every column should be happy, no need to repeat calculations. */
3,462✔
2440
                } else {
2441
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2442
                         * with the minimum they need, and then distribute anything left. */
2443
                        bool finalize = false;
21✔
2444
                        size_t extra;
21✔
2445

2446
                        extra = table_effective_width - table_minimum_width;
21✔
2447

2448
                        for (size_t j = 0; j < display_columns; j++)
73✔
2449
                                width[j] = SIZE_MAX;
52✔
2450

2451
                        for (;;) {
60✔
2452
                                bool restart = false;
60✔
2453

2454
                                for (size_t j = 0; j < display_columns; j++) {
171✔
2455
                                        size_t delta, w;
129✔
2456

2457
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2458
                                        if (width[j] != SIZE_MAX)
129✔
2459
                                                continue;
42✔
2460

2461
                                        if (weight_sum == 0)
87✔
2462
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2463
                                        else
2464
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
87✔
2465

2466
                                        if (w >= requested_width[j]) {
87✔
2467
                                                /* Never give more than requested. If we hit a column like this, there's more
2468
                                                 * space to allocate to other columns which means we need to restart the
2469
                                                 * iteration. However, if we hit a column like this, let's assign it the space
2470
                                                 * it wanted for good early. */
2471

2472
                                                w = requested_width[j];
2473
                                                restart = true;
2474

2475
                                        } else if (!finalize)
68✔
2476
                                                continue;
35✔
2477

2478
                                        width[j] = w;
52✔
2479

2480
                                        assert(w >= minimum_width[j]);
52✔
2481
                                        delta = w - minimum_width[j];
52✔
2482

2483
                                        assert(delta <= extra);
52✔
2484
                                        extra -= delta;
52✔
2485

2486
                                        assert(weight_sum >= column_weight[j]);
52✔
2487
                                        weight_sum -= column_weight[j];
52✔
2488

2489
                                        if (restart && !finalize)
52✔
2490
                                                break;
2491
                                }
2492

2493
                                if (finalize)
60✔
2494
                                        break;
2495

2496
                                if (!restart)
39✔
2497
                                        finalize = true;
21✔
2498
                        }
2499

2500
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
21✔
2501
                                        * let's try to reformat them with the new widths. Otherwise, let's
2502
                                        * move on. */
2503
                                break;
2504
                }
2505
        }
2506

2507
        /* Second pass: show output */
2508
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
53,370✔
2509
                size_t n_subline = 0;
49,908✔
2510
                bool more_sublines;
49,908✔
2511
                TableData **row;
49,908✔
2512

2513
                if (sorted)
49,908✔
2514
                        row = t->data + sorted[i];
3,033✔
2515
                else
2516
                        row = t->data + i * t->n_columns;
46,875✔
2517

2518
                do {
53,128✔
2519
                        const char *gap_color = NULL, *gap_underline = NULL;
53,128✔
2520
                        more_sublines = false;
53,128✔
2521

2522
                        for (size_t j = 0; j < display_columns; j++) {
224,033✔
2523
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
170,905✔
2524
                                bool lines_truncated = false;
170,905✔
2525
                                const char *field, *color = NULL, *underline = NULL;
170,905✔
2526
                                TableData *d;
170,905✔
2527
                                size_t l;
170,905✔
2528

2529
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
170,905✔
2530

2531
                                field = table_data_format(t, d, false, width[j], NULL);
170,905✔
2532
                                if (!field)
170,905✔
2533
                                        return -ENOMEM;
2534

2535
                                r = string_extract_line(field, n_subline, &extracted);
170,905✔
2536
                                if (r < 0)
170,905✔
2537
                                        return r;
2538
                                if (r > 0) {
170,905✔
2539
                                        /* There are more lines to come */
2540
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,257✔
2541
                                                more_sublines = true; /* There are more lines to come */
2542
                                        else
2543
                                                lines_truncated = true;
25✔
2544
                                }
2545
                                if (extracted)
170,905✔
2546
                                        field = extracted;
7,881✔
2547

2548
                                l = utf8_console_width(field);
170,905✔
2549
                                if (l > width[j]) {
170,905✔
2550
                                        /* Field is wider than allocated space. Let's ellipsize */
2551

2552
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
33✔
2553
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2554
                                        if (!buffer)
33✔
2555
                                                return -ENOMEM;
2556

2557
                                        field = buffer;
2558
                                } else {
2559
                                        if (lines_truncated) {
170,872✔
2560
                                                _cleanup_free_ char *padded = NULL;
25✔
2561

2562
                                                /* We truncated more lines of this cell, let's add an
2563
                                                 * ellipsis. We first append it, but that might make our
2564
                                                 * string grow above what we have space for, hence ellipsize
2565
                                                 * right after. This will truncate the ellipsis and add a new
2566
                                                 * one. */
2567

2568
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2569
                                                if (!padded)
25✔
2570
                                                        return -ENOMEM;
2571

2572
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2573
                                                if (!buffer)
25✔
2574
                                                        return -ENOMEM;
2575

2576
                                                field = buffer;
25✔
2577
                                                l = utf8_console_width(field);
25✔
2578
                                        }
2579

2580
                                        if (l < width[j]) {
170,872✔
2581
                                                _cleanup_free_ char *aligned = NULL;
×
2582
                                                /* Field is shorter than allocated space. Let's align with spaces */
2583

2584
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
124,883✔
2585
                                                if (!aligned)
124,883✔
2586
                                                        return -ENOMEM;
×
2587

2588
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2589
                                                if (j == display_columns - 1 &&
172,297✔
2590
                                                    (!colors_enabled() || !table_data_color(d)) &&
47,468✔
2591
                                                    (!underline_enabled() || !table_data_underline(d)) &&
94,880✔
2592
                                                    (!urlify_enabled() || !d->url))
47,464✔
2593
                                                        delete_trailing_chars(aligned, NULL);
47,412✔
2594

2595
                                                free_and_replace(buffer, aligned);
124,883✔
2596
                                                field = buffer;
124,883✔
2597
                                        }
2598
                                }
2599

2600
                                if (l >= width[j] && d->url) {
170,905✔
2601
                                        _cleanup_free_ char *clickable = NULL;
×
2602

2603
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2604
                                        if (r < 0)
22✔
2605
                                                return r;
×
2606

2607
                                        free_and_replace(buffer, clickable);
22✔
2608
                                        field = buffer;
22✔
2609
                                }
2610

2611
                                if (colors_enabled() && gap_color)
170,905✔
2612
                                        fputs(gap_color, f);
×
2613
                                if (underline_enabled() && gap_underline)
170,905✔
2614
                                        fputs(gap_underline, f);
16✔
2615

2616
                                if (j > 0)
170,905✔
2617
                                        fputc(' ', f); /* column separator left of cell */
117,777✔
2618

2619
                                /* Undo gap color/underline */
2620
                                if ((colors_enabled() && gap_color) ||
341,810✔
2621
                                    (underline_enabled() && gap_underline))
171,616✔
2622
                                        fputs(ANSI_NORMAL, f);
16✔
2623

2624
                                if (colors_enabled()) {
170,905✔
2625
                                        color = table_data_color(d);
711✔
2626
                                        if (color)
711✔
2627
                                                fputs(color, f);
294✔
2628
                                }
2629

2630
                                if (underline_enabled()) {
170,905✔
2631
                                        underline = table_data_underline(d);
711✔
2632
                                        if (underline)
711✔
2633
                                                fputs(underline, f);
18✔
2634
                                }
2635

2636
                                fputs(field, f);
170,905✔
2637

2638
                                if (color || underline)
170,905✔
2639
                                        fputs(ANSI_NORMAL, f);
312✔
2640

2641
                                gap_color = d->rgap_color;
170,905✔
2642
                                gap_underline = table_data_rgap_underline(d);
170,905✔
2643
                        }
2644

2645
                        fputc('\n', f);
53,128✔
2646
                        n_subline++;
53,128✔
2647
                } while (more_sublines);
53,128✔
2648
        }
2649

2650
        return fflush_and_check(f);
3,462✔
2651
}
2652

2653
int table_format(Table *t, char **ret) {
39✔
2654
        _cleanup_(memstream_done) MemStream m = {};
39✔
2655
        FILE *f;
39✔
2656
        int r;
39✔
2657

2658
        assert(t);
39✔
2659
        assert(ret);
39✔
2660

2661
        f = memstream_init(&m);
39✔
2662
        if (!f)
39✔
2663
                return -ENOMEM;
2664

2665
        r = table_print(t, f);
39✔
2666
        if (r < 0)
39✔
2667
                return r;
2668

2669
        return memstream_finalize(&m, ret, NULL);
39✔
2670
}
2671

2672
size_t table_get_rows(Table *t) {
1,717✔
2673
        if (!t)
1,717✔
2674
                return 0;
2675

2676
        assert(t->n_columns > 0);
1,717✔
2677
        return t->n_cells / t->n_columns;
1,717✔
2678
}
2679

2680
size_t table_get_columns(Table *t) {
34✔
2681
        if (!t)
34✔
2682
                return 0;
2683

2684
        assert(t->n_columns > 0);
34✔
2685
        return t->n_columns;
2686
}
2687

2688
size_t table_get_current_column(Table *t) {
80✔
2689
        if (!t)
80✔
2690
                return 0;
2691

2692
        assert(t->n_columns > 0);
80✔
2693
        return t->n_cells % t->n_columns;
80✔
2694
}
2695

2696
int table_set_reverse(Table *t, size_t column, bool b) {
81✔
2697
        assert(t);
81✔
2698
        assert(column < t->n_columns);
81✔
2699

2700
        if (!t->reverse_map) {
81✔
2701
                if (!b)
81✔
2702
                        return 0;
2703

2704
                t->reverse_map = new0(bool, t->n_columns);
27✔
2705
                if (!t->reverse_map)
27✔
2706
                        return -ENOMEM;
2707
        }
2708

2709
        t->reverse_map[column] = b;
27✔
2710
        return 0;
27✔
2711
}
2712

2713
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,057✔
2714
        size_t i;
8,057✔
2715

2716
        assert(t);
8,057✔
2717

2718
        if (column >= t->n_columns)
8,057✔
2719
                return NULL;
2720

2721
        i = row * t->n_columns + column;
8,057✔
2722
        if (i >= t->n_cells)
8,057✔
2723
                return NULL;
2724

2725
        return TABLE_INDEX_TO_CELL(i);
8,057✔
2726
}
2727

2728
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2729
        TableData *d;
4,374✔
2730

2731
        assert(t);
4,374✔
2732

2733
        d = table_get_data(t, cell);
4,374✔
2734
        if (!d)
4,374✔
2735
                return NULL;
2736

2737
        return d->data;
4,374✔
2738
}
2739

2740
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2741
        TableCell *cell;
4,374✔
2742

2743
        cell = table_get_cell(t, row, column);
4,374✔
2744
        if (!cell)
4,374✔
2745
                return NULL;
2746

2747
        return table_get(t, cell);
4,374✔
2748
}
2749

2750
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
3,983✔
2751

2752
        switch (d->type) {
3,983✔
2753

2754
        case TABLE_EMPTY:
323✔
2755
                return sd_json_variant_new_null(ret);
323✔
2756

2757
        case TABLE_STRING:
2,019✔
2758
        case TABLE_PATH:
2759
        case TABLE_PATH_BASENAME:
2760
        case TABLE_FIELD:
2761
        case TABLE_HEADER:
2762
        case TABLE_VERSION:
2763
                return sd_json_variant_new_string(ret, d->string);
2,019✔
2764

2765
        case TABLE_STRV:
3✔
2766
        case TABLE_STRV_WRAPPED:
2767
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2768

2769
        case TABLE_BOOLEAN_CHECKMARK:
152✔
2770
        case TABLE_BOOLEAN:
2771
                return sd_json_variant_new_boolean(ret, d->boolean);
152✔
2772

2773
        case TABLE_TIMESTAMP:
155✔
2774
        case TABLE_TIMESTAMP_UTC:
2775
        case TABLE_TIMESTAMP_RELATIVE:
2776
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2777
        case TABLE_TIMESTAMP_LEFT:
2778
        case TABLE_TIMESTAMP_DATE:
2779
                if (d->timestamp == USEC_INFINITY)
155✔
2780
                        return sd_json_variant_new_null(ret);
×
2781

2782
                return sd_json_variant_new_unsigned(ret, d->timestamp);
155✔
2783

2784
        case TABLE_TIMESPAN:
×
2785
        case TABLE_TIMESPAN_MSEC:
2786
        case TABLE_TIMESPAN_DAY:
2787
                if (d->timespan == USEC_INFINITY)
×
2788
                        return sd_json_variant_new_null(ret);
×
2789

2790
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2791

2792
        case TABLE_SIZE:
36✔
2793
        case TABLE_BPS:
2794
                if (d->size == UINT64_MAX)
36✔
2795
                        return sd_json_variant_new_null(ret);
16✔
2796

2797
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2798

2799
        case TABLE_INT:
27✔
2800
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2801

2802
        case TABLE_INT8:
6✔
2803
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2804

2805
        case TABLE_INT16:
6✔
2806
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2807

2808
        case TABLE_INT32:
6✔
2809
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2810

2811
        case TABLE_INT64:
46✔
2812
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2813

2814
        case TABLE_UINT:
15✔
2815
                return sd_json_variant_new_unsigned(ret, d->uint_val);
15✔
2816

2817
        case TABLE_UINT8:
4✔
2818
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2819

2820
        case TABLE_UINT16:
4✔
2821
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2822

2823
        case TABLE_UINT32:
109✔
2824
        case TABLE_UINT32_HEX:
2825
                return sd_json_variant_new_unsigned(ret, d->uint32);
109✔
2826

2827
        case TABLE_UINT64:
386✔
2828
        case TABLE_UINT64_HEX:
2829
                return sd_json_variant_new_unsigned(ret, d->uint64);
386✔
2830

2831
        case TABLE_PERCENT:
×
2832
                return sd_json_variant_new_integer(ret, d->percent);
×
2833

2834
        case TABLE_IFINDEX:
×
2835
                if (d->ifindex <= 0)
×
2836
                        return sd_json_variant_new_null(ret);
×
2837

2838
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2839

2840
        case TABLE_IN_ADDR:
2841
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2842

2843
        case TABLE_IN6_ADDR:
2844
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2845

2846
        case TABLE_ID128:
502✔
2847
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2848

2849
        case TABLE_UUID:
91✔
2850
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2851

2852
        case TABLE_UID:
8✔
2853
                if (!uid_is_valid(d->uid))
8✔
2854
                        return sd_json_variant_new_null(ret);
×
2855

2856
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2857

2858
        case TABLE_GID:
8✔
2859
                if (!gid_is_valid(d->gid))
8✔
2860
                        return sd_json_variant_new_null(ret);
×
2861

2862
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2863

2864
        case TABLE_PID:
14✔
2865
                if (!pid_is_valid(d->pid))
14✔
2866
                        return sd_json_variant_new_null(ret);
×
2867

2868
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2869

2870
        case TABLE_SIGNAL:
8✔
2871
                if (!SIGNAL_VALID(d->int_val))
8✔
2872
                        return sd_json_variant_new_null(ret);
×
2873

2874
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2875

2876
        case TABLE_MODE:
51✔
2877
        case TABLE_MODE_INODE_TYPE:
2878
                if (d->mode == MODE_INVALID)
51✔
2879
                        return sd_json_variant_new_null(ret);
×
2880

2881
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2882

2883
        case TABLE_DEVNUM:
4✔
2884
                if (devnum_is_zero(d->devnum))
4✔
2885
                        return sd_json_variant_new_null(ret);
2✔
2886

2887
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2888
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2889
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2890

2891
        default:
2892
                return -EINVAL;
2893
        }
2894
}
2895

2896
char* table_mangle_to_json_field_name(const char *str) {
713✔
2897
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2898
         * field name can be hence this is a bit vague and black magic. Here's what we do:
2899
         *  - Convert spaces to underscores
2900
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
2901
         *  - Convert most other symbols to underscores (for similar reasons)
2902
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
2903
         */
2904

2905
        bool new_word = true;
713✔
2906
        char *c;
713✔
2907

2908
        assert(str);
713✔
2909

2910
        c = strdup(str);
713✔
2911
        if (!c)
713✔
2912
                return NULL;
2913

2914
        for (char *x = c; *x; x++) {
5,346✔
2915
                if (!strchr(ALPHANUMERICAL, *x)) {
4,633✔
2916
                        *x = '_';
179✔
2917
                        new_word = true;
179✔
2918
                        continue;
179✔
2919
                }
2920

2921
                if (new_word) {
4,454✔
2922
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
887✔
2923
                                                                  * then we assume the whole word is all-caps
2924
                                                                  * and avoid lowercasing it. */
2925
                                *x = ascii_tolower(*x);
886✔
2926
                        new_word = false;
2927
                }
2928
        }
2929

2930
        return c;
2931
}
2932

2933
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
699✔
2934
        _cleanup_free_ char *mangled = NULL;
1,398✔
2935
        const char *n;
699✔
2936

2937
        assert(t);
699✔
2938
        assert(d);
699✔
2939
        assert(ret);
699✔
2940

2941
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
699✔
2942
                n = d->string;
699✔
2943
        else {
2944
                n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
×
2945
                if (!n)
×
2946
                        return -ENOMEM;
2947
        }
2948

2949
        mangled = table_mangle_to_json_field_name(n);
699✔
2950
        if (!mangled)
699✔
2951
                return -ENOMEM;
2952

2953
        *ret = TAKE_PTR(mangled);
699✔
2954
        return 0;
699✔
2955
}
2956

2957
static const char *table_get_json_field_name(Table *t, size_t idx) {
741✔
2958
        assert(t);
741✔
2959

2960
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
741✔
2961
}
2962

2963
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
90✔
2964
        sd_json_variant **rows = NULL, **elements = NULL;
90✔
2965
        _cleanup_free_ size_t *sorted = NULL;
90✔
2966
        size_t n_rows, display_columns;
90✔
2967
        int r;
90✔
2968

2969
        assert(t);
90✔
2970
        assert(!t->vertical);
90✔
2971

2972
        /* Ensure we have no incomplete rows */
2973
        assert(t->n_columns > 0);
90✔
2974
        assert(t->n_cells % t->n_columns == 0);
90✔
2975

2976
        n_rows = t->n_cells / t->n_columns;
90✔
2977
        assert(n_rows > 0); /* at least the header row must be complete */
90✔
2978

2979
        if (t->sort_map) {
90✔
2980
                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2981

2982
                sorted = new(size_t, n_rows);
26✔
2983
                if (!sorted)
26✔
2984
                        return -ENOMEM;
2985

2986
                for (size_t i = 0; i < n_rows; i++)
130✔
2987
                        sorted[i] = i * t->n_columns;
104✔
2988

2989
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
26✔
2990
        }
2991

2992
        if (t->display_map)
90✔
2993
                display_columns = t->n_display_map;
43✔
2994
        else
2995
                display_columns = t->n_columns;
47✔
2996
        assert(display_columns > 0);
90✔
2997

2998
        elements = new0(sd_json_variant*, display_columns * 2);
90✔
2999
        if (!elements)
90✔
3000
                return -ENOMEM;
3001

3002
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
90✔
3003

3004
        for (size_t j = 0; j < display_columns; j++) {
827✔
3005
                _cleanup_free_ char *mangled = NULL;
737✔
3006
                const char *n;
737✔
3007
                size_t c;
737✔
3008

3009
                c = t->display_map ? t->display_map[j] : j;
737✔
3010

3011
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3012
                n = table_get_json_field_name(t, c);
737✔
3013
                if (!n) {
737✔
3014
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
697✔
3015
                        if (r < 0)
697✔
3016
                                return r;
3017

3018
                        n = mangled;
697✔
3019
                }
3020

3021
                r = sd_json_variant_new_string(elements + j*2, n);
737✔
3022
                if (r < 0)
737✔
3023
                        return r;
3024
        }
3025

3026
        rows = new0(sd_json_variant*, n_rows-1);
90✔
3027
        if (!rows)
90✔
3028
                return -ENOMEM;
3029

3030
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
90✔
3031

3032
        for (size_t i = 1; i < n_rows; i++) {
1,001✔
3033
                TableData **row;
911✔
3034

3035
                if (sorted)
911✔
3036
                        row = t->data + sorted[i];
78✔
3037
                else
3038
                        row = t->data + i * t->n_columns;
833✔
3039

3040
                for (size_t j = 0; j < display_columns; j++) {
4,890✔
3041
                        TableData *d;
3,979✔
3042
                        size_t k;
3,979✔
3043

3044
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
3,979✔
3045

3046
                        k = j*2+1;
3,979✔
3047
                        elements[k] = sd_json_variant_unref(elements[k]);
3,979✔
3048

3049
                        r = table_data_to_json(d, elements + k);
3,979✔
3050
                        if (r < 0)
3,979✔
3051
                                return r;
3052
                }
3053

3054
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
911✔
3055
                if (r < 0)
911✔
3056
                        return r;
3057
        }
3058

3059
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
90✔
3060
}
3061

3062
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3063
        sd_json_variant **elements = NULL;
1✔
3064
        size_t n_elements = 0;
1✔
3065
        int r;
1✔
3066

3067
        assert(t);
1✔
3068
        assert(t->vertical);
1✔
3069

3070
        if (t->n_columns != 2)
1✔
3071
                return -EINVAL;
1✔
3072

3073
        /* Ensure we have no incomplete rows */
3074
        assert(t->n_cells % t->n_columns == 0);
1✔
3075

3076
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3077
        if (!elements)
1✔
3078
                return -ENOMEM;
3079

3080
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3081

3082
        for (size_t i = t->n_columns; i < t->n_cells; i++) {
9✔
3083

3084
                if (i % t->n_columns == 0) {
8✔
3085
                        _cleanup_free_ char *mangled = NULL;
4✔
3086
                        const char *n;
4✔
3087

3088
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3089
                        if (!n) {
4✔
3090
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3091
                                if (r < 0)
2✔
3092
                                        return r;
×
3093

3094
                                n = mangled;
2✔
3095
                        }
3096

3097
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3098
                } else
3099
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3100
                if (r < 0)
8✔
3101
                        return r;
3102

3103
                n_elements++;
8✔
3104
        }
3105

3106
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3107
}
3108

3109
int table_to_json(Table *t, sd_json_variant **ret) {
91✔
3110
        assert(t);
91✔
3111

3112
        if (t->vertical)
91✔
3113
                return table_to_json_vertical(t, ret);
1✔
3114

3115
        return table_to_json_regular(t, ret);
90✔
3116
}
3117

3118
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
404✔
3119
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
404✔
3120
        int r;
404✔
3121

3122
        assert(t);
404✔
3123

3124
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
404✔
3125
                return table_print(t, f);
340✔
3126

3127
        if (!f)
64✔
3128
                f = stdout;
2✔
3129

3130
        r = table_to_json(t, &v);
64✔
3131
        if (r < 0)
64✔
3132
                return r;
3133

3134
        sd_json_variant_dump(v, flags, f, NULL);
64✔
3135

3136
        return fflush_and_check(f);
64✔
3137
}
3138

3139
int table_print_with_pager(
402✔
3140
                Table *t,
3141
                sd_json_format_flags_t json_format_flags,
3142
                PagerFlags pager_flags,
3143
                bool show_header) {
3144

3145
        bool saved_header;
402✔
3146
        int r;
402✔
3147

3148
        assert(t);
402✔
3149

3150
        /* An all-in-one solution for showing tables, and turning on a pager first. Also optionally suppresses
3151
         * the table header and logs about any error. */
3152

3153
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
402✔
3154
                pager_open(pager_flags);
376✔
3155

3156
        saved_header = t->header;
402✔
3157
        t->header = show_header;
402✔
3158
        r = table_print_json(t, stdout, json_format_flags);
402✔
3159
        t->header = saved_header;
402✔
3160
        if (r < 0)
402✔
UNCOV
3161
                return table_log_print_error(r);
×
3162

3163
        return 0;
3164
}
3165

3166
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
384✔
3167
        int r;
384✔
3168

3169
        assert(t);
384✔
3170

3171
        if (name) {
384✔
3172
                size_t m;
382✔
3173

3174
                m = MAX(idx + 1, t->n_json_fields);
382✔
3175
                if (!GREEDY_REALLOC0(t->json_fields, m))
382✔
3176
                        return -ENOMEM;
3177

3178
                r = free_and_strdup(t->json_fields + idx, name);
382✔
3179
                if (r < 0)
382✔
3180
                        return r;
3181

3182
                t->n_json_fields = m;
382✔
3183
                return r;
382✔
3184
        } else {
3185
                if (idx >= t->n_json_fields)
2✔
3186
                        return 0;
3187

3188
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3189
                return 1;
2✔
3190
        }
3191
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc