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

systemd / systemd / 23518499972

24 Mar 2026 09:45PM UTC coverage: 72.567% (-0.01%) from 72.581%
23518499972

push

github

web-flow
resolved: add "static RRs" concept (#41213)

split out of #40980

301 of 337 new or added lines in 9 files covered. (89.32%)

3451 existing lines in 67 files now uncovered.

316989 of 436822 relevant lines covered (72.57%)

1153112.24 hits per line

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

90.84
/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 "json-util.h"
18
#include "memory-util.h"
19
#include "memstream-util.h"
20
#include "pager.h"
21
#include "path-util.h"
22
#include "pretty-print.h"
23
#include "process-util.h"
24
#include "signal-util.h"
25
#include "sort-util.h"
26
#include "stat-util.h"
27
#include "string-util.h"
28
#include "strv.h"
29
#include "strxcpyx.h"
30
#include "terminal-util.h"
31
#include "time-util.h"
32
#include "user-util.h"
33
#include "utf8.h"
34

35
#define DEFAULT_WEIGHT 100
36

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

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

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

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

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

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

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

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

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

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

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

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

119
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
421,012✔
120
        size_t i;
421,012✔
121

122
        assert(cell);
421,012✔
123

124
        i = PTR_TO_SIZE(cell);
421,012✔
125
        assert(i > 0);
421,012✔
126

127
        return i-1;
421,012✔
128
}
129

130
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
199,858✔
131
        assert(index != SIZE_MAX);
199,858✔
132
        return SIZE_TO_PTR(index + 1);
199,858✔
133
}
134

135
struct Table {
136
        size_t n_columns;
137
        size_t n_cells;
138

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

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

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

148
        TableData **data;
149

150
        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 */
151
        size_t n_display_map;
152

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

156
        char **json_fields;
157
        size_t n_json_fields;
158

159
        bool *reverse_map;
160
};
161

162
Table* table_new_raw(size_t n_columns) {
3,816✔
163
        _cleanup_(table_unrefp) Table *t = NULL;
3,816✔
164

165
        assert(n_columns > 0);
3,816✔
166

167
        t = new(Table, 1);
3,816✔
168
        if (!t)
3,816✔
169
                return NULL;
170

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

179
        return TAKE_PTR(t);
3,816✔
180
}
181

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

188
        assert(first_header);
825✔
189

190
        va_start(ap, first_header);
825✔
191
        for (;;) {
12,613✔
192
                if (!va_arg(ap, const char*))
6,719✔
193
                        break;
194

195
                n_columns++;
5,894✔
196
        }
197
        va_end(ap);
825✔
198

199
        t = table_new_raw(n_columns);
825✔
200
        if (!t)
825✔
201
                return NULL;
202

203
        va_start(ap, first_header);
825✔
204
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
7,544✔
205
                TableCell *cell;
6,719✔
206

207
                r = table_add_cell(t, &cell, TABLE_HEADER, h);
6,719✔
208
                if (r < 0) {
6,719✔
209
                        va_end(ap);
×
210
                        return NULL;
×
211
                }
212
        }
213
        va_end(ap);
825✔
214

215
        assert(t->n_columns == t->n_cells);
825✔
216
        return TAKE_PTR(t);
825✔
217
}
218

219
Table* table_new_vertical(void) {
2,942✔
220
        _cleanup_(table_unrefp) Table *t = NULL;
2,942✔
221
        TableCell *cell;
2,942✔
222

223
        t = table_new_raw(2);
2,942✔
224
        if (!t)
2,942✔
225
                return NULL;
226

227
        t->vertical = true;
2,942✔
228
        t->header = false;
2,942✔
229

230
        if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
2,942✔
231
                return NULL;
232

233
        if (table_set_align_percent(t, cell, 100) < 0)
2,942✔
234
                return NULL;
235

236
        if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
2,942✔
237
                return NULL;
238

239
        if (table_set_align_percent(t, cell, 0) < 0)
2,942✔
240
                return NULL;
241

242
        return TAKE_PTR(t);
2,942✔
243
}
244

245
static TableData* table_data_free(TableData *d) {
194,354✔
246
        assert(d);
194,354✔
247

248
        free(d->formatted);
194,354✔
249
        free(d->url);
194,354✔
250

251
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
194,354✔
252
                strv_free(d->strv);
6,399✔
253

254
        if (d->type == TABLE_JSON)
194,354✔
255
                sd_json_variant_unref(d->json);
2,644✔
256

257
        return mfree(d);
194,354✔
258
}
259

260
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
348,564✔
261
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
214,856✔
262

263
Table* table_unref(Table *t) {
3,816✔
264
        if (!t)
3,816✔
265
                return NULL;
266

267
        for (size_t i = 0; i < t->n_cells; i++)
218,684✔
268
                table_data_unref(t->data[i]);
214,868✔
269

270
        free(t->data);
3,816✔
271
        free(t->display_map);
3,816✔
272
        free(t->sort_map);
3,816✔
273
        free(t->reverse_map);
3,816✔
274

275
        for (size_t i = 0; i < t->n_json_fields; i++)
7,364✔
276
                free(t->json_fields[i]);
3,548✔
277

278
        free(t->json_fields);
3,816✔
279

280
        return mfree(t);
3,816✔
281
}
282

283
static size_t table_data_size(TableDataType type, const void *data) {
530,938✔
284

285
        switch (type) {
530,938✔
286

287
        case TABLE_EMPTY:
288
                return 0;
289

290
        case TABLE_STRING:
422,353✔
291
        case TABLE_STRING_WITH_ANSI:
292
        case TABLE_PATH:
293
        case TABLE_PATH_BASENAME:
294
        case TABLE_FIELD:
295
        case TABLE_HEADER:
296
        case TABLE_VERSION:
297
                return strlen(data) + 1;
422,353✔
298

299
        case TABLE_STRV:
13,107✔
300
        case TABLE_STRV_WRAPPED:
301
                return sizeof(char **);
13,107✔
302

303
        case TABLE_BOOLEAN_CHECKMARK:
10,010✔
304
        case TABLE_BOOLEAN:
305
                return sizeof(bool);
10,010✔
306

307
        case TABLE_TIMESTAMP:
7,125✔
308
        case TABLE_TIMESTAMP_UTC:
309
        case TABLE_TIMESTAMP_RELATIVE:
310
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
311
        case TABLE_TIMESTAMP_LEFT:
312
        case TABLE_TIMESTAMP_DATE:
313
        case TABLE_TIMESPAN:
314
        case TABLE_TIMESPAN_MSEC:
315
        case TABLE_TIMESPAN_DAY:
316
                return sizeof(usec_t);
7,125✔
317

318
        case TABLE_SIZE:
19,563✔
319
        case TABLE_INT64:
320
        case TABLE_UINT64:
321
        case TABLE_UINT64_HEX:
322
        case TABLE_UINT64_HEX_0x:
323
        case TABLE_BPS:
324
                return sizeof(uint64_t);
19,563✔
325

326
        case TABLE_INT32:
4,552✔
327
        case TABLE_UINT32:
328
        case TABLE_UINT32_HEX:
329
        case TABLE_UINT32_HEX_0x:
330
                return sizeof(uint32_t);
4,552✔
331

332
        case TABLE_INT16:
102✔
333
        case TABLE_UINT16:
334
                return sizeof(uint16_t);
102✔
335

336
        case TABLE_INT8:
54✔
337
        case TABLE_UINT8:
338
                return sizeof(uint8_t);
54✔
339

340
        case TABLE_INT:
4,324✔
341
        case TABLE_UINT:
342
        case TABLE_PERCENT:
343
        case TABLE_IFINDEX:
344
        case TABLE_SIGNAL:
345
                return sizeof(int);
4,324✔
346

347
        case TABLE_IN_ADDR:
289✔
348
                return sizeof(struct in_addr);
289✔
349

350
        case TABLE_IN6_ADDR:
38✔
351
                return sizeof(struct in6_addr);
38✔
352

353
        case TABLE_UUID:
5,857✔
354
        case TABLE_ID128:
355
                return sizeof(sd_id128_t);
5,857✔
356

357
        case TABLE_UID:
1,365✔
358
                return sizeof(uid_t);
1,365✔
359
        case TABLE_GID:
1,824✔
360
                return sizeof(gid_t);
1,824✔
361
        case TABLE_PID:
594✔
362
                return sizeof(pid_t);
594✔
363

364
        case TABLE_MODE:
700✔
365
        case TABLE_MODE_INODE_TYPE:
366
                return sizeof(mode_t);
700✔
367

368
        case TABLE_DEVNUM:
6✔
369
                return sizeof(dev_t);
6✔
370

371
        case TABLE_JSON:
7,916✔
372
                return sizeof(sd_json_variant*);
7,916✔
373

374
        default:
×
375
                assert_not_reached();
×
376
        }
377
}
378

379
static bool table_data_matches(
201,777✔
380
                TableData *d,
381
                TableDataType type,
382
                const void *data,
383
                size_t minimum_width,
384
                size_t maximum_width,
385
                unsigned weight,
386
                unsigned align_percent,
387
                unsigned ellipsize_percent,
388
                bool uppercase) {
389

390
        size_t k, l;
201,777✔
391
        assert(d);
201,777✔
392

393
        if (d->type != type)
201,777✔
394
                return false;
395

396
        if (d->minimum_width != minimum_width)
173,268✔
397
                return false;
398

399
        if (d->maximum_width != maximum_width)
173,268✔
400
                return false;
401

402
        if (d->weight != weight)
171,911✔
403
                return false;
404

405
        if (d->align_percent != align_percent)
171,911✔
406
                return false;
407

408
        if (d->ellipsize_percent != ellipsize_percent)
171,911✔
409
                return false;
410

411
        if (d->uppercase != uppercase)
171,911✔
412
                return false;
413

414
        /* If a color/url is set, refuse to merge */
415
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
171,910✔
416
                return false;
417
        if (d->url)
169,873✔
418
                return false;
419

420
        k = table_data_size(type, data);
168,292✔
421
        l = table_data_size(d->type, d->data);
168,292✔
422
        if (k != l)
168,292✔
423
                return false;
424

425
        return memcmp_safe(data, d->data, l) == 0;
106,349✔
426
}
427

428
static TableData* table_data_new(
194,354✔
429
                TableDataType type,
430
                const void *data,
431
                size_t minimum_width,
432
                size_t maximum_width,
433
                unsigned weight,
434
                unsigned align_percent,
435
                unsigned ellipsize_percent,
436
                bool uppercase) {
437

438
        _cleanup_free_ TableData *d = NULL;
194,354✔
439
        size_t data_size;
194,354✔
440

441
        data_size = table_data_size(type, data);
194,354✔
442

443
        d = malloc0(offsetof(TableData, data) + data_size);
194,354✔
444
        if (!d)
194,354✔
445
                return NULL;
446

447
        d->n_ref = 1;
194,354✔
448
        d->type = type;
194,354✔
449
        d->minimum_width = minimum_width;
194,354✔
450
        d->maximum_width = maximum_width;
194,354✔
451
        d->weight = weight;
194,354✔
452
        d->align_percent = align_percent;
194,354✔
453
        d->ellipsize_percent = ellipsize_percent;
194,354✔
454
        d->uppercase = uppercase;
194,354✔
455

456
        switch (type) {
194,354✔
457

458
        case TABLE_STRV:
6,399✔
459
        case TABLE_STRV_WRAPPED:
460
                d->strv = strv_copy(data);
6,399✔
461
                if (!d->strv)
6,399✔
462
                        return NULL;
×
463
                break;
464

465
        case TABLE_JSON:
2,644✔
466
                d->json = sd_json_variant_ref((sd_json_variant*) data);
2,644✔
467
                break;
2,644✔
468

469
        default:
185,311✔
470
                memcpy_safe(d->data, data, data_size);
185,311✔
471
        }
472

473
        return TAKE_PTR(d);
474
}
475

476
int table_add_cell_full(
214,856✔
477
                Table *t,
478
                TableCell **ret_cell,
479
                TableDataType dt,
480
                const void *data,
481
                size_t minimum_width,
482
                size_t maximum_width,
483
                unsigned weight,
484
                unsigned align_percent,
485
                unsigned ellipsize_percent) {
486

487
        _cleanup_(table_data_unrefp) TableData *d = NULL;
214,856✔
488
        bool uppercase;
214,856✔
489
        TableData *p;
214,856✔
490

491
        assert(t);
214,856✔
492
        assert(dt >= 0);
214,856✔
493
        assert(dt < _TABLE_DATA_TYPE_MAX);
214,856✔
494

495
        /* Special rule: patch NULL data fields to the empty field */
496
        if (!data)
214,856✔
497
                dt = TABLE_EMPTY;
15,523✔
498

499
        /* Determine the cell adjacent to the current one, but one row up */
500
        if (t->n_cells >= t->n_columns)
214,856✔
501
                assert_se(p = t->data[t->n_cells - t->n_columns]);
201,777✔
502
        else
503
                p = NULL;
504

505
        /* If formatting parameters are left unspecified, copy from the previous row */
506
        if (minimum_width == SIZE_MAX)
214,856✔
507
                minimum_width = p ? p->minimum_width : 1;
214,856✔
508

509
        if (weight == UINT_MAX)
214,856✔
510
                weight = p ? p->weight : DEFAULT_WEIGHT;
214,856✔
511

512
        if (align_percent == UINT_MAX)
214,856✔
513
                align_percent = p ? p->align_percent : 0;
214,856✔
514

515
        if (ellipsize_percent == UINT_MAX)
214,856✔
516
                ellipsize_percent = p ? p->ellipsize_percent : 100;
214,856✔
517

518
        assert(align_percent <= 100);
214,856✔
519
        assert(ellipsize_percent <= 100);
214,856✔
520

521
        uppercase = dt == TABLE_HEADER;
214,856✔
522

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

526
        if (p && table_data_matches(p, dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
214,856✔
527
                d = table_data_ref(p);
77,093✔
528
        else {
529
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
137,763✔
530
                if (!d)
137,763✔
531
                        return -ENOMEM;
532
        }
533

534
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
214,856✔
535
                return -ENOMEM;
536

537
        if (ret_cell)
214,856✔
538
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
191,078✔
539

540
        t->data[t->n_cells++] = TAKE_PTR(d);
214,856✔
541

542
        return 0;
214,856✔
543
}
544

545
int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
7,998✔
546
        _cleanup_free_ char *buffer = NULL;
7,998✔
547
        va_list ap;
7,998✔
548
        int r;
7,998✔
549

550
        assert(t);
7,998✔
551
        assert(IN_SET(dt, TABLE_STRING, TABLE_STRING_WITH_ANSI, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
7,998✔
552

553
        va_start(ap, format);
7,998✔
554
        r = vasprintf(&buffer, format, ap);
7,998✔
555
        va_end(ap);
7,998✔
556
        if (r < 0)
7,998✔
557
                return -ENOMEM;
558

559
        return table_add_cell(t, ret_cell, dt, buffer);
7,998✔
560
}
561

562
int table_fill_empty(Table *t, size_t until_column) {
53✔
563
        int r;
53✔
564

565
        assert(t);
53✔
566

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

570
        if (until_column >= t->n_columns)
53✔
571
                return -EINVAL;
572

573
        do {
53✔
574
                r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
53✔
575
                if (r < 0)
53✔
576
                        return r;
577

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

580
        return 0;
581
}
582

583
int table_dup_cell(Table *t, TableCell *cell) {
12✔
584
        size_t i;
12✔
585

586
        assert(t);
12✔
587

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

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

594
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
12✔
595
                return -ENOMEM;
596

597
        t->data[t->n_cells++] = table_data_ref(t->data[i]);
12✔
598
        return 0;
12✔
599
}
600

601
static int table_dedup_cell(Table *t, TableCell *cell) {
207,731✔
602
        _cleanup_free_ char *curl = NULL;
207,731✔
603
        TableData *nd, *od;
207,731✔
604
        size_t i;
207,731✔
605

606
        assert(t);
207,731✔
607

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

611
        i = TABLE_CELL_TO_INDEX(cell);
207,731✔
612
        if (i >= t->n_cells)
207,731✔
613
                return -ENXIO;
614

615
        assert_se(od = t->data[i]);
207,731✔
616
        if (od->n_ref == 1)
207,731✔
617
                return 0;
618

619
        assert(od->n_ref > 1);
55,468✔
620

621
        if (od->url) {
55,468✔
622
                curl = strdup(od->url);
×
623
                if (!curl)
×
624
                        return -ENOMEM;
625
        }
626

627
        nd = table_data_new(
110,936✔
628
                        od->type,
629
                        od->data,
55,468✔
630
                        od->minimum_width,
631
                        od->maximum_width,
632
                        od->weight,
633
                        od->align_percent,
634
                        od->ellipsize_percent,
635
                        od->uppercase);
55,468✔
636
        if (!nd)
55,468✔
637
                return -ENOMEM;
638

639
        nd->color = od->color;
55,468✔
640
        nd->rgap_color = od->rgap_color;
55,468✔
641
        nd->underline = od->underline;
55,468✔
642
        nd->rgap_underline = od->rgap_underline;
55,468✔
643
        nd->url = TAKE_PTR(curl);
55,468✔
644

645
        table_data_unref(od);
55,468✔
646
        t->data[i] = nd;
55,468✔
647

648
        assert(nd->n_ref == 1);
55,468✔
649

650
        return 1;
651
}
652

653
static TableData *table_get_data(Table *t, TableCell *cell) {
212,143✔
654
        size_t i;
212,143✔
655

656
        assert(t);
212,143✔
657
        assert(cell);
212,143✔
658

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

661
        i = TABLE_CELL_TO_INDEX(cell);
212,143✔
662
        if (i >= t->n_cells)
212,143✔
663
                return NULL;
664

665
        assert(t->data[i]);
212,143✔
666
        assert(t->data[i]->n_ref > 0);
212,143✔
667

668
        return t->data[i];
669
}
670

671
int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
1,477✔
672
        int r;
1,477✔
673

674
        assert(t);
1,477✔
675
        assert(cell);
1,477✔
676

677
        if (minimum_width == SIZE_MAX)
1,477✔
678
                minimum_width = 1;
×
679

680
        r = table_dedup_cell(t, cell);
1,477✔
681
        if (r < 0)
1,477✔
682
                return r;
683

684
        table_get_data(t, cell)->minimum_width = minimum_width;
1,477✔
685
        return 0;
1,477✔
686
}
687

688
int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
1,414✔
689
        int r;
1,414✔
690

691
        assert(t);
1,414✔
692
        assert(cell);
1,414✔
693

694
        r = table_dedup_cell(t, cell);
1,414✔
695
        if (r < 0)
1,414✔
696
                return r;
697

698
        table_get_data(t, cell)->maximum_width = maximum_width;
1,414✔
699
        return 0;
1,414✔
700
}
701

702
int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
5,189✔
703
        int r;
5,189✔
704

705
        assert(t);
5,189✔
706
        assert(cell);
5,189✔
707

708
        if (weight == UINT_MAX)
5,189✔
709
                weight = DEFAULT_WEIGHT;
×
710

711
        r = table_dedup_cell(t, cell);
5,189✔
712
        if (r < 0)
5,189✔
713
                return r;
714

715
        table_get_data(t, cell)->weight = weight;
5,189✔
716
        return 0;
5,189✔
717
}
718

719
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
13,982✔
720
        int r;
13,982✔
721

722
        assert(t);
13,982✔
723
        assert(cell);
13,982✔
724

725
        if (percent == UINT_MAX)
13,982✔
726
                percent = 0;
727

728
        assert(percent <= 100);
13,982✔
729

730
        r = table_dedup_cell(t, cell);
13,982✔
731
        if (r < 0)
13,982✔
732
                return r;
733

734
        table_get_data(t, cell)->align_percent = percent;
13,982✔
735
        return 0;
13,982✔
736
}
737

738
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
2,960✔
739
        int r;
2,960✔
740

741
        assert(t);
2,960✔
742
        assert(cell);
2,960✔
743

744
        if (percent == UINT_MAX)
2,960✔
745
                percent = 100;
746

747
        assert(percent <= 100);
2,960✔
748

749
        r = table_dedup_cell(t, cell);
2,960✔
750
        if (r < 0)
2,960✔
751
                return r;
752

753
        table_get_data(t, cell)->ellipsize_percent = percent;
2,960✔
754
        return 0;
2,960✔
755
}
756

757
int table_set_color(Table *t, TableCell *cell, const char *color) {
58,856✔
758
        int r;
58,856✔
759

760
        assert(t);
58,856✔
761
        assert(cell);
58,856✔
762

763
        r = table_dedup_cell(t, cell);
58,856✔
764
        if (r < 0)
58,856✔
765
                return r;
766

767
        table_get_data(t, cell)->color = empty_to_null(color);
73,769✔
768
        return 0;
58,856✔
769
}
770

771
int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
3,171✔
772
        int r;
3,171✔
773

774
        assert(t);
3,171✔
775
        assert(cell);
3,171✔
776

777
        r = table_dedup_cell(t, cell);
3,171✔
778
        if (r < 0)
3,171✔
779
                return r;
780

781
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,342✔
782
        return 0;
3,171✔
783
}
784

785
int table_set_underline(Table *t, TableCell *cell, bool b) {
59,493✔
786
        TableData *d;
59,493✔
787
        int r;
59,493✔
788

789
        assert(t);
59,493✔
790
        assert(cell);
59,493✔
791

792
        r = table_dedup_cell(t, cell);
59,493✔
793
        if (r < 0)
59,493✔
794
                return r;
795

796
        assert_se(d = table_get_data(t, cell));
59,493✔
797

798
        if (d->underline == b)
59,493✔
799
                return 0;
800

801
        d->underline = b;
322✔
802
        return 1;
322✔
803
}
804

805
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
59,493✔
806
        TableData *d;
59,493✔
807
        int r;
59,493✔
808

809
        assert(t);
59,493✔
810
        assert(cell);
59,493✔
811

812
        r = table_dedup_cell(t, cell);
59,493✔
813
        if (r < 0)
59,493✔
814
                return r;
815

816
        assert_se(d = table_get_data(t, cell));
59,493✔
817

818
        if (d->rgap_underline == b)
59,493✔
819
                return 0;
820

821
        d->rgap_underline = b;
322✔
822
        return 1;
322✔
823
}
824

825
int table_set_url(Table *t, TableCell *cell, const char *url) {
1,695✔
826
        _cleanup_free_ char *copy = NULL;
1,695✔
827
        int r;
1,695✔
828

829
        assert(t);
1,695✔
830
        assert(cell);
1,695✔
831

832
        if (url) {
1,695✔
833
                copy = strdup(url);
1,647✔
834
                if (!copy)
1,647✔
835
                        return -ENOMEM;
836
        }
837

838
        r = table_dedup_cell(t, cell);
1,695✔
839
        if (r < 0)
1,695✔
840
                return r;
841

842
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,695✔
843
}
844

845
int table_set_uppercase(Table *t, TableCell *cell, bool b) {
1✔
846
        TableData *d;
1✔
847
        int r;
1✔
848

849
        assert(t);
1✔
850
        assert(cell);
1✔
851

852
        r = table_dedup_cell(t, cell);
1✔
853
        if (r < 0)
1✔
854
                return r;
855

856
        assert_se(d = table_get_data(t, cell));
1✔
857

858
        if (d->uppercase == b)
1✔
859
                return 0;
860

861
        d->formatted = mfree(d->formatted);
1✔
862
        d->uppercase = b;
1✔
863
        return 1;
1✔
864
}
865

866
int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
1,123✔
867
        _cleanup_free_ char *curl = NULL;
1,123✔
868
        TableData *nd, *od;
1,123✔
869
        size_t i;
1,123✔
870

871
        assert(t);
1,123✔
872
        assert(cell);
1,123✔
873

874
        i = TABLE_CELL_TO_INDEX(cell);
1,123✔
875
        if (i >= t->n_cells)
1,123✔
876
                return -ENXIO;
877

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

880
        if (od->url) {
1,123✔
881
                curl = strdup(od->url);
×
882
                if (!curl)
×
883
                        return -ENOMEM;
884
        }
885

886
        nd = table_data_new(
2,246✔
887
                        type,
888
                        data,
889
                        od->minimum_width,
890
                        od->maximum_width,
891
                        od->weight,
892
                        od->align_percent,
893
                        od->ellipsize_percent,
894
                        od->uppercase);
1,123✔
895
        if (!nd)
1,123✔
896
                return -ENOMEM;
897

898
        nd->color = od->color;
1,123✔
899
        nd->rgap_color = od->rgap_color;
1,123✔
900
        nd->underline = od->underline;
1,123✔
901
        nd->rgap_underline = od->rgap_underline;
1,123✔
902
        nd->url = TAKE_PTR(curl);
1,123✔
903

904
        table_data_unref(od);
1,123✔
905
        t->data[i] = nd;
1,123✔
906

907
        return 0;
1,123✔
908
}
909

910
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
49,801✔
911
        TableCell *last_cell = NULL;
49,801✔
912
        va_list ap;
49,801✔
913
        int r;
49,801✔
914

915
        assert(t);
49,801✔
916
        assert(first_type >= 0);
49,801✔
917
        assert(first_type < _TABLE_DATA_TYPE_MAX);
49,801✔
918

919
        va_start(ap, first_type);
49,801✔
920

921
        for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
364,065✔
922
                const void *data;
364,065✔
923
                union {
364,065✔
924
                        uint64_t size;
925
                        usec_t usec;
926
                        int int_val;
927
                        int8_t int8;
928
                        int16_t int16;
929
                        int32_t int32;
930
                        int64_t int64;
931
                        unsigned uint_val;
932
                        uint8_t uint8;
933
                        uint16_t uint16;
934
                        uint32_t uint32;
935
                        uint64_t uint64;
936
                        int percent;
937
                        int ifindex;
938
                        bool b;
939
                        union in_addr_union address;
940
                        sd_id128_t id128;
941
                        uid_t uid;
942
                        gid_t gid;
943
                        pid_t pid;
944
                        mode_t mode;
945
                        dev_t devnum;
946
                } buffer;
947

948
                switch (type) {
364,065✔
949

950
                case TABLE_EMPTY:
951
                        data = NULL;
952
                        break;
953

954
                case TABLE_STRING:
137,491✔
955
                case TABLE_STRING_WITH_ANSI:
956
                case TABLE_PATH:
957
                case TABLE_PATH_BASENAME:
958
                case TABLE_FIELD:
959
                case TABLE_HEADER:
960
                case TABLE_VERSION:
961
                        data = va_arg(ap, const char *);
137,491✔
962
                        break;
137,491✔
963

964
                case TABLE_STRV:
6,772✔
965
                case TABLE_STRV_WRAPPED:
966
                        data = va_arg(ap, char * const *);
6,772✔
967
                        break;
6,772✔
968

969
                case TABLE_BOOLEAN_CHECKMARK:
5,938✔
970
                case TABLE_BOOLEAN:
971
                        buffer.b = va_arg(ap, int);
5,938✔
972
                        data = &buffer.b;
5,938✔
973
                        break;
5,938✔
974

975
                case TABLE_TIMESTAMP:
3,649✔
976
                case TABLE_TIMESTAMP_UTC:
977
                case TABLE_TIMESTAMP_RELATIVE:
978
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
979
                case TABLE_TIMESTAMP_LEFT:
980
                case TABLE_TIMESTAMP_DATE:
981
                case TABLE_TIMESPAN:
982
                case TABLE_TIMESPAN_MSEC:
983
                case TABLE_TIMESPAN_DAY:
984
                        buffer.usec = va_arg(ap, usec_t);
3,649✔
985
                        data = &buffer.usec;
3,649✔
986
                        break;
3,649✔
987

988
                case TABLE_SIZE:
752✔
989
                case TABLE_BPS:
990
                        buffer.size = va_arg(ap, uint64_t);
752✔
991
                        data = &buffer.size;
752✔
992
                        break;
752✔
993

994
                case TABLE_INT:
1,243✔
995
                case TABLE_SIGNAL:
996
                        buffer.int_val = va_arg(ap, int);
1,243✔
997
                        data = &buffer.int_val;
1,243✔
998
                        break;
1,243✔
999

1000
                case TABLE_INT8: {
3✔
1001
                        int x = va_arg(ap, int);
3✔
1002
                        assert(x >= INT8_MIN && x <= INT8_MAX);
3✔
1003

1004
                        buffer.int8 = x;
3✔
1005
                        data = &buffer.int8;
3✔
1006
                        break;
3✔
1007
                }
1008

1009
                case TABLE_INT16: {
3✔
1010
                        int x = va_arg(ap, int);
3✔
1011
                        assert(x >= INT16_MIN && x <= INT16_MAX);
3✔
1012

1013
                        buffer.int16 = x;
3✔
1014
                        data = &buffer.int16;
3✔
1015
                        break;
3✔
1016
                }
1017

1018
                case TABLE_INT32:
3✔
1019
                        buffer.int32 = va_arg(ap, int32_t);
3✔
1020
                        data = &buffer.int32;
3✔
1021
                        break;
3✔
1022

1023
                case TABLE_INT64:
195✔
1024
                        buffer.int64 = va_arg(ap, int64_t);
195✔
1025
                        data = &buffer.int64;
195✔
1026
                        break;
195✔
1027

1028
                case TABLE_UINT:
127✔
1029
                        buffer.uint_val = va_arg(ap, unsigned);
127✔
1030
                        data = &buffer.uint_val;
127✔
1031
                        break;
127✔
1032

1033
                case TABLE_UINT8: {
43✔
1034
                        unsigned x = va_arg(ap, unsigned);
43✔
1035
                        assert(x <= UINT8_MAX);
43✔
1036

1037
                        buffer.uint8 = x;
43✔
1038
                        data = &buffer.uint8;
43✔
1039
                        break;
43✔
1040
                }
1041

1042
                case TABLE_UINT16: {
91✔
1043
                        unsigned x = va_arg(ap, unsigned);
91✔
1044
                        assert(x <= UINT16_MAX);
91✔
1045

1046
                        buffer.uint16 = x;
91✔
1047
                        data = &buffer.uint16;
91✔
1048
                        break;
91✔
1049
                }
1050

1051
                case TABLE_UINT32:
1,813✔
1052
                case TABLE_UINT32_HEX:
1053
                case TABLE_UINT32_HEX_0x:
1054
                        buffer.uint32 = va_arg(ap, uint32_t);
1,813✔
1055
                        data = &buffer.uint32;
1,813✔
1056
                        break;
1,813✔
1057

1058
                case TABLE_UINT64:
6,664✔
1059
                case TABLE_UINT64_HEX:
1060
                case TABLE_UINT64_HEX_0x:
1061
                        buffer.uint64 = va_arg(ap, uint64_t);
6,664✔
1062
                        data = &buffer.uint64;
6,664✔
1063
                        break;
6,664✔
1064

1065
                case TABLE_PERCENT:
2✔
1066
                        buffer.percent = va_arg(ap, int);
2✔
1067
                        data = &buffer.percent;
2✔
1068
                        break;
2✔
1069

1070
                case TABLE_IFINDEX:
66✔
1071
                        buffer.ifindex = va_arg(ap, int);
66✔
1072
                        data = &buffer.ifindex;
66✔
1073
                        break;
66✔
1074

1075
                case TABLE_IN_ADDR:
179✔
1076
                        buffer.address = *va_arg(ap, union in_addr_union *);
179✔
1077
                        data = &buffer.address.in;
179✔
1078
                        break;
179✔
1079

1080
                case TABLE_IN6_ADDR:
26✔
1081
                        buffer.address = *va_arg(ap, union in_addr_union *);
26✔
1082
                        data = &buffer.address.in6;
26✔
1083
                        break;
26✔
1084

1085
                case TABLE_UUID:
1,973✔
1086
                case TABLE_ID128:
1087
                        buffer.id128 = va_arg(ap, sd_id128_t);
1,973✔
1088
                        data = &buffer.id128;
1,973✔
1089
                        break;
1,973✔
1090

1091
                case TABLE_UID:
520✔
1092
                        buffer.uid = va_arg(ap, uid_t);
520✔
1093
                        data = &buffer.uid;
520✔
1094
                        break;
520✔
1095

1096
                case TABLE_GID:
652✔
1097
                        buffer.gid = va_arg(ap, gid_t);
652✔
1098
                        data = &buffer.gid;
652✔
1099
                        break;
652✔
1100

1101
                case TABLE_PID:
241✔
1102
                        buffer.pid = va_arg(ap, pid_t);
241✔
1103
                        data = &buffer.pid;
241✔
1104
                        break;
241✔
1105

1106
                case TABLE_MODE:
4✔
1107
                case TABLE_MODE_INODE_TYPE:
1108
                        buffer.mode = va_arg(ap, mode_t);
4✔
1109
                        data = &buffer.mode;
4✔
1110
                        break;
4✔
1111

1112
                case TABLE_DEVNUM:
6✔
1113
                        buffer.devnum = va_arg(ap, dev_t);
6✔
1114
                        data = &buffer.devnum;
6✔
1115
                        break;
6✔
1116

1117
                case TABLE_JSON:
5,136✔
1118
                        data = va_arg(ap, sd_json_variant*);
5,136✔
1119
                        break;
5,136✔
1120

1121
                case TABLE_SET_MINIMUM_WIDTH: {
1,426✔
1122
                        size_t w = va_arg(ap, size_t);
1,426✔
1123

1124
                        r = table_set_minimum_width(t, last_cell, w);
1,426✔
1125
                        goto check;
1,426✔
1126
                }
1127

1128
                case TABLE_SET_MAXIMUM_WIDTH: {
1,412✔
1129
                        size_t w = va_arg(ap, size_t);
1,412✔
1130
                        r = table_set_maximum_width(t, last_cell, w);
1,412✔
1131
                        goto check;
1,412✔
1132
                }
1133

1134
                case TABLE_SET_WEIGHT: {
5,184✔
1135
                        unsigned w = va_arg(ap, unsigned);
5,184✔
1136
                        r = table_set_weight(t, last_cell, w);
5,184✔
1137
                        goto check;
5,184✔
1138
                }
1139

1140
                case TABLE_SET_ALIGN_PERCENT: {
6,308✔
1141
                        unsigned p = va_arg(ap, unsigned);
6,308✔
1142
                        r = table_set_align_percent(t, last_cell, p);
6,308✔
1143
                        goto check;
6,308✔
1144
                }
1145

1146
                case TABLE_SET_ELLIPSIZE_PERCENT: {
1,412✔
1147
                        unsigned p = va_arg(ap, unsigned);
1,412✔
1148
                        r = table_set_ellipsize_percent(t, last_cell, p);
1,412✔
1149
                        goto check;
1,412✔
1150
                }
1151

1152
                case TABLE_SET_COLOR: {
55,685✔
1153
                        const char *c = va_arg(ap, const char*);
55,685✔
1154
                        r = table_set_color(t, last_cell, c);
55,685✔
1155
                        goto check;
55,685✔
1156
                }
1157

1158
                case TABLE_SET_RGAP_COLOR: {
×
1159
                        const char *c = va_arg(ap, const char*);
×
1160
                        r = table_set_rgap_color(t, last_cell, c);
×
1161
                        goto check;
×
1162
                }
1163

1164
                case TABLE_SET_BOTH_COLORS: {
3,171✔
1165
                        const char *c = va_arg(ap, const char*);
3,171✔
1166

1167
                        r = table_set_color(t, last_cell, c);
3,171✔
1168
                        if (r < 0) {
3,171✔
1169
                                va_end(ap);
×
1170
                                return r;
×
1171
                        }
1172

1173
                        r = table_set_rgap_color(t, last_cell, c);
3,171✔
1174
                        goto check;
3,171✔
1175
                }
1176

1177
                case TABLE_SET_UNDERLINE: {
×
1178
                        int u = va_arg(ap, int);
×
1179
                        r = table_set_underline(t, last_cell, u);
×
1180
                        goto check;
×
1181
                }
1182

1183
                case TABLE_SET_RGAP_UNDERLINE: {
×
1184
                        int u = va_arg(ap, int);
×
1185
                        r = table_set_rgap_underline(t, last_cell, u);
×
1186
                        goto check;
×
1187
                }
1188

1189
                case TABLE_SET_BOTH_UNDERLINES: {
59,493✔
1190
                        int u = va_arg(ap, int);
59,493✔
1191

1192
                        r = table_set_underline(t, last_cell, u);
59,493✔
1193
                        if (r < 0) {
59,493✔
1194
                                va_end(ap);
×
1195
                                return r;
×
1196
                        }
1197

1198
                        r = table_set_rgap_underline(t, last_cell, u);
59,493✔
1199
                        goto check;
59,493✔
1200
                }
1201

1202
                case TABLE_SET_URL: {
1,695✔
1203
                        const char *u = va_arg(ap, const char*);
1,695✔
1204
                        r = table_set_url(t, last_cell, u);
1,695✔
1205
                        goto check;
1,695✔
1206
                }
1207

1208
                case TABLE_SET_UPPERCASE: {
1✔
1209
                        int u = va_arg(ap, int);
1✔
1210
                        r = table_set_uppercase(t, last_cell, u);
1✔
1211
                        goto check;
1✔
1212
                }
1213

1214
                case TABLE_SET_JSON_FIELD_NAME: {
2✔
1215
                        const char *n = va_arg(ap, const char*);
2✔
1216
                        size_t idx;
2✔
1217
                        if (t->vertical) {
2✔
1218
                                assert(TABLE_CELL_TO_INDEX(last_cell) >= t->n_columns);
1✔
1219
                                idx = TABLE_CELL_TO_INDEX(last_cell) / t->n_columns - 1;
1✔
1220
                        } else {
1221
                                idx = TABLE_CELL_TO_INDEX(last_cell);
1✔
1222
                                assert(idx < t->n_columns);
1✔
1223
                        }
1224
                        r = table_set_json_field_name(t, idx, n);
2✔
1225
                        goto check;
2✔
1226
                }
1227

1228
                case _TABLE_DATA_TYPE_MAX:
49,801✔
1229
                        /* Used as end marker */
1230
                        va_end(ap);
49,801✔
1231
                        return 0;
49,801✔
1232

1233
                default:
×
1234
                        assert_not_reached();
×
1235
                }
1236

1237
                r = table_add_cell(t, &last_cell, type, data);
178,475✔
1238
        check:
314,264✔
1239
                if (r < 0) {
314,264✔
1240
                        va_end(ap);
×
1241
                        return r;
×
1242
                }
1243
        }
1244
}
1245

1246
void table_set_header(Table *t, bool b) {
204✔
1247
        assert(t);
204✔
1248

1249
        t->header = b;
204✔
1250
}
204✔
1251

1252
void table_set_width(Table *t, size_t width) {
99✔
1253
        assert(t);
99✔
1254

1255
        t->width = width;
99✔
1256
}
99✔
1257

1258
void table_set_cell_height_max(Table *t, size_t height) {
32✔
1259
        assert(t);
32✔
1260
        assert(height >= 1 || height == SIZE_MAX);
32✔
1261

1262
        t->cell_height_max = height;
32✔
1263
}
32✔
1264

1265
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
704✔
1266
        assert(t);
704✔
1267
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
704✔
1268

1269
        t->ersatz = ersatz;
704✔
1270
}
704✔
1271

1272
static const char* table_ersatz_string(const Table *t) {
20,730✔
1273
        switch (t->ersatz) {
20,730✔
1274
        case TABLE_ERSATZ_EMPTY:
1275
                return "";
1276
        case TABLE_ERSATZ_DASH:
15,952✔
1277
                return "-";
15,952✔
1278
        case TABLE_ERSATZ_UNSET:
424✔
1279
                return "(unset)";
424✔
1280
        case TABLE_ERSATZ_NA:
×
1281
                return "n/a";
×
1282
        default:
×
1283
                assert_not_reached();
×
1284
        }
1285
}
1286

1287
static int table_set_display_all(Table *t) {
402✔
1288
        size_t *d;
402✔
1289

1290
        assert(t);
402✔
1291

1292
        /* Initialize the display map to the identity */
1293

1294
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
402✔
1295
        if (!d)
402✔
1296
                return -ENOMEM;
1297

1298
        for (size_t i = 0; i < t->n_columns; i++)
5,373✔
1299
                d[i] = i;
4,971✔
1300

1301
        t->display_map = d;
402✔
1302
        t->n_display_map = t->n_columns;
402✔
1303

1304
        return 0;
402✔
1305
}
1306

1307
int table_set_display_internal(Table *t, size_t first_column, ...) {
31✔
1308
        size_t column;
31✔
1309
        va_list ap;
31✔
1310

1311
        assert(t);
31✔
1312

1313
        column = first_column;
31✔
1314

1315
        va_start(ap, first_column);
31✔
1316
        for (;;) {
159✔
1317
                assert(column < t->n_columns);
159✔
1318

1319
                if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
159✔
1320
                        va_end(ap);
×
1321
                        return -ENOMEM;
×
1322
                }
1323

1324
                t->display_map[t->n_display_map++] = column;
159✔
1325

1326
                column = va_arg(ap, size_t);
159✔
1327
                if (column == SIZE_MAX)
159✔
1328
                        break;
1329

1330
        }
1331
        va_end(ap);
31✔
1332

1333
        return 0;
31✔
1334
}
1335

1336
int table_set_sort_internal(Table *t, size_t first_column, ...) {
272✔
1337
        size_t column;
272✔
1338
        va_list ap;
272✔
1339

1340
        assert(t);
272✔
1341

1342
        column = first_column;
272✔
1343

1344
        va_start(ap, first_column);
272✔
1345
        for (;;) {
366✔
1346
                assert(column < t->n_columns);
366✔
1347

1348
                if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
366✔
1349
                        va_end(ap);
×
1350
                        return -ENOMEM;
×
1351
                }
1352

1353
                t->sort_map[t->n_sort_map++] = column;
366✔
1354

1355
                column = va_arg(ap, size_t);
366✔
1356
                if (column == SIZE_MAX)
366✔
1357
                        break;
1358
        }
1359
        va_end(ap);
272✔
1360

1361
        return 0;
272✔
1362
}
1363

1364
int table_hide_column_from_display_internal(Table *t, ...) {
1,006✔
1365
        size_t cur = 0;
1,006✔
1366
        int r;
1,006✔
1367

1368
        assert(t);
1,006✔
1369

1370
        /* If the display map is empty, initialize it with all available columns */
1371
        if (!t->display_map) {
1,006✔
1372
                r = table_set_display_all(t);
402✔
1373
                if (r < 0)
402✔
1374
                        return r;
1375
        }
1376

1377
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
13,140✔
1378
                bool listed = false;
12,134✔
1379
                va_list ap;
12,134✔
1380

1381
                va_start(ap, t);
12,134✔
1382
                for (;;) {
23,726✔
1383
                        size_t column;
23,726✔
1384

1385
                        column = va_arg(ap, size_t);
23,726✔
1386
                        if (column == SIZE_MAX)
23,726✔
1387
                                break;
1388
                        if (column == *i) {
12,647✔
1389
                                listed = true;
1390
                                break;
1391
                        }
1392
                }
1393
                va_end(ap);
12,134✔
1394

1395
                if (listed)
12,134✔
1396
                        continue;
1,055✔
1397

1398
                t->display_map[cur++] = *i;
11,079✔
1399
        }
1400

1401
        t->n_display_map = cur;
1,006✔
1402

1403
        return 0;
1,006✔
1404
}
1405

1406
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
33,765✔
1407
        int r;
33,765✔
1408

1409
        assert(a);
33,765✔
1410
        assert(b);
33,765✔
1411

1412
        if (a->type == b->type) {
33,765✔
1413

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

1417
                switch (a->type) {
33,759✔
1418

1419
                case TABLE_STRING:
30,058✔
1420
                case TABLE_STRING_WITH_ANSI:
1421
                case TABLE_FIELD:
1422
                case TABLE_HEADER:
1423
                        return strcmp(a->string, b->string);
30,058✔
1424

1425
                case TABLE_PATH:
41✔
1426
                case TABLE_PATH_BASENAME:
1427
                        return path_compare(a->string, b->string);
41✔
1428

1429
                case TABLE_VERSION:
×
1430
                        return strverscmp_improved(a->string, b->string);
×
1431

1432
                case TABLE_STRV:
×
1433
                case TABLE_STRV_WRAPPED:
1434
                        return strv_compare(a->strv, b->strv);
×
1435

1436
                case TABLE_BOOLEAN:
2✔
1437
                        if (!a->boolean && b->boolean)
2✔
1438
                                return -1;
1439
                        if (a->boolean && !b->boolean)
2✔
1440
                                return 1;
1441
                        return 0;
×
1442

1443
                case TABLE_TIMESTAMP:
×
1444
                case TABLE_TIMESTAMP_UTC:
1445
                case TABLE_TIMESTAMP_RELATIVE:
1446
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1447
                case TABLE_TIMESTAMP_LEFT:
1448
                case TABLE_TIMESTAMP_DATE:
1449
                        return CMP(a->timestamp, b->timestamp);
×
1450

1451
                case TABLE_TIMESPAN:
810✔
1452
                case TABLE_TIMESPAN_MSEC:
1453
                case TABLE_TIMESPAN_DAY:
1454
                        return CMP(a->timespan, b->timespan);
810✔
1455

1456
                case TABLE_SIZE:
×
1457
                case TABLE_BPS:
1458
                        return CMP(a->size, b->size);
×
1459

1460
                case TABLE_INT:
16✔
1461
                case TABLE_SIGNAL:
1462
                        return CMP(a->int_val, b->int_val);
16✔
1463

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

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

1470
                case TABLE_INT32:
×
1471
                        return CMP(a->int32, b->int32);
×
1472

1473
                case TABLE_INT64:
188✔
1474
                        return CMP(a->int64, b->int64);
188✔
1475

1476
                case TABLE_UINT:
75✔
1477
                        return CMP(a->uint_val, b->uint_val);
75✔
1478

1479
                case TABLE_UINT8:
×
1480
                        return CMP(a->uint8, b->uint8);
×
1481

1482
                case TABLE_UINT16:
×
1483
                        return CMP(a->uint16, b->uint16);
×
1484

1485
                case TABLE_UINT32:
27✔
1486
                case TABLE_UINT32_HEX:
1487
                case TABLE_UINT32_HEX_0x:
1488
                        return CMP(a->uint32, b->uint32);
27✔
1489

1490
                case TABLE_UINT64:
×
1491
                case TABLE_UINT64_HEX:
1492
                case TABLE_UINT64_HEX_0x:
1493
                        return CMP(a->uint64, b->uint64);
×
1494

1495
                case TABLE_PERCENT:
×
1496
                        return CMP(a->percent, b->percent);
×
1497

1498
                case TABLE_IFINDEX:
×
1499
                        return CMP(a->ifindex, b->ifindex);
×
1500

1501
                case TABLE_IN_ADDR:
×
1502
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1503

1504
                case TABLE_IN6_ADDR:
1505
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1506

1507
                case TABLE_UUID:
×
1508
                case TABLE_ID128:
1509
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1510

1511
                case TABLE_UID:
1,125✔
1512
                        return CMP(a->uid, b->uid);
1,125✔
1513

1514
                case TABLE_GID:
1,297✔
1515
                        return CMP(a->gid, b->gid);
1,297✔
1516

1517
                case TABLE_PID:
×
1518
                        return CMP(a->pid, b->pid);
×
1519

1520
                case TABLE_MODE:
×
1521
                case TABLE_MODE_INODE_TYPE:
1522
                        return CMP(a->mode, b->mode);
×
1523

1524
                case TABLE_DEVNUM:
×
1525
                        r = CMP(major(a->devnum), major(b->devnum));
×
1526
                        if (r != 0)
×
1527
                                return r;
×
1528

1529
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1530

1531
                case TABLE_JSON:
×
1532
                        return json_variant_compare(a->json, b->json);
×
1533

1534
                default:
6✔
1535
                        ;
126✔
1536
                }
1537
        }
1538

1539
        /* Generic fallback using the original order in which the cells where added. */
1540
        return CMP(index_a, index_b);
126✔
1541
}
1542

1543
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
24,201✔
1544
        int r;
24,201✔
1545

1546
        assert(t);
24,201✔
1547
        assert(t->sort_map);
24,201✔
1548

1549
        /* Make sure the header stays at the beginning */
1550
        if (*a < t->n_columns && *b < t->n_columns)
24,201✔
1551
                return 0;
1552
        if (*a < t->n_columns)
24,201✔
1553
                return -1;
1554
        if (*b < t->n_columns)
23,599✔
1555
                return 1;
1556

1557
        /* Order other lines by the sorting map */
1558
        for (size_t i = 0; i < t->n_sort_map; i++) {
33,766✔
1559
                TableData *d, *dd;
33,765✔
1560

1561
                d = t->data[*a + t->sort_map[i]];
33,765✔
1562
                dd = t->data[*b + t->sort_map[i]];
33,765✔
1563

1564
                r = cell_data_compare(d, *a, dd, *b);
33,765✔
1565
                if (r != 0)
33,765✔
1566
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
23,598✔
1567
        }
1568

1569
        /* Order identical lines by the order there were originally added in */
1570
        return CMP(*a, *b);
1✔
1571
}
1572

1573
static char* format_strv_width(char **strv, size_t column_width) {
349✔
1574
        _cleanup_(memstream_done) MemStream m = {};
349✔
1575
        FILE *f;
349✔
1576

1577
        f = memstream_init(&m);
349✔
1578
        if (!f)
349✔
1579
                return NULL;
1580

1581
        size_t position = 0;
1582
        STRV_FOREACH(p, strv) {
1,509✔
1583
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1,160✔
1584
                                                          * If that happens, we'll just print one item per line. */
1585

1586
                if (position == 0) {
1,160✔
1587
                        fputs(*p, f);
349✔
1588
                        position = our_len;
1589
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
1,622✔
1590
                        fprintf(f, " %s", *p);
749✔
1591
                        position = size_add(size_add(position, 1), our_len);
1,909✔
1592
                } else {
1593
                        fprintf(f, "\n%s", *p);
62✔
1594
                        position = our_len;
1595
                }
1596
        }
1597

1598
        char *buf;
349✔
1599
        if (memstream_finalize(&m, &buf, NULL) < 0)
349✔
1600
                return NULL;
1601

1602
        return buf;
349✔
1603
}
1604

1605
static const char* table_data_format(
368,381✔
1606
                Table *t,
1607
                TableData *d,
1608
                bool avoid_uppercasing,
1609
                size_t column_width,
1610
                bool *have_soft) {
1611

1612
        assert(d);
368,381✔
1613

1614
        if (d->formatted &&
368,381✔
1615
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1616
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
68,426✔
1617
                return d->formatted;
1618

1619
        d->formatted = mfree(d->formatted);
300,191✔
1620

1621
        switch (d->type) {
300,191✔
1622
        case TABLE_EMPTY:
20,490✔
1623
                return table_ersatz_string(t);
20,490✔
1624

1625
        case TABLE_STRING:
247,343✔
1626
        case TABLE_STRING_WITH_ANSI:
1627
        case TABLE_PATH:
1628
        case TABLE_PATH_BASENAME:
1629
        case TABLE_FIELD:
1630
        case TABLE_HEADER:
1631
        case TABLE_VERSION: {
1632
                _cleanup_free_ char *bn = NULL;
247,343✔
1633
                const char *s;
247,343✔
1634

1635
                if (d->type == TABLE_PATH_BASENAME)
247,343✔
1636
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
411✔
1637
                else
1638
                        s = d->string;
246,932✔
1639

1640
                if (d->uppercase && !avoid_uppercasing) {
247,343✔
1641
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
4,479✔
1642
                        if (!d->formatted)
4,479✔
1643
                                return NULL;
1644

1645
                        char *q = d->formatted;
1646
                        for (const char *p = s; *p; p++)
31,282✔
1647
                                *(q++) = (char) toupper((unsigned char) *p);
26,803✔
1648

1649
                        if (d->type == TABLE_FIELD)
4,479✔
1650
                                *(q++) = ':';
×
1651

1652
                        *q = 0;
4,479✔
1653
                        return d->formatted;
4,479✔
1654
                }
1655

1656
                if (d->type == TABLE_FIELD)
242,864✔
1657
                        return (d->formatted = strjoin(s, ":"));
33,124✔
1658

1659
                if (bn)
209,740✔
1660
                        return (d->formatted = TAKE_PTR(bn));
409✔
1661

1662
                return d->string;
209,331✔
1663
        }
1664

1665
        case TABLE_STRV:
6,283✔
1666
                if (strv_isempty(d->strv))
6,283✔
1667
                        return table_ersatz_string(t);
×
1668

1669
                return (d->formatted = strv_join(d->strv, "\n"));
6,283✔
1670

1671
        case TABLE_STRV_WRAPPED:
349✔
1672
                if (strv_isempty(d->strv))
349✔
1673
                        return table_ersatz_string(t);
×
1674

1675
                d->formatted = format_strv_width(d->strv, column_width);
349✔
1676
                if (!d->formatted)
349✔
1677
                        return NULL;
1678

1679
                d->formatted_for_width = column_width;
349✔
1680
                if (have_soft)
349✔
1681
                        *have_soft = true;
227✔
1682
                return d->formatted;
349✔
1683

1684
        case TABLE_BOOLEAN:
4,809✔
1685
                return yes_no(d->boolean);
370,348✔
1686

1687
        case TABLE_BOOLEAN_CHECKMARK:
7,006✔
1688
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,788✔
1689

1690
        case TABLE_TIMESTAMP:
2,118✔
1691
        case TABLE_TIMESTAMP_UTC:
1692
        case TABLE_TIMESTAMP_RELATIVE:
1693
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1694
        case TABLE_TIMESTAMP_LEFT:
1695
        case TABLE_TIMESTAMP_DATE: {
1696
                char *ret;
2,118✔
1697

1698
                _cleanup_free_ char *p = new(
4,236✔
1699
                                char,
1700
                                IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1701
                                        FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1702
                if (!p)
2,118✔
1703
                        return NULL;
1704

1705
                if (d->type == TABLE_TIMESTAMP)
2,118✔
1706
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,433✔
1707
                else if (d->type == TABLE_TIMESTAMP_UTC)
685✔
1708
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1709
                else if (d->type == TABLE_TIMESTAMP_DATE)
685✔
1710
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
2✔
1711
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1712
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1713
                else
1714
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1715
                                                             d->timestamp, CLOCK_REALTIME,
1716
                                                             /* implicit_left= */ d->type == TABLE_TIMESTAMP_LEFT);
1717
                if (!ret)
2,118✔
1718
                        return "-";
1719

1720
                return (d->formatted = TAKE_PTR(p));
1,966✔
1721
        }
1722

1723
        case TABLE_TIMESPAN:
1724
        case TABLE_TIMESPAN_MSEC:
1725
        case TABLE_TIMESPAN_DAY: {
1726
                _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX);
3,016✔
1727
                if (!p)
1,508✔
1728
                        return NULL;
1729

1730
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
3,009✔
1731
                                     d->type == TABLE_TIMESPAN ? 0 :
1,501✔
1732
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1733
                        return "-";
1734

1735
                return (d->formatted = TAKE_PTR(p));
1,508✔
1736
        }
1737

1738
        case TABLE_SIZE: {
1739
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX);
720✔
1740
                if (!p)
360✔
1741
                        return NULL;
1742

1743
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
360✔
1744
                        return table_ersatz_string(t);
238✔
1745

1746
                return (d->formatted = TAKE_PTR(p));
122✔
1747
        }
1748

1749
        case TABLE_BPS: {
1750
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2);
824✔
1751
                if (!p)
412✔
1752
                        return NULL;
1753

1754
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
412✔
1755
                        return table_ersatz_string(t);
×
1756

1757
                size_t n = strlen(p);
412✔
1758
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
412✔
1759

1760
                return (d->formatted = TAKE_PTR(p));
412✔
1761
        }
1762

1763
        case TABLE_INT:
260✔
1764
                return (d->formatted = asprintf_safe("%i", d->int_val));
260✔
1765

1766
        case TABLE_INT8:
3✔
1767
                return (d->formatted = asprintf_safe("%" PRIi8, d->int8));
3✔
1768

1769
        case TABLE_INT16:
3✔
1770
                return (d->formatted = asprintf_safe("%" PRIi16, d->int16));
3✔
1771

1772
        case TABLE_INT32:
3✔
1773
                return (d->formatted = asprintf_safe("%" PRIi32, d->int32));
3✔
1774

1775
        case TABLE_INT64:
155✔
1776
                return (d->formatted = asprintf_safe("%" PRIi64, d->int64));
155✔
1777

1778
        case TABLE_UINT:
285✔
1779
                return (d->formatted = asprintf_safe("%u", d->uint_val));
285✔
1780

1781
        case TABLE_UINT8:
43✔
1782
                return (d->formatted = asprintf_safe("%" PRIu8, d->uint8));
43✔
1783

1784
        case TABLE_UINT16:
91✔
1785
                return (d->formatted = asprintf_safe("%" PRIu16, d->uint16));
91✔
1786

1787
        case TABLE_UINT32:
1,041✔
1788
                return (d->formatted = asprintf_safe("%" PRIu32, d->uint32));
1,041✔
1789

1790
        case TABLE_UINT32_HEX:
2✔
1791
                return (d->formatted = asprintf_safe("%" PRIx32, d->uint32));
2✔
1792

1793
        case TABLE_UINT32_HEX_0x:
7✔
1794
                return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32));
7✔
1795

1796
        case TABLE_UINT64:
1,514✔
1797
                return (d->formatted = asprintf_safe("%" PRIu64, d->uint64));
1,514✔
1798

1799
        case TABLE_UINT64_HEX:
50✔
1800
                return (d->formatted = asprintf_safe("%" PRIx64, d->uint64));
50✔
1801

1802
        case TABLE_UINT64_HEX_0x:
×
1803
                return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64));
×
1804

1805
        case TABLE_PERCENT:
2✔
1806
                return (d->formatted = asprintf_safe("%i%%" , d->percent));
2✔
1807

1808
        case TABLE_IFINDEX:
227✔
1809
                (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted);
227✔
1810
                return d->formatted;
227✔
1811

1812
        case TABLE_IN_ADDR:
205✔
1813
        case TABLE_IN6_ADDR:
1814
                (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1815
                                         &d->address,
205✔
1816
                                         &d->formatted);
1817
                return d->formatted;
205✔
1818

1819
        case TABLE_ID128:
1820
                d->formatted = new(char, SD_ID128_STRING_MAX);
1,095✔
1821
                if (!d->formatted)
1,095✔
1822
                        return NULL;
1823

1824
                return sd_id128_to_string(d->id128, d->formatted);
1,095✔
1825

1826
        case TABLE_UUID:
1827
                d->formatted = new(char, SD_ID128_UUID_STRING_MAX);
416✔
1828
                if (!d->formatted)
416✔
1829
                        return NULL;
1830

1831
                return sd_id128_to_uuid_string(d->id128, d->formatted);
416✔
1832

1833
        case TABLE_UID:
421✔
1834
                if (!uid_is_valid(d->uid))
421✔
1835
                        return table_ersatz_string(t);
×
1836

1837
                return (d->formatted = asprintf_safe(UID_FMT, d->uid));
421✔
1838

1839
        case TABLE_GID:
600✔
1840
                if (!gid_is_valid(d->gid))
600✔
1841
                        return table_ersatz_string(t);
×
1842

1843
                return (d->formatted = asprintf_safe(GID_FMT, d->gid));
600✔
1844

1845
        case TABLE_PID:
222✔
1846
                if (!pid_is_valid(d->pid))
222✔
1847
                        return table_ersatz_string(t);
×
1848

1849
                return (d->formatted = asprintf_safe(PID_FMT, d->pid));
222✔
1850

1851
        case TABLE_SIGNAL: {
123✔
1852
                const char *suffix = signal_to_string(d->int_val);
123✔
1853
                if (!suffix)
123✔
1854
                        return table_ersatz_string(t);
×
1855

1856
                return (d->formatted = strjoin("SIG", suffix));
123✔
1857
        }
1858

1859
        case TABLE_MODE:
96✔
1860
                if (d->mode == MODE_INVALID)
96✔
1861
                        return table_ersatz_string(t);
×
1862

1863
                return (d->formatted = asprintf_safe("%04o", d->mode & 07777));
96✔
1864

1865
        case TABLE_MODE_INODE_TYPE:
2✔
1866
                if (d->mode == MODE_INVALID)
2✔
1867
                        return table_ersatz_string(t);
×
1868

1869
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
1870

1871
        case TABLE_DEVNUM:
3✔
1872
                if (devnum_is_zero(d->devnum))
3✔
1873
                        return table_ersatz_string(t);
2✔
1874

1875
                return (d->formatted = asprintf_safe(DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)));
1✔
1876

1877
        case TABLE_JSON:
2,644✔
1878
                if (!d->json)
2,644✔
1879
                        return table_ersatz_string(t);
×
1880

1881
                (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted);
2,644✔
1882
                return d->formatted;
2,644✔
1883

1884
        default:
×
1885
                assert_not_reached();
×
1886
        }
1887
}
1888

1889
static const char* table_data_format_strip_ansi(
367,662✔
1890
                Table *t,
1891
                TableData *d,
1892
                bool avoid_uppercasing,
1893
                size_t column_width,
1894
                bool *have_soft,
1895
                char **ret_buffer) {
1896

1897
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
1898

1899
        assert(ret_buffer);
367,662✔
1900

1901
        const char *c;
367,662✔
1902

1903
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
367,662✔
1904
        if (!c)
367,662✔
1905
                return NULL;
367,662✔
1906

1907
        if (d->type != TABLE_STRING_WITH_ANSI) {
367,662✔
1908
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
1909
                 * original string as-is */
1910
                *ret_buffer = NULL;
367,647✔
1911
                return c;
367,647✔
1912
        }
1913

1914
        _cleanup_free_ char *s = strdup(c);
15✔
1915
        if (!s)
15✔
1916
                return NULL;
1917

1918
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
1919
                return NULL;
1920

1921
        *ret_buffer = TAKE_PTR(s);
15✔
1922
        return *ret_buffer;
15✔
1923
}
1924

1925
static int console_width_height(
181,079✔
1926
                const char *s,
1927
                size_t *ret_width,
1928
                size_t *ret_height) {
1929

1930
        size_t max_width = 0, height = 0;
181,079✔
1931
        const char *p;
181,079✔
1932

1933
        assert(s);
181,079✔
1934

1935
        /* Determine the width and height in console character cells the specified string needs. */
1936

1937
        do {
184,351✔
1938
                size_t k;
184,351✔
1939

1940
                p = strchr(s, '\n');
184,351✔
1941
                if (p) {
184,351✔
1942
                        _cleanup_free_ char *c = NULL;
3,275✔
1943

1944
                        c = strndup(s, p - s);
3,275✔
1945
                        if (!c)
3,275✔
1946
                                return -ENOMEM;
×
1947

1948
                        k = utf8_console_width(c);
3,275✔
1949
                        s = p + 1;
3,275✔
1950
                } else {
1951
                        k = utf8_console_width(s);
181,076✔
1952
                        s = NULL;
181,076✔
1953
                }
1954
                if (k == SIZE_MAX)
184,351✔
1955
                        return -EINVAL;
1956
                if (k > max_width)
184,351✔
1957
                        max_width = k;
179,450✔
1958

1959
                height++;
184,351✔
1960
        } while (!isempty(s));
187,626✔
1961

1962
        if (ret_width)
181,079✔
1963
                *ret_width = max_width;
181,079✔
1964

1965
        if (ret_height)
181,079✔
1966
                *ret_height = height;
181,079✔
1967

1968
        return 0;
1969
}
1970

1971
static int table_data_requested_width_height(
181,079✔
1972
                Table *table,
1973
                TableData *d,
1974
                size_t available_width,
1975
                size_t *ret_width,
1976
                size_t *ret_height,
1977
                bool *have_soft) {
1978

1979
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
181,079✔
1980
        bool truncation_applied = false;
181,079✔
1981
        size_t width, height;
181,079✔
1982
        bool soft = false;
181,079✔
1983
        const char *t;
181,079✔
1984
        int r;
181,079✔
1985

1986
        t = table_data_format_strip_ansi(
181,079✔
1987
                        table,
1988
                        d,
1989
                        /* avoid_uppercasing= */ false,
1990
                        available_width,
1991
                        &soft,
1992
                        &buffer);
1993
        if (!t)
181,079✔
1994
                return -ENOMEM;
1995

1996
        if (table->cell_height_max != SIZE_MAX) {
181,079✔
1997
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
252✔
1998
                if (r < 0)
252✔
1999
                        return r;
2000
                if (r > 0)
252✔
2001
                        truncation_applied = true;
25✔
2002

2003
                t = truncated;
252✔
2004
        }
2005

2006
        r = console_width_height(t, &width, &height);
181,079✔
2007
        if (r < 0)
181,079✔
2008
                return r;
2009

2010
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
181,079✔
2011
                width = d->maximum_width;
×
2012

2013
        if (width < d->minimum_width)
181,079✔
2014
                width = d->minimum_width;
3,364✔
2015

2016
        if (ret_width)
181,079✔
2017
                *ret_width = width;
181,079✔
2018
        if (ret_height)
181,079✔
2019
                *ret_height = height;
181,041✔
2020
        if (have_soft && soft)
181,079✔
2021
                *have_soft = true;
227✔
2022

2023
        return truncation_applied;
181,079✔
2024
}
2025

2026
static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
139,539✔
2027
        size_t w = 0, space, lspace, old_length, clickable_length;
139,539✔
2028
        _cleanup_free_ char *clickable = NULL;
139,539✔
2029
        const char *p;
139,539✔
2030
        char *ret;
139,539✔
2031
        int r;
139,539✔
2032

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

2035
        assert(str);
139,539✔
2036
        assert(percent <= 100);
139,539✔
2037

2038
        old_length = strlen(str);
139,539✔
2039

2040
        if (url) {
139,539✔
2041
                r = terminal_urlify(url, str, &clickable);
1,559✔
2042
                if (r < 0)
1,559✔
2043
                        return NULL;
2044

2045
                clickable_length = strlen(clickable);
1,559✔
2046
        } else
2047
                clickable_length = old_length;
2048

2049
        /* Determine current width on screen */
2050
        p = str;
139,539✔
2051
        while (p < str + old_length) {
2,078,774✔
2052
                char32_t c;
1,939,235✔
2053

2054
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,939,235✔
2055
                        p++, w++; /* count invalid chars as 1 */
×
2056
                        continue;
×
2057
                }
2058

2059
                p = utf8_next_char(p);
1,939,235✔
2060
                w += unichar_iswide(c) ? 2 : 1;
3,878,458✔
2061
        }
2062

2063
        /* Already wider than the target, if so, don't do anything */
2064
        if (w >= new_length)
139,539✔
2065
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2066

2067
        /* How much spaces shall we add? An how much on the left side? */
2068
        space = new_length - w;
139,539✔
2069
        lspace = space * percent / 100U;
139,539✔
2070

2071
        ret = new(char, space + clickable_length + 1);
139,539✔
2072
        if (!ret)
139,539✔
2073
                return NULL;
2074

2075
        for (size_t i = 0; i < lspace; i++)
772,643✔
2076
                ret[i] = ' ';
633,104✔
2077
        memcpy(ret + lspace, clickable ?: str, clickable_length);
139,539✔
2078
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,711,960✔
2079
                ret[i] = ' ';
3,572,421✔
2080

2081
        ret[space + clickable_length] = 0;
139,539✔
2082
        return ret;
139,539✔
2083
}
2084

2085
static bool table_data_isempty(const TableData *d) {
561✔
2086
        assert(d);
561✔
2087

2088
        if (d->type == TABLE_EMPTY)
561✔
2089
                return true;
2090

2091
        /* Let's also consider an empty strv as truly empty. */
2092
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
479✔
2093
                return strv_isempty(d->strv);
×
2094

2095
        if (d->type == TABLE_JSON)
479✔
2096
                return sd_json_variant_is_null(d->json);
×
2097

2098
        /* Note that an empty string we do not consider empty here! */
2099
        return false;
2100
}
2101

2102
static const char* table_data_color(const TableData *d) {
774✔
2103
        assert(d);
774✔
2104

2105
        if (d->color)
774✔
2106
                return d->color;
2107

2108
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2109
        if (table_data_isempty(d))
561✔
2110
                return ansi_grey();
82✔
2111

2112
        if (d->type == TABLE_FIELD)
479✔
2113
                return ansi_bright_blue();
×
2114

2115
        return NULL;
2116
}
2117

2118
static const char* table_data_underline(const TableData *d) {
774✔
2119
        assert(d);
774✔
2120

2121
        if (d->underline)
774✔
2122
                return ansi_add_underline_grey();
×
2123

2124
        if (d->type == TABLE_HEADER)
774✔
2125
                return ansi_add_underline();
50✔
2126

2127
        return NULL;
2128
}
2129

2130
static const char* table_data_rgap_underline(const TableData *d) {
187,277✔
2131
        assert(d);
187,277✔
2132

2133
        if (d->rgap_underline)
187,277✔
2134
                return ansi_add_underline_grey();
300✔
2135

2136
        if (d->type == TABLE_HEADER)
186,977✔
2137
                return ansi_add_underline();
9,088✔
2138

2139
        return NULL;
2140
}
2141

2142
int table_data_requested_width(Table *table, size_t column, size_t *ret) {
4✔
2143
        size_t width = 0;
4✔
2144
        int r;
4✔
2145

2146
        assert(table);
4✔
2147
        assert(ret);
4✔
2148

2149
        for (size_t row = 0; row < table_get_rows(table); row++) {
42✔
2150
                TableCell *cell = table_get_cell(table, row, column);
38✔
2151
                if (!cell)
38✔
UNCOV
2152
                        continue;
×
2153

2154
                TableData *data = table_get_data(table, cell);
38✔
2155
                if (!data)
38✔
UNCOV
2156
                        continue;
×
2157

2158
                size_t w;
38✔
2159

2160
                r = table_data_requested_width_height(
38✔
2161
                                table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL);
2162
                if (r < 0)
38✔
UNCOV
2163
                        return r;
×
2164

2165
                width = MAX(width, w);
38✔
2166
        }
2167

2168
        *ret = width;
4✔
2169
        return 0;
4✔
2170
}
2171

2172
int table_set_column_width(Table *t, size_t column, size_t width) {
4✔
2173
        int r = 0;
4✔
2174

2175
        assert(t);
4✔
2176

2177
        for (size_t row = 0; row < table_get_rows(t); row++) {
42✔
2178
                TableCell *cell = table_get_cell(t, row, column);
38✔
2179
                if (!cell)
38✔
UNCOV
2180
                        continue;
×
2181

2182
                RET_GATHER(r, table_set_minimum_width(t, cell, width));
38✔
2183
        }
2184

2185
        return r;
4✔
2186
}
2187

2188
int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b) {
2✔
2189
        size_t w1, w2;
2✔
2190
        int r;
2✔
2191

2192
        assert(a);
2✔
2193
        assert(b);
2✔
2194

2195
        /* Make both tables have specified columns of same width */
2196

2197
        r = table_data_requested_width(a, column_a, &w1);
2✔
2198
        if (r < 0)
2✔
UNCOV
2199
                return log_error_errno(r, "Failed to query table column width: %m");
×
2200

2201
        r = table_data_requested_width(b, column_b, &w2);
2✔
2202
        if (r < 0)
2✔
UNCOV
2203
                return log_error_errno(r, "Failed to query table column width: %m");
×
2204

2205
        r = 0;
2✔
2206
        RET_GATHER(r, table_set_column_width(a, column_a, MAX(w1, w2)));
2✔
2207
        RET_GATHER(r, table_set_column_width(b, column_b, MAX(w1, w2)));
2✔
2208
        return r;
2209
}
2210

2211
int table_print(Table *t, FILE *f) {
3,674✔
2212
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,674✔
2213
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2214
                *width = NULL;
3,674✔
2215
        _cleanup_free_ size_t *sorted = NULL;
3,674✔
2216
        uint64_t *column_weight, weight_sum;
3,674✔
2217
        int r;
3,674✔
2218

2219
        assert(t);
3,674✔
2220

2221
        if (!f)
3,674✔
2222
                f = stdout;
2,621✔
2223

2224
        /* Ensure we have no incomplete rows */
2225
        assert(t->n_cells % t->n_columns == 0);
3,674✔
2226

2227
        n_rows = t->n_cells / t->n_columns;
3,674✔
2228
        assert(n_rows > 0); /* at least the header row must be complete */
3,674✔
2229

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

2233
                sorted = new(size_t, n_rows);
221✔
2234
                if (!sorted)
221✔
2235
                        return -ENOMEM;
2236

2237
                for (size_t i = 0; i < n_rows; i++)
6,060✔
2238
                        sorted[i] = i * t->n_columns;
5,839✔
2239

2240
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
221✔
2241
        }
2242

2243
        if (t->display_map)
3,674✔
2244
                display_columns = t->n_display_map;
354✔
2245
        else
2246
                display_columns = t->n_columns;
3,320✔
2247

2248
        assert(display_columns > 0);
3,674✔
2249

2250
        minimum_width = newa(size_t, display_columns);
3,674✔
2251
        maximum_width = newa(size_t, display_columns);
3,674✔
2252
        requested_width = newa(size_t, display_columns);
3,674✔
2253
        column_weight = newa0(uint64_t, display_columns);
3,674✔
2254

2255
        for (size_t j = 0; j < display_columns; j++) {
14,552✔
2256
                minimum_width[j] = 1;
10,878✔
2257
                maximum_width[j] = SIZE_MAX;
10,878✔
2258
        }
2259

2260
        for (unsigned pass = 0; pass < 2; pass++) {
3,694✔
2261
                /* First pass: determine column sizes */
2262

2263
                for (size_t j = 0; j < display_columns; j++)
14,614✔
2264
                        requested_width[j] = SIZE_MAX;
10,920✔
2265

2266
                bool any_soft = false;
3,694✔
2267

2268
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
57,481✔
2269
                        TableData **row;
53,787✔
2270

2271
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2272
                         * hence we don't care for sorted[] during the first pass. */
2273
                        row = t->data + i * t->n_columns;
53,787✔
2274

2275
                        for (size_t j = 0; j < display_columns; j++) {
234,828✔
2276
                                TableData *d;
181,041✔
2277
                                size_t req_width, req_height;
181,041✔
2278

2279
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
181,041✔
2280

2281
                                r = table_data_requested_width_height(t, d,
181,329✔
2282
                                                                      width ? width[j] : SIZE_MAX,
288✔
2283
                                                                      &req_width, &req_height, &any_soft);
2284
                                if (r < 0)
181,041✔
UNCOV
2285
                                        return r;
×
2286
                                if (r > 0) { /* Truncated because too many lines? */
181,041✔
2287
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2288
                                        const char *field;
25✔
2289

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

2295
                                        field = table_data_format_strip_ansi(
28✔
2296
                                                        t,
2297
                                                        d,
2298
                                                        /* avoid_uppercasing= */ false,
2299
                                                        width ? width[j] : SIZE_MAX,
3✔
2300
                                                        &any_soft,
2301
                                                        &buffer);
2302
                                        if (!field)
25✔
2303
                                                return -ENOMEM;
2304

2305
                                        assert_se(t->cell_height_max > 0);
25✔
2306
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2307
                                        if (r < 0)
25✔
2308
                                                return r;
2309

2310
                                        req_width = MAX(req_width,
25✔
2311
                                                        utf8_console_width(last) +
2312
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2313
                                }
2314

2315
                                /* Determine the biggest width that any cell in this column would like to have */
2316
                                if (requested_width[j] == SIZE_MAX ||
181,041✔
2317
                                    requested_width[j] < req_width)
170,153✔
2318
                                        requested_width[j] = req_width;
24,271✔
2319

2320
                                /* Determine the minimum width any cell in this column needs */
2321
                                if (minimum_width[j] < d->minimum_width)
181,041✔
2322
                                        minimum_width[j] = d->minimum_width;
31✔
2323

2324
                                /* Determine the maximum width any cell in this column needs */
2325
                                if (d->maximum_width != SIZE_MAX &&
181,041✔
2326
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2327
                                     maximum_width[j] > d->maximum_width))
2328
                                        maximum_width[j] = d->maximum_width;
20✔
2329

2330
                                /* Determine the full columns weight */
2331
                                column_weight[j] += d->weight;
181,041✔
2332
                        }
2333
                }
2334

2335
                /* One space between each column */
2336
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,694✔
2337

2338
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2339
                weight_sum = 0;
3,694✔
2340
                for (size_t j = 0; j < display_columns; j++) {
14,614✔
2341
                        weight_sum += column_weight[j];
10,920✔
2342

2343
                        table_minimum_width += minimum_width[j];
10,920✔
2344

2345
                        if (maximum_width[j] == SIZE_MAX)
10,920✔
2346
                                table_maximum_width = SIZE_MAX;
2347
                        else
2348
                                table_maximum_width += maximum_width[j];
20✔
2349

2350
                        table_requested_width += requested_width[j];
10,920✔
2351
                }
2352

2353
                /* Calculate effective table width */
2354
                if (t->width != 0 && t->width != SIZE_MAX)
3,694✔
2355
                        table_effective_width = t->width;
2356
                else if (t->width == 0 ||
3,687✔
2357
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,616✔
2358
                        table_effective_width = table_requested_width;
2359
                else
2360
                        table_effective_width = MIN(table_requested_width, columns());
46✔
2361

2362
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,694✔
UNCOV
2363
                        table_effective_width = table_maximum_width;
×
2364

2365
                if (table_effective_width < table_minimum_width)
3,694✔
2366
                        table_effective_width = table_minimum_width;
2✔
2367

2368
                if (!width)
3,694✔
2369
                        width = newa(size_t, display_columns);
3,674✔
2370

2371
                if (table_effective_width >= table_requested_width) {
3,694✔
2372
                        size_t extra;
3,670✔
2373

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

2377
                        extra = table_effective_width - table_requested_width;
3,670✔
2378

2379
                        for (size_t j = 0; j < display_columns; j++) {
14,530✔
2380
                                size_t delta;
10,860✔
2381

2382
                                if (weight_sum == 0)
10,860✔
2383
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2384
                                else
2385
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
10,828✔
2386

2387
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
10,860✔
2388
                                        width[j] = maximum_width[j];
2✔
2389

2390
                                if (width[j] < minimum_width[j])
10,860✔
UNCOV
2391
                                        width[j] = minimum_width[j];
×
2392

2393
                                delta = LESS_BY(width[j], requested_width[j]);
10,860✔
2394

2395
                                /* Subtract what we just added from the rest */
2396
                                if (extra > delta)
10,860✔
2397
                                        extra -= delta;
13✔
2398
                                else
2399
                                        extra = 0;
2400

2401
                                assert(weight_sum >= column_weight[j]);
10,860✔
2402
                                weight_sum -= column_weight[j];
10,860✔
2403
                        }
2404

2405
                        break; /* Every column should be happy, no need to repeat calculations. */
3,674✔
2406
                } else {
2407
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2408
                         * with the minimum they need, and then distribute anything left. */
2409
                        bool finalize = false;
24✔
2410
                        size_t extra;
24✔
2411

2412
                        extra = table_effective_width - table_minimum_width;
24✔
2413

2414
                        for (size_t j = 0; j < display_columns; j++)
84✔
2415
                                width[j] = SIZE_MAX;
60✔
2416

2417
                        for (;;) {
70✔
2418
                                bool restart = false;
70✔
2419

2420
                                for (size_t j = 0; j < display_columns; j++) {
200✔
2421
                                        size_t delta, w;
152✔
2422

2423
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2424
                                        if (width[j] != SIZE_MAX)
152✔
2425
                                                continue;
51✔
2426

2427
                                        if (weight_sum == 0)
101✔
UNCOV
2428
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2429
                                        else
2430
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
101✔
2431

2432
                                        if (w >= requested_width[j]) {
101✔
2433
                                                /* Never give more than requested. If we hit a column like this, there's more
2434
                                                 * space to allocate to other columns which means we need to restart the
2435
                                                 * iteration. However, if we hit a column like this, let's assign it the space
2436
                                                 * it wanted for good early. */
2437

2438
                                                w = requested_width[j];
2439
                                                restart = true;
2440

2441
                                        } else if (!finalize)
78✔
2442
                                                continue;
41✔
2443

2444
                                        width[j] = w;
60✔
2445

2446
                                        assert(w >= minimum_width[j]);
60✔
2447
                                        delta = w - minimum_width[j];
60✔
2448

2449
                                        assert(delta <= extra);
60✔
2450
                                        extra -= delta;
60✔
2451

2452
                                        assert(weight_sum >= column_weight[j]);
60✔
2453
                                        weight_sum -= column_weight[j];
60✔
2454

2455
                                        if (restart && !finalize)
60✔
2456
                                                break;
2457
                                }
2458

2459
                                if (finalize)
70✔
2460
                                        break;
2461

2462
                                if (!restart)
46✔
2463
                                        finalize = true;
24✔
2464
                        }
2465

2466
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
24✔
2467
                                        * let's try to reformat them with the new widths. Otherwise, let's
2468
                                        * move on. */
2469
                                break;
2470
                }
2471
        }
2472

2473
        /* Second pass: show output */
2474
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
57,345✔
2475
                size_t n_subline = 0;
53,671✔
2476
                bool more_sublines;
53,671✔
2477
                TableData **row;
53,671✔
2478

2479
                if (sorted)
53,671✔
2480
                        row = t->data + sorted[i];
5,823✔
2481
                else
2482
                        row = t->data + i * t->n_columns;
47,848✔
2483

2484
                do {
56,931✔
2485
                        const char *gap_color = NULL, *gap_underline = NULL;
56,931✔
2486
                        more_sublines = false;
56,931✔
2487

2488
                        for (size_t j = 0; j < display_columns; j++) {
244,208✔
2489
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
187,277✔
2490
                                bool lines_truncated = false;
187,277✔
2491
                                const char *field, *color = NULL, *underline = NULL;
187,277✔
2492
                                TableData *d;
187,277✔
2493
                                size_t l;
187,277✔
2494

2495
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
187,277✔
2496

2497
                                if (colors_enabled())
187,277✔
2498
                                        field = table_data_format(
719✔
2499
                                                        t,
2500
                                                        d,
2501
                                                        /* avoid_uppercasing= */ false,
2502
                                                        width[j],
719✔
2503
                                                        /* have_soft= */ NULL);
2504
                                else
2505
                                        field = table_data_format_strip_ansi(
186,558✔
2506
                                                        t,
2507
                                                        d,
2508
                                                        /* avoid_uppercasing= */ false,
2509
                                                        width[j],
186,558✔
2510
                                                        /* have_soft= */ NULL,
2511
                                                        &stripped_ansi_buffer);
2512
                                if (!field)
187,277✔
2513
                                        return -ENOMEM;
2514

2515
                                r = string_extract_line(field, n_subline, &extracted);
187,277✔
2516
                                if (r < 0)
187,277✔
2517
                                        return r;
2518
                                if (r > 0) {
187,277✔
2519
                                        /* There are more lines to come */
2520
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,297✔
2521
                                                more_sublines = true; /* There are more lines to come */
2522
                                        else
2523
                                                lines_truncated = true;
25✔
2524
                                }
2525
                                if (extracted)
187,277✔
2526
                                        field = extracted;
7,993✔
2527

2528
                                l = utf8_console_width(field);
187,277✔
2529
                                if (l > width[j]) {
187,277✔
2530
                                        /* Field is wider than allocated space. Let's ellipsize */
2531

2532
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
35✔
2533
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2534
                                        if (!buffer)
35✔
2535
                                                return -ENOMEM;
2536

2537
                                        field = buffer;
2538
                                } else {
2539
                                        if (lines_truncated) {
187,242✔
2540
                                                _cleanup_free_ char *padded = NULL;
25✔
2541

2542
                                                /* We truncated more lines of this cell, let's add an
2543
                                                 * ellipsis. We first append it, but that might make our
2544
                                                 * string grow above what we have space for, hence ellipsize
2545
                                                 * right after. This will truncate the ellipsis and add a new
2546
                                                 * one. */
2547

2548
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2549
                                                if (!padded)
25✔
2550
                                                        return -ENOMEM;
2551

2552
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2553
                                                if (!buffer)
25✔
2554
                                                        return -ENOMEM;
2555

2556
                                                field = buffer;
25✔
2557
                                                l = utf8_console_width(field);
25✔
2558
                                        }
2559

2560
                                        if (l < width[j]) {
187,242✔
UNCOV
2561
                                                _cleanup_free_ char *aligned = NULL;
×
2562
                                                /* Field is shorter than allocated space. Let's align with spaces */
2563

2564
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
139,539✔
2565
                                                if (!aligned)
139,539✔
UNCOV
2566
                                                        return -ENOMEM;
×
2567

2568
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2569
                                                if (j == display_columns - 1 &&
190,230✔
2570
                                                    (!colors_enabled() || !table_data_color(d)) &&
50,746✔
2571
                                                    (!underline_enabled() || !table_data_underline(d)) &&
101,434✔
2572
                                                    (!urlify_enabled() || !d->url))
50,740✔
2573
                                                        delete_trailing_chars(aligned, NULL);
50,688✔
2574

2575
                                                free_and_replace(buffer, aligned);
139,539✔
2576
                                                field = buffer;
139,539✔
2577
                                        }
2578
                                }
2579

2580
                                if (l >= width[j] && d->url) {
187,277✔
UNCOV
2581
                                        _cleanup_free_ char *clickable = NULL;
×
2582

2583
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2584
                                        if (r < 0)
22✔
UNCOV
2585
                                                return r;
×
2586

2587
                                        free_and_replace(buffer, clickable);
22✔
2588
                                        field = buffer;
22✔
2589
                                }
2590

2591
                                if (colors_enabled() && gap_color)
187,277✔
UNCOV
2592
                                        fputs(gap_color, f);
×
2593
                                if (underline_enabled() && gap_underline)
187,277✔
2594
                                        fputs(gap_underline, f);
19✔
2595

2596
                                if (j > 0)
187,277✔
2597
                                        fputc(' ', f); /* column separator left of cell */
130,346✔
2598

2599
                                /* Undo gap color/underline */
2600
                                if ((colors_enabled() && gap_color) ||
374,554✔
2601
                                    (underline_enabled() && gap_underline))
187,996✔
2602
                                        fputs(ANSI_NORMAL, f);
19✔
2603

2604
                                if (colors_enabled()) {
187,277✔
2605
                                        color = table_data_color(d);
719✔
2606
                                        if (color)
719✔
2607
                                                fputs(color, f);
295✔
2608
                                }
2609

2610
                                if (underline_enabled()) {
187,277✔
2611
                                        underline = table_data_underline(d);
719✔
2612
                                        if (underline)
719✔
2613
                                                fputs(underline, f);
22✔
2614
                                }
2615

2616
                                fputs(field, f);
187,277✔
2617

2618
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2619
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
187,286✔
2620
                                        fputs(ANSI_NORMAL, f);
320✔
2621

2622
                                gap_color = d->rgap_color;
187,277✔
2623
                                gap_underline = table_data_rgap_underline(d);
187,277✔
2624
                        }
2625

2626
                        fputc('\n', f);
56,931✔
2627
                        n_subline++;
56,931✔
2628
                } while (more_sublines);
56,931✔
2629
        }
2630

2631
        return fflush_and_check(f);
3,674✔
2632
}
2633

2634
int table_format(Table *t, char **ret) {
41✔
2635
        _cleanup_(memstream_done) MemStream m = {};
41✔
2636
        FILE *f;
41✔
2637
        int r;
41✔
2638

2639
        assert(t);
41✔
2640
        assert(ret);
41✔
2641

2642
        f = memstream_init(&m);
41✔
2643
        if (!f)
41✔
2644
                return -ENOMEM;
2645

2646
        r = table_print(t, f);
41✔
2647
        if (r < 0)
41✔
2648
                return r;
2649

2650
        return memstream_finalize(&m, ret, NULL);
41✔
2651
}
2652

2653
size_t table_get_rows(Table *t) {
1,911✔
2654
        if (!t)
1,911✔
2655
                return 0;
2656

2657
        assert(t->n_columns > 0);
1,911✔
2658
        return t->n_cells / t->n_columns;
1,911✔
2659
}
2660

2661
size_t table_get_columns(Table *t) {
34✔
2662
        if (!t)
34✔
2663
                return 0;
2664

2665
        assert(t->n_columns > 0);
34✔
2666
        return t->n_columns;
2667
}
2668

2669
size_t table_get_current_column(Table *t) {
80✔
2670
        if (!t)
80✔
2671
                return 0;
2672

2673
        assert(t->n_columns > 0);
80✔
2674
        return t->n_cells % t->n_columns;
80✔
2675
}
2676

2677
int table_set_reverse(Table *t, size_t column, bool b) {
87✔
2678
        assert(t);
87✔
2679
        assert(column < t->n_columns);
87✔
2680

2681
        if (!t->reverse_map) {
87✔
2682
                if (!b)
87✔
2683
                        return 0;
2684

2685
                t->reverse_map = new0(bool, t->n_columns);
33✔
2686
                if (!t->reverse_map)
33✔
2687
                        return -ENOMEM;
2688
        }
2689

2690
        t->reverse_map[column] = b;
33✔
2691
        return 0;
33✔
2692
}
2693

2694
TableCell* table_get_cell(Table *t, size_t row, size_t column) {
8,780✔
2695
        size_t i;
8,780✔
2696

2697
        assert(t);
8,780✔
2698

2699
        if (column >= t->n_columns)
8,780✔
2700
                return NULL;
2701

2702
        i = row * t->n_columns + column;
8,780✔
2703
        if (i >= t->n_cells)
8,780✔
2704
                return NULL;
2705

2706
        return TABLE_INDEX_TO_CELL(i);
8,780✔
2707
}
2708

2709
const void* table_get(Table *t, TableCell *cell) {
4,374✔
2710
        TableData *d;
4,374✔
2711

2712
        assert(t);
4,374✔
2713

2714
        d = table_get_data(t, cell);
4,374✔
2715
        if (!d)
4,374✔
2716
                return NULL;
2717

2718
        return d->data;
4,374✔
2719
}
2720

2721
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2722
        TableCell *cell;
4,374✔
2723

2724
        cell = table_get_cell(t, row, column);
4,374✔
2725
        if (!cell)
4,374✔
2726
                return NULL;
2727

2728
        return table_get(t, cell);
4,374✔
2729
}
2730

2731
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
5,369✔
2732

2733
        switch (d->type) {
5,369✔
2734

2735
        case TABLE_EMPTY:
711✔
2736
                return sd_json_variant_new_null(ret);
711✔
2737

2738
        case TABLE_STRING:
2,525✔
2739
        case TABLE_PATH:
2740
        case TABLE_PATH_BASENAME:
2741
        case TABLE_FIELD:
2742
        case TABLE_HEADER:
2743
        case TABLE_VERSION:
2744
                return sd_json_variant_new_string(ret, d->string);
2,525✔
2745

2746
        case TABLE_STRV:
3✔
2747
        case TABLE_STRV_WRAPPED:
2748
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2749

2750
        case TABLE_BOOLEAN_CHECKMARK:
197✔
2751
        case TABLE_BOOLEAN:
2752
                return sd_json_variant_new_boolean(ret, d->boolean);
197✔
2753

2754
        case TABLE_TIMESTAMP:
299✔
2755
        case TABLE_TIMESTAMP_UTC:
2756
        case TABLE_TIMESTAMP_RELATIVE:
2757
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2758
        case TABLE_TIMESTAMP_LEFT:
2759
        case TABLE_TIMESTAMP_DATE:
2760
                if (d->timestamp == USEC_INFINITY)
299✔
UNCOV
2761
                        return sd_json_variant_new_null(ret);
×
2762

2763
                return sd_json_variant_new_unsigned(ret, d->timestamp);
299✔
2764

UNCOV
2765
        case TABLE_TIMESPAN:
×
2766
        case TABLE_TIMESPAN_MSEC:
2767
        case TABLE_TIMESPAN_DAY:
2768
                if (d->timespan == USEC_INFINITY)
×
UNCOV
2769
                        return sd_json_variant_new_null(ret);
×
2770

UNCOV
2771
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2772

2773
        case TABLE_SIZE:
36✔
2774
        case TABLE_BPS:
2775
                if (d->size == UINT64_MAX)
36✔
2776
                        return sd_json_variant_new_null(ret);
16✔
2777

2778
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2779

2780
        case TABLE_INT:
36✔
2781
                return sd_json_variant_new_integer(ret, d->int_val);
36✔
2782

2783
        case TABLE_INT8:
6✔
2784
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2785

2786
        case TABLE_INT16:
6✔
2787
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2788

2789
        case TABLE_INT32:
6✔
2790
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2791

2792
        case TABLE_INT64:
46✔
2793
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2794

2795
        case TABLE_UINT:
43✔
2796
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2797

2798
        case TABLE_UINT8:
4✔
2799
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2800

2801
        case TABLE_UINT16:
4✔
2802
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2803

2804
        case TABLE_UINT32:
111✔
2805
        case TABLE_UINT32_HEX:
2806
        case TABLE_UINT32_HEX_0x:
2807
                return sd_json_variant_new_unsigned(ret, d->uint32);
111✔
2808

2809
        case TABLE_UINT64:
458✔
2810
        case TABLE_UINT64_HEX:
2811
        case TABLE_UINT64_HEX_0x:
2812
                return sd_json_variant_new_unsigned(ret, d->uint64);
458✔
2813

UNCOV
2814
        case TABLE_PERCENT:
×
UNCOV
2815
                return sd_json_variant_new_integer(ret, d->percent);
×
2816

UNCOV
2817
        case TABLE_IFINDEX:
×
UNCOV
2818
                if (d->ifindex <= 0)
×
UNCOV
2819
                        return sd_json_variant_new_null(ret);
×
2820

UNCOV
2821
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2822

2823
        case TABLE_IN_ADDR:
UNCOV
2824
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2825

2826
        case TABLE_IN6_ADDR:
UNCOV
2827
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2828

2829
        case TABLE_ID128:
502✔
2830
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2831

2832
        case TABLE_UUID:
136✔
2833
                return sd_json_variant_new_uuid(ret, d->id128);
136✔
2834

2835
        case TABLE_UID:
8✔
2836
                if (!uid_is_valid(d->uid))
8✔
UNCOV
2837
                        return sd_json_variant_new_null(ret);
×
2838

2839
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2840

2841
        case TABLE_GID:
8✔
2842
                if (!gid_is_valid(d->gid))
8✔
UNCOV
2843
                        return sd_json_variant_new_null(ret);
×
2844

2845
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2846

2847
        case TABLE_PID:
14✔
2848
                if (!pid_is_valid(d->pid))
14✔
UNCOV
2849
                        return sd_json_variant_new_null(ret);
×
2850

2851
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2852

2853
        case TABLE_SIGNAL:
8✔
2854
                if (!SIGNAL_VALID(d->int_val))
8✔
UNCOV
2855
                        return sd_json_variant_new_null(ret);
×
2856

2857
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2858

2859
        case TABLE_MODE:
195✔
2860
        case TABLE_MODE_INODE_TYPE:
2861
                if (d->mode == MODE_INVALID)
195✔
UNCOV
2862
                        return sd_json_variant_new_null(ret);
×
2863

2864
                return sd_json_variant_new_unsigned(ret, d->mode);
195✔
2865

2866
        case TABLE_DEVNUM:
4✔
2867
                if (devnum_is_zero(d->devnum))
4✔
2868
                        return sd_json_variant_new_null(ret);
2✔
2869

2870
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2871
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2872
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2873

UNCOV
2874
        case TABLE_JSON:
×
UNCOV
2875
                if (!d->json)
×
UNCOV
2876
                        return sd_json_variant_new_null(ret);
×
2877

UNCOV
2878
                if (ret)
×
2879
                        *ret = sd_json_variant_ref(d->json);
×
2880

2881
                return 0;
2882

2883
        case TABLE_STRING_WITH_ANSI: {
3✔
2884
                _cleanup_free_ char *s = strdup(d->string);
3✔
2885
                if (!s)
3✔
2886
                        return -ENOMEM;
2887

2888
                /* We strip the ANSI data when outputting to JSON */
2889
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
2890
                        return -ENOMEM;
2891

2892
                return sd_json_variant_new_string(ret, s);
3✔
2893
        }
2894

2895
        default:
2896
                return -EINVAL;
2897
        }
2898
}
2899

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

2909
        bool new_word = true;
939✔
2910
        char *c;
939✔
2911

2912
        assert(str);
939✔
2913

2914
        c = strdup(str);
939✔
2915
        if (!c)
939✔
2916
                return NULL;
2917

2918
        for (char *x = c; *x; x++) {
6,687✔
2919
                if (!strchr(ALPHANUMERICAL, *x)) {
5,748✔
2920
                        *x = '_';
185✔
2921
                        new_word = true;
185✔
2922
                        continue;
185✔
2923
                }
2924

2925
                if (new_word) {
5,563✔
2926
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
1,119✔
2927
                                                                  * then we assume the whole word is all-caps
2928
                                                                  * and avoid lowercasing it. */
2929
                                *x = ascii_tolower(*x);
1,118✔
2930
                        new_word = false;
2931
                }
2932
        }
2933

2934
        return c;
2935
}
2936

2937
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
925✔
2938
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
925✔
2939
        const char *n;
925✔
2940

2941
        assert(t);
925✔
2942
        assert(d);
925✔
2943
        assert(ret);
925✔
2944

2945
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
925✔
2946
                n = d->string;
925✔
2947
        else {
UNCOV
2948
                n = table_data_format_strip_ansi(
×
2949
                                t,
2950
                                d,
2951
                                /* avoid_uppercasing= */ true,
2952
                                /* column_width= */ SIZE_MAX,
2953
                                /* have_soft= */ NULL,
2954
                                &buffer);
UNCOV
2955
                if (!n)
×
2956
                        return -ENOMEM;
2957
        }
2958

2959
        mangled = table_mangle_to_json_field_name(n);
925✔
2960
        if (!mangled)
925✔
2961
                return -ENOMEM;
2962

2963
        *ret = TAKE_PTR(mangled);
925✔
2964
        return 0;
925✔
2965
}
2966

2967
static const char* table_get_json_field_name(Table *t, size_t idx) {
1,027✔
2968
        assert(t);
1,027✔
2969

2970
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
1,027✔
2971
}
2972

2973
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
137✔
2974
        sd_json_variant **rows = NULL, **elements = NULL;
137✔
2975
        _cleanup_free_ size_t *sorted = NULL;
137✔
2976
        size_t n_rows, display_columns;
137✔
2977
        int r;
137✔
2978

2979
        assert(t);
137✔
2980
        assert(!t->vertical);
137✔
2981

2982
        /* Ensure we have no incomplete rows */
2983
        assert(t->n_columns > 0);
137✔
2984
        assert(t->n_cells % t->n_columns == 0);
137✔
2985

2986
        n_rows = t->n_cells / t->n_columns;
137✔
2987
        assert(n_rows > 0); /* at least the header row must be complete */
137✔
2988

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

2992
                sorted = new(size_t, n_rows);
32✔
2993
                if (!sorted)
32✔
2994
                        return -ENOMEM;
2995

2996
                for (size_t i = 0; i < n_rows; i++)
198✔
2997
                        sorted[i] = i * t->n_columns;
166✔
2998

2999
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
32✔
3000
        }
3001

3002
        if (t->display_map)
137✔
3003
                display_columns = t->n_display_map;
79✔
3004
        else
3005
                display_columns = t->n_columns;
58✔
3006
        assert(display_columns > 0);
137✔
3007

3008
        elements = new0(sd_json_variant*, display_columns * 2);
137✔
3009
        if (!elements)
137✔
3010
                return -ENOMEM;
3011

3012
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
137✔
3013

3014
        for (size_t j = 0; j < display_columns; j++) {
1,160✔
3015
                _cleanup_free_ char *mangled = NULL;
1,023✔
3016
                const char *n;
1,023✔
3017
                size_t c;
1,023✔
3018

3019
                c = t->display_map ? t->display_map[j] : j;
1,023✔
3020

3021
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3022
                n = table_get_json_field_name(t, c);
1,023✔
3023
                if (!n) {
1,023✔
3024
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
923✔
3025
                        if (r < 0)
923✔
3026
                                return r;
3027

3028
                        n = mangled;
923✔
3029
                }
3030

3031
                r = sd_json_variant_new_string(elements + j*2, n);
1,023✔
3032
                if (r < 0)
1,023✔
3033
                        return r;
3034
        }
3035

3036
        rows = new0(sd_json_variant*, n_rows-1);
137✔
3037
        if (!rows)
137✔
3038
                return -ENOMEM;
3039

3040
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
137✔
3041

3042
        for (size_t i = 1; i < n_rows; i++) {
1,296✔
3043
                TableData **row;
1,159✔
3044

3045
                if (sorted)
1,159✔
3046
                        row = t->data + sorted[i];
134✔
3047
                else
3048
                        row = t->data + i * t->n_columns;
1,025✔
3049

3050
                for (size_t j = 0; j < display_columns; j++) {
6,524✔
3051
                        TableData *d;
5,365✔
3052
                        size_t k;
5,365✔
3053

3054
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
5,365✔
3055

3056
                        k = j*2+1;
5,365✔
3057
                        elements[k] = sd_json_variant_unref(elements[k]);
5,365✔
3058

3059
                        r = table_data_to_json(d, elements + k);
5,365✔
3060
                        if (r < 0)
5,365✔
3061
                                return r;
3062
                }
3063

3064
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1,159✔
3065
                if (r < 0)
1,159✔
3066
                        return r;
3067
        }
3068

3069
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
137✔
3070
}
3071

3072
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3073
        sd_json_variant **elements = NULL;
1✔
3074
        size_t n_elements = 0;
1✔
3075
        int r;
1✔
3076

3077
        assert(t);
1✔
3078
        assert(t->vertical);
1✔
3079

3080
        if (t->n_columns != 2)
1✔
3081
                return -EINVAL;
1✔
3082

3083
        /* Ensure we have no incomplete rows */
3084
        assert(t->n_cells % t->n_columns == 0);
1✔
3085

3086
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3087
        if (!elements)
1✔
3088
                return -ENOMEM;
3089

3090
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3091

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

3094
                if (i % t->n_columns == 0) {
8✔
3095
                        _cleanup_free_ char *mangled = NULL;
4✔
3096
                        const char *n;
4✔
3097

3098
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3099
                        if (!n) {
4✔
3100
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3101
                                if (r < 0)
2✔
3102
                                        return r;
×
3103

3104
                                n = mangled;
2✔
3105
                        }
3106

3107
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3108
                } else
3109
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3110
                if (r < 0)
8✔
3111
                        return r;
3112

3113
                n_elements++;
8✔
3114
        }
3115

3116
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3117
}
3118

3119
int table_to_json(Table *t, sd_json_variant **ret) {
138✔
3120
        assert(t);
138✔
3121

3122
        if (t->vertical)
138✔
3123
                return table_to_json_vertical(t, ret);
1✔
3124

3125
        return table_to_json_regular(t, ret);
137✔
3126
}
3127

3128
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
551✔
3129
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
551✔
3130
        int r;
551✔
3131

3132
        assert(t);
551✔
3133

3134
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
551✔
3135
                return table_print(t, f);
480✔
3136

3137
        if (!f)
71✔
3138
                f = stdout;
2✔
3139

3140
        r = table_to_json(t, &v);
71✔
3141
        if (r < 0)
71✔
3142
                return r;
3143

3144
        sd_json_variant_dump(v, flags, f, NULL);
71✔
3145

3146
        return fflush_and_check(f);
71✔
3147
}
3148

3149
int table_print_with_pager(
549✔
3150
                Table *t,
3151
                sd_json_format_flags_t json_format_flags,
3152
                PagerFlags pager_flags,
3153
                bool show_header) {
3154

3155
        bool saved_header;
549✔
3156
        int r;
549✔
3157

3158
        assert(t);
549✔
3159

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

3163
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
549✔
3164
                pager_open(pager_flags);
523✔
3165

3166
        saved_header = t->header;
549✔
3167
        t->header = show_header;
549✔
3168
        r = table_print_json(t, stdout, json_format_flags);
549✔
3169
        t->header = saved_header;
549✔
3170
        if (r < 0)
549✔
UNCOV
3171
                return table_log_print_error(r);
×
3172

3173
        return 0;
3174
}
3175

3176
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
696✔
3177
        int r;
696✔
3178

3179
        assert(t);
696✔
3180

3181
        if (name) {
696✔
3182
                size_t m;
694✔
3183

3184
                m = MAX(idx + 1, t->n_json_fields);
694✔
3185
                if (!GREEDY_REALLOC0(t->json_fields, m))
694✔
3186
                        return -ENOMEM;
3187

3188
                r = free_and_strdup(t->json_fields + idx, name);
694✔
3189
                if (r < 0)
694✔
3190
                        return r;
3191

3192
                t->n_json_fields = m;
694✔
3193
                return r;
694✔
3194
        } else {
3195
                if (idx >= t->n_json_fields)
2✔
3196
                        return 0;
3197

3198
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3199
                return 1;
2✔
3200
        }
3201
}
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