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

systemd / systemd / 22246189288

20 Feb 2026 07:59PM UTC coverage: 72.238% (-0.2%) from 72.47%
22246189288

push

github

bluca
Add BNCF NewBook 11 ACCEL_MOUNT_MATRIX  to 60-sensor.hwdb

Corrects DE autorotation

Device description: https://www.bncfai.com/product/773/

313540 of 434040 relevant lines covered (72.24%)

1346131.39 hits per line

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

89.4
/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) {
392,390✔
120
        size_t i;
392,390✔
121

122
        assert(cell);
392,390✔
123

124
        i = PTR_TO_SIZE(cell);
392,390✔
125
        assert(i > 0);
392,390✔
126

127
        return i-1;
392,390✔
128
}
129

130
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
192,996✔
131
        assert(index != SIZE_MAX);
192,996✔
132
        return SIZE_TO_PTR(index + 1);
192,996✔
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,729✔
163
        _cleanup_(table_unrefp) Table *t = NULL;
3,729✔
164

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

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

171
        *t = (Table) {
3,729✔
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,729✔
180
}
181

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

188
        assert(first_header);
755✔
189

190
        va_start(ap, first_header);
755✔
191
        for (;;) {
11,757✔
192
                if (!va_arg(ap, const char*))
6,256✔
193
                        break;
194

195
                n_columns++;
5,501✔
196
        }
197
        va_end(ap);
755✔
198

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

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

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

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

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

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

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

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

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

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

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

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

245
static TableData *table_data_free(TableData *d) {
187,145✔
246
        assert(d);
187,145✔
247

248
        free(d->formatted);
187,145✔
249
        free(d->url);
187,145✔
250

251
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
187,145✔
252
                strv_free(d->strv);
6,340✔
253

254
        if (d->type == TABLE_JSON)
187,145✔
255
                sd_json_variant_unref(d->json);
2,596✔
256

257
        return mfree(d);
187,145✔
258
}
259

260
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
330,831✔
261
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
206,753✔
262

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

267
        for (size_t i = 0; i < t->n_cells; i++)
210,494✔
268
                table_data_unref(t->data[i]);
206,765✔
269

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

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

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

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

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

285
        switch (type) {
510,073✔
286

287
        case TABLE_EMPTY:
288
                return 0;
289

290
        case TABLE_STRING:
404,411✔
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;
404,411✔
298

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

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

307
        case TABLE_TIMESTAMP:
6,909✔
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);
6,909✔
317

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

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

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

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

340
        case TABLE_INT:
4,296✔
341
        case TABLE_UINT:
342
        case TABLE_PERCENT:
343
        case TABLE_IFINDEX:
344
        case TABLE_SIGNAL:
345
                return sizeof(int);
4,296✔
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,770✔
354
        case TABLE_ID128:
355
                return sizeof(sd_id128_t);
5,770✔
356

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

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

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

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

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

379
static bool table_data_matches(
194,185✔
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;
194,185✔
391
        assert(d);
194,185✔
392

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

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

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

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

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

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

411
        if (d->uppercase != uppercase)
165,084✔
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)
165,083✔
416
                return false;
417
        if (d->url)
163,045✔
418
                return false;
419

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

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

428
static TableData *table_data_new(
187,145✔
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;
187,145✔
439
        size_t data_size;
187,145✔
440

441
        data_size = table_data_size(type, data);
187,145✔
442

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

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

456
        switch (type) {
187,145✔
457

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

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

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

473
        return TAKE_PTR(d);
474
}
475

476
int table_add_cell_full(
206,753✔
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;
206,753✔
488
        bool uppercase;
206,753✔
489
        TableData *p;
206,753✔
490

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

495
        /* Special rule: patch NULL data fields to the empty field */
496
        if (!data)
206,753✔
497
                dt = TABLE_EMPTY;
14,449✔
498

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

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

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

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

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

518
        assert(align_percent <= 100);
206,753✔
519
        assert(ellipsize_percent <= 100);
206,753✔
520

521
        uppercase = dt == TABLE_HEADER;
206,753✔
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))
206,753✔
527
                d = table_data_ref(p);
71,831✔
528
        else {
529
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
134,922✔
530
                if (!d)
134,922✔
531
                        return -ENOMEM;
532
        }
533

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

537
        if (ret_cell)
206,753✔
538
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
184,427✔
539

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

542
        return 0;
206,753✔
543
}
544

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

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

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

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

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

565
        assert(t);
53✔
566

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

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

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

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

580
        return 0;
581
}
582

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

586
        assert(t);
12✔
587

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

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

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

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

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

606
        assert(t);
193,439✔
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);
193,439✔
612
        if (i >= t->n_cells)
193,439✔
613
                return -ENXIO;
614

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

619
        assert(od->n_ref > 1);
51,100✔
620

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

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

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

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

648
        assert(nd->n_ref == 1);
51,100✔
649

650
        return 1;
651
}
652

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

656
        assert(t);
197,813✔
657
        assert(cell);
197,813✔
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);
197,813✔
662
        if (i >= t->n_cells)
197,813✔
663
                return NULL;
664

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

734
        table_get_data(t, cell)->align_percent = percent;
13,817✔
735
        return 0;
13,817✔
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,134✔
758
        int r;
55,134✔
759

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

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

767
        table_get_data(t, cell)->color = empty_to_null(color);
69,998✔
768
        return 0;
55,134✔
769
}
770

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

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

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

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

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

789
        assert(t);
54,376✔
790
        assert(cell);
54,376✔
791

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

796
        assert_se(d = table_get_data(t, cell));
54,376✔
797

798
        if (d->underline == b)
54,376✔
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) {
54,376✔
806
        TableData *d;
54,376✔
807
        int r;
54,376✔
808

809
        assert(t);
54,376✔
810
        assert(cell);
54,376✔
811

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

816
        assert_se(d = table_get_data(t, cell));
54,376✔
817

818
        if (d->rgap_underline == b)
54,376✔
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,682✔
826
        _cleanup_free_ char *copy = NULL;
1,682✔
827
        int r;
1,682✔
828

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

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

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

842
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,682✔
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, ...) {
48,635✔
911
        TableCell *last_cell = NULL;
48,635✔
912
        va_list ap;
48,635✔
913
        int r;
48,635✔
914

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

919
        va_start(ap, first_type);
48,635✔
920

921
        for (TableDataType type = first_type;; type = va_arg(ap, TableDataType)) {
347,793✔
922
                const void *data;
347,793✔
923
                union {
347,793✔
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) {
347,793✔
949

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

954
                case TABLE_STRING:
131,557✔
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 *);
131,557✔
962
                        break;
131,557✔
963

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

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

975
                case TABLE_TIMESTAMP:
3,638✔
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,638✔
985
                        data = &buffer.usec;
3,638✔
986
                        break;
3,638✔
987

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1134
                case TABLE_SET_WEIGHT: {
5,088✔
1135
                        unsigned w = va_arg(ap, unsigned);
5,088✔
1136
                        r = table_set_weight(t, last_cell, w);
5,088✔
1137
                        goto check;
5,088✔
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,987✔
1153
                        const char *c = va_arg(ap, const char*);
51,987✔
1154
                        r = table_set_color(t, last_cell, c);
51,987✔
1155
                        goto check;
51,987✔
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,147✔
1165
                        const char *c = va_arg(ap, const char*);
3,147✔
1166

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

1173
                        r = table_set_rgap_color(t, last_cell, c);
3,147✔
1174
                        goto check;
3,147✔
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: {
54,376✔
1190
                        int u = va_arg(ap, int);
54,376✔
1191

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

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

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

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

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

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

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

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

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

1249
        t->header = b;
195✔
1250
}
195✔
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) {
28✔
1259
        assert(t);
28✔
1260
        assert(height >= 1 || height == SIZE_MAX);
28✔
1261

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

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

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

1272
static const char* table_ersatz_string(const Table *t) {
20,026✔
1273
        switch (t->ersatz) {
20,026✔
1274
        case TABLE_ERSATZ_EMPTY:
1275
                return "";
1276
        case TABLE_ERSATZ_DASH:
15,336✔
1277
                return "-";
15,336✔
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) {
379✔
1288
        size_t *d;
379✔
1289

1290
        assert(t);
379✔
1291

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

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

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

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

1304
        return 0;
379✔
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, ...) {
264✔
1337
        size_t column;
264✔
1338
        va_list ap;
264✔
1339

1340
        assert(t);
264✔
1341

1342
        column = first_column;
264✔
1343

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

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

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

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

1361
        return 0;
264✔
1362
}
1363

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

1368
        assert(t);
899✔
1369

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

1377
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
11,930✔
1378
                bool listed = false;
11,031✔
1379
                va_list ap;
11,031✔
1380

1381
                va_start(ap, t);
11,031✔
1382
                for (;;) {
21,513✔
1383
                        size_t column;
21,513✔
1384

1385
                        column = va_arg(ap, size_t);
21,513✔
1386
                        if (column == SIZE_MAX)
21,513✔
1387
                                break;
1388
                        if (column == *i) {
11,418✔
1389
                                listed = true;
1390
                                break;
1391
                        }
1392
                }
1393
                va_end(ap);
11,031✔
1394

1395
                if (listed)
11,031✔
1396
                        continue;
936✔
1397

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

1401
        t->n_display_map = cur;
899✔
1402

1403
        return 0;
899✔
1404
}
1405

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

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

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

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

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

1419
                case TABLE_STRING:
29,478✔
1420
                case TABLE_STRING_WITH_ANSI:
1421
                case TABLE_FIELD:
1422
                case TABLE_HEADER:
1423
                        return strcmp(a->string, b->string);
29,478✔
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:
811✔
1452
                case TABLE_TIMESPAN_MSEC:
1453
                case TABLE_TIMESPAN_DAY:
1454
                        return CMP(a->timespan, b->timespan);
811✔
1455

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1546
        assert(t);
23,875✔
1547
        assert(t->sort_map);
23,875✔
1548

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

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

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

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

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

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

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

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

1586
                if (position == 0) {
690✔
1587
                        fputs(*p, f);
254✔
1588
                        position = our_len;
1589
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
872✔
1590
                        fprintf(f, " %s", *p);
384✔
1591
                        position = size_add(size_add(position, 1), our_len);
1,074✔
1592
                } else {
1593
                        fprintf(f, "\n%s", *p);
52✔
1594
                        position = our_len;
1595
                }
1596
        }
1597

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

1602
        return buf;
254✔
1603
}
1604

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

1612
        assert(d);
356,157✔
1613

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

1619
        switch (d->type) {
288,684✔
1620
        case TABLE_EMPTY:
19,786✔
1621
                return table_ersatz_string(t);
19,786✔
1622

1623
        case TABLE_STRING:
236,943✔
1624
        case TABLE_STRING_WITH_ANSI:
1625
        case TABLE_PATH:
1626
        case TABLE_PATH_BASENAME:
1627
        case TABLE_FIELD:
1628
        case TABLE_HEADER:
1629
        case TABLE_VERSION: {
1630
                _cleanup_free_ char *bn = NULL;
236,943✔
1631
                const char *s;
236,943✔
1632

1633
                if (d->type == TABLE_PATH_BASENAME)
236,943✔
1634
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
405✔
1635
                else
1636
                        s = d->string;
236,538✔
1637

1638
                if (d->uppercase && !avoid_uppercasing) {
236,943✔
1639
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
4,229✔
1640
                        if (!d->formatted)
4,229✔
1641
                                return NULL;
1642

1643
                        char *q = d->formatted;
1644
                        for (const char *p = s; *p; p++)
29,600✔
1645
                                *(q++) = (char) toupper((unsigned char) *p);
25,371✔
1646

1647
                        if (d->type == TABLE_FIELD)
4,229✔
1648
                                *(q++) = ':';
×
1649

1650
                        *q = 0;
4,229✔
1651
                        return d->formatted;
4,229✔
1652
                } else if (d->type == TABLE_FIELD) {
232,714✔
1653
                        d->formatted = strjoin(s, ":");
32,962✔
1654
                        if (!d->formatted)
32,962✔
1655
                                return NULL;
1656

1657
                        return d->formatted;
32,962✔
1658
                }
1659

1660
                if (bn) {
199,752✔
1661
                        d->formatted = TAKE_PTR(bn);
403✔
1662
                        return d->formatted;
403✔
1663
                }
1664

1665
                return d->string;
199,349✔
1666
        }
1667

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

1672
                d->formatted = strv_join(d->strv, "\n");
6,261✔
1673
                if (!d->formatted)
6,261✔
1674
                        return NULL;
1675
                break;
1676

1677
        case TABLE_STRV_WRAPPED: {
254✔
1678
                if (strv_isempty(d->strv))
254✔
1679
                        return table_ersatz_string(t);
×
1680

1681
                char *buf = format_strv_width(d->strv, column_width);
254✔
1682
                if (!buf)
254✔
1683
                        return NULL;
1684

1685
                free_and_replace(d->formatted, buf);
254✔
1686
                d->formatted_for_width = column_width;
254✔
1687
                if (have_soft)
254✔
1688
                        *have_soft = true;
169✔
1689

1690
                break;
19,832✔
1691
        }
1692

1693
        case TABLE_BOOLEAN:
4,727✔
1694
                return yes_no(d->boolean);
358,054✔
1695

1696
        case TABLE_BOOLEAN_CHECKMARK:
7,002✔
1697
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,780✔
1698

1699
        case TABLE_TIMESTAMP:
2,085✔
1700
        case TABLE_TIMESTAMP_UTC:
1701
        case TABLE_TIMESTAMP_RELATIVE:
1702
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1703
        case TABLE_TIMESTAMP_LEFT:
1704
        case TABLE_TIMESTAMP_DATE: {
1705
                _cleanup_free_ char *p = NULL;
152✔
1706
                char *ret;
2,085✔
1707

1708
                p = new(char,
2,085✔
1709
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1710
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1711
                if (!p)
2,085✔
1712
                        return NULL;
1713

1714
                if (d->type == TABLE_TIMESTAMP)
2,085✔
1715
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,401✔
1716
                else if (d->type == TABLE_TIMESTAMP_UTC)
684✔
1717
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1718
                else if (d->type == TABLE_TIMESTAMP_DATE)
684✔
1719
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
1✔
1720
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1721
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1722
                else
1723
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1724
                                                             d->timestamp, CLOCK_REALTIME,
1725
                                                             /* implicit_left= */ d->type == TABLE_TIMESTAMP_LEFT);
1726
                if (!ret)
2,085✔
1727
                        return "-";
1728

1729
                d->formatted = TAKE_PTR(p);
1,933✔
1730
                break;
1,933✔
1731
        }
1732

1733
        case TABLE_TIMESPAN:
1,498✔
1734
        case TABLE_TIMESPAN_MSEC:
1735
        case TABLE_TIMESPAN_DAY: {
1736
                _cleanup_free_ char *p = NULL;
×
1737

1738
                p = new(char, FORMAT_TIMESPAN_MAX);
1,498✔
1739
                if (!p)
1,498✔
1740
                        return NULL;
1741

1742
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,989✔
1743
                                     d->type == TABLE_TIMESPAN ? 0 :
1,491✔
1744
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1745
                        return "-";
1746

1747
                d->formatted = TAKE_PTR(p);
1,498✔
1748
                break;
1,498✔
1749
        }
1750

1751
        case TABLE_SIZE: {
360✔
1752
                _cleanup_free_ char *p = NULL;
238✔
1753

1754
                p = new(char, FORMAT_BYTES_MAX);
360✔
1755
                if (!p)
360✔
1756
                        return NULL;
1757

1758
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
360✔
1759
                        return table_ersatz_string(t);
238✔
1760

1761
                d->formatted = TAKE_PTR(p);
122✔
1762
                break;
122✔
1763
        }
1764

1765
        case TABLE_BPS: {
410✔
1766
                _cleanup_free_ char *p = NULL;
×
1767
                size_t n;
410✔
1768

1769
                p = new(char, FORMAT_BYTES_MAX+2);
410✔
1770
                if (!p)
410✔
1771
                        return NULL;
1772

1773
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
410✔
1774
                        return table_ersatz_string(t);
×
1775

1776
                n = strlen(p);
410✔
1777
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
410✔
1778

1779
                d->formatted = TAKE_PTR(p);
410✔
1780
                break;
410✔
1781
        }
1782

1783
        case TABLE_INT: {
260✔
1784
                _cleanup_free_ char *p = NULL;
×
1785

1786
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
738✔
1787
                if (!p)
260✔
1788
                        return NULL;
×
1789

1790
                sprintf(p, "%i", d->int_val);
260✔
1791
                d->formatted = TAKE_PTR(p);
260✔
1792
                break;
260✔
1793
        }
1794

1795
        case TABLE_INT8: {
3✔
1796
                _cleanup_free_ char *p = NULL;
×
1797

1798
                p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
10✔
1799
                if (!p)
3✔
1800
                        return NULL;
×
1801

1802
                sprintf(p, "%" PRIi8, d->int8);
3✔
1803
                d->formatted = TAKE_PTR(p);
3✔
1804
                break;
3✔
1805
        }
1806

1807
        case TABLE_INT16: {
3✔
1808
                _cleanup_free_ char *p = NULL;
×
1809

1810
                p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
14✔
1811
                if (!p)
3✔
1812
                        return NULL;
×
1813

1814
                sprintf(p, "%" PRIi16, d->int16);
3✔
1815
                d->formatted = TAKE_PTR(p);
3✔
1816
                break;
3✔
1817
        }
1818

1819
        case TABLE_INT32: {
3✔
1820
                _cleanup_free_ char *p = NULL;
×
1821

1822
                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
24✔
1823
                if (!p)
3✔
1824
                        return NULL;
×
1825

1826
                sprintf(p, "%" PRIi32, d->int32);
3✔
1827
                d->formatted = TAKE_PTR(p);
3✔
1828
                break;
3✔
1829
        }
1830

1831
        case TABLE_INT64: {
155✔
1832
                _cleanup_free_ char *p = NULL;
×
1833

1834
                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
319✔
1835
                if (!p)
155✔
1836
                        return NULL;
×
1837

1838
                sprintf(p, "%" PRIi64, d->int64);
155✔
1839
                d->formatted = TAKE_PTR(p);
155✔
1840
                break;
155✔
1841
        }
1842

1843
        case TABLE_UINT: {
285✔
1844
                _cleanup_free_ char *p = NULL;
×
1845

1846
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
413✔
1847
                if (!p)
285✔
1848
                        return NULL;
×
1849

1850
                sprintf(p, "%u", d->uint_val);
285✔
1851
                d->formatted = TAKE_PTR(p);
285✔
1852
                break;
285✔
1853
        }
1854

1855
        case TABLE_UINT8: {
43✔
1856
                _cleanup_free_ char *p = NULL;
×
1857

1858
                p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
46✔
1859
                if (!p)
43✔
1860
                        return NULL;
×
1861

1862
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1863
                d->formatted = TAKE_PTR(p);
43✔
1864
                break;
43✔
1865
        }
1866

1867
        case TABLE_UINT16: {
89✔
1868
                _cleanup_free_ char *p = NULL;
×
1869

1870
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
304✔
1871
                if (!p)
89✔
1872
                        return NULL;
×
1873

1874
                sprintf(p, "%" PRIu16, d->uint16);
89✔
1875
                d->formatted = TAKE_PTR(p);
89✔
1876
                break;
89✔
1877
        }
1878

1879
        case TABLE_UINT32: {
1,040✔
1880
                _cleanup_free_ char *p = NULL;
×
1881

1882
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,563✔
1883
                if (!p)
1,040✔
1884
                        return NULL;
×
1885

1886
                sprintf(p, "%" PRIu32, d->uint32);
1,040✔
1887
                d->formatted = TAKE_PTR(p);
1,040✔
1888
                break;
1,040✔
1889
        }
1890

1891
        case TABLE_UINT32_HEX: {
2✔
1892
                _cleanup_free_ char *p = NULL;
×
1893

1894
                p = new(char, 8 + 1);
2✔
1895
                if (!p)
2✔
1896
                        return NULL;
×
1897

1898
                sprintf(p, "%" PRIx32, d->uint32);
2✔
1899
                d->formatted = TAKE_PTR(p);
2✔
1900
                break;
2✔
1901
        }
1902

1903
        case TABLE_UINT32_HEX_0x: {
6✔
1904
                _cleanup_free_ char *p = NULL;
×
1905

1906
                p = new(char, 2 + 8 + 1);
6✔
1907
                if (!p)
6✔
1908
                        return NULL;
×
1909

1910
                sprintf(p, "0x%" PRIx32, d->uint32);
6✔
1911
                d->formatted = TAKE_PTR(p);
6✔
1912
                break;
6✔
1913
        }
1914

1915
        case TABLE_UINT64: {
1,490✔
1916
                _cleanup_free_ char *p = NULL;
×
1917

1918
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
6,560✔
1919
                if (!p)
1,490✔
1920
                        return NULL;
×
1921

1922
                sprintf(p, "%" PRIu64, d->uint64);
1,490✔
1923
                d->formatted = TAKE_PTR(p);
1,490✔
1924
                break;
1,490✔
1925
        }
1926

1927
        case TABLE_UINT64_HEX: {
38✔
1928
                _cleanup_free_ char *p = NULL;
×
1929

1930
                p = new(char, 16 + 1);
38✔
1931
                if (!p)
38✔
1932
                        return NULL;
×
1933

1934
                sprintf(p, "%" PRIx64, d->uint64);
38✔
1935
                d->formatted = TAKE_PTR(p);
38✔
1936
                break;
38✔
1937
        }
1938

1939
        case TABLE_UINT64_HEX_0x: {
×
1940
                _cleanup_free_ char *p = NULL;
×
1941

1942
                p = new(char, 2 + 16 + 1);
×
1943
                if (!p)
×
1944
                        return NULL;
×
1945

1946
                sprintf(p, "0x%" PRIx64, d->uint64);
×
1947
                d->formatted = TAKE_PTR(p);
×
1948
                break;
×
1949
        }
1950

1951
        case TABLE_PERCENT: {
2✔
1952
                _cleanup_free_ char *p = NULL;
×
1953

1954
                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
5✔
1955
                if (!p)
2✔
1956
                        return NULL;
×
1957

1958
                sprintf(p, "%i%%" , d->percent);
2✔
1959
                d->formatted = TAKE_PTR(p);
2✔
1960
                break;
2✔
1961
        }
1962

1963
        case TABLE_IFINDEX: {
223✔
1964
                _cleanup_free_ char *p = NULL;
×
1965

1966
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
223✔
1967
                        return NULL;
×
1968

1969
                d->formatted = TAKE_PTR(p);
223✔
1970
                break;
223✔
1971
        }
1972

1973
        case TABLE_IN_ADDR:
205✔
1974
        case TABLE_IN6_ADDR: {
1975
                _cleanup_free_ char *p = NULL;
×
1976

1977
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1978
                                      &d->address, &p) < 0)
205✔
1979
                        return NULL;
×
1980

1981
                d->formatted = TAKE_PTR(p);
205✔
1982
                break;
205✔
1983
        }
1984

1985
        case TABLE_ID128: {
1986
                char *p;
1,097✔
1987

1988
                p = new(char, SD_ID128_STRING_MAX);
1,097✔
1989
                if (!p)
1,097✔
1990
                        return NULL;
1991

1992
                d->formatted = sd_id128_to_string(d->id128, p);
1,097✔
1993
                break;
1,097✔
1994
        }
1995

1996
        case TABLE_UUID: {
1997
                char *p;
396✔
1998

1999
                p = new(char, SD_ID128_UUID_STRING_MAX);
396✔
2000
                if (!p)
396✔
2001
                        return NULL;
2002

2003
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
396✔
2004
                break;
396✔
2005
        }
2006

2007
        case TABLE_UID: {
408✔
2008
                char *p;
408✔
2009

2010
                if (!uid_is_valid(d->uid))
408✔
2011
                        return table_ersatz_string(t);
×
2012

2013
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,425✔
2014
                if (!p)
408✔
2015
                        return NULL;
2016
                sprintf(p, UID_FMT, d->uid);
408✔
2017

2018
                d->formatted = p;
408✔
2019
                break;
408✔
2020
        }
2021

2022
        case TABLE_GID: {
600✔
2023
                char *p;
600✔
2024

2025
                if (!gid_is_valid(d->gid))
600✔
2026
                        return table_ersatz_string(t);
×
2027

2028
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,763✔
2029
                if (!p)
600✔
2030
                        return NULL;
2031
                sprintf(p, GID_FMT, d->gid);
600✔
2032

2033
                d->formatted = p;
600✔
2034
                break;
600✔
2035
        }
2036

2037
        case TABLE_PID: {
214✔
2038
                char *p;
214✔
2039

2040
                if (!pid_is_valid(d->pid))
214✔
2041
                        return table_ersatz_string(t);
×
2042

2043
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
574✔
2044
                if (!p)
214✔
2045
                        return NULL;
2046
                sprintf(p, PID_FMT, d->pid);
214✔
2047

2048
                d->formatted = p;
214✔
2049
                break;
214✔
2050
        }
2051

2052
        case TABLE_SIGNAL: {
123✔
2053
                const char *suffix;
123✔
2054
                char *p;
123✔
2055

2056
                suffix = signal_to_string(d->int_val);
123✔
2057
                if (!suffix)
123✔
2058
                        return table_ersatz_string(t);
×
2059

2060
                p = strjoin("SIG", suffix);
123✔
2061
                if (!p)
123✔
2062
                        return NULL;
2063

2064
                d->formatted = p;
123✔
2065
                break;
123✔
2066
        }
2067

2068
        case TABLE_MODE: {
72✔
2069
                char *p;
72✔
2070

2071
                if (d->mode == MODE_INVALID)
72✔
2072
                        return table_ersatz_string(t);
×
2073

2074
                p = new(char, 4 + 1);
72✔
2075
                if (!p)
72✔
2076
                        return NULL;
2077

2078
                sprintf(p, "%04o", d->mode & 07777);
72✔
2079
                d->formatted = p;
72✔
2080
                break;
72✔
2081
        }
2082

2083
        case TABLE_MODE_INODE_TYPE:
2✔
2084

2085
                if (d->mode == MODE_INVALID)
2✔
2086
                        return table_ersatz_string(t);
×
2087

2088
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2089

2090
        case TABLE_DEVNUM:
3✔
2091
                if (devnum_is_zero(d->devnum))
3✔
2092
                        return table_ersatz_string(t);
2✔
2093

2094
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2095
                        return NULL;
2096

2097
                break;
2098

2099
        case TABLE_JSON: {
2,596✔
2100
                if (!d->json)
2,596✔
2101
                        return table_ersatz_string(t);
×
2102

2103
                char *p;
2,596✔
2104
                if (sd_json_variant_format(d->json, /* flags= */ 0, &p) < 0)
2,596✔
2105
                        return NULL;
2106

2107
                d->formatted = p;
2,596✔
2108
                break;
2,596✔
2109
        }
2110

2111
        default:
×
2112
                assert_not_reached();
×
2113
        }
2114

2115
        return d->formatted;
19,832✔
2116
}
2117

2118
static const char *table_data_format_strip_ansi(
355,438✔
2119
                Table *t,
2120
                TableData *d,
2121
                bool avoid_uppercasing,
2122
                size_t column_width,
2123
                bool *have_soft,
2124
                char **ret_buffer) {
2125

2126
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
2127

2128
        assert(ret_buffer);
355,438✔
2129

2130
        const char *c;
355,438✔
2131

2132
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
355,438✔
2133
        if (!c)
355,438✔
2134
                return NULL;
355,438✔
2135

2136
        if (d->type != TABLE_STRING_WITH_ANSI) {
355,438✔
2137
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
2138
                 * original string as-is */
2139
                *ret_buffer = NULL;
355,423✔
2140
                return c;
355,423✔
2141
        }
2142

2143
        _cleanup_free_ char *s = strdup(c);
15✔
2144
        if (!s)
15✔
2145
                return NULL;
2146

2147
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
2148
                return NULL;
2149

2150
        *ret_buffer = TAKE_PTR(s);
15✔
2151
        return *ret_buffer;
15✔
2152
}
2153

2154
static int console_width_height(
174,947✔
2155
                const char *s,
2156
                size_t *ret_width,
2157
                size_t *ret_height) {
2158

2159
        size_t max_width = 0, height = 0;
174,947✔
2160
        const char *p;
174,947✔
2161

2162
        assert(s);
174,947✔
2163

2164
        /* Determine the width and height in console character cells the specified string needs. */
2165

2166
        do {
178,199✔
2167
                size_t k;
178,199✔
2168

2169
                p = strchr(s, '\n');
178,199✔
2170
                if (p) {
178,199✔
2171
                        _cleanup_free_ char *c = NULL;
3,255✔
2172

2173
                        c = strndup(s, p - s);
3,255✔
2174
                        if (!c)
3,255✔
2175
                                return -ENOMEM;
×
2176

2177
                        k = utf8_console_width(c);
3,255✔
2178
                        s = p + 1;
3,255✔
2179
                } else {
2180
                        k = utf8_console_width(s);
174,944✔
2181
                        s = NULL;
174,944✔
2182
                }
2183
                if (k == SIZE_MAX)
178,199✔
2184
                        return -EINVAL;
2185
                if (k > max_width)
178,199✔
2186
                        max_width = k;
173,393✔
2187

2188
                height++;
178,199✔
2189
        } while (!isempty(s));
181,454✔
2190

2191
        if (ret_width)
174,947✔
2192
                *ret_width = max_width;
174,947✔
2193

2194
        if (ret_height)
174,947✔
2195
                *ret_height = height;
174,947✔
2196

2197
        return 0;
2198
}
2199

2200
static int table_data_requested_width_height(
174,947✔
2201
                Table *table,
2202
                TableData *d,
2203
                size_t available_width,
2204
                size_t *ret_width,
2205
                size_t *ret_height,
2206
                bool *have_soft) {
2207

2208
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
174,947✔
2209
        bool truncation_applied = false;
174,947✔
2210
        size_t width, height;
174,947✔
2211
        bool soft = false;
174,947✔
2212
        const char *t;
174,947✔
2213
        int r;
174,947✔
2214

2215
        t = table_data_format_strip_ansi(
174,947✔
2216
                        table,
2217
                        d,
2218
                        /* avoid_uppercasing= */ false,
2219
                        available_width,
2220
                        &soft,
2221
                        &buffer);
2222
        if (!t)
174,947✔
2223
                return -ENOMEM;
2224

2225
        if (table->cell_height_max != SIZE_MAX) {
174,947✔
2226
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
180✔
2227
                if (r < 0)
180✔
2228
                        return r;
2229
                if (r > 0)
180✔
2230
                        truncation_applied = true;
25✔
2231

2232
                t = truncated;
180✔
2233
        }
2234

2235
        r = console_width_height(t, &width, &height);
174,947✔
2236
        if (r < 0)
174,947✔
2237
                return r;
2238

2239
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
174,947✔
2240
                width = d->maximum_width;
×
2241

2242
        if (width < d->minimum_width)
174,947✔
2243
                width = d->minimum_width;
3,247✔
2244

2245
        if (ret_width)
174,947✔
2246
                *ret_width = width;
174,947✔
2247
        if (ret_height)
174,947✔
2248
                *ret_height = height;
174,947✔
2249
        if (have_soft && soft)
174,947✔
2250
                *have_soft = true;
169✔
2251

2252
        return truncation_applied;
174,947✔
2253
}
2254

2255
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
136,183✔
2256
        size_t w = 0, space, lspace, old_length, clickable_length;
136,183✔
2257
        _cleanup_free_ char *clickable = NULL;
136,183✔
2258
        const char *p;
136,183✔
2259
        char *ret;
136,183✔
2260
        int r;
136,183✔
2261

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

2264
        assert(str);
136,183✔
2265
        assert(percent <= 100);
136,183✔
2266

2267
        old_length = strlen(str);
136,183✔
2268

2269
        if (url) {
136,183✔
2270
                r = terminal_urlify(url, str, &clickable);
1,558✔
2271
                if (r < 0)
1,558✔
2272
                        return NULL;
2273

2274
                clickable_length = strlen(clickable);
1,558✔
2275
        } else
2276
                clickable_length = old_length;
2277

2278
        /* Determine current width on screen */
2279
        p = str;
136,183✔
2280
        while (p < str + old_length) {
2,007,289✔
2281
                char32_t c;
1,871,106✔
2282

2283
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,871,106✔
2284
                        p++, w++; /* count invalid chars as 1 */
×
2285
                        continue;
×
2286
                }
2287

2288
                p = utf8_next_char(p);
1,871,106✔
2289
                w += unichar_iswide(c) ? 2 : 1;
3,742,200✔
2290
        }
2291

2292
        /* Already wider than the target, if so, don't do anything */
2293
        if (w >= new_length)
136,183✔
2294
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2295

2296
        /* How much spaces shall we add? An how much on the left side? */
2297
        space = new_length - w;
136,183✔
2298
        lspace = space * percent / 100U;
136,183✔
2299

2300
        ret = new(char, space + clickable_length + 1);
136,183✔
2301
        if (!ret)
136,183✔
2302
                return NULL;
2303

2304
        for (size_t i = 0; i < lspace; i++)
764,574✔
2305
                ret[i] = ' ';
628,391✔
2306
        memcpy(ret + lspace, clickable ?: str, clickable_length);
136,183✔
2307
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,596,367✔
2308
                ret[i] = ' ';
3,460,184✔
2309

2310
        ret[space + clickable_length] = 0;
136,183✔
2311
        return ret;
136,183✔
2312
}
2313

2314
static bool table_data_isempty(const TableData *d) {
561✔
2315
        assert(d);
561✔
2316

2317
        if (d->type == TABLE_EMPTY)
561✔
2318
                return true;
2319

2320
        /* Let's also consider an empty strv as truly empty. */
2321
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
479✔
2322
                return strv_isempty(d->strv);
×
2323

2324
        if (d->type == TABLE_JSON)
479✔
2325
                return sd_json_variant_is_null(d->json);
×
2326

2327
        /* Note that an empty string we do not consider empty here! */
2328
        return false;
2329
}
2330

2331
static const char* table_data_color(const TableData *d) {
774✔
2332
        assert(d);
774✔
2333

2334
        if (d->color)
774✔
2335
                return d->color;
2336

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

2341
        if (d->type == TABLE_FIELD)
479✔
2342
                return ansi_bright_blue();
×
2343

2344
        return NULL;
2345
}
2346

2347
static const char* table_data_underline(const TableData *d) {
774✔
2348
        assert(d);
774✔
2349

2350
        if (d->underline)
774✔
2351
                return ansi_add_underline_grey();
×
2352

2353
        if (d->type == TABLE_HEADER)
774✔
2354
                return ansi_add_underline();
50✔
2355

2356
        return NULL;
2357
}
2358

2359
static const char* table_data_rgap_underline(const TableData *d) {
181,185✔
2360
        assert(d);
181,185✔
2361

2362
        if (d->rgap_underline)
181,185✔
2363
                return ansi_add_underline_grey();
300✔
2364

2365
        if (d->type == TABLE_HEADER)
180,885✔
2366
                return ansi_add_underline();
8,596✔
2367

2368
        return NULL;
2369
}
2370

2371
int table_print(Table *t, FILE *f) {
3,604✔
2372
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,604✔
2373
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2374
                *width = NULL;
3,604✔
2375
        _cleanup_free_ size_t *sorted = NULL;
3,604✔
2376
        uint64_t *column_weight, weight_sum;
3,604✔
2377
        int r;
3,604✔
2378

2379
        assert(t);
3,604✔
2380

2381
        if (!f)
3,604✔
2382
                f = stdout;
2,608✔
2383

2384
        /* Ensure we have no incomplete rows */
2385
        assert(t->n_cells % t->n_columns == 0);
3,604✔
2386

2387
        n_rows = t->n_cells / t->n_columns;
3,604✔
2388
        assert(n_rows > 0); /* at least the header row must be complete */
3,604✔
2389

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

2393
                sorted = new(size_t, n_rows);
215✔
2394
                if (!sorted)
215✔
2395
                        return -ENOMEM;
2396

2397
                for (size_t i = 0; i < n_rows; i++)
5,971✔
2398
                        sorted[i] = i * t->n_columns;
5,756✔
2399

2400
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
215✔
2401
        }
2402

2403
        if (t->display_map)
3,604✔
2404
                display_columns = t->n_display_map;
343✔
2405
        else
2406
                display_columns = t->n_columns;
3,261✔
2407

2408
        assert(display_columns > 0);
3,604✔
2409

2410
        minimum_width = newa(size_t, display_columns);
3,604✔
2411
        maximum_width = newa(size_t, display_columns);
3,604✔
2412
        requested_width = newa(size_t, display_columns);
3,604✔
2413
        column_weight = newa0(uint64_t, display_columns);
3,604✔
2414

2415
        for (size_t j = 0; j < display_columns; j++) {
14,202✔
2416
                minimum_width[j] = 1;
10,598✔
2417
                maximum_width[j] = SIZE_MAX;
10,598✔
2418
        }
2419

2420
        for (unsigned pass = 0; pass < 2; pass++) {
3,622✔
2421
                /* First pass: determine column sizes */
2422

2423
                for (size_t j = 0; j < display_columns; j++)
14,258✔
2424
                        requested_width[j] = SIZE_MAX;
10,636✔
2425

2426
                bool any_soft = false;
3,622✔
2427

2428
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
56,229✔
2429
                        TableData **row;
52,607✔
2430

2431
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2432
                         * hence we don't care for sorted[] during the first pass. */
2433
                        row = t->data + i * t->n_columns;
52,607✔
2434

2435
                        for (size_t j = 0; j < display_columns; j++) {
227,554✔
2436
                                TableData *d;
174,947✔
2437
                                size_t req_width, req_height;
174,947✔
2438

2439
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
174,947✔
2440

2441
                                r = table_data_requested_width_height(t, d,
175,191✔
2442
                                                                      width ? width[j] : SIZE_MAX,
244✔
2443
                                                                      &req_width, &req_height, &any_soft);
2444
                                if (r < 0)
174,947✔
2445
                                        return r;
×
2446
                                if (r > 0) { /* Truncated because too many lines? */
174,947✔
2447
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2448
                                        const char *field;
25✔
2449

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

2455
                                        field = table_data_format_strip_ansi(
28✔
2456
                                                        t,
2457
                                                        d,
2458
                                                        /* avoid_uppercasing= */ false,
2459
                                                        width ? width[j] : SIZE_MAX,
3✔
2460
                                                        &any_soft,
2461
                                                        &buffer);
2462
                                        if (!field)
25✔
2463
                                                return -ENOMEM;
2464

2465
                                        assert_se(t->cell_height_max > 0);
25✔
2466
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2467
                                        if (r < 0)
25✔
2468
                                                return r;
2469

2470
                                        req_width = MAX(req_width,
25✔
2471
                                                        utf8_console_width(last) +
2472
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2473
                                }
2474

2475
                                /* Determine the biggest width that any cell in this column would like to have */
2476
                                if (requested_width[j] == SIZE_MAX ||
174,947✔
2477
                                    requested_width[j] < req_width)
164,343✔
2478
                                        requested_width[j] = req_width;
23,767✔
2479

2480
                                /* Determine the minimum width any cell in this column needs */
2481
                                if (minimum_width[j] < d->minimum_width)
174,947✔
2482
                                        minimum_width[j] = d->minimum_width;
27✔
2483

2484
                                /* Determine the maximum width any cell in this column needs */
2485
                                if (d->maximum_width != SIZE_MAX &&
174,947✔
2486
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2487
                                     maximum_width[j] > d->maximum_width))
2488
                                        maximum_width[j] = d->maximum_width;
20✔
2489

2490
                                /* Determine the full columns weight */
2491
                                column_weight[j] += d->weight;
174,947✔
2492
                        }
2493
                }
2494

2495
                /* One space between each column */
2496
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,622✔
2497

2498
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2499
                weight_sum = 0;
3,622✔
2500
                for (size_t j = 0; j < display_columns; j++) {
14,258✔
2501
                        weight_sum += column_weight[j];
10,636✔
2502

2503
                        table_minimum_width += minimum_width[j];
10,636✔
2504

2505
                        if (maximum_width[j] == SIZE_MAX)
10,636✔
2506
                                table_maximum_width = SIZE_MAX;
2507
                        else
2508
                                table_maximum_width += maximum_width[j];
20✔
2509

2510
                        table_requested_width += requested_width[j];
10,636✔
2511
                }
2512

2513
                /* Calculate effective table width */
2514
                if (t->width != 0 && t->width != SIZE_MAX)
3,622✔
2515
                        table_effective_width = t->width;
2516
                else if (t->width == 0 ||
3,615✔
2517
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,544✔
2518
                        table_effective_width = table_requested_width;
2519
                else
2520
                        table_effective_width = MIN(table_requested_width, columns());
40✔
2521

2522
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,622✔
2523
                        table_effective_width = table_maximum_width;
×
2524

2525
                if (table_effective_width < table_minimum_width)
3,622✔
2526
                        table_effective_width = table_minimum_width;
2✔
2527

2528
                if (!width)
3,622✔
2529
                        width = newa(size_t, display_columns);
3,604✔
2530

2531
                if (table_effective_width >= table_requested_width) {
3,622✔
2532
                        size_t extra;
3,600✔
2533

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

2537
                        extra = table_effective_width - table_requested_width;
3,600✔
2538

2539
                        for (size_t j = 0; j < display_columns; j++) {
14,180✔
2540
                                size_t delta;
10,580✔
2541

2542
                                if (weight_sum == 0)
10,580✔
2543
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2544
                                else
2545
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
10,548✔
2546

2547
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
10,580✔
2548
                                        width[j] = maximum_width[j];
2✔
2549

2550
                                if (width[j] < minimum_width[j])
10,580✔
2551
                                        width[j] = minimum_width[j];
×
2552

2553
                                delta = LESS_BY(width[j], requested_width[j]);
10,580✔
2554

2555
                                /* Subtract what we just added from the rest */
2556
                                if (extra > delta)
10,580✔
2557
                                        extra -= delta;
13✔
2558
                                else
2559
                                        extra = 0;
2560

2561
                                assert(weight_sum >= column_weight[j]);
10,580✔
2562
                                weight_sum -= column_weight[j];
10,580✔
2563
                        }
2564

2565
                        break; /* Every column should be happy, no need to repeat calculations. */
3,604✔
2566
                } else {
2567
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2568
                         * with the minimum they need, and then distribute anything left. */
2569
                        bool finalize = false;
22✔
2570
                        size_t extra;
22✔
2571

2572
                        extra = table_effective_width - table_minimum_width;
22✔
2573

2574
                        for (size_t j = 0; j < display_columns; j++)
78✔
2575
                                width[j] = SIZE_MAX;
56✔
2576

2577
                        for (;;) {
64✔
2578
                                bool restart = false;
64✔
2579

2580
                                for (size_t j = 0; j < display_columns; j++) {
186✔
2581
                                        size_t delta, w;
142✔
2582

2583
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2584
                                        if (width[j] != SIZE_MAX)
142✔
2585
                                                continue;
47✔
2586

2587
                                        if (weight_sum == 0)
95✔
2588
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2589
                                        else
2590
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
95✔
2591

2592
                                        if (w >= requested_width[j]) {
95✔
2593
                                                /* Never give more than requested. If we hit a column like this, there's more
2594
                                                 * space to allocate to other columns which means we need to restart the
2595
                                                 * iteration. However, if we hit a column like this, let's assign it the space
2596
                                                 * it wanted for good early. */
2597

2598
                                                w = requested_width[j];
2599
                                                restart = true;
2600

2601
                                        } else if (!finalize)
74✔
2602
                                                continue;
39✔
2603

2604
                                        width[j] = w;
56✔
2605

2606
                                        assert(w >= minimum_width[j]);
56✔
2607
                                        delta = w - minimum_width[j];
56✔
2608

2609
                                        assert(delta <= extra);
56✔
2610
                                        extra -= delta;
56✔
2611

2612
                                        assert(weight_sum >= column_weight[j]);
56✔
2613
                                        weight_sum -= column_weight[j];
56✔
2614

2615
                                        if (restart && !finalize)
56✔
2616
                                                break;
2617
                                }
2618

2619
                                if (finalize)
64✔
2620
                                        break;
2621

2622
                                if (!restart)
42✔
2623
                                        finalize = true;
22✔
2624
                        }
2625

2626
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
22✔
2627
                                        * let's try to reformat them with the new widths. Otherwise, let's
2628
                                        * move on. */
2629
                                break;
2630
                }
2631
        }
2632

2633
        /* Second pass: show output */
2634
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
56,116✔
2635
                size_t n_subline = 0;
52,512✔
2636
                bool more_sublines;
52,512✔
2637
                TableData **row;
52,512✔
2638

2639
                if (sorted)
52,512✔
2640
                        row = t->data + sorted[i];
5,740✔
2641
                else
2642
                        row = t->data + i * t->n_columns;
46,772✔
2643

2644
                do {
55,752✔
2645
                        const char *gap_color = NULL, *gap_underline = NULL;
55,752✔
2646
                        more_sublines = false;
55,752✔
2647

2648
                        for (size_t j = 0; j < display_columns; j++) {
236,937✔
2649
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
181,185✔
2650
                                bool lines_truncated = false;
181,185✔
2651
                                const char *field, *color = NULL, *underline = NULL;
181,185✔
2652
                                TableData *d;
181,185✔
2653
                                size_t l;
181,185✔
2654

2655
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
181,185✔
2656

2657
                                if (colors_enabled())
181,185✔
2658
                                        field = table_data_format(
719✔
2659
                                                        t,
2660
                                                        d,
2661
                                                        /* avoid_uppercasing= */ false,
2662
                                                        width[j],
719✔
2663
                                                        /* have_soft= */ NULL);
2664
                                else
2665
                                        field = table_data_format_strip_ansi(
180,466✔
2666
                                                        t,
2667
                                                        d,
2668
                                                        /* avoid_uppercasing= */ false,
2669
                                                        width[j],
180,466✔
2670
                                                        /* have_soft= */ NULL,
2671
                                                        &stripped_ansi_buffer);
2672
                                if (!field)
181,185✔
2673
                                        return -ENOMEM;
2674

2675
                                r = string_extract_line(field, n_subline, &extracted);
181,185✔
2676
                                if (r < 0)
181,185✔
2677
                                        return r;
2678
                                if (r > 0) {
181,185✔
2679
                                        /* There are more lines to come */
2680
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,277✔
2681
                                                more_sublines = true; /* There are more lines to come */
2682
                                        else
2683
                                                lines_truncated = true;
25✔
2684
                                }
2685
                                if (extracted)
181,185✔
2686
                                        field = extracted;
7,942✔
2687

2688
                                l = utf8_console_width(field);
181,185✔
2689
                                if (l > width[j]) {
181,185✔
2690
                                        /* Field is wider than allocated space. Let's ellipsize */
2691

2692
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
35✔
2693
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2694
                                        if (!buffer)
35✔
2695
                                                return -ENOMEM;
2696

2697
                                        field = buffer;
2698
                                } else {
2699
                                        if (lines_truncated) {
181,150✔
2700
                                                _cleanup_free_ char *padded = NULL;
25✔
2701

2702
                                                /* We truncated more lines of this cell, let's add an
2703
                                                 * ellipsis. We first append it, but that might make our
2704
                                                 * string grow above what we have space for, hence ellipsize
2705
                                                 * right after. This will truncate the ellipsis and add a new
2706
                                                 * one. */
2707

2708
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2709
                                                if (!padded)
25✔
2710
                                                        return -ENOMEM;
2711

2712
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2713
                                                if (!buffer)
25✔
2714
                                                        return -ENOMEM;
2715

2716
                                                field = buffer;
25✔
2717
                                                l = utf8_console_width(field);
25✔
2718
                                        }
2719

2720
                                        if (l < width[j]) {
181,150✔
2721
                                                _cleanup_free_ char *aligned = NULL;
×
2722
                                                /* Field is shorter than allocated space. Let's align with spaces */
2723

2724
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
136,183✔
2725
                                                if (!aligned)
136,183✔
2726
                                                        return -ENOMEM;
×
2727

2728
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2729
                                                if (j == display_columns - 1 &&
185,798✔
2730
                                                    (!colors_enabled() || !table_data_color(d)) &&
49,670✔
2731
                                                    (!underline_enabled() || !table_data_underline(d)) &&
99,282✔
2732
                                                    (!urlify_enabled() || !d->url))
49,664✔
2733
                                                        delete_trailing_chars(aligned, NULL);
49,612✔
2734

2735
                                                free_and_replace(buffer, aligned);
136,183✔
2736
                                                field = buffer;
136,183✔
2737
                                        }
2738
                                }
2739

2740
                                if (l >= width[j] && d->url) {
181,185✔
2741
                                        _cleanup_free_ char *clickable = NULL;
×
2742

2743
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2744
                                        if (r < 0)
22✔
2745
                                                return r;
×
2746

2747
                                        free_and_replace(buffer, clickable);
22✔
2748
                                        field = buffer;
22✔
2749
                                }
2750

2751
                                if (colors_enabled() && gap_color)
181,185✔
2752
                                        fputs(gap_color, f);
×
2753
                                if (underline_enabled() && gap_underline)
181,185✔
2754
                                        fputs(gap_underline, f);
19✔
2755

2756
                                if (j > 0)
181,185✔
2757
                                        fputc(' ', f); /* column separator left of cell */
125,433✔
2758

2759
                                /* Undo gap color/underline */
2760
                                if ((colors_enabled() && gap_color) ||
362,370✔
2761
                                    (underline_enabled() && gap_underline))
181,904✔
2762
                                        fputs(ANSI_NORMAL, f);
19✔
2763

2764
                                if (colors_enabled()) {
181,185✔
2765
                                        color = table_data_color(d);
719✔
2766
                                        if (color)
719✔
2767
                                                fputs(color, f);
295✔
2768
                                }
2769

2770
                                if (underline_enabled()) {
181,185✔
2771
                                        underline = table_data_underline(d);
719✔
2772
                                        if (underline)
719✔
2773
                                                fputs(underline, f);
22✔
2774
                                }
2775

2776
                                fputs(field, f);
181,185✔
2777

2778
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2779
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
181,194✔
2780
                                        fputs(ANSI_NORMAL, f);
320✔
2781

2782
                                gap_color = d->rgap_color;
181,185✔
2783
                                gap_underline = table_data_rgap_underline(d);
181,185✔
2784
                        }
2785

2786
                        fputc('\n', f);
55,752✔
2787
                        n_subline++;
55,752✔
2788
                } while (more_sublines);
55,752✔
2789
        }
2790

2791
        return fflush_and_check(f);
3,604✔
2792
}
2793

2794
int table_format(Table *t, char **ret) {
41✔
2795
        _cleanup_(memstream_done) MemStream m = {};
41✔
2796
        FILE *f;
41✔
2797
        int r;
41✔
2798

2799
        assert(t);
41✔
2800
        assert(ret);
41✔
2801

2802
        f = memstream_init(&m);
41✔
2803
        if (!f)
41✔
2804
                return -ENOMEM;
2805

2806
        r = table_print(t, f);
41✔
2807
        if (r < 0)
41✔
2808
                return r;
2809

2810
        return memstream_finalize(&m, ret, NULL);
41✔
2811
}
2812

2813
size_t table_get_rows(Table *t) {
1,812✔
2814
        if (!t)
1,812✔
2815
                return 0;
2816

2817
        assert(t->n_columns > 0);
1,812✔
2818
        return t->n_cells / t->n_columns;
1,812✔
2819
}
2820

2821
size_t table_get_columns(Table *t) {
34✔
2822
        if (!t)
34✔
2823
                return 0;
2824

2825
        assert(t->n_columns > 0);
34✔
2826
        return t->n_columns;
2827
}
2828

2829
size_t table_get_current_column(Table *t) {
80✔
2830
        if (!t)
80✔
2831
                return 0;
2832

2833
        assert(t->n_columns > 0);
80✔
2834
        return t->n_cells % t->n_columns;
80✔
2835
}
2836

2837
int table_set_reverse(Table *t, size_t column, bool b) {
85✔
2838
        assert(t);
85✔
2839
        assert(column < t->n_columns);
85✔
2840

2841
        if (!t->reverse_map) {
85✔
2842
                if (!b)
85✔
2843
                        return 0;
2844

2845
                t->reverse_map = new0(bool, t->n_columns);
31✔
2846
                if (!t->reverse_map)
31✔
2847
                        return -ENOMEM;
2848
        }
2849

2850
        t->reverse_map[column] = b;
31✔
2851
        return 0;
31✔
2852
}
2853

2854
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,569✔
2855
        size_t i;
8,569✔
2856

2857
        assert(t);
8,569✔
2858

2859
        if (column >= t->n_columns)
8,569✔
2860
                return NULL;
2861

2862
        i = row * t->n_columns + column;
8,569✔
2863
        if (i >= t->n_cells)
8,569✔
2864
                return NULL;
2865

2866
        return TABLE_INDEX_TO_CELL(i);
8,569✔
2867
}
2868

2869
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2870
        TableData *d;
4,374✔
2871

2872
        assert(t);
4,374✔
2873

2874
        d = table_get_data(t, cell);
4,374✔
2875
        if (!d)
4,374✔
2876
                return NULL;
2877

2878
        return d->data;
4,374✔
2879
}
2880

2881
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2882
        TableCell *cell;
4,374✔
2883

2884
        cell = table_get_cell(t, row, column);
4,374✔
2885
        if (!cell)
4,374✔
2886
                return NULL;
2887

2888
        return table_get(t, cell);
4,374✔
2889
}
2890

2891
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
4,887✔
2892

2893
        switch (d->type) {
4,887✔
2894

2895
        case TABLE_EMPTY:
579✔
2896
                return sd_json_variant_new_null(ret);
579✔
2897

2898
        case TABLE_STRING:
2,346✔
2899
        case TABLE_PATH:
2900
        case TABLE_PATH_BASENAME:
2901
        case TABLE_FIELD:
2902
        case TABLE_HEADER:
2903
        case TABLE_VERSION:
2904
                return sd_json_variant_new_string(ret, d->string);
2,346✔
2905

2906
        case TABLE_STRV:
3✔
2907
        case TABLE_STRV_WRAPPED:
2908
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2909

2910
        case TABLE_BOOLEAN_CHECKMARK:
176✔
2911
        case TABLE_BOOLEAN:
2912
                return sd_json_variant_new_boolean(ret, d->boolean);
176✔
2913

2914
        case TABLE_TIMESTAMP:
251✔
2915
        case TABLE_TIMESTAMP_UTC:
2916
        case TABLE_TIMESTAMP_RELATIVE:
2917
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2918
        case TABLE_TIMESTAMP_LEFT:
2919
        case TABLE_TIMESTAMP_DATE:
2920
                if (d->timestamp == USEC_INFINITY)
251✔
2921
                        return sd_json_variant_new_null(ret);
×
2922

2923
                return sd_json_variant_new_unsigned(ret, d->timestamp);
251✔
2924

2925
        case TABLE_TIMESPAN:
×
2926
        case TABLE_TIMESPAN_MSEC:
2927
        case TABLE_TIMESPAN_DAY:
2928
                if (d->timespan == USEC_INFINITY)
×
2929
                        return sd_json_variant_new_null(ret);
×
2930

2931
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2932

2933
        case TABLE_SIZE:
36✔
2934
        case TABLE_BPS:
2935
                if (d->size == UINT64_MAX)
36✔
2936
                        return sd_json_variant_new_null(ret);
16✔
2937

2938
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2939

2940
        case TABLE_INT:
27✔
2941
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2942

2943
        case TABLE_INT8:
6✔
2944
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2945

2946
        case TABLE_INT16:
6✔
2947
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2948

2949
        case TABLE_INT32:
6✔
2950
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2951

2952
        case TABLE_INT64:
46✔
2953
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2954

2955
        case TABLE_UINT:
43✔
2956
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2957

2958
        case TABLE_UINT8:
4✔
2959
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2960

2961
        case TABLE_UINT16:
4✔
2962
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2963

2964
        case TABLE_UINT32:
111✔
2965
        case TABLE_UINT32_HEX:
2966
        case TABLE_UINT32_HEX_0x:
2967
                return sd_json_variant_new_unsigned(ret, d->uint32);
111✔
2968

2969
        case TABLE_UINT64:
434✔
2970
        case TABLE_UINT64_HEX:
2971
        case TABLE_UINT64_HEX_0x:
2972
                return sd_json_variant_new_unsigned(ret, d->uint64);
434✔
2973

2974
        case TABLE_PERCENT:
×
2975
                return sd_json_variant_new_integer(ret, d->percent);
×
2976

2977
        case TABLE_IFINDEX:
×
2978
                if (d->ifindex <= 0)
×
2979
                        return sd_json_variant_new_null(ret);
×
2980

2981
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2982

2983
        case TABLE_IN_ADDR:
2984
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2985

2986
        case TABLE_IN6_ADDR:
2987
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2988

2989
        case TABLE_ID128:
502✔
2990
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2991

2992
        case TABLE_UUID:
115✔
2993
                return sd_json_variant_new_uuid(ret, d->id128);
115✔
2994

2995
        case TABLE_UID:
8✔
2996
                if (!uid_is_valid(d->uid))
8✔
2997
                        return sd_json_variant_new_null(ret);
×
2998

2999
                return sd_json_variant_new_integer(ret, d->uid);
8✔
3000

3001
        case TABLE_GID:
8✔
3002
                if (!gid_is_valid(d->gid))
8✔
3003
                        return sd_json_variant_new_null(ret);
×
3004

3005
                return sd_json_variant_new_integer(ret, d->gid);
8✔
3006

3007
        case TABLE_PID:
14✔
3008
                if (!pid_is_valid(d->pid))
14✔
3009
                        return sd_json_variant_new_null(ret);
×
3010

3011
                return sd_json_variant_new_integer(ret, d->pid);
14✔
3012

3013
        case TABLE_SIGNAL:
8✔
3014
                if (!SIGNAL_VALID(d->int_val))
8✔
3015
                        return sd_json_variant_new_null(ret);
×
3016

3017
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
3018

3019
        case TABLE_MODE:
147✔
3020
        case TABLE_MODE_INODE_TYPE:
3021
                if (d->mode == MODE_INVALID)
147✔
3022
                        return sd_json_variant_new_null(ret);
×
3023

3024
                return sd_json_variant_new_unsigned(ret, d->mode);
147✔
3025

3026
        case TABLE_DEVNUM:
4✔
3027
                if (devnum_is_zero(d->devnum))
4✔
3028
                        return sd_json_variant_new_null(ret);
2✔
3029

3030
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
3031
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
3032
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
3033

3034
        case TABLE_JSON:
×
3035
                if (!d->json)
×
3036
                        return sd_json_variant_new_null(ret);
×
3037

3038
                if (ret)
×
3039
                        *ret = sd_json_variant_ref(d->json);
×
3040

3041
                return 0;
3042

3043
        case TABLE_STRING_WITH_ANSI: {
3✔
3044
                _cleanup_free_ char *s = strdup(d->string);
3✔
3045
                if (!s)
3✔
3046
                        return -ENOMEM;
3047

3048
                /* We strip the ANSI data when outputting to JSON */
3049
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
3050
                        return -ENOMEM;
3051

3052
                return sd_json_variant_new_string(ret, s);
3✔
3053
        }
3054

3055
        default:
3056
                return -EINVAL;
3057
        }
3058
}
3059

3060
char* table_mangle_to_json_field_name(const char *str) {
843✔
3061
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
3062
         * field name can be hence this is a bit vague and black magic. Here's what we do:
3063
         *  - Convert spaces to underscores
3064
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
3065
         *  - Convert most other symbols to underscores (for similar reasons)
3066
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
3067
         */
3068

3069
        bool new_word = true;
843✔
3070
        char *c;
843✔
3071

3072
        assert(str);
843✔
3073

3074
        c = strdup(str);
843✔
3075
        if (!c)
843✔
3076
                return NULL;
3077

3078
        for (char *x = c; *x; x++) {
6,054✔
3079
                if (!strchr(ALPHANUMERICAL, *x)) {
5,211✔
3080
                        *x = '_';
179✔
3081
                        new_word = true;
179✔
3082
                        continue;
179✔
3083
                }
3084

3085
                if (new_word) {
5,032✔
3086
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
1,017✔
3087
                                                                  * then we assume the whole word is all-caps
3088
                                                                  * and avoid lowercasing it. */
3089
                                *x = ascii_tolower(*x);
1,016✔
3090
                        new_word = false;
3091
                }
3092
        }
3093

3094
        return c;
3095
}
3096

3097
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
829✔
3098
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
829✔
3099
        const char *n;
829✔
3100

3101
        assert(t);
829✔
3102
        assert(d);
829✔
3103
        assert(ret);
829✔
3104

3105
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
829✔
3106
                n = d->string;
829✔
3107
        else {
3108
                n = table_data_format_strip_ansi(
×
3109
                                t,
3110
                                d,
3111
                                /* avoid_uppercasing= */ true,
3112
                                /* column_width= */ SIZE_MAX,
3113
                                /* have_soft= */ NULL,
3114
                                &buffer);
3115
                if (!n)
×
3116
                        return -ENOMEM;
3117
        }
3118

3119
        mangled = table_mangle_to_json_field_name(n);
829✔
3120
        if (!mangled)
829✔
3121
                return -ENOMEM;
3122

3123
        *ret = TAKE_PTR(mangled);
829✔
3124
        return 0;
829✔
3125
}
3126

3127
static const char *table_get_json_field_name(Table *t, size_t idx) {
919✔
3128
        assert(t);
919✔
3129

3130
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
919✔
3131
}
3132

3133
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
122✔
3134
        sd_json_variant **rows = NULL, **elements = NULL;
122✔
3135
        _cleanup_free_ size_t *sorted = NULL;
122✔
3136
        size_t n_rows, display_columns;
122✔
3137
        int r;
122✔
3138

3139
        assert(t);
122✔
3140
        assert(!t->vertical);
122✔
3141

3142
        /* Ensure we have no incomplete rows */
3143
        assert(t->n_columns > 0);
122✔
3144
        assert(t->n_cells % t->n_columns == 0);
122✔
3145

3146
        n_rows = t->n_cells / t->n_columns;
122✔
3147
        assert(n_rows > 0); /* at least the header row must be complete */
122✔
3148

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

3152
                sorted = new(size_t, n_rows);
32✔
3153
                if (!sorted)
32✔
3154
                        return -ENOMEM;
3155

3156
                for (size_t i = 0; i < n_rows; i++)
197✔
3157
                        sorted[i] = i * t->n_columns;
165✔
3158

3159
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
32✔
3160
        }
3161

3162
        if (t->display_map)
122✔
3163
                display_columns = t->n_display_map;
67✔
3164
        else
3165
                display_columns = t->n_columns;
55✔
3166
        assert(display_columns > 0);
122✔
3167

3168
        elements = new0(sd_json_variant*, display_columns * 2);
122✔
3169
        if (!elements)
122✔
3170
                return -ENOMEM;
3171

3172
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
122✔
3173

3174
        for (size_t j = 0; j < display_columns; j++) {
1,037✔
3175
                _cleanup_free_ char *mangled = NULL;
915✔
3176
                const char *n;
915✔
3177
                size_t c;
915✔
3178

3179
                c = t->display_map ? t->display_map[j] : j;
915✔
3180

3181
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3182
                n = table_get_json_field_name(t, c);
915✔
3183
                if (!n) {
915✔
3184
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
827✔
3185
                        if (r < 0)
827✔
3186
                                return r;
3187

3188
                        n = mangled;
827✔
3189
                }
3190

3191
                r = sd_json_variant_new_string(elements + j*2, n);
915✔
3192
                if (r < 0)
915✔
3193
                        return r;
3194
        }
3195

3196
        rows = new0(sd_json_variant*, n_rows-1);
122✔
3197
        if (!rows)
122✔
3198
                return -ENOMEM;
3199

3200
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
122✔
3201

3202
        for (size_t i = 1; i < n_rows; i++) {
1,211✔
3203
                TableData **row;
1,089✔
3204

3205
                if (sorted)
1,089✔
3206
                        row = t->data + sorted[i];
133✔
3207
                else
3208
                        row = t->data + i * t->n_columns;
956✔
3209

3210
                for (size_t j = 0; j < display_columns; j++) {
5,972✔
3211
                        TableData *d;
4,883✔
3212
                        size_t k;
4,883✔
3213

3214
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
4,883✔
3215

3216
                        k = j*2+1;
4,883✔
3217
                        elements[k] = sd_json_variant_unref(elements[k]);
4,883✔
3218

3219
                        r = table_data_to_json(d, elements + k);
4,883✔
3220
                        if (r < 0)
4,883✔
3221
                                return r;
3222
                }
3223

3224
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1,089✔
3225
                if (r < 0)
1,089✔
3226
                        return r;
3227
        }
3228

3229
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
122✔
3230
}
3231

3232
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3233
        sd_json_variant **elements = NULL;
1✔
3234
        size_t n_elements = 0;
1✔
3235
        int r;
1✔
3236

3237
        assert(t);
1✔
3238
        assert(t->vertical);
1✔
3239

3240
        if (t->n_columns != 2)
1✔
3241
                return -EINVAL;
1✔
3242

3243
        /* Ensure we have no incomplete rows */
3244
        assert(t->n_cells % t->n_columns == 0);
1✔
3245

3246
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3247
        if (!elements)
1✔
3248
                return -ENOMEM;
3249

3250
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3251

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

3254
                if (i % t->n_columns == 0) {
8✔
3255
                        _cleanup_free_ char *mangled = NULL;
4✔
3256
                        const char *n;
4✔
3257

3258
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3259
                        if (!n) {
4✔
3260
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3261
                                if (r < 0)
2✔
3262
                                        return r;
×
3263

3264
                                n = mangled;
2✔
3265
                        }
3266

3267
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3268
                } else
3269
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3270
                if (r < 0)
8✔
3271
                        return r;
3272

3273
                n_elements++;
8✔
3274
        }
3275

3276
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3277
}
3278

3279
int table_to_json(Table *t, sd_json_variant **ret) {
123✔
3280
        assert(t);
123✔
3281

3282
        if (t->vertical)
123✔
3283
                return table_to_json_vertical(t, ret);
1✔
3284

3285
        return table_to_json_regular(t, ret);
122✔
3286
}
3287

3288
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
502✔
3289
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
502✔
3290
        int r;
502✔
3291

3292
        assert(t);
502✔
3293

3294
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
502✔
3295
                return table_print(t, f);
431✔
3296

3297
        if (!f)
71✔
3298
                f = stdout;
2✔
3299

3300
        r = table_to_json(t, &v);
71✔
3301
        if (r < 0)
71✔
3302
                return r;
3303

3304
        sd_json_variant_dump(v, flags, f, NULL);
71✔
3305

3306
        return fflush_and_check(f);
71✔
3307
}
3308

3309
int table_print_with_pager(
500✔
3310
                Table *t,
3311
                sd_json_format_flags_t json_format_flags,
3312
                PagerFlags pager_flags,
3313
                bool show_header) {
3314

3315
        bool saved_header;
500✔
3316
        int r;
500✔
3317

3318
        assert(t);
500✔
3319

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

3323
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
500✔
3324
                pager_open(pager_flags);
474✔
3325

3326
        saved_header = t->header;
500✔
3327
        t->header = show_header;
500✔
3328
        r = table_print_json(t, stdout, json_format_flags);
500✔
3329
        t->header = saved_header;
500✔
3330
        if (r < 0)
500✔
3331
                return table_log_print_error(r);
×
3332

3333
        return 0;
3334
}
3335

3336
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
652✔
3337
        int r;
652✔
3338

3339
        assert(t);
652✔
3340

3341
        if (name) {
652✔
3342
                size_t m;
650✔
3343

3344
                m = MAX(idx + 1, t->n_json_fields);
650✔
3345
                if (!GREEDY_REALLOC0(t->json_fields, m))
650✔
3346
                        return -ENOMEM;
3347

3348
                r = free_and_strdup(t->json_fields + idx, name);
650✔
3349
                if (r < 0)
650✔
3350
                        return r;
3351

3352
                t->n_json_fields = m;
650✔
3353
                return r;
650✔
3354
        } else {
3355
                if (idx >= t->n_json_fields)
2✔
3356
                        return 0;
3357

3358
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3359
                return 1;
2✔
3360
        }
3361
}
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