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

systemd / systemd / 14414052759

12 Apr 2025 12:06AM UTC coverage: 72.01% (+0.04%) from 71.967%
14414052759

push

github

web-flow
fido2: hook up with plymouth for notifications (#37089)

Show notifications for fido2 messages in plymouth, so that they
show up in the initrd like the passphrase prompt already does.

12 of 29 new or added lines in 3 files covered. (41.38%)

572 existing lines in 41 files now uncovered.

297369 of 412956 relevant lines covered (72.01%)

685877.32 hits per line

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

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

3
#include <ctype.h>
4
#include <net/if.h>
5
#include <unistd.h>
6

7
#include "sd-id128.h"
8

9
#include "alloc-util.h"
10
#include "devnum-util.h"
11
#include "fd-util.h"
12
#include "fileio.h"
13
#include "format-ifname.h"
14
#include "format-table.h"
15
#include "format-util.h"
16
#include "fs-util.h"
17
#include "glyph-util.h"
18
#include "gunicode.h"
19
#include "id128-util.h"
20
#include "in-addr-util.h"
21
#include "memory-util.h"
22
#include "memstream-util.h"
23
#include "pager.h"
24
#include "parse-util.h"
25
#include "path-util.h"
26
#include "pretty-print.h"
27
#include "process-util.h"
28
#include "signal-util.h"
29
#include "sort-util.h"
30
#include "stat-util.h"
31
#include "string-util.h"
32
#include "strxcpyx.h"
33
#include "terminal-util.h"
34
#include "time-util.h"
35
#include "user-util.h"
36
#include "utf8.h"
37

38
#define DEFAULT_WEIGHT 100
39

40
/*
41
   A few notes on implementation details:
42

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

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

51
 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
52
   previous one.
53

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

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

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

70
typedef struct TableData {
71
        unsigned n_ref;
72
        TableDataType type;
73

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

81
        bool uppercase:1;           /* Uppercase string on display */
82
        bool underline:1;
83
        bool rgap_underline:1;
84

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

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

121
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
279,208✔
122
        size_t i;
279,208✔
123

124
        assert(cell);
279,208✔
125

126
        i = PTR_TO_SIZE(cell);
279,208✔
127
        assert(i > 0);
279,208✔
128

129
        return i-1;
279,208✔
130
}
131

132
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
144,108✔
133
        assert(index != SIZE_MAX);
144,108✔
134
        return SIZE_TO_PTR(index + 1);
144,108✔
135
}
136

137
struct Table {
138
        size_t n_columns;
139
        size_t n_cells;
140

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

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

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

150
        TableData **data;
151

152
        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 */
153
        size_t n_display_map;
154

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

158
        char **json_fields;
159
        size_t n_json_fields;
160

161
        bool *reverse_map;
162
};
163

164
Table *table_new_raw(size_t n_columns) {
2,768✔
165
        _cleanup_(table_unrefp) Table *t = NULL;
2,768✔
166

167
        assert(n_columns > 0);
2,768✔
168

169
        t = new(Table, 1);
2,768✔
170
        if (!t)
2,768✔
171
                return NULL;
172

173
        *t = (Table) {
2,768✔
174
                .n_columns = n_columns,
175
                .header = true,
176
                .width = SIZE_MAX,
177
                .cell_height_max = SIZE_MAX,
178
                .ersatz = TABLE_ERSATZ_EMPTY,
179
        };
180

181
        return TAKE_PTR(t);
2,768✔
182
}
183

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

190
        assert(first_header);
602✔
191

192
        va_start(ap, first_header);
602✔
193
        for (;;) {
8,786✔
194
                if (!va_arg(ap, const char*))
4,694✔
195
                        break;
196

197
                n_columns++;
4,092✔
198
        }
199
        va_end(ap);
602✔
200

201
        t = table_new_raw(n_columns);
602✔
202
        if (!t)
602✔
203
                return NULL;
204

205
        va_start(ap, first_header);
602✔
206
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,296✔
207
                TableCell *cell;
4,694✔
208

209
                r = table_add_cell(t, &cell, TABLE_HEADER, h);
4,694✔
210
                if (r < 0) {
4,694✔
211
                        va_end(ap);
×
212
                        return NULL;
×
213
                }
214
        }
215
        va_end(ap);
602✔
216

217
        assert(t->n_columns == t->n_cells);
602✔
218
        return TAKE_PTR(t);
602✔
219
}
220

221
Table *table_new_vertical(void) {
2,123✔
222
        _cleanup_(table_unrefp) Table *t = NULL;
2,123✔
223
        TableCell *cell;
2,123✔
224

225
        t = table_new_raw(2);
2,123✔
226
        if (!t)
2,123✔
227
                return NULL;
228

229
        t->vertical = true;
2,123✔
230
        t->header = false;
2,123✔
231

232
        if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
2,123✔
233
                return NULL;
234

235
        if (table_set_align_percent(t, cell, 100) < 0)
2,123✔
236
                return NULL;
237

238
        if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
2,123✔
239
                return NULL;
240

241
        if (table_set_align_percent(t, cell, 0) < 0)
2,123✔
242
                return NULL;
243

244
        return TAKE_PTR(t);
2,123✔
245
}
246

247
static TableData *table_data_free(TableData *d) {
138,699✔
248
        assert(d);
138,699✔
249

250
        free(d->formatted);
138,699✔
251
        free(d->url);
138,699✔
252

253
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
138,699✔
254
                strv_free(d->strv);
5,565✔
255

256
        return mfree(d);
138,699✔
257
}
258

259
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
233,943✔
260
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
150,918✔
261

262
Table *table_unref(Table *t) {
2,768✔
263
        if (!t)
2,768✔
264
                return NULL;
265

266
        for (size_t i = 0; i < t->n_cells; i++)
153,698✔
267
                table_data_unref(t->data[i]);
150,930✔
268

269
        free(t->data);
2,768✔
270
        free(t->display_map);
2,768✔
271
        free(t->sort_map);
2,768✔
272
        free(t->reverse_map);
2,768✔
273

274
        for (size_t i = 0; i < t->n_json_fields; i++)
5,075✔
275
                free(t->json_fields[i]);
2,307✔
276

277
        free(t->json_fields);
2,768✔
278

279
        return mfree(t);
2,768✔
280
}
281

282
static size_t table_data_size(TableDataType type, const void *data) {
370,207✔
283

284
        switch (type) {
370,207✔
285

286
        case TABLE_EMPTY:
287
                return 0;
288

289
        case TABLE_STRING:
293,985✔
290
        case TABLE_PATH:
291
        case TABLE_PATH_BASENAME:
292
        case TABLE_FIELD:
293
        case TABLE_HEADER:
294
                return strlen(data) + 1;
293,985✔
295

296
        case TABLE_STRV:
11,401✔
297
        case TABLE_STRV_WRAPPED:
298
                return sizeof(char **);
11,401✔
299

300
        case TABLE_BOOLEAN_CHECKMARK:
9,129✔
301
        case TABLE_BOOLEAN:
302
                return sizeof(bool);
9,129✔
303

304
        case TABLE_TIMESTAMP:
3,826✔
305
        case TABLE_TIMESTAMP_UTC:
306
        case TABLE_TIMESTAMP_RELATIVE:
307
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
308
        case TABLE_TIMESTAMP_LEFT:
309
        case TABLE_TIMESTAMP_DATE:
310
        case TABLE_TIMESPAN:
311
        case TABLE_TIMESPAN_MSEC:
312
        case TABLE_TIMESPAN_DAY:
313
                return sizeof(usec_t);
3,826✔
314

315
        case TABLE_SIZE:
18,291✔
316
        case TABLE_INT64:
317
        case TABLE_UINT64:
318
        case TABLE_UINT64_HEX:
319
        case TABLE_BPS:
320
                return sizeof(uint64_t);
18,291✔
321

322
        case TABLE_INT32:
4,382✔
323
        case TABLE_UINT32:
324
        case TABLE_UINT32_HEX:
325
                return sizeof(uint32_t);
4,382✔
326

327
        case TABLE_INT16:
100✔
328
        case TABLE_UINT16:
329
                return sizeof(uint16_t);
100✔
330

331
        case TABLE_INT8:
54✔
332
        case TABLE_UINT8:
333
                return sizeof(uint8_t);
54✔
334

335
        case TABLE_INT:
3,669✔
336
        case TABLE_UINT:
337
        case TABLE_PERCENT:
338
        case TABLE_IFINDEX:
339
        case TABLE_SIGNAL:
340
                return sizeof(int);
3,669✔
341

342
        case TABLE_IN_ADDR:
211✔
343
                return sizeof(struct in_addr);
211✔
344

345
        case TABLE_IN6_ADDR:
38✔
346
                return sizeof(struct in6_addr);
38✔
347

348
        case TABLE_UUID:
5,495✔
349
        case TABLE_ID128:
350
                return sizeof(sd_id128_t);
5,495✔
351

352
        case TABLE_UID:
1,250✔
353
                return sizeof(uid_t);
1,250✔
354
        case TABLE_GID:
1,551✔
355
                return sizeof(gid_t);
1,551✔
356
        case TABLE_PID:
597✔
357
                return sizeof(pid_t);
597✔
358

359
        case TABLE_MODE:
178✔
360
        case TABLE_MODE_INODE_TYPE:
361
                return sizeof(mode_t);
178✔
362

363
        case TABLE_DEVNUM:
6✔
364
                return sizeof(dev_t);
6✔
365

366
        default:
×
367
                assert_not_reached();
×
368
        }
369
}
370

371
static bool table_data_matches(
141,556✔
372
                TableData *d,
373
                TableDataType type,
374
                const void *data,
375
                size_t minimum_width,
376
                size_t maximum_width,
377
                unsigned weight,
378
                unsigned align_percent,
379
                unsigned ellipsize_percent,
380
                bool uppercase) {
381

382
        size_t k, l;
141,556✔
383
        assert(d);
141,556✔
384

385
        if (d->type != type)
141,556✔
386
                return false;
387

388
        if (d->minimum_width != minimum_width)
120,663✔
389
                return false;
390

391
        if (d->maximum_width != maximum_width)
120,663✔
392
                return false;
393

394
        if (d->weight != weight)
119,306✔
395
                return false;
396

397
        if (d->align_percent != align_percent)
119,306✔
398
                return false;
399

400
        if (d->ellipsize_percent != ellipsize_percent)
119,306✔
401
                return false;
402

403
        if (d->uppercase != uppercase)
119,306✔
404
                return false;
405

406
        /* If a color/url is set, refuse to merge */
407
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
119,305✔
408
                return false;
409
        if (d->url)
117,338✔
410
                return false;
411

412
        k = table_data_size(type, data);
115,754✔
413
        l = table_data_size(d->type, d->data);
115,754✔
414
        if (k != l)
115,754✔
415
                return false;
416

417
        return memcmp_safe(data, d->data, l) == 0;
68,417✔
418
}
419

420
static TableData *table_data_new(
138,699✔
421
                TableDataType type,
422
                const void *data,
423
                size_t minimum_width,
424
                size_t maximum_width,
425
                unsigned weight,
426
                unsigned align_percent,
427
                unsigned ellipsize_percent,
428
                bool uppercase) {
429

430
        _cleanup_free_ TableData *d = NULL;
138,699✔
431
        size_t data_size;
138,699✔
432

433
        data_size = table_data_size(type, data);
138,699✔
434

435
        d = malloc0(offsetof(TableData, data) + data_size);
138,699✔
436
        if (!d)
138,699✔
437
                return NULL;
438

439
        d->n_ref = 1;
138,699✔
440
        d->type = type;
138,699✔
441
        d->minimum_width = minimum_width;
138,699✔
442
        d->maximum_width = maximum_width;
138,699✔
443
        d->weight = weight;
138,699✔
444
        d->align_percent = align_percent;
138,699✔
445
        d->ellipsize_percent = ellipsize_percent;
138,699✔
446
        d->uppercase = uppercase;
138,699✔
447

448
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
138,699✔
449
                d->strv = strv_copy(data);
5,565✔
450
                if (!d->strv)
5,565✔
451
                        return NULL;
×
452
        } else
453
                memcpy_safe(d->data, data, data_size);
133,134✔
454

455
        return TAKE_PTR(d);
456
}
457

458
int table_add_cell_full(
150,918✔
459
                Table *t,
460
                TableCell **ret_cell,
461
                TableDataType type,
462
                const void *data,
463
                size_t minimum_width,
464
                size_t maximum_width,
465
                unsigned weight,
466
                unsigned align_percent,
467
                unsigned ellipsize_percent) {
468

469
        _cleanup_(table_data_unrefp) TableData *d = NULL;
150,918✔
470
        bool uppercase;
150,918✔
471
        TableData *p;
150,918✔
472

473
        assert(t);
150,918✔
474
        assert(type >= 0);
150,918✔
475
        assert(type < _TABLE_DATA_TYPE_MAX);
150,918✔
476

477
        /* Special rule: patch NULL data fields to the empty field */
478
        if (!data)
150,918✔
479
                type = TABLE_EMPTY;
8,365✔
480

481
        /* Determine the cell adjacent to the current one, but one row up */
482
        if (t->n_cells >= t->n_columns)
150,918✔
483
                assert_se(p = t->data[t->n_cells - t->n_columns]);
141,556✔
484
        else
485
                p = NULL;
486

487
        /* If formatting parameters are left unspecified, copy from the previous row */
488
        if (minimum_width == SIZE_MAX)
150,918✔
489
                minimum_width = p ? p->minimum_width : 1;
150,918✔
490

491
        if (weight == UINT_MAX)
150,918✔
492
                weight = p ? p->weight : DEFAULT_WEIGHT;
150,918✔
493

494
        if (align_percent == UINT_MAX)
150,918✔
495
                align_percent = p ? p->align_percent : 0;
150,918✔
496

497
        if (ellipsize_percent == UINT_MAX)
150,918✔
498
                ellipsize_percent = p ? p->ellipsize_percent : 100;
150,918✔
499

500
        assert(align_percent <= 100);
150,918✔
501
        assert(ellipsize_percent <= 100);
150,918✔
502

503
        uppercase = type == TABLE_HEADER;
150,918✔
504

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

508
        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
150,918✔
509
                d = table_data_ref(p);
47,610✔
510
        else {
511
                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
103,308✔
512
                if (!d)
103,308✔
513
                        return -ENOMEM;
514
        }
515

516
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
150,918✔
517
                return -ENOMEM;
518

519
        if (ret_cell)
150,918✔
520
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
136,098✔
521

522
        t->data[t->n_cells++] = TAKE_PTR(d);
150,918✔
523

524
        return 0;
150,918✔
525
}
526

527
int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
5,432✔
528
        _cleanup_free_ char *buffer = NULL;
5,432✔
529
        va_list ap;
5,432✔
530
        int r;
5,432✔
531

532
        assert(t);
5,432✔
533
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER));
5,432✔
534

535
        va_start(ap, format);
5,432✔
536
        r = vasprintf(&buffer, format, ap);
5,432✔
537
        va_end(ap);
5,432✔
538
        if (r < 0)
5,432✔
539
                return -ENOMEM;
540

541
        return table_add_cell(t, ret_cell, dt, buffer);
5,432✔
542
}
543

544
int table_fill_empty(Table *t, size_t until_column) {
52✔
545
        int r;
52✔
546

547
        assert(t);
52✔
548

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

552
        if (until_column >= t->n_columns)
52✔
553
                return -EINVAL;
554

555
        do {
52✔
556
                r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
52✔
557
                if (r < 0)
52✔
558
                        return r;
559

560
        } while ((t->n_cells % t->n_columns) != until_column);
52✔
561

562
        return 0;
563
}
564

565
int table_dup_cell(Table *t, TableCell *cell) {
12✔
566
        size_t i;
12✔
567

568
        assert(t);
12✔
569

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

572
        i = TABLE_CELL_TO_INDEX(cell);
12✔
573
        if (i >= t->n_cells)
12✔
574
                return -ENXIO;
575

576
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
12✔
577
                return -ENOMEM;
578

579
        t->data[t->n_cells++] = table_data_ref(t->data[i]);
12✔
580
        return 0;
12✔
581
}
582

583
static int table_dedup_cell(Table *t, TableCell *cell) {
136,848✔
584
        _cleanup_free_ char *curl = NULL;
136,848✔
585
        TableData *nd, *od;
136,848✔
586
        size_t i;
136,848✔
587

588
        assert(t);
136,848✔
589

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

593
        i = TABLE_CELL_TO_INDEX(cell);
136,848✔
594
        if (i >= t->n_cells)
136,848✔
595
                return -ENXIO;
596

597
        assert_se(od = t->data[i]);
136,848✔
598
        if (od->n_ref == 1)
136,848✔
599
                return 0;
600

601
        assert(od->n_ref > 1);
34,268✔
602

603
        if (od->url) {
34,268✔
604
                curl = strdup(od->url);
×
605
                if (!curl)
×
606
                        return -ENOMEM;
607
        }
608

609
        nd = table_data_new(
68,536✔
610
                        od->type,
611
                        od->data,
34,268✔
612
                        od->minimum_width,
613
                        od->maximum_width,
614
                        od->weight,
615
                        od->align_percent,
616
                        od->ellipsize_percent,
617
                        od->uppercase);
34,268✔
618
        if (!nd)
34,268✔
619
                return -ENOMEM;
620

621
        nd->color = od->color;
34,268✔
622
        nd->rgap_color = od->rgap_color;
34,268✔
623
        nd->underline = od->underline;
34,268✔
624
        nd->rgap_underline = od->rgap_underline;
34,268✔
625
        nd->url = TAKE_PTR(curl);
34,268✔
626

627
        table_data_unref(od);
34,268✔
628
        t->data[i] = nd;
34,268✔
629

630
        assert(nd->n_ref == 1);
34,268✔
631

632
        return 1;
633
}
634

635
static TableData *table_get_data(Table *t, TableCell *cell) {
141,222✔
636
        size_t i;
141,222✔
637

638
        assert(t);
141,222✔
639
        assert(cell);
141,222✔
640

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

643
        i = TABLE_CELL_TO_INDEX(cell);
141,222✔
644
        if (i >= t->n_cells)
141,222✔
645
                return NULL;
646

647
        assert(t->data[i]);
141,222✔
648
        assert(t->data[i]->n_ref > 0);
141,222✔
649

650
        return t->data[i];
651
}
652

653
int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
1,434✔
654
        int r;
1,434✔
655

656
        assert(t);
1,434✔
657
        assert(cell);
1,434✔
658

659
        if (minimum_width == SIZE_MAX)
1,434✔
660
                minimum_width = 1;
×
661

662
        r = table_dedup_cell(t, cell);
1,434✔
663
        if (r < 0)
1,434✔
664
                return r;
665

666
        table_get_data(t, cell)->minimum_width = minimum_width;
1,434✔
667
        return 0;
1,434✔
668
}
669

670
int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
1,412✔
671
        int r;
1,412✔
672

673
        assert(t);
1,412✔
674
        assert(cell);
1,412✔
675

676
        r = table_dedup_cell(t, cell);
1,412✔
677
        if (r < 0)
1,412✔
678
                return r;
679

680
        table_get_data(t, cell)->maximum_width = maximum_width;
1,412✔
681
        return 0;
1,412✔
682
}
683

684
int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
5✔
685
        int r;
5✔
686

687
        assert(t);
5✔
688
        assert(cell);
5✔
689

690
        if (weight == UINT_MAX)
5✔
691
                weight = DEFAULT_WEIGHT;
×
692

693
        r = table_dedup_cell(t, cell);
5✔
694
        if (r < 0)
5✔
695
                return r;
696

697
        table_get_data(t, cell)->weight = weight;
5✔
698
        return 0;
5✔
699
}
700

701
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
11,658✔
702
        int r;
11,658✔
703

704
        assert(t);
11,658✔
705
        assert(cell);
11,658✔
706

707
        if (percent == UINT_MAX)
11,658✔
708
                percent = 0;
709

710
        assert(percent <= 100);
11,658✔
711

712
        r = table_dedup_cell(t, cell);
11,658✔
713
        if (r < 0)
11,658✔
714
                return r;
715

716
        table_get_data(t, cell)->align_percent = percent;
11,658✔
717
        return 0;
11,658✔
718
}
719

720
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
2,946✔
721
        int r;
2,946✔
722

723
        assert(t);
2,946✔
724
        assert(cell);
2,946✔
725

726
        if (percent == UINT_MAX)
2,946✔
727
                percent = 100;
728

729
        assert(percent <= 100);
2,946✔
730

731
        r = table_dedup_cell(t, cell);
2,946✔
732
        if (r < 0)
2,946✔
733
                return r;
734

735
        table_get_data(t, cell)->ellipsize_percent = percent;
2,946✔
736
        return 0;
2,946✔
737
}
738

739
int table_set_color(Table *t, TableCell *cell, const char *color) {
40,984✔
740
        int r;
40,984✔
741

742
        assert(t);
40,984✔
743
        assert(cell);
40,984✔
744

745
        r = table_dedup_cell(t, cell);
40,984✔
746
        if (r < 0)
40,984✔
747
                return r;
748

749
        table_get_data(t, cell)->color = empty_to_null(color);
54,612✔
750
        return 0;
40,984✔
751
}
752

753
int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
3,204✔
754
        int r;
3,204✔
755

756
        assert(t);
3,204✔
757
        assert(cell);
3,204✔
758

759
        r = table_dedup_cell(t, cell);
3,204✔
760
        if (r < 0)
3,204✔
761
                return r;
762

763
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,408✔
764
        return 0;
3,204✔
765
}
766

767
int table_set_underline(Table *t, TableCell *cell, bool b) {
36,778✔
768
        TableData *d;
36,778✔
769
        int r;
36,778✔
770

771
        assert(t);
36,778✔
772
        assert(cell);
36,778✔
773

774
        r = table_dedup_cell(t, cell);
36,778✔
775
        if (r < 0)
36,778✔
776
                return r;
777

778
        assert_se(d = table_get_data(t, cell));
36,778✔
779

780
        if (d->underline == b)
36,778✔
781
                return 0;
782

783
        d->underline = b;
322✔
784
        return 1;
322✔
785
}
786

787
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
36,778✔
788
        TableData *d;
36,778✔
789
        int r;
36,778✔
790

791
        assert(t);
36,778✔
792
        assert(cell);
36,778✔
793

794
        r = table_dedup_cell(t, cell);
36,778✔
795
        if (r < 0)
36,778✔
796
                return r;
797

798
        assert_se(d = table_get_data(t, cell));
36,778✔
799

800
        if (d->rgap_underline == b)
36,778✔
801
                return 0;
802

803
        d->rgap_underline = b;
322✔
804
        return 1;
322✔
805
}
806

807
int table_set_url(Table *t, TableCell *cell, const char *url) {
1,648✔
808
        _cleanup_free_ char *copy = NULL;
1,648✔
809
        int r;
1,648✔
810

811
        assert(t);
1,648✔
812
        assert(cell);
1,648✔
813

814
        if (url) {
1,648✔
815
                copy = strdup(url);
1,636✔
816
                if (!copy)
1,636✔
817
                        return -ENOMEM;
818
        }
819

820
        r = table_dedup_cell(t, cell);
1,648✔
821
        if (r < 0)
1,648✔
822
                return r;
823

824
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,648✔
825
}
826

827
int table_set_uppercase(Table *t, TableCell *cell, bool b) {
1✔
828
        TableData *d;
1✔
829
        int r;
1✔
830

831
        assert(t);
1✔
832
        assert(cell);
1✔
833

834
        r = table_dedup_cell(t, cell);
1✔
835
        if (r < 0)
1✔
836
                return r;
837

838
        assert_se(d = table_get_data(t, cell));
1✔
839

840
        if (d->uppercase == b)
1✔
841
                return 0;
842

843
        d->formatted = mfree(d->formatted);
1✔
844
        d->uppercase = b;
1✔
845
        return 1;
1✔
846
}
847

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

853
        assert(t);
1,123✔
854
        assert(cell);
1,123✔
855

856
        i = TABLE_CELL_TO_INDEX(cell);
1,123✔
857
        if (i >= t->n_cells)
1,123✔
858
                return -ENXIO;
859

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

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

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

880
        nd->color = od->color;
1,123✔
881
        nd->rgap_color = od->rgap_color;
1,123✔
882
        nd->underline = od->underline;
1,123✔
883
        nd->rgap_underline = od->rgap_underline;
1,123✔
884
        nd->url = TAKE_PTR(curl);
1,123✔
885

886
        table_data_unref(od);
1,123✔
887
        t->data[i] = nd;
1,123✔
888

889
        return 0;
1,123✔
890
}
891

892
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
37,293✔
893
        TableCell *last_cell = NULL;
37,293✔
894
        va_list ap;
37,293✔
895
        int r;
37,293✔
896

897
        assert(t);
37,293✔
898
        assert(first_type >= 0);
37,293✔
899
        assert(first_type < _TABLE_DATA_TYPE_MAX);
37,293✔
900

901
        va_start(ap, first_type);
37,293✔
902

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

930
                switch (type) {
254,420✔
931

932
                case TABLE_EMPTY:
933
                        data = NULL;
934
                        break;
935

936
                case TABLE_STRING:
96,096✔
937
                case TABLE_PATH:
938
                case TABLE_PATH_BASENAME:
939
                case TABLE_FIELD:
940
                case TABLE_HEADER:
941
                        data = va_arg(ap, const char *);
96,096✔
942
                        break;
96,096✔
943

944
                case TABLE_STRV:
5,599✔
945
                case TABLE_STRV_WRAPPED:
946
                        data = va_arg(ap, char * const *);
5,599✔
947
                        break;
5,599✔
948

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

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

968
                case TABLE_SIZE:
619✔
969
                case TABLE_BPS:
970
                        buffer.size = va_arg(ap, uint64_t);
619✔
971
                        data = &buffer.size;
619✔
972
                        break;
619✔
973

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

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

984
                        buffer.int8 = x;
3✔
985
                        data = &buffer.int8;
3✔
986
                        break;
3✔
987
                }
988

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

993
                        buffer.int16 = x;
3✔
994
                        data = &buffer.int16;
3✔
995
                        break;
3✔
996
                }
997

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

1003
                case TABLE_INT64:
194✔
1004
                        buffer.int64 = va_arg(ap, int64_t);
194✔
1005
                        data = &buffer.int64;
194✔
1006
                        break;
194✔
1007

1008
                case TABLE_UINT:
97✔
1009
                        buffer.uint_val = va_arg(ap, unsigned);
97✔
1010
                        data = &buffer.uint_val;
97✔
1011
                        break;
97✔
1012

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

1017
                        buffer.uint8 = x;
43✔
1018
                        data = &buffer.uint8;
43✔
1019
                        break;
43✔
1020
                }
1021

1022
                case TABLE_UINT16: {
89✔
1023
                        unsigned x = va_arg(ap, unsigned);
89✔
1024
                        assert(x <= UINT16_MAX);
89✔
1025

1026
                        buffer.uint16 = x;
89✔
1027
                        data = &buffer.uint16;
89✔
1028
                        break;
89✔
1029
                }
1030

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

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

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

1048
                case TABLE_IFINDEX:
57✔
1049
                        buffer.ifindex = va_arg(ap, int);
57✔
1050
                        data = &buffer.ifindex;
57✔
1051
                        break;
57✔
1052

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

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

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

1069
                case TABLE_UID:
480✔
1070
                        buffer.uid = va_arg(ap, uid_t);
480✔
1071
                        data = &buffer.uid;
480✔
1072
                        break;
480✔
1073

1074
                case TABLE_GID:
559✔
1075
                        buffer.gid = va_arg(ap, gid_t);
559✔
1076
                        data = &buffer.gid;
559✔
1077
                        break;
559✔
1078

1079
                case TABLE_PID:
240✔
1080
                        buffer.pid = va_arg(ap, pid_t);
240✔
1081
                        data = &buffer.pid;
240✔
1082
                        break;
240✔
1083

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

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

1095
                case TABLE_SET_MINIMUM_WIDTH: {
1,421✔
1096
                        size_t w = va_arg(ap, size_t);
1,421✔
1097

1098
                        r = table_set_minimum_width(t, last_cell, w);
1,421✔
1099
                        goto check;
1,421✔
1100
                }
1101

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

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

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

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

1126
                case TABLE_SET_COLOR: {
37,780✔
1127
                        const char *c = va_arg(ap, const char*);
37,780✔
1128
                        r = table_set_color(t, last_cell, c);
37,780✔
1129
                        goto check;
37,780✔
1130
                }
1131

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

1138
                case TABLE_SET_BOTH_COLORS: {
3,204✔
1139
                        const char *c = va_arg(ap, const char*);
3,204✔
1140

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

1147
                        r = table_set_rgap_color(t, last_cell, c);
3,204✔
1148
                        goto check;
3,204✔
1149
                }
1150

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

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

1163
                case TABLE_SET_BOTH_UNDERLINES: {
36,778✔
1164
                        int u = va_arg(ap, int);
36,778✔
1165

1166
                        r = table_set_underline(t, last_cell, u);
36,778✔
1167
                        if (r < 0) {
36,778✔
1168
                                va_end(ap);
×
1169
                                return r;
×
1170
                        }
1171

1172
                        r = table_set_rgap_underline(t, last_cell, u);
36,778✔
1173
                        goto check;
36,778✔
1174
                }
1175

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

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

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

1202
                case _TABLE_DATA_TYPE_MAX:
37,293✔
1203
                        /* Used as end marker */
1204
                        va_end(ap);
37,293✔
1205
                        return 0;
37,293✔
1206

1207
                default:
×
1208
                        assert_not_reached();
×
1209
                }
1210

1211
                r = table_add_cell(t, &last_cell, type, data);
127,158✔
1212
        check:
217,127✔
1213
                if (r < 0) {
217,127✔
1214
                        va_end(ap);
×
1215
                        return r;
×
1216
                }
1217
        }
1218
}
1219

1220
void table_set_header(Table *t, bool b) {
175✔
1221
        assert(t);
175✔
1222

1223
        t->header = b;
175✔
1224
}
175✔
1225

1226
void table_set_width(Table *t, size_t width) {
96✔
1227
        assert(t);
96✔
1228

1229
        t->width = width;
96✔
1230
}
96✔
1231

1232
void table_set_cell_height_max(Table *t, size_t height) {
27✔
1233
        assert(t);
27✔
1234
        assert(height >= 1 || height == SIZE_MAX);
27✔
1235

1236
        t->cell_height_max = height;
27✔
1237
}
27✔
1238

1239
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
601✔
1240
        assert(t);
601✔
1241
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
601✔
1242

1243
        t->ersatz = ersatz;
601✔
1244
}
601✔
1245

1246
static const char* table_ersatz_string(const Table *t) {
11,546✔
1247
        switch (t->ersatz) {
11,546✔
1248
        case TABLE_ERSATZ_EMPTY:
1249
                return "";
1250
        case TABLE_ERSATZ_DASH:
8,242✔
1251
                return "-";
8,242✔
1252
        case TABLE_ERSATZ_UNSET:
414✔
1253
                return "(unset)";
414✔
1254
        case TABLE_ERSATZ_NA:
×
1255
                return "n/a";
×
1256
        default:
×
1257
                assert_not_reached();
×
1258
        }
1259
}
1260

1261
static int table_set_display_all(Table *t) {
290✔
1262
        size_t *d;
290✔
1263

1264
        assert(t);
290✔
1265

1266
        /* Initialize the display map to the identity */
1267

1268
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
290✔
1269
        if (!d)
290✔
1270
                return -ENOMEM;
1271

1272
        for (size_t i = 0; i < t->n_columns; i++)
3,682✔
1273
                d[i] = i;
3,392✔
1274

1275
        t->display_map = d;
290✔
1276
        t->n_display_map = t->n_columns;
290✔
1277

1278
        return 0;
290✔
1279
}
1280

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

1285
        assert(t);
31✔
1286

1287
        column = first_column;
31✔
1288

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

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

1298
                t->display_map[t->n_display_map++] = column;
159✔
1299

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

1304
        }
1305
        va_end(ap);
31✔
1306

1307
        return 0;
31✔
1308
}
1309

1310
int table_set_sort_internal(Table *t, size_t first_column, ...) {
218✔
1311
        size_t column;
218✔
1312
        va_list ap;
218✔
1313

1314
        assert(t);
218✔
1315

1316
        column = first_column;
218✔
1317

1318
        va_start(ap, first_column);
218✔
1319
        for (;;) {
288✔
1320
                assert(column < t->n_columns);
288✔
1321

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

1327
                t->sort_map[t->n_sort_map++] = column;
288✔
1328

1329
                column = va_arg(ap, size_t);
288✔
1330
                if (column == SIZE_MAX)
288✔
1331
                        break;
1332
        }
1333
        va_end(ap);
218✔
1334

1335
        return 0;
218✔
1336
}
1337

1338
int table_hide_column_from_display_internal(Table *t, ...) {
578✔
1339
        size_t cur = 0;
578✔
1340
        int r;
578✔
1341

1342
        assert(t);
578✔
1343

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

1351
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
7,714✔
1352
                bool listed = false;
7,136✔
1353
                va_list ap;
7,136✔
1354

1355
                va_start(ap, t);
7,136✔
1356
                for (;;) {
13,816✔
1357
                        size_t column;
13,816✔
1358

1359
                        column = va_arg(ap, size_t);
13,816✔
1360
                        if (column == SIZE_MAX)
13,816✔
1361
                                break;
1362
                        if (column == *i) {
7,271✔
1363
                                listed = true;
1364
                                break;
1365
                        }
1366
                }
1367
                va_end(ap);
7,136✔
1368

1369
                if (listed)
7,136✔
1370
                        continue;
591✔
1371

1372
                t->display_map[cur++] = *i;
6,545✔
1373
        }
1374

1375
        t->n_display_map = cur;
578✔
1376

1377
        return 0;
578✔
1378
}
1379

1380
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
11,265✔
1381
        int r;
11,265✔
1382

1383
        assert(a);
11,265✔
1384
        assert(b);
11,265✔
1385

1386
        if (a->type == b->type) {
11,265✔
1387

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

1391
                switch (a->type) {
11,259✔
1392

1393
                case TABLE_STRING:
8,348✔
1394
                case TABLE_FIELD:
1395
                case TABLE_HEADER:
1396
                        return strcmp(a->string, b->string);
8,348✔
1397

1398
                case TABLE_PATH:
22✔
1399
                case TABLE_PATH_BASENAME:
1400
                        return path_compare(a->string, b->string);
22✔
1401

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1479
                case TABLE_UID:
970✔
1480
                        return CMP(a->uid, b->uid);
970✔
1481

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

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

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

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

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

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

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

1508
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
11,461✔
1509
        int r;
11,461✔
1510

1511
        assert(t);
11,461✔
1512
        assert(t->sort_map);
11,461✔
1513

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

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

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

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

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

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

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

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

1551
                if (position == 0) {
360✔
1552
                        fputs(*p, f);
116✔
1553
                        position = our_len;
1554
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
488✔
1555
                        fprintf(f, " %s", *p);
210✔
1556
                        position = size_add(size_add(position, 1), our_len);
570✔
1557
                } else {
1558
                        fprintf(f, "\n%s", *p);
34✔
1559
                        position = our_len;
1560
                }
1561
        }
1562

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

1567
        return buf;
116✔
1568
}
1569

1570
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
258,095✔
1571
        assert(d);
258,095✔
1572

1573
        if (d->formatted &&
258,095✔
1574
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1575
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
51,826✔
1576
                return d->formatted;
1577

1578
        switch (d->type) {
206,364✔
1579
        case TABLE_EMPTY:
11,404✔
1580
                return table_ersatz_string(t);
11,404✔
1581

1582
        case TABLE_STRING:
170,241✔
1583
        case TABLE_PATH:
1584
        case TABLE_PATH_BASENAME:
1585
        case TABLE_FIELD:
1586
        case TABLE_HEADER: {
1587
                _cleanup_free_ char *bn = NULL;
170,241✔
1588
                const char *s;
170,241✔
1589

1590
                if (d->type == TABLE_PATH_BASENAME)
170,241✔
1591
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
326✔
1592
                else
1593
                        s = d->string;
169,915✔
1594

1595
                if (d->uppercase && !avoid_uppercasing) {
170,241✔
1596
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,214✔
1597
                        if (!d->formatted)
3,214✔
1598
                                return NULL;
1599

1600
                        char *q = d->formatted;
1601
                        for (const char *p = s; *p; p++)
22,667✔
1602
                                *(q++) = (char) toupper((unsigned char) *p);
19,453✔
1603

1604
                        if (d->type == TABLE_FIELD)
3,214✔
1605
                                *(q++) = ':';
×
1606

1607
                        *q = 0;
3,214✔
1608
                        return d->formatted;
3,214✔
1609
                } else if (d->type == TABLE_FIELD) {
167,027✔
1610
                        d->formatted = strjoin(s, ":");
25,534✔
1611
                        if (!d->formatted)
25,534✔
1612
                                return NULL;
1613

1614
                        return d->formatted;
25,534✔
1615
                }
1616

1617
                if (bn) {
141,493✔
1618
                        d->formatted = TAKE_PTR(bn);
324✔
1619
                        return d->formatted;
324✔
1620
                }
1621

1622
                return d->string;
141,169✔
1623
        }
1624

1625
        case TABLE_STRV:
5,277✔
1626
                if (strv_isempty(d->strv))
5,277✔
1627
                        return table_ersatz_string(t);
×
1628

1629
                d->formatted = strv_join(d->strv, "\n");
5,277✔
1630
                if (!d->formatted)
5,277✔
1631
                        return NULL;
1632
                break;
1633

1634
        case TABLE_STRV_WRAPPED: {
116✔
1635
                if (strv_isempty(d->strv))
116✔
1636
                        return table_ersatz_string(t);
×
1637

1638
                char *buf = format_strv_width(d->strv, column_width);
116✔
1639
                if (!buf)
116✔
1640
                        return NULL;
1641

1642
                free_and_replace(d->formatted, buf);
116✔
1643
                d->formatted_for_width = column_width;
116✔
1644
                if (have_soft)
116✔
1645
                        *have_soft = true;
72✔
1646

1647
                break;
13,835✔
1648
        }
1649

1650
        case TABLE_BOOLEAN:
3,812✔
1651
                return yes_no(d->boolean);
259,602✔
1652

1653
        case TABLE_BOOLEAN_CHECKMARK:
6,852✔
1654
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,596✔
1655

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

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

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

1686
                d->formatted = TAKE_PTR(p);
1,814✔
1687
                break;
1,814✔
1688
        }
1689

1690
        case TABLE_TIMESPAN:
328✔
1691
        case TABLE_TIMESPAN_MSEC:
1692
        case TABLE_TIMESPAN_DAY: {
1693
                _cleanup_free_ char *p = NULL;
×
1694

1695
                p = new(char, FORMAT_TIMESPAN_MAX);
328✔
1696
                if (!p)
328✔
1697
                        return NULL;
1698

1699
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
649✔
1700
                                     d->type == TABLE_TIMESPAN ? 0 :
1701
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
321✔
1702
                        return "-";
1703

1704
                d->formatted = TAKE_PTR(p);
328✔
1705
                break;
328✔
1706
        }
1707

1708
        case TABLE_SIZE: {
258✔
1709
                _cleanup_free_ char *p = NULL;
140✔
1710

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

1715
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
258✔
1716
                        return table_ersatz_string(t);
140✔
1717

1718
                d->formatted = TAKE_PTR(p);
118✔
1719
                break;
118✔
1720
        }
1721

1722
        case TABLE_BPS: {
360✔
1723
                _cleanup_free_ char *p = NULL;
×
1724
                size_t n;
360✔
1725

1726
                p = new(char, FORMAT_BYTES_MAX+2);
360✔
1727
                if (!p)
360✔
1728
                        return NULL;
1729

1730
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
360✔
1731
                        return table_ersatz_string(t);
×
1732

1733
                n = strlen(p);
360✔
1734
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
360✔
1735

1736
                d->formatted = TAKE_PTR(p);
360✔
1737
                break;
360✔
1738
        }
1739

1740
        case TABLE_INT: {
259✔
1741
                _cleanup_free_ char *p = NULL;
×
1742

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

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

1752
        case TABLE_INT8: {
3✔
1753
                _cleanup_free_ char *p = NULL;
×
1754

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

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

1764
        case TABLE_INT16: {
3✔
1765
                _cleanup_free_ char *p = NULL;
×
1766

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

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

1776
        case TABLE_INT32: {
3✔
1777
                _cleanup_free_ char *p = NULL;
×
1778

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

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

1788
        case TABLE_INT64: {
155✔
1789
                _cleanup_free_ char *p = NULL;
×
1790

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

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

1800
        case TABLE_UINT: {
283✔
1801
                _cleanup_free_ char *p = NULL;
×
1802

1803
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
405✔
1804
                if (!p)
283✔
1805
                        return NULL;
×
1806

1807
                sprintf(p, "%u", d->uint_val);
283✔
1808
                d->formatted = TAKE_PTR(p);
283✔
1809
                break;
283✔
1810
        }
1811

1812
        case TABLE_UINT8: {
43✔
1813
                _cleanup_free_ char *p = NULL;
×
1814

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

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

1824
        case TABLE_UINT16: {
89✔
1825
                _cleanup_free_ char *p = NULL;
×
1826

1827
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
304✔
1828
                if (!p)
89✔
1829
                        return NULL;
×
1830

1831
                sprintf(p, "%" PRIu16, d->uint16);
89✔
1832
                d->formatted = TAKE_PTR(p);
89✔
1833
                break;
89✔
1834
        }
1835

1836
        case TABLE_UINT32: {
1,003✔
1837
                _cleanup_free_ char *p = NULL;
×
1838

1839
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,520✔
1840
                if (!p)
1,003✔
1841
                        return NULL;
×
1842

1843
                sprintf(p, "%" PRIu32, d->uint32);
1,003✔
1844
                d->formatted = TAKE_PTR(p);
1,003✔
1845
                break;
1,003✔
1846
        }
1847

1848
        case TABLE_UINT32_HEX: {
2✔
1849
                _cleanup_free_ char *p = NULL;
×
1850

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

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

1860
        case TABLE_UINT64: {
1,093✔
1861
                _cleanup_free_ char *p = NULL;
×
1862

1863
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
5,255✔
1864
                if (!p)
1,093✔
1865
                        return NULL;
×
1866

1867
                sprintf(p, "%" PRIu64, d->uint64);
1,093✔
1868
                d->formatted = TAKE_PTR(p);
1,093✔
1869
                break;
1,093✔
1870
        }
1871

1872
        case TABLE_UINT64_HEX: {
14✔
1873
                _cleanup_free_ char *p = NULL;
×
1874

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

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

1884
        case TABLE_PERCENT: {
2✔
1885
                _cleanup_free_ char *p = NULL;
×
1886

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

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

1896
        case TABLE_IFINDEX: {
72✔
1897
                _cleanup_free_ char *p = NULL;
×
1898

1899
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
72✔
1900
                        return NULL;
×
1901

1902
                d->formatted = TAKE_PTR(p);
72✔
1903
                break;
72✔
1904
        }
1905

1906
        case TABLE_IN_ADDR:
153✔
1907
        case TABLE_IN6_ADDR: {
1908
                _cleanup_free_ char *p = NULL;
×
1909

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

1914
                d->formatted = TAKE_PTR(p);
153✔
1915
                break;
153✔
1916
        }
1917

1918
        case TABLE_ID128: {
1919
                char *p;
1,095✔
1920

1921
                p = new(char, SD_ID128_STRING_MAX);
1,095✔
1922
                if (!p)
1,095✔
1923
                        return NULL;
1924

1925
                d->formatted = sd_id128_to_string(d->id128, p);
1,095✔
1926
                break;
1,095✔
1927
        }
1928

1929
        case TABLE_UUID: {
1930
                char *p;
286✔
1931

1932
                p = new(char, SD_ID128_UUID_STRING_MAX);
286✔
1933
                if (!p)
286✔
1934
                        return NULL;
1935

1936
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
286✔
1937
                break;
286✔
1938
        }
1939

1940
        case TABLE_UID: {
384✔
1941
                char *p;
384✔
1942

1943
                if (!uid_is_valid(d->uid))
384✔
1944
                        return table_ersatz_string(t);
×
1945

1946
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,306✔
1947
                if (!p)
384✔
1948
                        return NULL;
1949
                sprintf(p, UID_FMT, d->uid);
384✔
1950

1951
                d->formatted = p;
384✔
1952
                break;
384✔
1953
        }
1954

1955
        case TABLE_GID: {
509✔
1956
                char *p;
509✔
1957

1958
                if (!gid_is_valid(d->gid))
509✔
1959
                        return table_ersatz_string(t);
×
1960

1961
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,509✔
1962
                if (!p)
509✔
1963
                        return NULL;
1964
                sprintf(p, GID_FMT, d->gid);
509✔
1965

1966
                d->formatted = p;
509✔
1967
                break;
509✔
1968
        }
1969

1970
        case TABLE_PID: {
223✔
1971
                char *p;
223✔
1972

1973
                if (!pid_is_valid(d->pid))
223✔
1974
                        return table_ersatz_string(t);
×
1975

1976
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
613✔
1977
                if (!p)
223✔
1978
                        return NULL;
1979
                sprintf(p, PID_FMT, d->pid);
223✔
1980

1981
                d->formatted = p;
223✔
1982
                break;
223✔
1983
        }
1984

1985
        case TABLE_SIGNAL: {
123✔
1986
                const char *suffix;
123✔
1987
                char *p;
123✔
1988

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

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

1997
                d->formatted = p;
123✔
1998
                break;
123✔
1999
        }
2000

2001
        case TABLE_MODE: {
24✔
2002
                char *p;
24✔
2003

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

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

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

2016
        case TABLE_MODE_INODE_TYPE:
2✔
2017

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

2021
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2022

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

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

2030
                break;
2031

2032
        default:
×
2033
                assert_not_reached();
×
2034
        }
2035

2036
        return d->formatted;
13,835✔
2037
}
2038

2039
static int console_width_height(
126,178✔
2040
                const char *s,
2041
                size_t *ret_width,
2042
                size_t *ret_height) {
2043

2044
        size_t max_width = 0, height = 0;
126,178✔
2045
        const char *p;
126,178✔
2046

2047
        assert(s);
126,178✔
2048

2049
        /* Determine the width and height in console character cells the specified string needs. */
2050

2051
        do {
129,082✔
2052
                size_t k;
129,082✔
2053

2054
                p = strchr(s, '\n');
129,082✔
2055
                if (p) {
129,082✔
2056
                        _cleanup_free_ char *c = NULL;
2,907✔
2057

2058
                        c = strndup(s, p - s);
2,907✔
2059
                        if (!c)
2,907✔
2060
                                return -ENOMEM;
×
2061

2062
                        k = utf8_console_width(c);
2,907✔
2063
                        s = p + 1;
2,907✔
2064
                } else {
2065
                        k = utf8_console_width(s);
126,175✔
2066
                        s = NULL;
126,175✔
2067
                }
2068
                if (k == SIZE_MAX)
129,082✔
2069
                        return -EINVAL;
2070
                if (k > max_width)
129,082✔
2071
                        max_width = k;
125,178✔
2072

2073
                height++;
129,082✔
2074
        } while (!isempty(s));
131,989✔
2075

2076
        if (ret_width)
126,178✔
2077
                *ret_width = max_width;
126,178✔
2078

2079
        if (ret_height)
126,178✔
2080
                *ret_height = height;
126,178✔
2081

2082
        return 0;
2083
}
2084

2085
static int table_data_requested_width_height(
126,178✔
2086
                Table *table,
2087
                TableData *d,
2088
                size_t available_width,
2089
                size_t *ret_width,
2090
                size_t *ret_height,
2091
                bool *have_soft) {
2092

2093
        _cleanup_free_ char *truncated = NULL;
126,178✔
2094
        bool truncation_applied = false;
126,178✔
2095
        size_t width, height;
126,178✔
2096
        const char *t;
126,178✔
2097
        int r;
126,178✔
2098
        bool soft = false;
126,178✔
2099

2100
        t = table_data_format(table, d, false, available_width, &soft);
126,178✔
2101
        if (!t)
126,178✔
2102
                return -ENOMEM;
2103

2104
        if (table->cell_height_max != SIZE_MAX) {
126,178✔
2105
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
162✔
2106
                if (r < 0)
162✔
2107
                        return r;
2108
                if (r > 0)
162✔
2109
                        truncation_applied = true;
23✔
2110

2111
                t = truncated;
162✔
2112
        }
2113

2114
        r = console_width_height(t, &width, &height);
126,178✔
2115
        if (r < 0)
126,178✔
2116
                return r;
2117

2118
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
126,178✔
2119
                width = d->maximum_width;
×
2120

2121
        if (width < d->minimum_width)
126,178✔
2122
                width = d->minimum_width;
2,430✔
2123

2124
        if (ret_width)
126,178✔
2125
                *ret_width = width;
126,178✔
2126
        if (ret_height)
126,178✔
2127
                *ret_height = height;
126,178✔
2128
        if (have_soft && soft)
126,178✔
2129
                *have_soft = true;
72✔
2130

2131
        return truncation_applied;
126,178✔
2132
}
2133

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

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

2143
        assert(str);
98,656✔
2144
        assert(percent <= 100);
98,656✔
2145

2146
        old_length = strlen(str);
98,656✔
2147

2148
        if (url) {
98,656✔
2149
                r = terminal_urlify(url, str, &clickable);
1,546✔
2150
                if (r < 0)
1,546✔
2151
                        return NULL;
2152

2153
                clickable_length = strlen(clickable);
1,546✔
2154
        } else
2155
                clickable_length = old_length;
2156

2157
        /* Determine current width on screen */
2158
        p = str;
98,656✔
2159
        while (p < str + old_length) {
1,375,903✔
2160
                char32_t c;
1,277,247✔
2161

2162
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,277,247✔
2163
                        p++, w++; /* count invalid chars as 1 */
×
2164
                        continue;
×
2165
                }
2166

2167
                p = utf8_next_char(p);
1,277,247✔
2168
                w += unichar_iswide(c) ? 2 : 1;
2,554,487✔
2169
        }
2170

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

2175
        /* How much spaces shall we add? An how much on the left side? */
2176
        space = new_length - w;
98,656✔
2177
        lspace = space * percent / 100U;
98,656✔
2178

2179
        ret = new(char, space + clickable_length + 1);
98,656✔
2180
        if (!ret)
98,656✔
2181
                return NULL;
2182

2183
        for (size_t i = 0; i < lspace; i++)
602,963✔
2184
                ret[i] = ' ';
504,307✔
2185
        memcpy(ret + lspace, clickable ?: str, clickable_length);
98,656✔
2186
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
2,763,210✔
2187
                ret[i] = ' ';
2,664,554✔
2188

2189
        ret[space + clickable_length] = 0;
98,656✔
2190
        return ret;
98,656✔
2191
}
2192

2193
static bool table_data_isempty(const TableData *d) {
537✔
2194
        assert(d);
537✔
2195

2196
        if (d->type == TABLE_EMPTY)
537✔
2197
                return true;
2198

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

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

2207
static const char* table_data_color(const TableData *d) {
745✔
2208
        assert(d);
745✔
2209

2210
        if (d->color)
745✔
2211
                return d->color;
2212

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

2217
        if (d->type == TABLE_FIELD)
457✔
2218
                return ansi_bright_blue();
×
2219

2220
        return NULL;
2221
}
2222

2223
static const char* table_data_underline(const TableData *d) {
745✔
2224
        assert(d);
745✔
2225

2226
        if (d->underline)
745✔
2227
                return ansi_add_underline_grey();
×
2228

2229
        if (d->type == TABLE_HEADER)
745✔
2230
                return ansi_add_underline();
40✔
2231

2232
        return NULL;
2233
}
2234

2235
static const char* table_data_rgap_underline(const TableData *d) {
131,894✔
2236
        assert(d);
131,894✔
2237

2238
        if (d->rgap_underline)
131,894✔
2239
                return ansi_add_underline_grey();
300✔
2240

2241
        if (d->type == TABLE_HEADER)
131,594✔
2242
                return ansi_add_underline();
6,550✔
2243

2244
        return NULL;
2245
}
2246

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

2255
        assert(t);
2,682✔
2256

2257
        if (!f)
2,682✔
2258
                f = stdout;
2,299✔
2259

2260
        /* Ensure we have no incomplete rows */
2261
        assert(t->n_cells % t->n_columns == 0);
2,682✔
2262

2263
        n_rows = t->n_cells / t->n_columns;
2,682✔
2264
        assert(n_rows > 0); /* at least the header row must be complete */
2,682✔
2265

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

2269
                sorted = new(size_t, n_rows);
180✔
2270
                if (!sorted)
180✔
2271
                        return -ENOMEM;
2272

2273
                for (size_t i = 0; i < n_rows; i++)
3,072✔
2274
                        sorted[i] = i * t->n_columns;
2,892✔
2275

2276
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
180✔
2277
        }
2278

2279
        if (t->display_map)
2,682✔
2280
                display_columns = t->n_display_map;
278✔
2281
        else
2282
                display_columns = t->n_columns;
2,404✔
2283

2284
        assert(display_columns > 0);
2,682✔
2285

2286
        minimum_width = newa(size_t, display_columns);
2,682✔
2287
        maximum_width = newa(size_t, display_columns);
2,682✔
2288
        requested_width = newa(size_t, display_columns);
2,682✔
2289
        column_weight = newa0(uint64_t, display_columns);
2,682✔
2290

2291
        for (size_t j = 0; j < display_columns; j++) {
10,629✔
2292
                minimum_width[j] = 1;
7,947✔
2293
                maximum_width[j] = SIZE_MAX;
7,947✔
2294
        }
2295

2296
        for (unsigned pass = 0; pass < 2; pass++) {
2,691✔
2297
                /* First pass: determine column sizes */
2298

2299
                for (size_t j = 0; j < display_columns; j++)
10,656✔
2300
                        requested_width[j] = SIZE_MAX;
7,965✔
2301

2302
                bool any_soft = false;
2,691✔
2303

2304
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
41,474✔
2305
                        TableData **row;
38,783✔
2306

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

2311
                        for (size_t j = 0; j < display_columns; j++) {
164,961✔
2312
                                TableData *d;
126,178✔
2313
                                size_t req_width, req_height;
126,178✔
2314

2315
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
126,178✔
2316

2317
                                r = table_data_requested_width_height(t, d,
126,246✔
2318
                                                                      width ? width[j] : SIZE_MAX,
68✔
2319
                                                                      &req_width, &req_height, &any_soft);
2320
                                if (r < 0)
126,178✔
2321
                                        return r;
×
2322
                                if (r > 0) { /* Truncated because too many lines? */
126,178✔
2323
                                        _cleanup_free_ char *last = NULL;
23✔
2324
                                        const char *field;
23✔
2325

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

2331
                                        field = table_data_format(t, d, false,
26✔
2332
                                                                  width ? width[j] : SIZE_MAX,
3✔
2333
                                                                  &any_soft);
2334
                                        if (!field)
23✔
2335
                                                return -ENOMEM;
2336

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

2342
                                        req_width = MAX(req_width,
23✔
2343
                                                        utf8_console_width(last) +
2344
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2345
                                }
2346

2347
                                /* Determine the biggest width that any cell in this column would like to have */
2348
                                if (requested_width[j] == SIZE_MAX ||
126,178✔
2349
                                    requested_width[j] < req_width)
118,245✔
2350
                                        requested_width[j] = req_width;
17,913✔
2351

2352
                                /* Determine the minimum width any cell in this column needs */
2353
                                if (minimum_width[j] < d->minimum_width)
126,178✔
2354
                                        minimum_width[j] = d->minimum_width;
22✔
2355

2356
                                /* Determine the maximum width any cell in this column needs */
2357
                                if (d->maximum_width != SIZE_MAX &&
126,178✔
2358
                                    (maximum_width[j] == SIZE_MAX ||
1,412✔
2359
                                     maximum_width[j] > d->maximum_width))
2360
                                        maximum_width[j] = d->maximum_width;
18✔
2361

2362
                                /* Determine the full columns weight */
2363
                                column_weight[j] += d->weight;
126,178✔
2364
                        }
2365
                }
2366

2367
                /* One space between each column */
2368
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
2,691✔
2369

2370
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2371
                weight_sum = 0;
2,691✔
2372
                for (size_t j = 0; j < display_columns; j++) {
10,656✔
2373
                        weight_sum += column_weight[j];
7,965✔
2374

2375
                        table_minimum_width += minimum_width[j];
7,965✔
2376

2377
                        if (maximum_width[j] == SIZE_MAX)
7,965✔
2378
                                table_maximum_width = SIZE_MAX;
2379
                        else
2380
                                table_maximum_width += maximum_width[j];
18✔
2381

2382
                        table_requested_width += requested_width[j];
7,965✔
2383
                }
2384

2385
                /* Calculate effective table width */
2386
                if (t->width != 0 && t->width != SIZE_MAX)
2,691✔
2387
                        table_effective_width = t->width;
2388
                else if (t->width == 0 ||
2,684✔
2389
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
2,615✔
2390
                        table_effective_width = table_requested_width;
2391
                else
2392
                        table_effective_width = MIN(table_requested_width, columns());
19✔
2393

2394
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
2,691✔
2395
                        table_effective_width = table_maximum_width;
×
2396

2397
                if (table_effective_width < table_minimum_width)
2,691✔
2398
                        table_effective_width = table_minimum_width;
2✔
2399

2400
                if (!width)
2,691✔
2401
                        width = newa(size_t, display_columns);
2,682✔
2402

2403
                if (table_effective_width >= table_requested_width) {
2,691✔
2404
                        size_t extra;
2,678✔
2405

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

2409
                        extra = table_effective_width - table_requested_width;
2,678✔
2410

2411
                        for (size_t j = 0; j < display_columns; j++) {
10,607✔
2412
                                size_t delta;
7,929✔
2413

2414
                                if (weight_sum == 0)
7,929✔
2415
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2416
                                else
2417
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
7,897✔
2418

2419
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
7,929✔
2420
                                        width[j] = maximum_width[j];
×
2421

2422
                                if (width[j] < minimum_width[j])
7,929✔
2423
                                        width[j] = minimum_width[j];
×
2424

2425
                                delta = LESS_BY(width[j], requested_width[j]);
7,929✔
2426

2427
                                /* Subtract what we just added from the rest */
2428
                                if (extra > delta)
7,929✔
2429
                                        extra -= delta;
13✔
2430
                                else
2431
                                        extra = 0;
2432

2433
                                assert(weight_sum >= column_weight[j]);
7,929✔
2434
                                weight_sum -= column_weight[j];
7,929✔
2435
                        }
2436

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

2444
                        extra = table_effective_width - table_minimum_width;
13✔
2445

2446
                        for (size_t j = 0; j < display_columns; j++)
49✔
2447
                                width[j] = SIZE_MAX;
36✔
2448

2449
                        for (;;) {
36✔
2450
                                bool restart = false;
36✔
2451

2452
                                for (size_t j = 0; j < display_columns; j++) {
115✔
2453
                                        size_t delta, w;
89✔
2454

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

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

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

2470
                                                w = requested_width[j];
2471
                                                restart = true;
2472

2473
                                        } else if (!finalize)
52✔
2474
                                                continue;
27✔
2475

2476
                                        width[j] = w;
36✔
2477

2478
                                        assert(w >= minimum_width[j]);
36✔
2479
                                        delta = w - minimum_width[j];
36✔
2480

2481
                                        assert(delta <= extra);
36✔
2482
                                        extra -= delta;
36✔
2483

2484
                                        assert(weight_sum >= column_weight[j]);
36✔
2485
                                        weight_sum -= column_weight[j];
36✔
2486

2487
                                        if (restart && !finalize)
36✔
2488
                                                break;
2489
                                }
2490

2491
                                if (finalize)
36✔
2492
                                        break;
2493

2494
                                if (!restart)
23✔
2495
                                        finalize = true;
13✔
2496
                        }
2497

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

2505
        /* Second pass: show output */
2506
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
41,431✔
2507
                size_t n_subline = 0;
38,749✔
2508
                bool more_sublines;
38,749✔
2509
                TableData **row;
38,749✔
2510

2511
                if (sorted)
38,749✔
2512
                        row = t->data + sorted[i];
2,880✔
2513
                else
2514
                        row = t->data + i * t->n_columns;
35,869✔
2515

2516
                do {
41,641✔
2517
                        const char *gap_color = NULL, *gap_underline = NULL;
41,641✔
2518
                        more_sublines = false;
41,641✔
2519

2520
                        for (size_t j = 0; j < display_columns; j++) {
173,535✔
2521
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
131,894✔
2522
                                bool lines_truncated = false;
131,894✔
2523
                                const char *field, *color = NULL, *underline = NULL;
131,894✔
2524
                                TableData *d;
131,894✔
2525
                                size_t l;
131,894✔
2526

2527
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
131,894✔
2528

2529
                                field = table_data_format(t, d, false, width[j], NULL);
131,894✔
2530
                                if (!field)
131,894✔
2531
                                        return -ENOMEM;
2532

2533
                                r = string_extract_line(field, n_subline, &extracted);
131,894✔
2534
                                if (r < 0)
131,894✔
2535
                                        return r;
2536
                                if (r > 0) {
131,894✔
2537
                                        /* There are more lines to come */
2538
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
2,927✔
2539
                                                more_sublines = true; /* There are more lines to come */
2540
                                        else
2541
                                                lines_truncated = true;
23✔
2542
                                }
2543
                                if (extracted)
131,894✔
2544
                                        field = extracted;
7,054✔
2545

2546
                                l = utf8_console_width(field);
131,894✔
2547
                                if (l > width[j]) {
131,894✔
2548
                                        /* Field is wider than allocated space. Let's ellipsize */
2549

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

2555
                                        field = buffer;
2556
                                } else {
2557
                                        if (lines_truncated) {
131,865✔
2558
                                                _cleanup_free_ char *padded = NULL;
23✔
2559

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

2566
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
23✔
2567
                                                if (!padded)
23✔
2568
                                                        return -ENOMEM;
2569

2570
                                                buffer = ellipsize(padded, width[j], 100);
23✔
2571
                                                if (!buffer)
23✔
2572
                                                        return -ENOMEM;
2573

2574
                                                field = buffer;
23✔
2575
                                                l = utf8_console_width(field);
23✔
2576
                                        }
2577

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

2582
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
98,656✔
2583
                                                if (!aligned)
98,656✔
2584
                                                        return -ENOMEM;
×
2585

2586
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2587
                                                if (j == display_columns - 1 &&
135,437✔
2588
                                                    (!colors_enabled() || !table_data_color(d)) &&
36,833✔
2589
                                                    (!underline_enabled() || !table_data_underline(d)) &&
73,612✔
2590
                                                    (!urlify_enabled() || !d->url))
36,829✔
2591
                                                        delete_trailing_chars(aligned, NULL);
36,779✔
2592

2593
                                                free_and_replace(buffer, aligned);
98,656✔
2594
                                                field = buffer;
98,656✔
2595
                                        }
2596
                                }
2597

2598
                                if (l >= width[j] && d->url) {
131,894✔
2599
                                        _cleanup_free_ char *clickable = NULL;
×
2600

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

2605
                                        free_and_replace(buffer, clickable);
22✔
2606
                                        field = buffer;
22✔
2607
                                }
2608

2609
                                if (colors_enabled() && gap_color)
131,894✔
2610
                                        fputs(gap_color, f);
×
2611
                                if (underline_enabled() && gap_underline)
131,894✔
2612
                                        fputs(gap_underline, f);
16✔
2613

2614
                                if (j > 0)
131,894✔
2615
                                        fputc(' ', f); /* column separator left of cell */
90,253✔
2616

2617
                                /* Undo gap color/underline */
2618
                                if ((colors_enabled() && gap_color) ||
263,788✔
2619
                                    (underline_enabled() && gap_underline))
132,587✔
2620
                                        fputs(ANSI_NORMAL, f);
16✔
2621

2622
                                if (colors_enabled()) {
131,894✔
2623
                                        color = table_data_color(d);
693✔
2624
                                        if (color)
693✔
2625
                                                fputs(color, f);
288✔
2626
                                }
2627

2628
                                if (underline_enabled()) {
131,894✔
2629
                                        underline = table_data_underline(d);
693✔
2630
                                        if (underline)
693✔
2631
                                                fputs(underline, f);
18✔
2632
                                }
2633

2634
                                fputs(field, f);
131,894✔
2635

2636
                                if (color || underline)
131,894✔
2637
                                        fputs(ANSI_NORMAL, f);
306✔
2638

2639
                                gap_color = d->rgap_color;
131,894✔
2640
                                gap_underline = table_data_rgap_underline(d);
131,894✔
2641
                        }
2642

2643
                        fputc('\n', f);
41,641✔
2644
                        n_subline++;
41,641✔
2645
                } while (more_sublines);
41,641✔
2646
        }
2647

2648
        return fflush_and_check(f);
2,682✔
2649
}
2650

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

2656
        assert(t);
39✔
2657
        assert(ret);
39✔
2658

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

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

2667
        return memstream_finalize(&m, ret, NULL);
39✔
2668
}
2669

2670
size_t table_get_rows(Table *t) {
1,710✔
2671
        if (!t)
1,710✔
2672
                return 0;
2673

2674
        assert(t->n_columns > 0);
1,710✔
2675
        return t->n_cells / t->n_columns;
1,710✔
2676
}
2677

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

2682
        assert(t->n_columns > 0);
34✔
2683
        return t->n_columns;
2684
}
2685

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

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

2694
int table_set_reverse(Table *t, size_t column, bool b) {
80✔
2695
        assert(t);
80✔
2696
        assert(column < t->n_columns);
80✔
2697

2698
        if (!t->reverse_map) {
80✔
2699
                if (!b)
80✔
2700
                        return 0;
2701

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

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

2711
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,010✔
2712
        size_t i;
8,010✔
2713

2714
        assert(t);
8,010✔
2715

2716
        if (column >= t->n_columns)
8,010✔
2717
                return NULL;
2718

2719
        i = row * t->n_columns + column;
8,010✔
2720
        if (i >= t->n_cells)
8,010✔
2721
                return NULL;
2722

2723
        return TABLE_INDEX_TO_CELL(i);
8,010✔
2724
}
2725

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

2729
        assert(t);
4,374✔
2730

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

2735
        return d->data;
4,374✔
2736
}
2737

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

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

2745
        return table_get(t, cell);
4,374✔
2746
}
2747

2748
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
3,963✔
2749

2750
        switch (d->type) {
3,963✔
2751

2752
        case TABLE_EMPTY:
319✔
2753
                return sd_json_variant_new_null(ret);
319✔
2754

2755
        case TABLE_STRING:
2,012✔
2756
        case TABLE_PATH:
2757
        case TABLE_PATH_BASENAME:
2758
        case TABLE_FIELD:
2759
        case TABLE_HEADER:
2760
                return sd_json_variant_new_string(ret, d->string);
2,012✔
2761

2762
        case TABLE_STRV:
3✔
2763
        case TABLE_STRV_WRAPPED:
2764
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2765

2766
        case TABLE_BOOLEAN_CHECKMARK:
150✔
2767
        case TABLE_BOOLEAN:
2768
                return sd_json_variant_new_boolean(ret, d->boolean);
150✔
2769

2770
        case TABLE_TIMESTAMP:
153✔
2771
        case TABLE_TIMESTAMP_UTC:
2772
        case TABLE_TIMESTAMP_RELATIVE:
2773
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2774
        case TABLE_TIMESTAMP_LEFT:
2775
        case TABLE_TIMESTAMP_DATE:
2776
                if (d->timestamp == USEC_INFINITY)
153✔
2777
                        return sd_json_variant_new_null(ret);
×
2778

2779
                return sd_json_variant_new_unsigned(ret, d->timestamp);
153✔
2780

2781
        case TABLE_TIMESPAN:
×
2782
        case TABLE_TIMESPAN_MSEC:
2783
        case TABLE_TIMESPAN_DAY:
2784
                if (d->timespan == USEC_INFINITY)
×
2785
                        return sd_json_variant_new_null(ret);
×
2786

2787
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2788

2789
        case TABLE_SIZE:
36✔
2790
        case TABLE_BPS:
2791
                if (d->size == UINT64_MAX)
36✔
2792
                        return sd_json_variant_new_null(ret);
16✔
2793

2794
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2795

2796
        case TABLE_INT:
27✔
2797
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2798

2799
        case TABLE_INT8:
6✔
2800
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2801

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

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

2808
        case TABLE_INT64:
45✔
2809
                return sd_json_variant_new_integer(ret, d->int64);
45✔
2810

2811
        case TABLE_UINT:
15✔
2812
                return sd_json_variant_new_unsigned(ret, d->uint_val);
15✔
2813

2814
        case TABLE_UINT8:
4✔
2815
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2816

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

2820
        case TABLE_UINT32:
107✔
2821
        case TABLE_UINT32_HEX:
2822
                return sd_json_variant_new_unsigned(ret, d->uint32);
107✔
2823

2824
        case TABLE_UINT64:
385✔
2825
        case TABLE_UINT64_HEX:
2826
                return sd_json_variant_new_unsigned(ret, d->uint64);
385✔
2827

2828
        case TABLE_PERCENT:
×
2829
                return sd_json_variant_new_integer(ret, d->percent);
×
2830

2831
        case TABLE_IFINDEX:
×
2832
                if (d->ifindex <= 0)
×
2833
                        return sd_json_variant_new_null(ret);
×
2834

2835
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2836

2837
        case TABLE_IN_ADDR:
2838
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2839

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

2843
        case TABLE_ID128:
501✔
2844
                return sd_json_variant_new_id128(ret, d->id128);
501✔
2845

2846
        case TABLE_UUID:
91✔
2847
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2848

2849
        case TABLE_UID:
8✔
2850
                if (!uid_is_valid(d->uid))
8✔
2851
                        return sd_json_variant_new_null(ret);
×
2852

2853
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2854

2855
        case TABLE_GID:
8✔
2856
                if (!gid_is_valid(d->gid))
8✔
2857
                        return sd_json_variant_new_null(ret);
×
2858

2859
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2860

2861
        case TABLE_PID:
14✔
2862
                if (!pid_is_valid(d->pid))
14✔
2863
                        return sd_json_variant_new_null(ret);
×
2864

2865
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2866

2867
        case TABLE_SIGNAL:
8✔
2868
                if (!SIGNAL_VALID(d->int_val))
8✔
2869
                        return sd_json_variant_new_null(ret);
×
2870

2871
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2872

2873
        case TABLE_MODE:
51✔
2874
        case TABLE_MODE_INODE_TYPE:
2875
                if (d->mode == MODE_INVALID)
51✔
2876
                        return sd_json_variant_new_null(ret);
×
2877

2878
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2879

2880
        case TABLE_DEVNUM:
4✔
2881
                if (devnum_is_zero(d->devnum))
4✔
2882
                        return sd_json_variant_new_null(ret);
2✔
2883

2884
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2885
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2886
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2887

2888
        default:
2889
                return -EINVAL;
2890
        }
2891
}
2892

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

2902
        bool new_word = true;
710✔
2903
        char *c;
710✔
2904

2905
        assert(str);
710✔
2906

2907
        c = strdup(str);
710✔
2908
        if (!c)
710✔
2909
                return NULL;
2910

2911
        for (char *x = c; *x; x++) {
5,315✔
2912
                if (!strchr(ALPHANUMERICAL, *x)) {
4,605✔
2913
                        *x = '_';
176✔
2914
                        new_word = true;
176✔
2915
                        continue;
176✔
2916
                }
2917

2918
                if (new_word) {
4,429✔
2919
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
881✔
2920
                                                                  * then we assume the whole word is all-caps
2921
                                                                  * and avoid lowercasing it. */
2922
                                *x = ascii_tolower(*x);
880✔
2923
                        new_word = false;
2924
                }
2925
        }
2926

2927
        return c;
2928
}
2929

2930
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
696✔
2931
        _cleanup_free_ char *mangled = NULL;
1,392✔
2932
        const char *n;
696✔
2933

2934
        assert(t);
696✔
2935
        assert(d);
696✔
2936
        assert(ret);
696✔
2937

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

2946
        mangled = table_mangle_to_json_field_name(n);
696✔
2947
        if (!mangled)
696✔
2948
                return -ENOMEM;
2949

2950
        *ret = TAKE_PTR(mangled);
696✔
2951
        return 0;
696✔
2952
}
2953

2954
static const char *table_get_json_field_name(Table *t, size_t idx) {
737✔
2955
        assert(t);
737✔
2956

2957
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
737✔
2958
}
2959

2960
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
89✔
2961
        sd_json_variant **rows = NULL, **elements = NULL;
89✔
2962
        _cleanup_free_ size_t *sorted = NULL;
89✔
2963
        size_t n_rows, display_columns;
89✔
2964
        int r;
89✔
2965

2966
        assert(t);
89✔
2967
        assert(!t->vertical);
89✔
2968

2969
        /* Ensure we have no incomplete rows */
2970
        assert(t->n_columns > 0);
89✔
2971
        assert(t->n_cells % t->n_columns == 0);
89✔
2972

2973
        n_rows = t->n_cells / t->n_columns;
89✔
2974
        assert(n_rows > 0); /* at least the header row must be complete */
89✔
2975

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

2979
                sorted = new(size_t, n_rows);
25✔
2980
                if (!sorted)
25✔
2981
                        return -ENOMEM;
2982

2983
                for (size_t i = 0; i < n_rows; i++)
126✔
2984
                        sorted[i] = i * t->n_columns;
101✔
2985

2986
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
25✔
2987
        }
2988

2989
        if (t->display_map)
89✔
2990
                display_columns = t->n_display_map;
43✔
2991
        else
2992
                display_columns = t->n_columns;
46✔
2993
        assert(display_columns > 0);
89✔
2994

2995
        elements = new0(sd_json_variant*, display_columns * 2);
89✔
2996
        if (!elements)
89✔
2997
                return -ENOMEM;
2998

2999
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
89✔
3000

3001
        for (size_t j = 0; j < display_columns; j++) {
822✔
3002
                _cleanup_free_ char *mangled = NULL;
733✔
3003
                const char *n;
733✔
3004
                size_t c;
733✔
3005

3006
                c = t->display_map ? t->display_map[j] : j;
733✔
3007

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

3015
                        n = mangled;
694✔
3016
                }
3017

3018
                r = sd_json_variant_new_string(elements + j*2, n);
733✔
3019
                if (r < 0)
733✔
3020
                        return r;
3021
        }
3022

3023
        rows = new0(sd_json_variant*, n_rows-1);
89✔
3024
        if (!rows)
89✔
3025
                return -ENOMEM;
3026

3027
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
89✔
3028

3029
        for (size_t i = 1; i < n_rows; i++) {
998✔
3030
                TableData **row;
909✔
3031

3032
                if (sorted)
909✔
3033
                        row = t->data + sorted[i];
76✔
3034
                else
3035
                        row = t->data + i * t->n_columns;
833✔
3036

3037
                for (size_t j = 0; j < display_columns; j++) {
4,868✔
3038
                        TableData *d;
3,959✔
3039
                        size_t k;
3,959✔
3040

3041
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
3,959✔
3042

3043
                        k = j*2+1;
3,959✔
3044
                        elements[k] = sd_json_variant_unref(elements[k]);
3,959✔
3045

3046
                        r = table_data_to_json(d, elements + k);
3,959✔
3047
                        if (r < 0)
3,959✔
3048
                                return r;
3049
                }
3050

3051
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
909✔
3052
                if (r < 0)
909✔
3053
                        return r;
3054
        }
3055

3056
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
89✔
3057
}
3058

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

3064
        assert(t);
1✔
3065
        assert(t->vertical);
1✔
3066

3067
        if (t->n_columns != 2)
1✔
3068
                return -EINVAL;
1✔
3069

3070
        /* Ensure we have no incomplete rows */
3071
        assert(t->n_cells % t->n_columns == 0);
1✔
3072

3073
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3074
        if (!elements)
1✔
3075
                return -ENOMEM;
3076

3077
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3078

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

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

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

3091
                                n = mangled;
2✔
3092
                        }
3093

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

3100
                n_elements++;
8✔
3101
        }
3102

3103
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3104
}
3105

3106
int table_to_json(Table *t, sd_json_variant **ret) {
90✔
3107
        assert(t);
90✔
3108

3109
        if (t->vertical)
90✔
3110
                return table_to_json_vertical(t, ret);
1✔
3111

3112
        return table_to_json_regular(t, ret);
89✔
3113
}
3114

3115
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
395✔
3116
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
395✔
3117
        int r;
395✔
3118

3119
        assert(t);
395✔
3120

3121
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
395✔
3122
                return table_print(t, f);
332✔
3123

3124
        if (!f)
63✔
3125
                f = stdout;
2✔
3126

3127
        r = table_to_json(t, &v);
63✔
3128
        if (r < 0)
63✔
3129
                return r;
3130

3131
        sd_json_variant_dump(v, flags, f, NULL);
63✔
3132

3133
        return fflush_and_check(f);
63✔
3134
}
3135

3136
int table_print_with_pager(
393✔
3137
                Table *t,
3138
                sd_json_format_flags_t json_format_flags,
3139
                PagerFlags pager_flags,
3140
                bool show_header) {
3141

3142
        bool saved_header;
393✔
3143
        int r;
393✔
3144

3145
        assert(t);
393✔
3146

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

3150
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
393✔
3151
                pager_open(pager_flags);
368✔
3152

3153
        saved_header = t->header;
393✔
3154
        t->header = show_header;
393✔
3155
        r = table_print_json(t, stdout, json_format_flags);
393✔
3156
        t->header = saved_header;
393✔
3157
        if (r < 0)
393✔
3158
                return table_log_print_error(r);
1✔
3159

3160
        return 0;
3161
}
3162

3163
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
379✔
3164
        int r;
379✔
3165

3166
        assert(t);
379✔
3167

3168
        if (name) {
379✔
3169
                size_t m;
377✔
3170

3171
                m = MAX(idx + 1, t->n_json_fields);
377✔
3172
                if (!GREEDY_REALLOC0(t->json_fields, m))
377✔
3173
                        return -ENOMEM;
3174

3175
                r = free_and_strdup(t->json_fields + idx, name);
377✔
3176
                if (r < 0)
377✔
3177
                        return r;
3178

3179
                t->n_json_fields = m;
377✔
3180
                return r;
377✔
3181
        } else {
3182
                if (idx >= t->n_json_fields)
2✔
3183
                        return 0;
3184

3185
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3186
                return 1;
2✔
3187
        }
3188
}
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