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

systemd / systemd / 23990547145

04 Apr 2026 09:30PM UTC coverage: 72.373% (+0.3%) from 72.107%
23990547145

push

github

web-flow
shutdown: enforce a minimum uptime to make boot loops less annoying (#41215)

Fixes: #9453

Split out of #41016

3 of 39 new or added lines in 2 files covered. (7.69%)

2565 existing lines in 66 files now uncovered.

319531 of 441505 relevant lines covered (72.37%)

1187721.39 hits per line

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

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

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

6
#include "sd-id128.h"
7

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

35
#define DEFAULT_WEIGHT 100
36

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

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

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

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

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

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

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

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

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

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

82
        const char *color;          /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
83
        const char *rgap_color;     /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */
84
        char *url;                  /* A URL to use for a clickable hyperlink */
85
        char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
86

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

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

122
        assert(cell);
407,784✔
123

124
        i = PTR_TO_SIZE(cell);
407,784✔
125
        assert(i > 0);
407,784✔
126

127
        return i-1;
407,784✔
128
}
129

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

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

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

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

144
        size_t width;  /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
145
                        * width or less wide, but not wider. Otherwise the width to format this table in. */
146
        size_t cell_height_max; /* Maximum number of lines per cell. (If there are more, ellipsis is shown. If SIZE_MAX then no limit is set, the default. == 0 is not allowed.) */
147

148
        TableData **data;
149

150
        size_t *display_map;  /* List of columns to show (by their index). It's fine if columns are listed multiple times or not at all */
151
        size_t n_display_map;
152

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

156
        char **json_fields;
157
        size_t n_json_fields;
158

159
        bool *reverse_map;
160
};
161

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

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

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

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

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

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

188
        assert(first_header);
848✔
189

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

195
                n_columns++;
6,034✔
196
        }
197
        va_end(ap);
848✔
198

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

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

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

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

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

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

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

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

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

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

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

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

245
static TableData* table_data_free(TableData *d) {
202,627✔
246
        assert(d);
202,627✔
247

248
        free(d->formatted);
202,627✔
249
        free(d->url);
202,627✔
250

251
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
202,627✔
252
                strv_free(d->strv);
6,433✔
253

254
        if (d->type == TABLE_JSON)
202,627✔
255
                sd_json_variant_unref(d->json);
9,296✔
256

257
        return mfree(d);
202,627✔
258
}
259

260
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
365,681✔
261
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
230,007✔
262

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

267
        for (size_t i = 0; i < t->n_cells; i++)
233,856✔
268
                table_data_unref(t->data[i]);
230,019✔
269

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

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

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

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

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

285
        switch (type) {
568,397✔
286

287
        case TABLE_EMPTY:
288
                return 0;
289

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

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

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

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

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

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

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

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

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

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

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

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

357
        case TABLE_UID:
1,381✔
358
                return sizeof(uid_t);
1,381✔
359
        case TABLE_GID:
1,890✔
360
                return sizeof(gid_t);
1,890✔
361
        case TABLE_PID:
578✔
362
                return sizeof(pid_t);
578✔
363

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

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

371
        case TABLE_JSON:
27,856✔
372
                return sizeof(sd_json_variant*);
27,856✔
373

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

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

390
        size_t k, l;
216,769✔
391
        assert(d);
216,769✔
392

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

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

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

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

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

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

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

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

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

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

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

438
        _cleanup_free_ TableData *d = NULL;
202,627✔
439
        size_t data_size;
202,627✔
440

441
        data_size = table_data_size(type, data);
202,627✔
442

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

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

456
        switch (type) {
202,627✔
457

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

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

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

473
        return TAKE_PTR(d);
474
}
475

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

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

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

495
        /* Special rule: patch NULL data fields to the empty field */
496
        if (!data)
230,007✔
497
                dt = TABLE_EMPTY;
18,342✔
498

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

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

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

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

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

518
        assert(align_percent <= 100);
230,007✔
519
        assert(ellipsize_percent <= 100);
230,007✔
520

521
        uppercase = dt == TABLE_HEADER;
230,007✔
522

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

526
        if (p && table_data_matches(p, dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
230,007✔
527
                d = table_data_ref(p);
81,515✔
528
        else {
529
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
148,492✔
530
                if (!d)
148,492✔
531
                        return -ENOMEM;
532
        }
533

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

537
        if (ret_cell)
230,007✔
538
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
205,888✔
539

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

542
        return 0;
230,007✔
543
}
544

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

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

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

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

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

565
        assert(t);
53✔
566

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

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

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

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

580
        return 0;
581
}
582

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

586
        assert(t);
12✔
587

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

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

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

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

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

606
        assert(t);
201,102✔
607

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

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

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

619
        assert(od->n_ref > 1);
53,012✔
620

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

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

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

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

648
        assert(nd->n_ref == 1);
53,012✔
649

650
        return 1;
651
}
652

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

656
        assert(t);
205,540✔
657
        assert(cell);
205,540✔
658

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

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

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

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

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

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

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

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

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

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

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

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

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

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

705
        assert(t);
14,393✔
706
        assert(cell);
14,393✔
707

708
        if (weight == UINT_MAX)
14,393✔
709
                weight = DEFAULT_WEIGHT;
×
710

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

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

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

722
        assert(t);
14,022✔
723
        assert(cell);
14,022✔
724

725
        if (percent == UINT_MAX)
14,022✔
726
                percent = 0;
727

728
        assert(percent <= 100);
14,022✔
729

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

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

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

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

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

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

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

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

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

760
        assert(t);
55,099✔
761
        assert(cell);
55,099✔
762

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

767
        table_get_data(t, cell)->color = empty_to_null(color);
70,414✔
768
        return 0;
55,099✔
769
}
770

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

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

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

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

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

789
        assert(t);
53,396✔
790
        assert(cell);
53,396✔
791

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

796
        assert_se(d = table_get_data(t, cell));
53,396✔
797

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

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

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

809
        assert(t);
53,396✔
810
        assert(cell);
53,396✔
811

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

816
        assert_se(d = table_get_data(t, cell));
53,396✔
817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

907
        return 0;
1,123✔
908
}
909

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

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

919
        va_start(ap, first_type);
54,235✔
920

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

948
                switch (type) {
382,503✔
949

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1290
        assert(t);
409✔
1291

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

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

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

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

1304
        return 0;
409✔
1305
}
1306

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

1311
        assert(t);
31✔
1312

1313
        column = first_column;
31✔
1314

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

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

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

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

1330
        }
1331
        va_end(ap);
31✔
1332

1333
        return 0;
31✔
1334
}
1335

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

1340
        assert(t);
280✔
1341

1342
        column = first_column;
280✔
1343

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

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

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

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

1361
        return 0;
280✔
1362
}
1363

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

1368
        assert(t);
1,023✔
1369

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

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

1381
                va_start(ap, t);
12,399✔
1382
                for (;;) {
24,239✔
1383
                        size_t column;
24,239✔
1384

1385
                        column = va_arg(ap, size_t);
24,239✔
1386
                        if (column == SIZE_MAX)
24,239✔
1387
                                break;
1388
                        if (column == *i) {
12,912✔
1389
                                listed = true;
1390
                                break;
1391
                        }
1392
                }
1393
                va_end(ap);
12,399✔
1394

1395
                if (listed)
12,399✔
1396
                        continue;
1,072✔
1397

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

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

1403
        return 0;
1,023✔
1404
}
1405

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

1409
        assert(a);
81,324✔
1410
        assert(b);
81,324✔
1411

1412
        if (a->type == b->type) {
81,324✔
1413

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

1417
                switch (a->type) {
81,318✔
1418

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1546
        /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we
1547
           just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */
1548
        POINTER_MAY_BE_NULL(a);
50,483✔
1549
        POINTER_MAY_BE_NULL(b);
50,483✔
1550
        assert(t);
50,483✔
1551
        assert(t->sort_map);
50,483✔
1552

1553
        /* Make sure the header stays at the beginning */
1554
        if (*a < t->n_columns && *b < t->n_columns)
50,483✔
1555
                return 0;
1556
        if (*a < t->n_columns)
50,483✔
1557
                return -1;
1558
        if (*b < t->n_columns)
49,861✔
1559
                return 1;
1560

1561
        /* Order other lines by the sorting map */
1562
        for (size_t i = 0; i < t->n_sort_map; i++) {
81,333✔
1563
                TableData *d, *dd;
81,324✔
1564

1565
                d = t->data[*a + t->sort_map[i]];
81,324✔
1566
                dd = t->data[*b + t->sort_map[i]];
81,324✔
1567

1568
                r = cell_data_compare(d, *a, dd, *b);
81,324✔
1569
                if (r != 0)
81,324✔
1570
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
49,852✔
1571
        }
1572

1573
        /* Order identical lines by the order there were originally added in */
1574
        return CMP(*a, *b);
9✔
1575
}
1576

1577
static char* format_strv_width(char **strv, size_t column_width) {
446✔
1578
        _cleanup_(memstream_done) MemStream m = {};
446✔
1579
        FILE *f;
446✔
1580

1581
        f = memstream_init(&m);
446✔
1582
        if (!f)
446✔
1583
                return NULL;
1584

1585
        size_t position = 0;
1586
        STRV_FOREACH(p, strv) {
2,104✔
1587
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
1,658✔
1588
                                                          * If that happens, we'll just print one item per line. */
1589

1590
                if (position == 0) {
1,658✔
1591
                        fputs(*p, f);
446✔
1592
                        position = our_len;
1593
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
2,424✔
1594
                        fprintf(f, " %s", *p);
1,146✔
1595
                        position = size_add(size_add(position, 1), our_len);
2,804✔
1596
                } else {
1597
                        fprintf(f, "\n%s", *p);
66✔
1598
                        position = our_len;
1599
                }
1600
        }
1601

1602
        char *buf;
446✔
1603
        if (memstream_finalize(&m, &buf, NULL) < 0)
446✔
1604
                return NULL;
1605

1606
        return buf;
446✔
1607
}
1608

1609
static const char* table_data_format(
399,435✔
1610
                Table *t,
1611
                TableData *d,
1612
                bool avoid_uppercasing,
1613
                size_t column_width,
1614
                bool *have_soft) {
1615

1616
        assert(d);
399,435✔
1617

1618
        if (d->formatted &&
399,435✔
1619
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1620
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
75,819✔
1621
                return d->formatted;
1622

1623
        d->formatted = mfree(d->formatted);
323,909✔
1624

1625
        switch (d->type) {
323,909✔
1626
        case TABLE_EMPTY:
25,944✔
1627
                return table_ersatz_string(t);
25,944✔
1628

1629
        case TABLE_STRING:
258,188✔
1630
        case TABLE_STRING_WITH_ANSI:
1631
        case TABLE_PATH:
1632
        case TABLE_PATH_BASENAME:
1633
        case TABLE_FIELD:
1634
        case TABLE_HEADER:
1635
        case TABLE_VERSION: {
1636
                _cleanup_free_ char *bn = NULL;
258,188✔
1637
                const char *s;
258,188✔
1638

1639
                if (d->type == TABLE_PATH_BASENAME)
258,188✔
1640
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
451✔
1641
                else
1642
                        s = d->string;
257,737✔
1643

1644
                if (d->uppercase && !avoid_uppercasing) {
258,188✔
1645
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
4,589✔
1646
                        if (!d->formatted)
4,589✔
1647
                                return NULL;
1648

1649
                        char *q = d->formatted;
1650
                        for (const char *p = s; *p; p++)
32,085✔
1651
                                *(q++) = (char) toupper((unsigned char) *p);
27,496✔
1652

1653
                        if (d->type == TABLE_FIELD)
4,589✔
1654
                                *(q++) = ':';
×
1655

1656
                        *q = 0;
4,589✔
1657
                        return d->formatted;
4,589✔
1658
                }
1659

1660
                if (d->type == TABLE_FIELD)
253,599✔
1661
                        return (d->formatted = strjoin(s, ":"));
33,087✔
1662

1663
                if (bn)
220,512✔
1664
                        return (d->formatted = TAKE_PTR(bn));
449✔
1665

1666
                return d->string;
220,063✔
1667
        }
1668

1669
        case TABLE_STRV:
6,277✔
1670
                if (strv_isempty(d->strv))
6,277✔
1671
                        return table_ersatz_string(t);
×
1672

1673
                return (d->formatted = strv_join(d->strv, "\n"));
6,277✔
1674

1675
        case TABLE_STRV_WRAPPED:
446✔
1676
                if (strv_isempty(d->strv))
446✔
1677
                        return table_ersatz_string(t);
×
1678

1679
                d->formatted = format_strv_width(d->strv, column_width);
446✔
1680
                if (!d->formatted)
446✔
1681
                        return NULL;
1682

1683
                d->formatted_for_width = column_width;
446✔
1684
                if (have_soft)
446✔
1685
                        *have_soft = true;
284✔
1686
                return d->formatted;
446✔
1687

1688
        case TABLE_BOOLEAN:
4,795✔
1689
                return yes_no(d->boolean);
401,388✔
1690

1691
        case TABLE_BOOLEAN_CHECKMARK:
7,332✔
1692
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
10,556✔
1693

1694
        case TABLE_TIMESTAMP:
2,117✔
1695
        case TABLE_TIMESTAMP_UTC:
1696
        case TABLE_TIMESTAMP_RELATIVE:
1697
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1698
        case TABLE_TIMESTAMP_LEFT:
1699
        case TABLE_TIMESTAMP_DATE: {
1700
                char *ret;
2,117✔
1701

1702
                _cleanup_free_ char *p = new(
4,234✔
1703
                                char,
1704
                                IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1705
                                        FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1706
                if (!p)
2,117✔
1707
                        return NULL;
1708

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

1724
                return (d->formatted = TAKE_PTR(p));
1,965✔
1725
        }
1726

1727
        case TABLE_TIMESPAN:
1728
        case TABLE_TIMESPAN_MSEC:
1729
        case TABLE_TIMESPAN_DAY: {
1730
                _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX);
3,014✔
1731
                if (!p)
1,507✔
1732
                        return NULL;
1733

1734
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
3,007✔
1735
                                     d->type == TABLE_TIMESPAN ? 0 :
1,500✔
1736
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1737
                        return "-";
1738

1739
                return (d->formatted = TAKE_PTR(p));
1,507✔
1740
        }
1741

1742
        case TABLE_SIZE: {
1743
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX);
728✔
1744
                if (!p)
364✔
1745
                        return NULL;
1746

1747
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
364✔
1748
                        return table_ersatz_string(t);
238✔
1749

1750
                return (d->formatted = TAKE_PTR(p));
126✔
1751
        }
1752

1753
        case TABLE_BPS: {
1754
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2);
824✔
1755
                if (!p)
412✔
1756
                        return NULL;
1757

1758
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
412✔
1759
                        return table_ersatz_string(t);
×
1760

1761
                size_t n = strlen(p);
412✔
1762
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
412✔
1763

1764
                return (d->formatted = TAKE_PTR(p));
412✔
1765
        }
1766

1767
        case TABLE_INT:
260✔
1768
                return (d->formatted = asprintf_safe("%i", d->int_val));
260✔
1769

1770
        case TABLE_INT8:
3✔
1771
                return (d->formatted = asprintf_safe("%" PRIi8, d->int8));
3✔
1772

1773
        case TABLE_INT16:
3✔
1774
                return (d->formatted = asprintf_safe("%" PRIi16, d->int16));
3✔
1775

1776
        case TABLE_INT32:
3✔
1777
                return (d->formatted = asprintf_safe("%" PRIi32, d->int32));
3✔
1778

1779
        case TABLE_INT64:
155✔
1780
                return (d->formatted = asprintf_safe("%" PRIi64, d->int64));
155✔
1781

1782
        case TABLE_UINT:
285✔
1783
                return (d->formatted = asprintf_safe("%u", d->uint_val));
285✔
1784

1785
        case TABLE_UINT8:
43✔
1786
                return (d->formatted = asprintf_safe("%" PRIu8, d->uint8));
43✔
1787

1788
        case TABLE_UINT16:
90✔
1789
                return (d->formatted = asprintf_safe("%" PRIu16, d->uint16));
90✔
1790

1791
        case TABLE_UINT32:
1,204✔
1792
                return (d->formatted = asprintf_safe("%" PRIu32, d->uint32));
1,204✔
1793

1794
        case TABLE_UINT32_HEX:
2✔
1795
                return (d->formatted = asprintf_safe("%" PRIx32, d->uint32));
2✔
1796

1797
        case TABLE_UINT32_HEX_0x:
7✔
1798
                return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32));
7✔
1799

1800
        case TABLE_UINT64:
1,654✔
1801
                return (d->formatted = asprintf_safe("%" PRIu64, d->uint64));
1,654✔
1802

1803
        case TABLE_UINT64_HEX:
50✔
1804
                return (d->formatted = asprintf_safe("%" PRIx64, d->uint64));
50✔
1805

1806
        case TABLE_UINT64_HEX_0x:
×
1807
                return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64));
×
1808

1809
        case TABLE_PERCENT:
2✔
1810
                return (d->formatted = asprintf_safe("%i%%" , d->percent));
2✔
1811

1812
        case TABLE_IFINDEX:
227✔
1813
                (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted);
227✔
1814
                return d->formatted;
227✔
1815

1816
        case TABLE_IN_ADDR:
205✔
1817
        case TABLE_IN6_ADDR:
1818
                (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1819
                                         &d->address,
205✔
1820
                                         &d->formatted);
1821
                return d->formatted;
205✔
1822

1823
        case TABLE_ID128:
1824
                d->formatted = new(char, SD_ID128_STRING_MAX);
1,097✔
1825
                if (!d->formatted)
1,097✔
1826
                        return NULL;
1827

1828
                return sd_id128_to_string(d->id128, d->formatted);
1,097✔
1829

1830
        case TABLE_UUID:
1831
                d->formatted = new(char, SD_ID128_UUID_STRING_MAX);
448✔
1832
                if (!d->formatted)
448✔
1833
                        return NULL;
1834

1835
                return sd_id128_to_uuid_string(d->id128, d->formatted);
448✔
1836

1837
        case TABLE_UID:
433✔
1838
                if (!uid_is_valid(d->uid))
433✔
1839
                        return table_ersatz_string(t);
×
1840

1841
                return (d->formatted = asprintf_safe(UID_FMT, d->uid));
433✔
1842

1843
        case TABLE_GID:
622✔
1844
                if (!gid_is_valid(d->gid))
622✔
1845
                        return table_ersatz_string(t);
×
1846

1847
                return (d->formatted = asprintf_safe(GID_FMT, d->gid));
622✔
1848

1849
        case TABLE_PID:
214✔
1850
                if (!pid_is_valid(d->pid))
214✔
1851
                        return table_ersatz_string(t);
×
1852

1853
                return (d->formatted = asprintf_safe(PID_FMT, d->pid));
214✔
1854

1855
        case TABLE_SIGNAL: {
123✔
1856
                const char *suffix = signal_to_string(d->int_val);
123✔
1857
                if (!suffix)
123✔
1858
                        return table_ersatz_string(t);
×
1859

1860
                return (d->formatted = strjoin("SIG", suffix));
123✔
1861
        }
1862

1863
        case TABLE_MODE:
96✔
1864
                if (d->mode == MODE_INVALID)
96✔
1865
                        return table_ersatz_string(t);
×
1866

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

1869
        case TABLE_MODE_INODE_TYPE:
2✔
1870
                if (d->mode == MODE_INVALID)
2✔
1871
                        return table_ersatz_string(t);
×
1872

1873
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
1874

1875
        case TABLE_DEVNUM:
3✔
1876
                if (devnum_is_zero(d->devnum))
3✔
1877
                        return table_ersatz_string(t);
2✔
1878

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

1881
        case TABLE_JSON:
9,296✔
1882
                if (!d->json)
9,296✔
1883
                        return table_ersatz_string(t);
×
1884

1885
                (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted);
9,296✔
1886
                return d->formatted;
9,296✔
1887

1888
        default:
×
1889
                assert_not_reached();
×
1890
        }
1891
}
1892

1893
static const char* table_data_format_strip_ansi(
398,653✔
1894
                Table *t,
1895
                TableData *d,
1896
                bool avoid_uppercasing,
1897
                size_t column_width,
1898
                bool *have_soft,
1899
                char **ret_buffer) {
1900

1901
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
1902

1903
        assert(ret_buffer);
398,653✔
1904

1905
        const char *c;
398,653✔
1906

1907
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
398,653✔
1908
        if (!c)
398,653✔
1909
                return NULL;
398,653✔
1910

1911
        if (d->type != TABLE_STRING_WITH_ANSI) {
398,653✔
1912
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
1913
                 * original string as-is */
1914
                *ret_buffer = NULL;
398,638✔
1915
                return c;
398,638✔
1916
        }
1917

1918
        _cleanup_free_ char *s = strdup(c);
15✔
1919
        if (!s)
15✔
1920
                return NULL;
1921

1922
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
1923
                return NULL;
1924

1925
        *ret_buffer = TAKE_PTR(s);
15✔
1926
        return *ret_buffer;
15✔
1927
}
1928

1929
static int console_width_height(
196,639✔
1930
                const char *s,
1931
                size_t *ret_width,
1932
                size_t *ret_height) {
1933

1934
        size_t max_width = 0, height = 0;
196,639✔
1935
        const char *p;
196,639✔
1936

1937
        assert(s);
196,639✔
1938

1939
        /* Determine the width and height in console character cells the specified string needs. */
1940

1941
        do {
199,908✔
1942
                size_t k;
199,908✔
1943

1944
                p = strchr(s, '\n');
199,908✔
1945
                if (p) {
199,908✔
1946
                        _cleanup_free_ char *c = NULL;
3,272✔
1947

1948
                        c = strndup(s, p - s);
3,272✔
1949
                        if (!c)
3,272✔
1950
                                return -ENOMEM;
×
1951

1952
                        k = utf8_console_width(c);
3,272✔
1953
                        s = p + 1;
3,272✔
1954
                } else {
1955
                        k = utf8_console_width(s);
196,636✔
1956
                        s = NULL;
196,636✔
1957
                }
1958
                if (k == SIZE_MAX)
199,908✔
1959
                        return -EINVAL;
1960
                if (k > max_width)
199,908✔
1961
                        max_width = k;
194,937✔
1962

1963
                height++;
199,908✔
1964
        } while (!isempty(s));
203,180✔
1965

1966
        if (ret_width)
196,639✔
1967
                *ret_width = max_width;
196,639✔
1968

1969
        if (ret_height)
196,639✔
1970
                *ret_height = height;
196,639✔
1971

1972
        return 0;
1973
}
1974

1975
static int table_data_requested_width_height(
196,639✔
1976
                Table *table,
1977
                TableData *d,
1978
                size_t available_width,
1979
                size_t *ret_width,
1980
                size_t *ret_height,
1981
                bool *have_soft) {
1982

1983
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
196,639✔
1984
        bool truncation_applied = false;
196,639✔
1985
        size_t width, height;
196,639✔
1986
        bool soft = false;
196,639✔
1987
        const char *t;
196,639✔
1988
        int r;
196,639✔
1989

1990
        t = table_data_format_strip_ansi(
196,639✔
1991
                        table,
1992
                        d,
1993
                        /* avoid_uppercasing= */ false,
1994
                        available_width,
1995
                        &soft,
1996
                        &buffer);
1997
        if (!t)
196,639✔
1998
                return -ENOMEM;
1999

2000
        if (table->cell_height_max != SIZE_MAX) {
196,639✔
2001
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
252✔
2002
                if (r < 0)
252✔
2003
                        return r;
2004
                if (r > 0)
252✔
2005
                        truncation_applied = true;
25✔
2006

2007
                t = truncated;
252✔
2008
        }
2009

2010
        r = console_width_height(t, &width, &height);
196,639✔
2011
        if (r < 0)
196,639✔
2012
                return r;
2013

2014
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
196,639✔
2015
                width = d->maximum_width;
×
2016

2017
        if (width < d->minimum_width)
196,639✔
2018
                width = d->minimum_width;
3,480✔
2019

2020
        if (ret_width)
196,639✔
2021
                *ret_width = width;
196,639✔
2022
        if (ret_height)
196,639✔
2023
                *ret_height = height;
196,575✔
2024
        if (have_soft && soft)
196,639✔
2025
                *have_soft = true;
284✔
2026

2027
        return truncation_applied;
196,639✔
2028
}
2029

2030
static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
154,440✔
2031
        size_t w = 0, space, lspace, old_length, clickable_length;
154,440✔
2032
        _cleanup_free_ char *clickable = NULL;
154,440✔
2033
        const char *p;
154,440✔
2034
        char *ret;
154,440✔
2035
        int r;
154,440✔
2036

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

2039
        assert(str);
154,440✔
2040
        assert(percent <= 100);
154,440✔
2041

2042
        old_length = strlen(str);
154,440✔
2043

2044
        if (url) {
154,440✔
2045
                r = terminal_urlify(url, str, &clickable);
1,560✔
2046
                if (r < 0)
1,560✔
2047
                        return NULL;
2048

2049
                clickable_length = strlen(clickable);
1,560✔
2050
        } else
2051
                clickable_length = old_length;
2052

2053
        /* Determine current width on screen */
2054
        p = str;
154,440✔
2055
        while (p < str + old_length) {
2,305,826✔
2056
                char32_t c;
2,151,386✔
2057

2058
                if (utf8_encoded_to_unichar(p, &c) < 0) {
2,151,386✔
2059
                        p++, w++; /* count invalid chars as 1 */
×
2060
                        continue;
×
2061
                }
2062

2063
                p = utf8_next_char(p);
2,151,386✔
2064
                w += unichar_iswide(c) ? 2 : 1;
4,302,760✔
2065
        }
2066

2067
        /* Already wider than the target, if so, don't do anything */
2068
        if (w >= new_length)
154,440✔
2069
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2070

2071
        /* How much spaces shall we add? An how much on the left side? */
2072
        space = new_length - w;
154,440✔
2073
        lspace = space * percent / 100U;
154,440✔
2074

2075
        ret = new(char, space + clickable_length + 1);
154,440✔
2076
        if (!ret)
154,440✔
2077
                return NULL;
2078

2079
        for (size_t i = 0; i < lspace; i++)
789,255✔
2080
                ret[i] = ' ';
634,815✔
2081
        memcpy(ret + lspace, clickable ?: str, clickable_length);
154,440✔
2082
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
4,008,663✔
2083
                ret[i] = ' ';
3,854,223✔
2084

2085
        ret[space + clickable_length] = 0;
154,440✔
2086
        return ret;
154,440✔
2087
}
2088

2089
static bool table_data_isempty(const TableData *d) {
617✔
2090
        assert(d);
617✔
2091

2092
        if (d->type == TABLE_EMPTY)
617✔
2093
                return true;
2094

2095
        /* Let's also consider an empty strv as truly empty. */
2096
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
528✔
2097
                return strv_isempty(d->strv);
×
2098

2099
        if (d->type == TABLE_JSON)
528✔
2100
                return sd_json_variant_is_null(d->json);
×
2101

2102
        /* Note that an empty string we do not consider empty here! */
2103
        return false;
2104
}
2105

2106
static const char* table_data_color(const TableData *d) {
844✔
2107
        assert(d);
844✔
2108

2109
        if (d->color)
844✔
2110
                return d->color;
2111

2112
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2113
        if (table_data_isempty(d))
617✔
2114
                return ansi_grey();
89✔
2115

2116
        if (d->type == TABLE_FIELD)
528✔
2117
                return ansi_bright_blue();
×
2118

2119
        return NULL;
2120
}
2121

2122
static const char* table_data_underline(const TableData *d) {
844✔
2123
        assert(d);
844✔
2124

2125
        if (d->underline)
844✔
2126
                return ansi_add_underline_grey();
×
2127

2128
        if (d->type == TABLE_HEADER)
844✔
2129
                return ansi_add_underline();
50✔
2130

2131
        return NULL;
2132
}
2133

2134
static const char* table_data_rgap_underline(const TableData *d) {
202,771✔
2135
        assert(d);
202,771✔
2136

2137
        if (d->rgap_underline)
202,771✔
2138
                return ansi_add_underline_grey();
300✔
2139

2140
        if (d->type == TABLE_HEADER)
202,471✔
2141
                return ansi_add_underline();
9,302✔
2142

2143
        return NULL;
2144
}
2145

2146
int table_data_requested_width(Table *table, size_t column, size_t *ret) {
7✔
2147
        size_t width = 0;
7✔
2148
        int r;
7✔
2149

2150
        assert(table);
7✔
2151
        assert(ret);
7✔
2152

2153
        for (size_t row = 0; row < table_get_rows(table); row++) {
71✔
2154
                TableCell *cell = table_get_cell(table, row, column);
64✔
2155
                if (!cell)
64✔
2156
                        continue;
×
2157

2158
                TableData *data = table_get_data(table, cell);
64✔
2159
                if (!data)
64✔
2160
                        continue;
×
2161

2162
                size_t w;
64✔
2163

2164
                r = table_data_requested_width_height(
64✔
2165
                                table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL);
2166
                if (r < 0)
64✔
2167
                        return r;
×
2168

2169
                width = MAX(width, w);
64✔
2170
        }
2171

2172
        *ret = width;
7✔
2173
        return 0;
7✔
2174
}
2175

2176
int table_set_column_width(Table *t, size_t column, size_t width) {
7✔
2177
        int r = 0;
7✔
2178

2179
        assert(t);
7✔
2180

2181
        for (size_t row = 0; row < table_get_rows(t); row++) {
71✔
2182
                TableCell *cell = table_get_cell(t, row, column);
64✔
2183
                if (!cell)
64✔
2184
                        continue;
×
2185

2186
                RET_GATHER(r, table_set_minimum_width(t, cell, width));
64✔
2187
        }
2188

2189
        return r;
7✔
2190
}
2191

2192
int _table_sync_column_widths(size_t column, Table *a, ...) {
3✔
2193
        size_t max = 0;
3✔
2194
        va_list ap;
3✔
2195
        int r = 0;
3✔
2196

2197
        assert(a);
3✔
2198

2199
        /* Make the specified column have the same width in the tables. */
2200

2201
        va_start(ap, a);
3✔
2202
        for (Table *t = a; t; t = va_arg(ap, Table*)) {
10✔
2203
                size_t w;
7✔
2204

2205
                r = table_data_requested_width(t, column, &w);
7✔
2206
                if (r < 0)
7✔
2207
                        break;
2208

2209
                max = MAX(max, w);
7✔
2210
        }
2211
        va_end(ap);
3✔
2212
        if (r < 0)
3✔
2213
                return log_error_errno(r, "Failed to query table column width: %m");
×
2214

2215
        r = 0;
3✔
2216
        va_start(ap, a);
3✔
2217
        for (Table *t = a; t; t = va_arg(ap, Table*))
10✔
2218
                RET_GATHER(r, table_set_column_width(t, column, max));
7✔
2219
        va_end(ap);
3✔
2220

2221
        return r;
3✔
2222
}
2223

2224
int table_print_full(Table *t, FILE *f, bool flush) {
3,693✔
2225
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,693✔
2226
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2227
                *width = NULL;
3,693✔
2228
        _cleanup_free_ size_t *sorted = NULL;
3,693✔
2229
        uint64_t *column_weight, weight_sum;
3,693✔
2230
        int r;
3,693✔
2231

2232
        assert(t);
3,693✔
2233

2234
        if (!f)
3,693✔
2235
                f = stdout;
2,636✔
2236

2237
        /* Ensure we have no incomplete rows */
2238
        assert(t->n_cells % t->n_columns == 0);
3,693✔
2239

2240
        n_rows = t->n_cells / t->n_columns;
3,693✔
2241
        assert(n_rows > 0); /* at least the header row must be complete */
3,693✔
2242

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

2246
                sorted = new(size_t, n_rows);
229✔
2247
                if (!sorted)
229✔
2248
                        return -ENOMEM;
2249

2250
                for (size_t i = 0; i < n_rows; i++)
10,735✔
2251
                        sorted[i] = i * t->n_columns;
10,506✔
2252

2253
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
229✔
2254
        }
2255

2256
        if (t->display_map)
3,693✔
2257
                display_columns = t->n_display_map;
361✔
2258
        else
2259
                display_columns = t->n_columns;
3,332✔
2260

2261
        assert(display_columns > 0);
3,693✔
2262

2263
        minimum_width = newa(size_t, display_columns);
3,693✔
2264
        maximum_width = newa(size_t, display_columns);
3,693✔
2265
        requested_width = newa(size_t, display_columns);
3,693✔
2266
        column_weight = newa0(uint64_t, display_columns);
3,693✔
2267

2268
        for (size_t j = 0; j < display_columns; j++) {
14,693✔
2269
                minimum_width[j] = 1;
11,000✔
2270
                maximum_width[j] = SIZE_MAX;
11,000✔
2271
        }
2272

2273
        for (unsigned pass = 0; pass < 2; pass++) {
3,714✔
2274
                /* First pass: determine column sizes */
2275

2276
                for (size_t j = 0; j < display_columns; j++)
14,758✔
2277
                        requested_width[j] = SIZE_MAX;
11,044✔
2278

2279
                bool any_soft = false;
3,714✔
2280

2281
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
61,548✔
2282
                        TableData **row;
57,834✔
2283

2284
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2285
                         * hence we don't care for sorted[] during the first pass. */
2286
                        row = t->data + i * t->n_columns;
57,834✔
2287

2288
                        for (size_t j = 0; j < display_columns; j++) {
254,409✔
2289
                                TableData *d;
196,575✔
2290
                                size_t req_width, req_height;
196,575✔
2291

2292
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
196,575✔
2293

2294
                                r = table_data_requested_width_height(t, d,
196,897✔
2295
                                                                      width ? width[j] : SIZE_MAX,
322✔
2296
                                                                      &req_width, &req_height, &any_soft);
2297
                                if (r < 0)
196,575✔
2298
                                        return r;
×
2299
                                if (r > 0) { /* Truncated because too many lines? */
196,575✔
2300
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2301
                                        const char *field;
25✔
2302

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

2308
                                        field = table_data_format_strip_ansi(
28✔
2309
                                                        t,
2310
                                                        d,
2311
                                                        /* avoid_uppercasing= */ false,
2312
                                                        width ? width[j] : SIZE_MAX,
3✔
2313
                                                        &any_soft,
2314
                                                        &buffer);
2315
                                        if (!field)
25✔
2316
                                                return -ENOMEM;
2317

2318
                                        assert_se(t->cell_height_max > 0);
25✔
2319
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2320
                                        if (r < 0)
25✔
2321
                                                return r;
2322

2323
                                        req_width = MAX(req_width,
25✔
2324
                                                        utf8_console_width(last) +
2325
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2326
                                }
2327

2328
                                /* Determine the biggest width that any cell in this column would like to have */
2329
                                if (requested_width[j] == SIZE_MAX ||
196,575✔
2330
                                    requested_width[j] < req_width)
185,569✔
2331
                                        requested_width[j] = req_width;
24,462✔
2332

2333
                                /* Determine the minimum width any cell in this column needs */
2334
                                if (minimum_width[j] < d->minimum_width)
196,575✔
2335
                                        minimum_width[j] = d->minimum_width;
34✔
2336

2337
                                /* Determine the maximum width any cell in this column needs */
2338
                                if (d->maximum_width != SIZE_MAX &&
196,575✔
2339
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2340
                                     maximum_width[j] > d->maximum_width))
2341
                                        maximum_width[j] = d->maximum_width;
20✔
2342

2343
                                /* Determine the full columns weight */
2344
                                column_weight[j] += d->weight;
196,575✔
2345
                        }
2346
                }
2347

2348
                /* One space between each column */
2349
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,714✔
2350

2351
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2352
                weight_sum = 0;
3,714✔
2353
                for (size_t j = 0; j < display_columns; j++) {
14,758✔
2354
                        weight_sum += column_weight[j];
11,044✔
2355

2356
                        table_minimum_width += minimum_width[j];
11,044✔
2357

2358
                        if (maximum_width[j] == SIZE_MAX)
11,044✔
2359
                                table_maximum_width = SIZE_MAX;
2360
                        else
2361
                                table_maximum_width += maximum_width[j];
20✔
2362

2363
                        table_requested_width += requested_width[j];
11,044✔
2364
                }
2365

2366
                /* Calculate effective table width */
2367
                if (t->width != 0 && t->width != SIZE_MAX)
3,714✔
2368
                        table_effective_width = t->width;
2369
                else if (t->width == 0 ||
3,707✔
2370
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,636✔
2371
                        table_effective_width = table_requested_width;
2372
                else
2373
                        table_effective_width = MIN(table_requested_width, columns());
50✔
2374

2375
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,714✔
2376
                        table_effective_width = table_maximum_width;
×
2377

2378
                if (table_effective_width < table_minimum_width)
3,714✔
2379
                        table_effective_width = table_minimum_width;
2✔
2380

2381
                if (!width)
3,714✔
2382
                        width = newa(size_t, display_columns);
3,693✔
2383

2384
                if (table_effective_width >= table_requested_width) {
3,714✔
2385
                        size_t extra;
3,689✔
2386

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

2390
                        extra = table_effective_width - table_requested_width;
3,689✔
2391

2392
                        for (size_t j = 0; j < display_columns; j++) {
14,671✔
2393
                                size_t delta;
10,982✔
2394

2395
                                if (weight_sum == 0)
10,982✔
2396
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
38✔
2397
                                else
2398
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
10,944✔
2399

2400
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
10,982✔
2401
                                        width[j] = maximum_width[j];
2✔
2402

2403
                                if (width[j] < minimum_width[j])
10,982✔
2404
                                        width[j] = minimum_width[j];
×
2405

2406
                                delta = LESS_BY(width[j], requested_width[j]);
10,982✔
2407

2408
                                /* Subtract what we just added from the rest */
2409
                                if (extra > delta)
10,982✔
2410
                                        extra -= delta;
13✔
2411
                                else
2412
                                        extra = 0;
2413

2414
                                assert(weight_sum >= column_weight[j]);
10,982✔
2415
                                weight_sum -= column_weight[j];
10,982✔
2416
                        }
2417

2418
                        break; /* Every column should be happy, no need to repeat calculations. */
3,693✔
2419
                } else {
2420
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2421
                         * with the minimum they need, and then distribute anything left. */
2422
                        bool finalize = false;
25✔
2423
                        size_t extra;
25✔
2424

2425
                        extra = table_effective_width - table_minimum_width;
25✔
2426

2427
                        for (size_t j = 0; j < display_columns; j++)
87✔
2428
                                width[j] = SIZE_MAX;
62✔
2429

2430
                        for (;;) {
73✔
2431
                                bool restart = false;
73✔
2432

2433
                                for (size_t j = 0; j < display_columns; j++) {
207✔
2434
                                        size_t delta, w;
157✔
2435

2436
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2437
                                        if (width[j] != SIZE_MAX)
157✔
2438
                                                continue;
53✔
2439

2440
                                        if (weight_sum == 0)
104✔
2441
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2442
                                        else
2443
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
104✔
2444

2445
                                        if (w >= requested_width[j]) {
104✔
2446
                                                /* Never give more than requested. If we hit a column like this, there's more
2447
                                                 * space to allocate to other columns which means we need to restart the
2448
                                                 * iteration. However, if we hit a column like this, let's assign it the space
2449
                                                 * it wanted for good early. */
2450

2451
                                                w = requested_width[j];
2452
                                                restart = true;
2453

2454
                                        } else if (!finalize)
80✔
2455
                                                continue;
42✔
2456

2457
                                        width[j] = w;
62✔
2458

2459
                                        assert(w >= minimum_width[j]);
62✔
2460
                                        delta = w - minimum_width[j];
62✔
2461

2462
                                        assert(delta <= extra);
62✔
2463
                                        extra -= delta;
62✔
2464

2465
                                        assert(weight_sum >= column_weight[j]);
62✔
2466
                                        weight_sum -= column_weight[j];
62✔
2467

2468
                                        if (restart && !finalize)
62✔
2469
                                                break;
2470
                                }
2471

2472
                                if (finalize)
73✔
2473
                                        break;
2474

2475
                                if (!restart)
48✔
2476
                                        finalize = true;
25✔
2477
                        }
2478

2479
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
25✔
2480
                                        * let's try to reformat them with the new widths. Otherwise, let's
2481
                                        * move on. */
2482
                                break;
2483
                }
2484
        }
2485

2486
        /* Second pass: show output */
2487
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
61,394✔
2488
                size_t n_subline = 0;
57,701✔
2489
                bool more_sublines;
57,701✔
2490
                TableData **row;
57,701✔
2491

2492
                if (sorted)
57,701✔
2493
                        row = t->data + sorted[i];
10,488✔
2494
                else
2495
                        row = t->data + i * t->n_columns;
47,213✔
2496

2497
                do {
60,958✔
2498
                        const char *gap_color = NULL, *gap_underline = NULL;
60,958✔
2499
                        more_sublines = false;
60,958✔
2500

2501
                        for (size_t j = 0; j < display_columns; j++) {
263,729✔
2502
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
202,771✔
2503
                                bool lines_truncated = false;
202,771✔
2504
                                const char *field, *color = NULL, *underline = NULL;
202,771✔
2505
                                TableData *d;
202,771✔
2506
                                size_t l;
202,771✔
2507

2508
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
202,771✔
2509

2510
                                if (colors_enabled())
202,771✔
2511
                                        field = table_data_format(
782✔
2512
                                                        t,
2513
                                                        d,
2514
                                                        /* avoid_uppercasing= */ false,
2515
                                                        width[j],
782✔
2516
                                                        /* have_soft= */ NULL);
2517
                                else
2518
                                        field = table_data_format_strip_ansi(
201,989✔
2519
                                                        t,
2520
                                                        d,
2521
                                                        /* avoid_uppercasing= */ false,
2522
                                                        width[j],
201,989✔
2523
                                                        /* have_soft= */ NULL,
2524
                                                        &stripped_ansi_buffer);
2525
                                if (!field)
202,771✔
2526
                                        return -ENOMEM;
2527

2528
                                r = string_extract_line(field, n_subline, &extracted);
202,771✔
2529
                                if (r < 0)
202,771✔
2530
                                        return r;
2531
                                if (r > 0) {
202,771✔
2532
                                        /* There are more lines to come */
2533
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,294✔
2534
                                                more_sublines = true; /* There are more lines to come */
2535
                                        else
2536
                                                lines_truncated = true;
25✔
2537
                                }
2538
                                if (extracted)
202,771✔
2539
                                        field = extracted;
7,990✔
2540

2541
                                l = utf8_console_width(field);
202,771✔
2542
                                if (l > width[j]) {
202,771✔
2543
                                        /* Field is wider than allocated space. Let's ellipsize */
2544

2545
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
35✔
2546
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2547
                                        if (!buffer)
35✔
2548
                                                return -ENOMEM;
2549

2550
                                        field = buffer;
2551
                                } else {
2552
                                        if (lines_truncated) {
202,736✔
2553
                                                _cleanup_free_ char *padded = NULL;
25✔
2554

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

2561
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2562
                                                if (!padded)
25✔
2563
                                                        return -ENOMEM;
2564

2565
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2566
                                                if (!buffer)
25✔
2567
                                                        return -ENOMEM;
2568

2569
                                                field = buffer;
25✔
2570
                                                l = utf8_console_width(field);
25✔
2571
                                        }
2572

2573
                                        if (l < width[j]) {
202,736✔
2574
                                                _cleanup_free_ char *aligned = NULL;
×
2575
                                                /* Field is shorter than allocated space. Let's align with spaces */
2576

2577
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
154,440✔
2578
                                                if (!aligned)
154,440✔
2579
                                                        return -ENOMEM;
×
2580

2581
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2582
                                                if (j == display_columns - 1 &&
207,458✔
2583
                                                    (!colors_enabled() || !table_data_color(d)) &&
53,080✔
2584
                                                    (!underline_enabled() || !table_data_underline(d)) &&
106,095✔
2585
                                                    (!urlify_enabled() || !d->url))
53,074✔
2586
                                                        delete_trailing_chars(aligned, NULL);
53,015✔
2587

2588
                                                free_and_replace(buffer, aligned);
154,440✔
2589
                                                field = buffer;
154,440✔
2590
                                        }
2591
                                }
2592

2593
                                if (l >= width[j] && d->url) {
202,771✔
2594
                                        _cleanup_free_ char *clickable = NULL;
×
2595

2596
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2597
                                        if (r < 0)
22✔
2598
                                                return r;
×
2599

2600
                                        free_and_replace(buffer, clickable);
22✔
2601
                                        field = buffer;
22✔
2602
                                }
2603

2604
                                if (colors_enabled() && gap_color)
202,771✔
2605
                                        fputs(gap_color, f);
×
2606
                                if (underline_enabled() && gap_underline)
202,771✔
2607
                                        fputs(gap_underline, f);
19✔
2608

2609
                                if (j > 0)
202,771✔
2610
                                        fputc(' ', f); /* column separator left of cell */
141,813✔
2611

2612
                                /* Undo gap color/underline */
2613
                                if ((colors_enabled() && gap_color) ||
405,542✔
2614
                                    (underline_enabled() && gap_underline))
203,553✔
2615
                                        fputs(ANSI_NORMAL, f);
19✔
2616

2617
                                if (colors_enabled()) {
202,771✔
2618
                                        color = table_data_color(d);
782✔
2619
                                        if (color)
782✔
2620
                                                fputs(color, f);
316✔
2621
                                }
2622

2623
                                if (underline_enabled()) {
202,771✔
2624
                                        underline = table_data_underline(d);
782✔
2625
                                        if (underline)
782✔
2626
                                                fputs(underline, f);
22✔
2627
                                }
2628

2629
                                fputs(field, f);
202,771✔
2630

2631
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2632
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
202,780✔
2633
                                        fputs(ANSI_NORMAL, f);
341✔
2634

2635
                                gap_color = d->rgap_color;
202,771✔
2636
                                gap_underline = table_data_rgap_underline(d);
202,771✔
2637
                        }
2638

2639
                        fputc('\n', f);
60,958✔
2640
                        n_subline++;
60,958✔
2641
                } while (more_sublines);
60,958✔
2642
        }
2643

2644
        if (!flush)
3,693✔
2645
                return 0;
2646

2647
        return fflush_and_check(f);
1,057✔
2648
}
2649

2650
int table_print_or_warn(Table *t) {
2,509✔
2651
        int r;
2,509✔
2652

2653
        r = table_print(t);
2,509✔
2654
        if (r < 0)
2,509✔
UNCOV
2655
                return table_log_print_error(r);
×
2656
        return 0;
2657
}
2658

2659
int table_format(Table *t, char **ret) {
41✔
2660
        _cleanup_(memstream_done) MemStream m = {};
41✔
2661
        FILE *f;
41✔
2662
        int r;
41✔
2663

2664
        assert(t);
41✔
2665
        assert(ret);
41✔
2666

2667
        f = memstream_init(&m);
41✔
2668
        if (!f)
41✔
2669
                return -ENOMEM;
2670

2671
        r = table_print_full(t, f, /* flush= */ true);
41✔
2672
        if (r < 0)
41✔
2673
                return r;
2674

2675
        return memstream_finalize(&m, ret, NULL);
41✔
2676
}
2677

2678
size_t table_get_rows(Table *t) {
1,990✔
2679
        if (!t)
1,990✔
2680
                return 0;
2681

2682
        assert(t->n_columns > 0);
1,990✔
2683
        return t->n_cells / t->n_columns;
1,990✔
2684
}
2685

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

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

2694
size_t table_get_current_column(Table *t) {
80✔
2695
        if (!t)
80✔
2696
                return 0;
2697

2698
        assert(t->n_columns > 0);
80✔
2699
        return t->n_cells % t->n_columns;
80✔
2700
}
2701

2702
int table_set_reverse(Table *t, size_t column, bool b) {
87✔
2703
        assert(t);
87✔
2704
        assert(column < t->n_columns);
87✔
2705

2706
        if (!t->reverse_map) {
87✔
2707
                if (!b)
87✔
2708
                        return 0;
2709

2710
                t->reverse_map = new0(bool, t->n_columns);
33✔
2711
                if (!t->reverse_map)
33✔
2712
                        return -ENOMEM;
2713
        }
2714

2715
        t->reverse_map[column] = b;
33✔
2716
        return 0;
33✔
2717
}
2718

2719
TableCell* table_get_cell(Table *t, size_t row, size_t column) {
8,876✔
2720
        size_t i;
8,876✔
2721

2722
        assert(t);
8,876✔
2723

2724
        if (column >= t->n_columns)
8,876✔
2725
                return NULL;
2726

2727
        i = row * t->n_columns + column;
8,876✔
2728
        if (i >= t->n_cells)
8,876✔
2729
                return NULL;
2730

2731
        return TABLE_INDEX_TO_CELL(i);
8,876✔
2732
}
2733

2734
const void* table_get(Table *t, TableCell *cell) {
4,374✔
2735
        TableData *d;
4,374✔
2736

2737
        assert(t);
4,374✔
2738

2739
        d = table_get_data(t, cell);
4,374✔
2740
        if (!d)
4,374✔
2741
                return NULL;
2742

2743
        return d->data;
4,374✔
2744
}
2745

2746
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2747
        TableCell *cell;
4,374✔
2748

2749
        cell = table_get_cell(t, row, column);
4,374✔
2750
        if (!cell)
4,374✔
2751
                return NULL;
2752

2753
        return table_get(t, cell);
4,374✔
2754
}
2755

2756
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
5,521✔
2757

2758
        switch (d->type) {
5,521✔
2759

2760
        case TABLE_EMPTY:
722✔
2761
                return sd_json_variant_new_null(ret);
722✔
2762

2763
        case TABLE_STRING:
2,612✔
2764
        case TABLE_PATH:
2765
        case TABLE_PATH_BASENAME:
2766
        case TABLE_FIELD:
2767
        case TABLE_HEADER:
2768
        case TABLE_VERSION:
2769
                return sd_json_variant_new_string(ret, d->string);
2,612✔
2770

2771
        case TABLE_STRV:
3✔
2772
        case TABLE_STRV_WRAPPED:
2773
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2774

2775
        case TABLE_BOOLEAN_CHECKMARK:
208✔
2776
        case TABLE_BOOLEAN:
2777
                return sd_json_variant_new_boolean(ret, d->boolean);
208✔
2778

2779
        case TABLE_TIMESTAMP:
299✔
2780
        case TABLE_TIMESTAMP_UTC:
2781
        case TABLE_TIMESTAMP_RELATIVE:
2782
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2783
        case TABLE_TIMESTAMP_LEFT:
2784
        case TABLE_TIMESTAMP_DATE:
2785
                if (d->timestamp == USEC_INFINITY)
299✔
UNCOV
2786
                        return sd_json_variant_new_null(ret);
×
2787

2788
                return sd_json_variant_new_unsigned(ret, d->timestamp);
299✔
2789

UNCOV
2790
        case TABLE_TIMESPAN:
×
2791
        case TABLE_TIMESPAN_MSEC:
2792
        case TABLE_TIMESPAN_DAY:
UNCOV
2793
                if (d->timespan == USEC_INFINITY)
×
UNCOV
2794
                        return sd_json_variant_new_null(ret);
×
2795

UNCOV
2796
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2797

2798
        case TABLE_SIZE:
36✔
2799
        case TABLE_BPS:
2800
                if (d->size == UINT64_MAX)
36✔
2801
                        return sd_json_variant_new_null(ret);
16✔
2802

2803
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2804

2805
        case TABLE_INT:
40✔
2806
                return sd_json_variant_new_integer(ret, d->int_val);
40✔
2807

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

2811
        case TABLE_INT16:
6✔
2812
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2813

2814
        case TABLE_INT32:
6✔
2815
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2816

2817
        case TABLE_INT64:
46✔
2818
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2819

2820
        case TABLE_UINT:
43✔
2821
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2822

2823
        case TABLE_UINT8:
4✔
2824
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2825

2826
        case TABLE_UINT16:
4✔
2827
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2828

2829
        case TABLE_UINT32:
118✔
2830
        case TABLE_UINT32_HEX:
2831
        case TABLE_UINT32_HEX_0x:
2832
                return sd_json_variant_new_unsigned(ret, d->uint32);
118✔
2833

2834
        case TABLE_UINT64:
482✔
2835
        case TABLE_UINT64_HEX:
2836
        case TABLE_UINT64_HEX_0x:
2837
                return sd_json_variant_new_unsigned(ret, d->uint64);
482✔
2838

UNCOV
2839
        case TABLE_PERCENT:
×
2840
                return sd_json_variant_new_integer(ret, d->percent);
×
2841

UNCOV
2842
        case TABLE_IFINDEX:
×
UNCOV
2843
                if (d->ifindex <= 0)
×
UNCOV
2844
                        return sd_json_variant_new_null(ret);
×
2845

UNCOV
2846
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2847

2848
        case TABLE_IN_ADDR:
UNCOV
2849
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2850

2851
        case TABLE_IN6_ADDR:
UNCOV
2852
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2853

2854
        case TABLE_ID128:
502✔
2855
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2856

2857
        case TABLE_UUID:
144✔
2858
                return sd_json_variant_new_uuid(ret, d->id128);
144✔
2859

2860
        case TABLE_UID:
8✔
2861
                if (!uid_is_valid(d->uid))
8✔
2862
                        return sd_json_variant_new_null(ret);
×
2863

2864
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2865

2866
        case TABLE_GID:
8✔
2867
                if (!gid_is_valid(d->gid))
8✔
2868
                        return sd_json_variant_new_null(ret);
×
2869

2870
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2871

2872
        case TABLE_PID:
14✔
2873
                if (!pid_is_valid(d->pid))
14✔
UNCOV
2874
                        return sd_json_variant_new_null(ret);
×
2875

2876
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2877

2878
        case TABLE_SIGNAL:
8✔
2879
                if (!SIGNAL_VALID(d->int_val))
8✔
UNCOV
2880
                        return sd_json_variant_new_null(ret);
×
2881

2882
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2883

2884
        case TABLE_MODE:
195✔
2885
        case TABLE_MODE_INODE_TYPE:
2886
                if (d->mode == MODE_INVALID)
195✔
2887
                        return sd_json_variant_new_null(ret);
×
2888

2889
                return sd_json_variant_new_unsigned(ret, d->mode);
195✔
2890

2891
        case TABLE_DEVNUM:
4✔
2892
                if (devnum_is_zero(d->devnum))
4✔
2893
                        return sd_json_variant_new_null(ret);
2✔
2894

2895
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2896
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2897
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2898

UNCOV
2899
        case TABLE_JSON:
×
UNCOV
2900
                if (!d->json)
×
UNCOV
2901
                        return sd_json_variant_new_null(ret);
×
2902

UNCOV
2903
                if (ret)
×
UNCOV
2904
                        *ret = sd_json_variant_ref(d->json);
×
2905

2906
                return 0;
2907

2908
        case TABLE_STRING_WITH_ANSI: {
3✔
2909
                _cleanup_free_ char *s = strdup(d->string);
3✔
2910
                if (!s)
3✔
2911
                        return -ENOMEM;
2912

2913
                /* We strip the ANSI data when outputting to JSON */
2914
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
2915
                        return -ENOMEM;
2916

2917
                return sd_json_variant_new_string(ret, s);
3✔
2918
        }
2919

2920
        default:
2921
                return -EINVAL;
2922
        }
2923
}
2924

2925
char* table_mangle_to_json_field_name(const char *str) {
959✔
2926
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2927
         * field name can be hence this is a bit vague and black magic. Here's what we do:
2928
         *  - Convert spaces to underscores
2929
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
2930
         *  - Convert most other symbols to underscores (for similar reasons)
2931
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
2932
         */
2933

2934
        bool new_word = true;
959✔
2935
        char *c;
959✔
2936

2937
        assert(str);
959✔
2938

2939
        c = strdup(str);
959✔
2940
        if (!c)
959✔
2941
                return NULL;
2942

2943
        for (char *x = c; *x; x++) {
6,869✔
2944
                if (!strchr(ALPHANUMERICAL, *x)) {
5,910✔
2945
                        *x = '_';
189✔
2946
                        new_word = true;
189✔
2947
                        continue;
189✔
2948
                }
2949

2950
                if (new_word) {
5,721✔
2951
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
1,143✔
2952
                                                                  * then we assume the whole word is all-caps
2953
                                                                  * and avoid lowercasing it. */
2954
                                *x = ascii_tolower(*x);
1,142✔
2955
                        new_word = false;
2956
                }
2957
        }
2958

2959
        return c;
2960
}
2961

2962
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
945✔
2963
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
945✔
2964
        const char *n;
945✔
2965

2966
        assert(t);
945✔
2967
        assert(d);
945✔
2968
        assert(ret);
945✔
2969

2970
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
945✔
2971
                n = d->string;
945✔
2972
        else {
UNCOV
2973
                n = table_data_format_strip_ansi(
×
2974
                                t,
2975
                                d,
2976
                                /* avoid_uppercasing= */ true,
2977
                                /* column_width= */ SIZE_MAX,
2978
                                /* have_soft= */ NULL,
2979
                                &buffer);
UNCOV
2980
                if (!n)
×
2981
                        return -ENOMEM;
2982
        }
2983

2984
        mangled = table_mangle_to_json_field_name(n);
945✔
2985
        if (!mangled)
945✔
2986
                return -ENOMEM;
2987

2988
        *ret = TAKE_PTR(mangled);
945✔
2989
        return 0;
945✔
2990
}
2991

2992
static const char* table_get_json_field_name(Table *t, size_t idx) {
1,047✔
2993
        assert(t);
1,047✔
2994

2995
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
1,047✔
2996
}
2997

2998
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
139✔
2999
        sd_json_variant **rows = NULL, **elements = NULL;
139✔
3000
        _cleanup_free_ size_t *sorted = NULL;
139✔
3001
        size_t n_rows, display_columns;
139✔
3002
        int r;
139✔
3003

3004
        assert(t);
139✔
3005
        assert(!t->vertical);
139✔
3006

3007
        /* Ensure we have no incomplete rows */
3008
        assert(t->n_columns > 0);
139✔
3009
        assert(t->n_cells % t->n_columns == 0);
139✔
3010

3011
        n_rows = t->n_cells / t->n_columns;
139✔
3012
        assert(n_rows > 0); /* at least the header row must be complete */
139✔
3013

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

3017
                sorted = new(size_t, n_rows);
32✔
3018
                if (!sorted)
32✔
3019
                        return -ENOMEM;
3020

3021
                for (size_t i = 0; i < n_rows; i++)
198✔
3022
                        sorted[i] = i * t->n_columns;
166✔
3023

3024
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
32✔
3025
        }
3026

3027
        if (t->display_map)
139✔
3028
                display_columns = t->n_display_map;
79✔
3029
        else
3030
                display_columns = t->n_columns;
60✔
3031
        assert(display_columns > 0);
139✔
3032

3033
        elements = new0(sd_json_variant*, display_columns * 2);
139✔
3034
        if (!elements)
139✔
3035
                return -ENOMEM;
3036

3037
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
139✔
3038

3039
        for (size_t j = 0; j < display_columns; j++) {
1,182✔
3040
                _cleanup_free_ char *mangled = NULL;
1,043✔
3041
                const char *n;
1,043✔
3042
                size_t c;
1,043✔
3043

3044
                c = t->display_map ? t->display_map[j] : j;
1,043✔
3045

3046
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3047
                n = table_get_json_field_name(t, c);
1,043✔
3048
                if (!n) {
1,043✔
3049
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
943✔
3050
                        if (r < 0)
943✔
3051
                                return r;
3052

3053
                        n = mangled;
943✔
3054
                }
3055

3056
                r = sd_json_variant_new_string(elements + j*2, n);
1,043✔
3057
                if (r < 0)
1,043✔
3058
                        return r;
3059
        }
3060

3061
        rows = new0(sd_json_variant*, n_rows-1);
139✔
3062
        if (!rows)
139✔
3063
                return -ENOMEM;
3064

3065
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
139✔
3066

3067
        for (size_t i = 1; i < n_rows; i++) {
1,313✔
3068
                TableData **row;
1,174✔
3069

3070
                if (sorted)
1,174✔
3071
                        row = t->data + sorted[i];
134✔
3072
                else
3073
                        row = t->data + i * t->n_columns;
1,040✔
3074

3075
                for (size_t j = 0; j < display_columns; j++) {
6,691✔
3076
                        TableData *d;
5,517✔
3077
                        size_t k;
5,517✔
3078

3079
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
5,517✔
3080

3081
                        k = j*2+1;
5,517✔
3082
                        elements[k] = sd_json_variant_unref(elements[k]);
5,517✔
3083

3084
                        r = table_data_to_json(d, elements + k);
5,517✔
3085
                        if (r < 0)
5,517✔
3086
                                return r;
3087
                }
3088

3089
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1,174✔
3090
                if (r < 0)
1,174✔
3091
                        return r;
3092
        }
3093

3094
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
139✔
3095
}
3096

3097
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3098
        sd_json_variant **elements = NULL;
1✔
3099
        size_t n_elements = 0;
1✔
3100
        int r;
1✔
3101

3102
        assert(t);
1✔
3103
        assert(t->vertical);
1✔
3104

3105
        if (t->n_columns != 2)
1✔
3106
                return -EINVAL;
1✔
3107

3108
        /* Ensure we have no incomplete rows */
3109
        assert(t->n_cells % t->n_columns == 0);
1✔
3110

3111
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3112
        if (!elements)
1✔
3113
                return -ENOMEM;
3114

3115
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3116

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

3119
                if (i % t->n_columns == 0) {
8✔
3120
                        _cleanup_free_ char *mangled = NULL;
4✔
3121
                        const char *n;
4✔
3122

3123
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3124
                        if (!n) {
4✔
3125
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3126
                                if (r < 0)
2✔
UNCOV
3127
                                        return r;
×
3128

3129
                                n = mangled;
2✔
3130
                        }
3131

3132
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3133
                } else
3134
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3135
                if (r < 0)
8✔
3136
                        return r;
3137

3138
                n_elements++;
8✔
3139
        }
3140

3141
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3142
}
3143

3144
int table_to_json(Table *t, sd_json_variant **ret) {
140✔
3145
        assert(t);
140✔
3146

3147
        if (t->vertical)
140✔
3148
                return table_to_json_vertical(t, ret);
1✔
3149

3150
        return table_to_json_regular(t, ret);
139✔
3151
}
3152

3153
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
572✔
3154
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
572✔
3155
        int r;
572✔
3156

3157
        assert(t);
572✔
3158

3159
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
572✔
3160
                return table_print_full(t, f, /* flush= */ true);
501✔
3161

3162
        if (!f)
71✔
3163
                f = stdout;
2✔
3164

3165
        r = table_to_json(t, &v);
71✔
3166
        if (r < 0)
71✔
3167
                return r;
3168

3169
        sd_json_variant_dump(v, flags, f, NULL);
71✔
3170

3171
        return fflush_and_check(f);
71✔
3172
}
3173

3174
int table_print_with_pager(
570✔
3175
                Table *t,
3176
                sd_json_format_flags_t json_format_flags,
3177
                PagerFlags pager_flags,
3178
                bool show_header) {
3179

3180
        bool saved_header;
570✔
3181
        int r;
570✔
3182

3183
        assert(t);
570✔
3184

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

3188
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
570✔
3189
                pager_open(pager_flags);
544✔
3190

3191
        saved_header = t->header;
570✔
3192
        t->header = show_header;
570✔
3193
        r = table_print_json(t, stdout, json_format_flags);
570✔
3194
        t->header = saved_header;
570✔
3195
        if (r < 0)
570✔
UNCOV
3196
                return table_log_print_error(r);
×
3197

3198
        return 0;
3199
}
3200

3201
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
710✔
3202
        int r;
710✔
3203

3204
        assert(t);
710✔
3205

3206
        if (name) {
710✔
3207
                size_t m;
708✔
3208

3209
                m = MAX(idx + 1, t->n_json_fields);
708✔
3210
                if (!GREEDY_REALLOC0(t->json_fields, m))
708✔
3211
                        return -ENOMEM;
3212

3213
                r = free_and_strdup(t->json_fields + idx, name);
708✔
3214
                if (r < 0)
708✔
3215
                        return r;
3216

3217
                t->n_json_fields = m;
708✔
3218
                return r;
708✔
3219
        } else {
3220
                if (idx >= t->n_json_fields)
2✔
3221
                        return 0;
3222

3223
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3224
                return 1;
2✔
3225
        }
3226
}
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