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

systemd / systemd / 20495933613

24 Dec 2025 06:25PM UTC coverage: 72.64% (-0.06%) from 72.701%
20495933613

push

github

YHNdnzj
man: document version for BindNetworkInterface instead of using ignore list

The ignore list is for older stuff, all new interfaces must be documented
with a version.

Follow-up for c1c787651

309829 of 426528 relevant lines covered (72.64%)

1135837.86 hits per line

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

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

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

6
#include "sd-id128.h"
7

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

34
#define DEFAULT_WEIGHT 100
35

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

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

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

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

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

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

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

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

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

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

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

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

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

120
        assert(cell);
418,186✔
121

122
        i = PTR_TO_SIZE(cell);
418,186✔
123
        assert(i > 0);
418,186✔
124

125
        return i-1;
418,186✔
126
}
127

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

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

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

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

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

146
        TableData **data;
147

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

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

154
        char **json_fields;
155
        size_t n_json_fields;
156

157
        bool *reverse_map;
158
};
159

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

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

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

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

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

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

186
        assert(first_header);
651✔
187

188
        va_start(ap, first_header);
651✔
189
        for (;;) {
9,763✔
190
                if (!va_arg(ap, const char*))
5,207✔
191
                        break;
192

193
                n_columns++;
4,556✔
194
        }
195
        va_end(ap);
651✔
196

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

201
        va_start(ap, first_header);
651✔
202
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,858✔
203
                TableCell *cell;
5,207✔
204

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

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

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

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

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

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

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

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

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

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

243
static TableData *table_data_free(TableData *d) {
182,584✔
244
        assert(d);
182,584✔
245

246
        free(d->formatted);
182,584✔
247
        free(d->url);
182,584✔
248

249
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
182,584✔
250
                strv_free(d->strv);
6,333✔
251

252
        return mfree(d);
182,584✔
253
}
254

255
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
323,916✔
256
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
197,657✔
257

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

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

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

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

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

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

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

280
        switch (type) {
492,992✔
281

282
        case TABLE_EMPTY:
283
                return 0;
284

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

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

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

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

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

320
        case TABLE_INT32:
4,532✔
321
        case TABLE_UINT32:
322
        case TABLE_UINT32_HEX:
323
        case TABLE_UINT32_HEX_0x:
324
                return sizeof(uint32_t);
4,532✔
325

326
        case TABLE_INT16:
99✔
327
        case TABLE_UINT16:
328
                return sizeof(uint16_t);
99✔
329

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

334
        case TABLE_INT:
4,317✔
335
        case TABLE_UINT:
336
        case TABLE_PERCENT:
337
        case TABLE_IFINDEX:
338
        case TABLE_SIGNAL:
339
                return sizeof(int);
4,317✔
340

341
        case TABLE_IN_ADDR:
289✔
342
                return sizeof(struct in_addr);
289✔
343

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

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

351
        case TABLE_UID:
1,387✔
352
                return sizeof(uid_t);
1,387✔
353
        case TABLE_GID:
1,866✔
354
                return sizeof(gid_t);
1,866✔
355
        case TABLE_PID:
565✔
356
                return sizeof(pid_t);
565✔
357

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

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

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

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

381
        size_t k, l;
186,206✔
382
        assert(d);
186,206✔
383

384
        if (d->type != type)
186,206✔
385
                return false;
386

387
        if (d->minimum_width != minimum_width)
160,168✔
388
                return false;
389

390
        if (d->maximum_width != maximum_width)
160,168✔
391
                return false;
392

393
        if (d->weight != weight)
158,811✔
394
                return false;
395

396
        if (d->align_percent != align_percent)
158,811✔
397
                return false;
398

399
        if (d->ellipsize_percent != ellipsize_percent)
158,811✔
400
                return false;
401

402
        if (d->uppercase != uppercase)
158,811✔
403
                return false;
404

405
        /* If a color/url is set, refuse to merge */
406
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
158,810✔
407
                return false;
408
        if (d->url)
156,785✔
409
                return false;
410

411
        k = table_data_size(type, data);
155,204✔
412
        l = table_data_size(d->type, d->data);
155,204✔
413
        if (k != l)
155,204✔
414
                return false;
415

416
        return memcmp_safe(data, d->data, l) == 0;
96,657✔
417
}
418

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

429
        _cleanup_free_ TableData *d = NULL;
182,584✔
430
        size_t data_size;
182,584✔
431

432
        data_size = table_data_size(type, data);
182,584✔
433

434
        d = malloc0(offsetof(TableData, data) + data_size);
182,584✔
435
        if (!d)
182,584✔
436
                return NULL;
437

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

447
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
182,584✔
448
                d->strv = strv_copy(data);
6,333✔
449
                if (!d->strv)
6,333✔
450
                        return NULL;
×
451
        } else
452
                memcpy_safe(d->data, data, data_size);
176,251✔
453

454
        return TAKE_PTR(d);
455
}
456

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

468
        _cleanup_(table_data_unrefp) TableData *d = NULL;
197,657✔
469
        bool uppercase;
197,657✔
470
        TableData *p;
197,657✔
471

472
        assert(t);
197,657✔
473
        assert(dt >= 0);
197,657✔
474
        assert(dt < _TABLE_DATA_TYPE_MAX);
197,657✔
475

476
        /* Special rule: patch NULL data fields to the empty field */
477
        if (!data)
197,657✔
478
                dt = TABLE_EMPTY;
9,633✔
479

480
        /* Determine the cell adjacent to the current one, but one row up */
481
        if (t->n_cells >= t->n_columns)
197,657✔
482
                assert_se(p = t->data[t->n_cells - t->n_columns]);
186,206✔
483
        else
484
                p = NULL;
485

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

490
        if (weight == UINT_MAX)
197,657✔
491
                weight = p ? p->weight : DEFAULT_WEIGHT;
197,657✔
492

493
        if (align_percent == UINT_MAX)
197,657✔
494
                align_percent = p ? p->align_percent : 0;
197,657✔
495

496
        if (ellipsize_percent == UINT_MAX)
197,657✔
497
                ellipsize_percent = p ? p->ellipsize_percent : 100;
197,657✔
498

499
        assert(align_percent <= 100);
197,657✔
500
        assert(ellipsize_percent <= 100);
197,657✔
501

502
        uppercase = dt == TABLE_HEADER;
197,657✔
503

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

507
        if (p && table_data_matches(p, dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
197,657✔
508
                d = table_data_ref(p);
70,654✔
509
        else {
510
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
127,003✔
511
                if (!d)
127,003✔
512
                        return -ENOMEM;
513
        }
514

515
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
197,657✔
516
                return -ENOMEM;
517

518
        if (ret_cell)
197,657✔
519
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
178,027✔
520

521
        t->data[t->n_cells++] = TAKE_PTR(d);
197,657✔
522

523
        return 0;
197,657✔
524
}
525

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

531
        assert(t);
7,930✔
532
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
7,930✔
533

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

540
        return table_add_cell(t, ret_cell, dt, buffer);
7,930✔
541
}
542

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

546
        assert(t);
52✔
547

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

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

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

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

561
        return 0;
562
}
563

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

567
        assert(t);
12✔
568

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

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

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

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

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

587
        assert(t);
206,337✔
588

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

592
        i = TABLE_CELL_TO_INDEX(cell);
206,337✔
593
        if (i >= t->n_cells)
206,337✔
594
                return -ENXIO;
595

596
        assert_se(od = t->data[i]);
206,337✔
597
        if (od->n_ref == 1)
206,337✔
598
                return 0;
599

600
        assert(od->n_ref > 1);
54,458✔
601

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

608
        nd = table_data_new(
108,916✔
609
                        od->type,
610
                        od->data,
54,458✔
611
                        od->minimum_width,
612
                        od->maximum_width,
613
                        od->weight,
614
                        od->align_percent,
615
                        od->ellipsize_percent,
616
                        od->uppercase);
54,458✔
617
        if (!nd)
54,458✔
618
                return -ENOMEM;
619

620
        nd->color = od->color;
54,458✔
621
        nd->rgap_color = od->rgap_color;
54,458✔
622
        nd->underline = od->underline;
54,458✔
623
        nd->rgap_underline = od->rgap_underline;
54,458✔
624
        nd->url = TAKE_PTR(curl);
54,458✔
625

626
        table_data_unref(od);
54,458✔
627
        t->data[i] = nd;
54,458✔
628

629
        assert(nd->n_ref == 1);
54,458✔
630

631
        return 1;
632
}
633

634
static TableData *table_get_data(Table *t, TableCell *cell) {
210,711✔
635
        size_t i;
210,711✔
636

637
        assert(t);
210,711✔
638
        assert(cell);
210,711✔
639

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

642
        i = TABLE_CELL_TO_INDEX(cell);
210,711✔
643
        if (i >= t->n_cells)
210,711✔
644
                return NULL;
645

646
        assert(t->data[i]);
210,711✔
647
        assert(t->data[i]->n_ref > 0);
210,711✔
648

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

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

655
        assert(t);
1,450✔
656
        assert(cell);
1,450✔
657

658
        if (minimum_width == SIZE_MAX)
1,450✔
659
                minimum_width = 1;
×
660

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

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

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

672
        assert(t);
1,414✔
673
        assert(cell);
1,414✔
674

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

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

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

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

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

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

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

700
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
13,394✔
701
        int r;
13,394✔
702

703
        assert(t);
13,394✔
704
        assert(cell);
13,394✔
705

706
        if (percent == UINT_MAX)
13,394✔
707
                percent = 0;
708

709
        assert(percent <= 100);
13,394✔
710

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

715
        table_get_data(t, cell)->align_percent = percent;
13,394✔
716
        return 0;
13,394✔
717
}
718

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

722
        assert(t);
2,960✔
723
        assert(cell);
2,960✔
724

725
        if (percent == UINT_MAX)
2,960✔
726
                percent = 100;
727

728
        assert(percent <= 100);
2,960✔
729

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

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

738
int table_set_color(Table *t, TableCell *cell, const char *color) {
59,761✔
739
        int r;
59,761✔
740

741
        assert(t);
59,761✔
742
        assert(cell);
59,761✔
743

744
        r = table_dedup_cell(t, cell);
59,761✔
745
        if (r < 0)
59,761✔
746
                return r;
747

748
        table_get_data(t, cell)->color = empty_to_null(color);
75,399✔
749
        return 0;
59,761✔
750
}
751

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

755
        assert(t);
3,141✔
756
        assert(cell);
3,141✔
757

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

762
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,282✔
763
        return 0;
3,141✔
764
}
765

766
int table_set_underline(Table *t, TableCell *cell, bool b) {
61,278✔
767
        TableData *d;
61,278✔
768
        int r;
61,278✔
769

770
        assert(t);
61,278✔
771
        assert(cell);
61,278✔
772

773
        r = table_dedup_cell(t, cell);
61,278✔
774
        if (r < 0)
61,278✔
775
                return r;
776

777
        assert_se(d = table_get_data(t, cell));
61,278✔
778

779
        if (d->underline == b)
61,278✔
780
                return 0;
781

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

786
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
61,278✔
787
        TableData *d;
61,278✔
788
        int r;
61,278✔
789

790
        assert(t);
61,278✔
791
        assert(cell);
61,278✔
792

793
        r = table_dedup_cell(t, cell);
61,278✔
794
        if (r < 0)
61,278✔
795
                return r;
796

797
        assert_se(d = table_get_data(t, cell));
61,278✔
798

799
        if (d->rgap_underline == b)
61,278✔
800
                return 0;
801

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

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

810
        assert(t);
1,655✔
811
        assert(cell);
1,655✔
812

813
        if (url) {
1,655✔
814
                copy = strdup(url);
1,643✔
815
                if (!copy)
1,643✔
816
                        return -ENOMEM;
817
        }
818

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

888
        return 0;
1,123✔
889
}
890

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

896
        assert(t);
46,553✔
897
        assert(first_type >= 0);
46,553✔
898
        assert(first_type < _TABLE_DATA_TYPE_MAX);
46,553✔
899

900
        va_start(ap, first_type);
46,553✔
901

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

929
                switch (type) {
346,817✔
930

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1050
                case TABLE_IFINDEX:
66✔
1051
                        buffer.ifindex = va_arg(ap, int);
66✔
1052
                        data = &buffer.ifindex;
66✔
1053
                        break;
66✔
1054

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

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

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

1071
                case TABLE_UID:
520✔
1072
                        buffer.uid = va_arg(ap, uid_t);
520✔
1073
                        data = &buffer.uid;
520✔
1074
                        break;
520✔
1075

1076
                case TABLE_GID:
666✔
1077
                        buffer.gid = va_arg(ap, gid_t);
666✔
1078
                        data = &buffer.gid;
666✔
1079
                        break;
666✔
1080

1081
                case TABLE_PID:
231✔
1082
                        buffer.pid = va_arg(ap, pid_t);
231✔
1083
                        data = &buffer.pid;
231✔
1084
                        break;
231✔
1085

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

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

1097
                case TABLE_SET_MINIMUM_WIDTH: {
1,437✔
1098
                        size_t w = va_arg(ap, size_t);
1,437✔
1099

1100
                        r = table_set_minimum_width(t, last_cell, w);
1,437✔
1101
                        goto check;
1,437✔
1102
                }
1103

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

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

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

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

1128
                case TABLE_SET_COLOR: {
56,620✔
1129
                        const char *c = va_arg(ap, const char*);
56,620✔
1130
                        r = table_set_color(t, last_cell, c);
56,620✔
1131
                        goto check;
56,620✔
1132
                }
1133

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

1140
                case TABLE_SET_BOTH_COLORS: {
3,141✔
1141
                        const char *c = va_arg(ap, const char*);
3,141✔
1142

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

1149
                        r = table_set_rgap_color(t, last_cell, c);
3,141✔
1150
                        goto check;
3,141✔
1151
                }
1152

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

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

1165
                case TABLE_SET_BOTH_UNDERLINES: {
61,278✔
1166
                        int u = va_arg(ap, int);
61,278✔
1167

1168
                        r = table_set_underline(t, last_cell, u);
61,278✔
1169
                        if (r < 0) {
61,278✔
1170
                                va_end(ap);
×
1171
                                return r;
×
1172
                        }
1173

1174
                        r = table_set_rgap_underline(t, last_cell, u);
61,278✔
1175
                        goto check;
61,278✔
1176
                }
1177

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

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

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

1204
                case _TABLE_DATA_TYPE_MAX:
46,553✔
1205
                        /* Used as end marker */
1206
                        va_end(ap);
46,553✔
1207
                        return 0;
46,553✔
1208

1209
                default:
×
1210
                        assert_not_reached();
×
1211
                }
1212

1213
                r = table_add_cell(t, &last_cell, type, data);
166,998✔
1214
        check:
300,264✔
1215
                if (r < 0) {
300,264✔
1216
                        va_end(ap);
×
1217
                        return r;
×
1218
                }
1219
        }
1220
}
1221

1222
void table_set_header(Table *t, bool b) {
188✔
1223
        assert(t);
188✔
1224

1225
        t->header = b;
188✔
1226
}
188✔
1227

1228
void table_set_width(Table *t, size_t width) {
99✔
1229
        assert(t);
99✔
1230

1231
        t->width = width;
99✔
1232
}
99✔
1233

1234
void table_set_cell_height_max(Table *t, size_t height) {
28✔
1235
        assert(t);
28✔
1236
        assert(height >= 1 || height == SIZE_MAX);
28✔
1237

1238
        t->cell_height_max = height;
28✔
1239
}
28✔
1240

1241
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
620✔
1242
        assert(t);
620✔
1243
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
620✔
1244

1245
        t->ersatz = ersatz;
620✔
1246
}
620✔
1247

1248
static const char* table_ersatz_string(const Table *t) {
13,412✔
1249
        switch (t->ersatz) {
13,412✔
1250
        case TABLE_ERSATZ_EMPTY:
1251
                return "";
1252
        case TABLE_ERSATZ_DASH:
9,360✔
1253
                return "-";
9,360✔
1254
        case TABLE_ERSATZ_UNSET:
424✔
1255
                return "(unset)";
424✔
1256
        case TABLE_ERSATZ_NA:
×
1257
                return "n/a";
×
1258
        default:
×
1259
                assert_not_reached();
×
1260
        }
1261
}
1262

1263
static int table_set_display_all(Table *t) {
316✔
1264
        size_t *d;
316✔
1265

1266
        assert(t);
316✔
1267

1268
        /* Initialize the display map to the identity */
1269

1270
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
316✔
1271
        if (!d)
316✔
1272
                return -ENOMEM;
1273

1274
        for (size_t i = 0; i < t->n_columns; i++)
4,105✔
1275
                d[i] = i;
3,789✔
1276

1277
        t->display_map = d;
316✔
1278
        t->n_display_map = t->n_columns;
316✔
1279

1280
        return 0;
316✔
1281
}
1282

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

1287
        assert(t);
31✔
1288

1289
        column = first_column;
31✔
1290

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

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

1300
                t->display_map[t->n_display_map++] = column;
159✔
1301

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

1306
        }
1307
        va_end(ap);
31✔
1308

1309
        return 0;
31✔
1310
}
1311

1312
int table_set_sort_internal(Table *t, size_t first_column, ...) {
237✔
1313
        size_t column;
237✔
1314
        va_list ap;
237✔
1315

1316
        assert(t);
237✔
1317

1318
        column = first_column;
237✔
1319

1320
        va_start(ap, first_column);
237✔
1321
        for (;;) {
310✔
1322
                assert(column < t->n_columns);
310✔
1323

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

1329
                t->sort_map[t->n_sort_map++] = column;
310✔
1330

1331
                column = va_arg(ap, size_t);
310✔
1332
                if (column == SIZE_MAX)
310✔
1333
                        break;
1334
        }
1335
        va_end(ap);
237✔
1336

1337
        return 0;
237✔
1338
}
1339

1340
int table_hide_column_from_display_internal(Table *t, ...) {
641✔
1341
        size_t cur = 0;
641✔
1342
        int r;
641✔
1343

1344
        assert(t);
641✔
1345

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

1353
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
8,745✔
1354
                bool listed = false;
8,104✔
1355
                va_list ap;
8,104✔
1356

1357
                va_start(ap, t);
8,104✔
1358
                for (;;) {
15,689✔
1359
                        size_t column;
15,689✔
1360

1361
                        column = va_arg(ap, size_t);
15,689✔
1362
                        if (column == SIZE_MAX)
15,689✔
1363
                                break;
1364
                        if (column == *i) {
8,239✔
1365
                                listed = true;
1366
                                break;
1367
                        }
1368
                }
1369
                va_end(ap);
8,104✔
1370

1371
                if (listed)
8,104✔
1372
                        continue;
654✔
1373

1374
                t->display_map[cur++] = *i;
7,450✔
1375
        }
1376

1377
        t->n_display_map = cur;
641✔
1378

1379
        return 0;
641✔
1380
}
1381

1382
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
11,982✔
1383
        int r;
11,982✔
1384

1385
        assert(a);
11,982✔
1386
        assert(b);
11,982✔
1387

1388
        if (a->type == b->type) {
11,982✔
1389

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

1393
                switch (a->type) {
11,976✔
1394

1395
                case TABLE_STRING:
8,383✔
1396
                case TABLE_FIELD:
1397
                case TABLE_HEADER:
1398
                        return strcmp(a->string, b->string);
8,383✔
1399

1400
                case TABLE_PATH:
41✔
1401
                case TABLE_PATH_BASENAME:
1402
                        return path_compare(a->string, b->string);
41✔
1403

1404
                case TABLE_VERSION:
×
1405
                        return strverscmp_improved(a->string, b->string);
×
1406

1407
                case TABLE_STRV:
×
1408
                case TABLE_STRV_WRAPPED:
1409
                        return strv_compare(a->strv, b->strv);
×
1410

1411
                case TABLE_BOOLEAN:
2✔
1412
                        if (!a->boolean && b->boolean)
2✔
1413
                                return -1;
1414
                        if (a->boolean && !b->boolean)
2✔
1415
                                return 1;
1416
                        return 0;
×
1417

1418
                case TABLE_TIMESTAMP:
×
1419
                case TABLE_TIMESTAMP_UTC:
1420
                case TABLE_TIMESTAMP_RELATIVE:
1421
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1422
                case TABLE_TIMESTAMP_LEFT:
1423
                case TABLE_TIMESTAMP_DATE:
1424
                        return CMP(a->timestamp, b->timestamp);
×
1425

1426
                case TABLE_TIMESPAN:
760✔
1427
                case TABLE_TIMESPAN_MSEC:
1428
                case TABLE_TIMESPAN_DAY:
1429
                        return CMP(a->timespan, b->timespan);
760✔
1430

1431
                case TABLE_SIZE:
×
1432
                case TABLE_BPS:
1433
                        return CMP(a->size, b->size);
×
1434

1435
                case TABLE_INT:
16✔
1436
                case TABLE_SIGNAL:
1437
                        return CMP(a->int_val, b->int_val);
16✔
1438

1439
                case TABLE_INT8:
×
1440
                        return CMP(a->int8, b->int8);
×
1441

1442
                case TABLE_INT16:
×
1443
                        return CMP(a->int16, b->int16);
×
1444

1445
                case TABLE_INT32:
×
1446
                        return CMP(a->int32, b->int32);
×
1447

1448
                case TABLE_INT64:
188✔
1449
                        return CMP(a->int64, b->int64);
188✔
1450

1451
                case TABLE_UINT:
75✔
1452
                        return CMP(a->uint_val, b->uint_val);
75✔
1453

1454
                case TABLE_UINT8:
×
1455
                        return CMP(a->uint8, b->uint8);
×
1456

1457
                case TABLE_UINT16:
×
1458
                        return CMP(a->uint16, b->uint16);
×
1459

1460
                case TABLE_UINT32:
27✔
1461
                case TABLE_UINT32_HEX:
1462
                case TABLE_UINT32_HEX_0x:
1463
                        return CMP(a->uint32, b->uint32);
27✔
1464

1465
                case TABLE_UINT64:
×
1466
                case TABLE_UINT64_HEX:
1467
                case TABLE_UINT64_HEX_0x:
1468
                        return CMP(a->uint64, b->uint64);
×
1469

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

1473
                case TABLE_IFINDEX:
×
1474
                        return CMP(a->ifindex, b->ifindex);
×
1475

1476
                case TABLE_IN_ADDR:
×
1477
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1478

1479
                case TABLE_IN6_ADDR:
1480
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1481

1482
                case TABLE_UUID:
×
1483
                case TABLE_ID128:
1484
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1485

1486
                case TABLE_UID:
1,154✔
1487
                        return CMP(a->uid, b->uid);
1,154✔
1488

1489
                case TABLE_GID:
1,330✔
1490
                        return CMP(a->gid, b->gid);
1,330✔
1491

1492
                case TABLE_PID:
×
1493
                        return CMP(a->pid, b->pid);
×
1494

1495
                case TABLE_MODE:
×
1496
                case TABLE_MODE_INODE_TYPE:
1497
                        return CMP(a->mode, b->mode);
×
1498

1499
                case TABLE_DEVNUM:
×
1500
                        r = CMP(major(a->devnum), major(b->devnum));
×
1501
                        if (r != 0)
×
1502
                                return r;
×
1503

1504
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1505

1506
                default:
6✔
1507
                        ;
6✔
1508
                }
1509
        }
1510

1511
        /* Generic fallback using the original order in which the cells where added. */
1512
        return CMP(index_a, index_b);
6✔
1513
}
1514

1515
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
12,212✔
1516
        int r;
12,212✔
1517

1518
        assert(t);
12,212✔
1519
        assert(t->sort_map);
12,212✔
1520

1521
        /* Make sure the header stays at the beginning */
1522
        if (*a < t->n_columns && *b < t->n_columns)
12,212✔
1523
                return 0;
1524
        if (*a < t->n_columns)
12,212✔
1525
                return -1;
1526
        if (*b < t->n_columns)
11,704✔
1527
                return 1;
1528

1529
        /* Order other lines by the sorting map */
1530
        for (size_t i = 0; i < t->n_sort_map; i++) {
11,982✔
1531
                TableData *d, *dd;
11,982✔
1532

1533
                d = t->data[*a + t->sort_map[i]];
11,982✔
1534
                dd = t->data[*b + t->sort_map[i]];
11,982✔
1535

1536
                r = cell_data_compare(d, *a, dd, *b);
11,982✔
1537
                if (r != 0)
11,982✔
1538
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
11,704✔
1539
        }
1540

1541
        /* Order identical lines by the order there were originally added in */
1542
        return CMP(*a, *b);
×
1543
}
1544

1545
static char* format_strv_width(char **strv, size_t column_width) {
243✔
1546
        _cleanup_(memstream_done) MemStream m = {};
243✔
1547
        FILE *f;
243✔
1548

1549
        f = memstream_init(&m);
243✔
1550
        if (!f)
243✔
1551
                return NULL;
1552

1553
        size_t position = 0;
1554
        STRV_FOREACH(p, strv) {
919✔
1555
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
676✔
1556
                                                          * If that happens, we'll just print one item per line. */
1557

1558
                if (position == 0) {
676✔
1559
                        fputs(*p, f);
243✔
1560
                        position = our_len;
1561
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
866✔
1562
                        fprintf(f, " %s", *p);
381✔
1563
                        position = size_add(size_add(position, 1), our_len);
1,057✔
1564
                } else {
1565
                        fprintf(f, "\n%s", *p);
52✔
1566
                        position = our_len;
1567
                }
1568
        }
1569

1570
        char *buf;
243✔
1571
        if (memstream_finalize(&m, &buf, NULL) < 0)
243✔
1572
                return NULL;
1573

1574
        return buf;
243✔
1575
}
1576

1577
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
341,187✔
1578
        assert(d);
341,187✔
1579

1580
        if (d->formatted &&
341,187✔
1581
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1582
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
63,476✔
1583
                return d->formatted;
1584

1585
        switch (d->type) {
277,883✔
1586
        case TABLE_EMPTY:
13,202✔
1587
                return table_ersatz_string(t);
13,202✔
1588

1589
        case TABLE_STRING:
236,005✔
1590
        case TABLE_PATH:
1591
        case TABLE_PATH_BASENAME:
1592
        case TABLE_FIELD:
1593
        case TABLE_HEADER:
1594
        case TABLE_VERSION: {
1595
                _cleanup_free_ char *bn = NULL;
236,005✔
1596
                const char *s;
236,005✔
1597

1598
                if (d->type == TABLE_PATH_BASENAME)
236,005✔
1599
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
367✔
1600
                else
1601
                        s = d->string;
235,638✔
1602

1603
                if (d->uppercase && !avoid_uppercasing) {
236,005✔
1604
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,609✔
1605
                        if (!d->formatted)
3,609✔
1606
                                return NULL;
1607

1608
                        char *q = d->formatted;
1609
                        for (const char *p = s; *p; p++)
25,315✔
1610
                                *(q++) = (char) toupper((unsigned char) *p);
21,706✔
1611

1612
                        if (d->type == TABLE_FIELD)
3,609✔
1613
                                *(q++) = ':';
×
1614

1615
                        *q = 0;
3,609✔
1616
                        return d->formatted;
3,609✔
1617
                } else if (d->type == TABLE_FIELD) {
232,396✔
1618
                        d->formatted = strjoin(s, ":");
32,876✔
1619
                        if (!d->formatted)
32,876✔
1620
                                return NULL;
1621

1622
                        return d->formatted;
32,876✔
1623
                }
1624

1625
                if (bn) {
199,520✔
1626
                        d->formatted = TAKE_PTR(bn);
365✔
1627
                        return d->formatted;
365✔
1628
                }
1629

1630
                return d->string;
199,155✔
1631
        }
1632

1633
        case TABLE_STRV:
6,259✔
1634
                if (strv_isempty(d->strv))
6,259✔
1635
                        return table_ersatz_string(t);
×
1636

1637
                d->formatted = strv_join(d->strv, "\n");
6,259✔
1638
                if (!d->formatted)
6,259✔
1639
                        return NULL;
1640
                break;
1641

1642
        case TABLE_STRV_WRAPPED: {
243✔
1643
                if (strv_isempty(d->strv))
243✔
1644
                        return table_ersatz_string(t);
×
1645

1646
                char *buf = format_strv_width(d->strv, column_width);
243✔
1647
                if (!buf)
243✔
1648
                        return NULL;
1649

1650
                free_and_replace(d->formatted, buf);
243✔
1651
                d->formatted_for_width = column_width;
243✔
1652
                if (have_soft)
243✔
1653
                        *have_soft = true;
163✔
1654

1655
                break;
16,775✔
1656
        }
1657

1658
        case TABLE_BOOLEAN:
4,581✔
1659
                return yes_no(d->boolean);
342,966✔
1660

1661
        case TABLE_BOOLEAN_CHECKMARK:
6,976✔
1662
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,746✔
1663

1664
        case TABLE_TIMESTAMP:
1,974✔
1665
        case TABLE_TIMESTAMP_UTC:
1666
        case TABLE_TIMESTAMP_RELATIVE:
1667
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1668
        case TABLE_TIMESTAMP_LEFT:
1669
        case TABLE_TIMESTAMP_DATE: {
1670
                _cleanup_free_ char *p = NULL;
132✔
1671
                char *ret;
1,974✔
1672

1673
                p = new(char,
1,974✔
1674
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1675
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1676
                if (!p)
1,974✔
1677
                        return NULL;
1678

1679
                if (d->type == TABLE_TIMESTAMP)
1,974✔
1680
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,290✔
1681
                else if (d->type == TABLE_TIMESTAMP_UTC)
684✔
1682
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1683
                else if (d->type == TABLE_TIMESTAMP_DATE)
684✔
1684
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
1✔
1685
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1686
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1687
                else
1688
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1689
                                                             d->timestamp, CLOCK_REALTIME,
1690
                                                             /* implicit_left= */ d->type == TABLE_TIMESTAMP_LEFT);
1691
                if (!ret)
1,974✔
1692
                        return "-";
1693

1694
                d->formatted = TAKE_PTR(p);
1,842✔
1695
                break;
1,842✔
1696
        }
1697

1698
        case TABLE_TIMESPAN:
1,474✔
1699
        case TABLE_TIMESPAN_MSEC:
1700
        case TABLE_TIMESPAN_DAY: {
1701
                _cleanup_free_ char *p = NULL;
×
1702

1703
                p = new(char, FORMAT_TIMESPAN_MAX);
1,474✔
1704
                if (!p)
1,474✔
1705
                        return NULL;
1706

1707
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,941✔
1708
                                     d->type == TABLE_TIMESPAN ? 0 :
1,467✔
1709
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1710
                        return "-";
1711

1712
                d->formatted = TAKE_PTR(p);
1,474✔
1713
                break;
1,474✔
1714
        }
1715

1716
        case TABLE_SIZE: {
320✔
1717
                _cleanup_free_ char *p = NULL;
208✔
1718

1719
                p = new(char, FORMAT_BYTES_MAX);
320✔
1720
                if (!p)
320✔
1721
                        return NULL;
1722

1723
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
320✔
1724
                        return table_ersatz_string(t);
208✔
1725

1726
                d->formatted = TAKE_PTR(p);
112✔
1727
                break;
112✔
1728
        }
1729

1730
        case TABLE_BPS: {
410✔
1731
                _cleanup_free_ char *p = NULL;
×
1732
                size_t n;
410✔
1733

1734
                p = new(char, FORMAT_BYTES_MAX+2);
410✔
1735
                if (!p)
410✔
1736
                        return NULL;
1737

1738
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
410✔
1739
                        return table_ersatz_string(t);
×
1740

1741
                n = strlen(p);
410✔
1742
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
410✔
1743

1744
                d->formatted = TAKE_PTR(p);
410✔
1745
                break;
410✔
1746
        }
1747

1748
        case TABLE_INT: {
259✔
1749
                _cleanup_free_ char *p = NULL;
×
1750

1751
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
734✔
1752
                if (!p)
259✔
1753
                        return NULL;
×
1754

1755
                sprintf(p, "%i", d->int_val);
259✔
1756
                d->formatted = TAKE_PTR(p);
259✔
1757
                break;
259✔
1758
        }
1759

1760
        case TABLE_INT8: {
3✔
1761
                _cleanup_free_ char *p = NULL;
×
1762

1763
                p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
10✔
1764
                if (!p)
3✔
1765
                        return NULL;
×
1766

1767
                sprintf(p, "%" PRIi8, d->int8);
3✔
1768
                d->formatted = TAKE_PTR(p);
3✔
1769
                break;
3✔
1770
        }
1771

1772
        case TABLE_INT16: {
3✔
1773
                _cleanup_free_ char *p = NULL;
×
1774

1775
                p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
14✔
1776
                if (!p)
3✔
1777
                        return NULL;
×
1778

1779
                sprintf(p, "%" PRIi16, d->int16);
3✔
1780
                d->formatted = TAKE_PTR(p);
3✔
1781
                break;
3✔
1782
        }
1783

1784
        case TABLE_INT32: {
3✔
1785
                _cleanup_free_ char *p = NULL;
×
1786

1787
                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
24✔
1788
                if (!p)
3✔
1789
                        return NULL;
×
1790

1791
                sprintf(p, "%" PRIi32, d->int32);
3✔
1792
                d->formatted = TAKE_PTR(p);
3✔
1793
                break;
3✔
1794
        }
1795

1796
        case TABLE_INT64: {
155✔
1797
                _cleanup_free_ char *p = NULL;
×
1798

1799
                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
319✔
1800
                if (!p)
155✔
1801
                        return NULL;
×
1802

1803
                sprintf(p, "%" PRIi64, d->int64);
155✔
1804
                d->formatted = TAKE_PTR(p);
155✔
1805
                break;
155✔
1806
        }
1807

1808
        case TABLE_UINT: {
285✔
1809
                _cleanup_free_ char *p = NULL;
×
1810

1811
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
410✔
1812
                if (!p)
285✔
1813
                        return NULL;
×
1814

1815
                sprintf(p, "%u", d->uint_val);
285✔
1816
                d->formatted = TAKE_PTR(p);
285✔
1817
                break;
285✔
1818
        }
1819

1820
        case TABLE_UINT8: {
43✔
1821
                _cleanup_free_ char *p = NULL;
×
1822

1823
                p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
46✔
1824
                if (!p)
43✔
1825
                        return NULL;
×
1826

1827
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1828
                d->formatted = TAKE_PTR(p);
43✔
1829
                break;
43✔
1830
        }
1831

1832
        case TABLE_UINT16: {
88✔
1833
                _cleanup_free_ char *p = NULL;
×
1834

1835
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
302✔
1836
                if (!p)
88✔
1837
                        return NULL;
×
1838

1839
                sprintf(p, "%" PRIu16, d->uint16);
88✔
1840
                d->formatted = TAKE_PTR(p);
88✔
1841
                break;
88✔
1842
        }
1843

1844
        case TABLE_UINT32: {
1,040✔
1845
                _cleanup_free_ char *p = NULL;
×
1846

1847
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,563✔
1848
                if (!p)
1,040✔
1849
                        return NULL;
×
1850

1851
                sprintf(p, "%" PRIu32, d->uint32);
1,040✔
1852
                d->formatted = TAKE_PTR(p);
1,040✔
1853
                break;
1,040✔
1854
        }
1855

1856
        case TABLE_UINT32_HEX: {
2✔
1857
                _cleanup_free_ char *p = NULL;
×
1858

1859
                p = new(char, 8 + 1);
2✔
1860
                if (!p)
2✔
1861
                        return NULL;
×
1862

1863
                sprintf(p, "%" PRIx32, d->uint32);
2✔
1864
                d->formatted = TAKE_PTR(p);
2✔
1865
                break;
2✔
1866
        }
1867

1868
        case TABLE_UINT32_HEX_0x: {
6✔
1869
                _cleanup_free_ char *p = NULL;
×
1870

1871
                p = new(char, 2 + 8 + 1);
6✔
1872
                if (!p)
6✔
1873
                        return NULL;
×
1874

1875
                sprintf(p, "0x%" PRIx32, d->uint32);
6✔
1876
                d->formatted = TAKE_PTR(p);
6✔
1877
                break;
6✔
1878
        }
1879

1880
        case TABLE_UINT64: {
1,274✔
1881
                _cleanup_free_ char *p = NULL;
×
1882

1883
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
5,944✔
1884
                if (!p)
1,274✔
1885
                        return NULL;
×
1886

1887
                sprintf(p, "%" PRIu64, d->uint64);
1,274✔
1888
                d->formatted = TAKE_PTR(p);
1,274✔
1889
                break;
1,274✔
1890
        }
1891

1892
        case TABLE_UINT64_HEX: {
14✔
1893
                _cleanup_free_ char *p = NULL;
×
1894

1895
                p = new(char, 16 + 1);
14✔
1896
                if (!p)
14✔
1897
                        return NULL;
×
1898

1899
                sprintf(p, "%" PRIx64, d->uint64);
14✔
1900
                d->formatted = TAKE_PTR(p);
14✔
1901
                break;
14✔
1902
        }
1903

1904
        case TABLE_UINT64_HEX_0x: {
×
1905
                _cleanup_free_ char *p = NULL;
×
1906

1907
                p = new(char, 2 + 16 + 1);
×
1908
                if (!p)
×
1909
                        return NULL;
×
1910

1911
                sprintf(p, "0x%" PRIx64, d->uint64);
×
1912
                d->formatted = TAKE_PTR(p);
×
1913
                break;
×
1914
        }
1915

1916
        case TABLE_PERCENT: {
2✔
1917
                _cleanup_free_ char *p = NULL;
×
1918

1919
                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
5✔
1920
                if (!p)
2✔
1921
                        return NULL;
×
1922

1923
                sprintf(p, "%i%%" , d->percent);
2✔
1924
                d->formatted = TAKE_PTR(p);
2✔
1925
                break;
2✔
1926
        }
1927

1928
        case TABLE_IFINDEX: {
223✔
1929
                _cleanup_free_ char *p = NULL;
×
1930

1931
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
223✔
1932
                        return NULL;
×
1933

1934
                d->formatted = TAKE_PTR(p);
223✔
1935
                break;
223✔
1936
        }
1937

1938
        case TABLE_IN_ADDR:
205✔
1939
        case TABLE_IN6_ADDR: {
1940
                _cleanup_free_ char *p = NULL;
×
1941

1942
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1943
                                      &d->address, &p) < 0)
205✔
1944
                        return NULL;
×
1945

1946
                d->formatted = TAKE_PTR(p);
205✔
1947
                break;
205✔
1948
        }
1949

1950
        case TABLE_ID128: {
1951
                char *p;
1,096✔
1952

1953
                p = new(char, SD_ID128_STRING_MAX);
1,096✔
1954
                if (!p)
1,096✔
1955
                        return NULL;
1956

1957
                d->formatted = sd_id128_to_string(d->id128, p);
1,096✔
1958
                break;
1,096✔
1959
        }
1960

1961
        case TABLE_UUID: {
1962
                char *p;
318✔
1963

1964
                p = new(char, SD_ID128_UUID_STRING_MAX);
318✔
1965
                if (!p)
318✔
1966
                        return NULL;
1967

1968
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
318✔
1969
                break;
318✔
1970
        }
1971

1972
        case TABLE_UID: {
443✔
1973
                char *p;
443✔
1974

1975
                if (!uid_is_valid(d->uid))
443✔
1976
                        return table_ersatz_string(t);
×
1977

1978
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,528✔
1979
                if (!p)
443✔
1980
                        return NULL;
1981
                sprintf(p, UID_FMT, d->uid);
443✔
1982

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

1987
        case TABLE_GID: {
614✔
1988
                char *p;
614✔
1989

1990
                if (!gid_is_valid(d->gid))
614✔
1991
                        return table_ersatz_string(t);
×
1992

1993
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,805✔
1994
                if (!p)
614✔
1995
                        return NULL;
1996
                sprintf(p, GID_FMT, d->gid);
614✔
1997

1998
                d->formatted = p;
614✔
1999
                break;
614✔
2000
        }
2001

2002
        case TABLE_PID: {
211✔
2003
                char *p;
211✔
2004

2005
                if (!pid_is_valid(d->pid))
211✔
2006
                        return table_ersatz_string(t);
×
2007

2008
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
564✔
2009
                if (!p)
211✔
2010
                        return NULL;
2011
                sprintf(p, PID_FMT, d->pid);
211✔
2012

2013
                d->formatted = p;
211✔
2014
                break;
211✔
2015
        }
2016

2017
        case TABLE_SIGNAL: {
123✔
2018
                const char *suffix;
123✔
2019
                char *p;
123✔
2020

2021
                suffix = signal_to_string(d->int_val);
123✔
2022
                if (!suffix)
123✔
2023
                        return table_ersatz_string(t);
×
2024

2025
                p = strjoin("SIG", suffix);
123✔
2026
                if (!p)
123✔
2027
                        return NULL;
2028

2029
                d->formatted = p;
123✔
2030
                break;
123✔
2031
        }
2032

2033
        case TABLE_MODE: {
24✔
2034
                char *p;
24✔
2035

2036
                if (d->mode == MODE_INVALID)
24✔
2037
                        return table_ersatz_string(t);
×
2038

2039
                p = new(char, 4 + 1);
24✔
2040
                if (!p)
24✔
2041
                        return NULL;
2042

2043
                sprintf(p, "%04o", d->mode & 07777);
24✔
2044
                d->formatted = p;
24✔
2045
                break;
24✔
2046
        }
2047

2048
        case TABLE_MODE_INODE_TYPE:
2✔
2049

2050
                if (d->mode == MODE_INVALID)
2✔
2051
                        return table_ersatz_string(t);
×
2052

2053
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2054

2055
        case TABLE_DEVNUM:
3✔
2056
                if (devnum_is_zero(d->devnum))
3✔
2057
                        return table_ersatz_string(t);
2✔
2058

2059
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2060
                        return NULL;
2061

2062
                break;
2063

2064
        default:
×
2065
                assert_not_reached();
×
2066
        }
2067

2068
        return d->formatted;
16,775✔
2069
}
2070

2071
static int console_width_height(
167,464✔
2072
                const char *s,
2073
                size_t *ret_width,
2074
                size_t *ret_height) {
2075

2076
        size_t max_width = 0, height = 0;
167,464✔
2077
        const char *p;
167,464✔
2078

2079
        assert(s);
167,464✔
2080

2081
        /* Determine the width and height in console character cells the specified string needs. */
2082

2083
        do {
170,705✔
2084
                size_t k;
170,705✔
2085

2086
                p = strchr(s, '\n');
170,705✔
2087
                if (p) {
170,705✔
2088
                        _cleanup_free_ char *c = NULL;
3,244✔
2089

2090
                        c = strndup(s, p - s);
3,244✔
2091
                        if (!c)
3,244✔
2092
                                return -ENOMEM;
×
2093

2094
                        k = utf8_console_width(c);
3,244✔
2095
                        s = p + 1;
3,244✔
2096
                } else {
2097
                        k = utf8_console_width(s);
167,461✔
2098
                        s = NULL;
167,461✔
2099
                }
2100
                if (k == SIZE_MAX)
170,705✔
2101
                        return -EINVAL;
2102
                if (k > max_width)
170,705✔
2103
                        max_width = k;
166,216✔
2104

2105
                height++;
170,705✔
2106
        } while (!isempty(s));
173,949✔
2107

2108
        if (ret_width)
167,464✔
2109
                *ret_width = max_width;
167,464✔
2110

2111
        if (ret_height)
167,464✔
2112
                *ret_height = height;
167,464✔
2113

2114
        return 0;
2115
}
2116

2117
static int table_data_requested_width_height(
167,464✔
2118
                Table *table,
2119
                TableData *d,
2120
                size_t available_width,
2121
                size_t *ret_width,
2122
                size_t *ret_height,
2123
                bool *have_soft) {
2124

2125
        _cleanup_free_ char *truncated = NULL;
167,464✔
2126
        bool truncation_applied = false;
167,464✔
2127
        size_t width, height;
167,464✔
2128
        const char *t;
167,464✔
2129
        int r;
167,464✔
2130
        bool soft = false;
167,464✔
2131

2132
        t = table_data_format(table, d, false, available_width, &soft);
167,464✔
2133
        if (!t)
167,464✔
2134
                return -ENOMEM;
2135

2136
        if (table->cell_height_max != SIZE_MAX) {
167,464✔
2137
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
180✔
2138
                if (r < 0)
180✔
2139
                        return r;
2140
                if (r > 0)
180✔
2141
                        truncation_applied = true;
25✔
2142

2143
                t = truncated;
180✔
2144
        }
2145

2146
        r = console_width_height(t, &width, &height);
167,464✔
2147
        if (r < 0)
167,464✔
2148
                return r;
2149

2150
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
167,464✔
2151
                width = d->maximum_width;
×
2152

2153
        if (width < d->minimum_width)
167,464✔
2154
                width = d->minimum_width;
2,965✔
2155

2156
        if (ret_width)
167,464✔
2157
                *ret_width = width;
167,464✔
2158
        if (ret_height)
167,464✔
2159
                *ret_height = height;
167,464✔
2160
        if (have_soft && soft)
167,464✔
2161
                *have_soft = true;
163✔
2162

2163
        return truncation_applied;
167,464✔
2164
}
2165

2166
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
126,875✔
2167
        size_t w = 0, space, lspace, old_length, clickable_length;
126,875✔
2168
        _cleanup_free_ char *clickable = NULL;
126,875✔
2169
        const char *p;
126,875✔
2170
        char *ret;
126,875✔
2171
        int r;
126,875✔
2172

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

2175
        assert(str);
126,875✔
2176
        assert(percent <= 100);
126,875✔
2177

2178
        old_length = strlen(str);
126,875✔
2179

2180
        if (url) {
126,875✔
2181
                r = terminal_urlify(url, str, &clickable);
1,555✔
2182
                if (r < 0)
1,555✔
2183
                        return NULL;
2184

2185
                clickable_length = strlen(clickable);
1,555✔
2186
        } else
2187
                clickable_length = old_length;
2188

2189
        /* Determine current width on screen */
2190
        p = str;
126,875✔
2191
        while (p < str + old_length) {
1,904,197✔
2192
                char32_t c;
1,777,322✔
2193

2194
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,777,322✔
2195
                        p++, w++; /* count invalid chars as 1 */
×
2196
                        continue;
×
2197
                }
2198

2199
                p = utf8_next_char(p);
1,777,322✔
2200
                w += unichar_iswide(c) ? 2 : 1;
3,554,637✔
2201
        }
2202

2203
        /* Already wider than the target, if so, don't do anything */
2204
        if (w >= new_length)
126,875✔
2205
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2206

2207
        /* How much spaces shall we add? An how much on the left side? */
2208
        space = new_length - w;
126,875✔
2209
        lspace = space * percent / 100U;
126,875✔
2210

2211
        ret = new(char, space + clickable_length + 1);
126,875✔
2212
        if (!ret)
126,875✔
2213
                return NULL;
2214

2215
        for (size_t i = 0; i < lspace; i++)
748,497✔
2216
                ret[i] = ' ';
621,622✔
2217
        memcpy(ret + lspace, clickable ?: str, clickable_length);
126,875✔
2218
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,557,061✔
2219
                ret[i] = ' ';
3,430,186✔
2220

2221
        ret[space + clickable_length] = 0;
126,875✔
2222
        return ret;
126,875✔
2223
}
2224

2225
static bool table_data_isempty(const TableData *d) {
552✔
2226
        assert(d);
552✔
2227

2228
        if (d->type == TABLE_EMPTY)
552✔
2229
                return true;
2230

2231
        /* Let's also consider an empty strv as truly empty. */
2232
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
470✔
2233
                return strv_isempty(d->strv);
×
2234

2235
        /* Note that an empty string we do not consider empty here! */
2236
        return false;
2237
}
2238

2239
static const char* table_data_color(const TableData *d) {
765✔
2240
        assert(d);
765✔
2241

2242
        if (d->color)
765✔
2243
                return d->color;
2244

2245
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2246
        if (table_data_isempty(d))
552✔
2247
                return ansi_grey();
82✔
2248

2249
        if (d->type == TABLE_FIELD)
470✔
2250
                return ansi_bright_blue();
×
2251

2252
        return NULL;
2253
}
2254

2255
static const char* table_data_underline(const TableData *d) {
765✔
2256
        assert(d);
765✔
2257

2258
        if (d->underline)
765✔
2259
                return ansi_add_underline_grey();
×
2260

2261
        if (d->type == TABLE_HEADER)
765✔
2262
                return ansi_add_underline();
40✔
2263

2264
        return NULL;
2265
}
2266

2267
static const char* table_data_rgap_underline(const TableData *d) {
173,698✔
2268
        assert(d);
173,698✔
2269

2270
        if (d->rgap_underline)
173,698✔
2271
                return ansi_add_underline_grey();
300✔
2272

2273
        if (d->type == TABLE_HEADER)
173,398✔
2274
                return ansi_add_underline();
7,340✔
2275

2276
        return NULL;
2277
}
2278

2279
int table_print(Table *t, FILE *f) {
3,510✔
2280
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,510✔
2281
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2282
                *width = NULL;
3,510✔
2283
        _cleanup_free_ size_t *sorted = NULL;
3,510✔
2284
        uint64_t *column_weight, weight_sum;
3,510✔
2285
        int r;
3,510✔
2286

2287
        assert(t);
3,510✔
2288

2289
        if (!f)
3,510✔
2290
                f = stdout;
2,597✔
2291

2292
        /* Ensure we have no incomplete rows */
2293
        assert(t->n_cells % t->n_columns == 0);
3,510✔
2294

2295
        n_rows = t->n_cells / t->n_columns;
3,510✔
2296
        assert(n_rows > 0); /* at least the header row must be complete */
3,510✔
2297

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

2301
                sorted = new(size_t, n_rows);
193✔
2302
                if (!sorted)
193✔
2303
                        return -ENOMEM;
2304

2305
                for (size_t i = 0; i < n_rows; i++)
3,277✔
2306
                        sorted[i] = i * t->n_columns;
3,084✔
2307

2308
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
193✔
2309
        }
2310

2311
        if (t->display_map)
3,510✔
2312
                display_columns = t->n_display_map;
304✔
2313
        else
2314
                display_columns = t->n_columns;
3,206✔
2315

2316
        assert(display_columns > 0);
3,510✔
2317

2318
        minimum_width = newa(size_t, display_columns);
3,510✔
2319
        maximum_width = newa(size_t, display_columns);
3,510✔
2320
        requested_width = newa(size_t, display_columns);
3,510✔
2321
        column_weight = newa0(uint64_t, display_columns);
3,510✔
2322

2323
        for (size_t j = 0; j < display_columns; j++) {
13,447✔
2324
                minimum_width[j] = 1;
9,937✔
2325
                maximum_width[j] = SIZE_MAX;
9,937✔
2326
        }
2327

2328
        for (unsigned pass = 0; pass < 2; pass++) {
3,528✔
2329
                /* First pass: determine column sizes */
2330

2331
                for (size_t j = 0; j < display_columns; j++)
13,503✔
2332
                        requested_width[j] = SIZE_MAX;
9,975✔
2333

2334
                bool any_soft = false;
3,528✔
2335

2336
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
54,085✔
2337
                        TableData **row;
50,557✔
2338

2339
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2340
                         * hence we don't care for sorted[] during the first pass. */
2341
                        row = t->data + i * t->n_columns;
50,557✔
2342

2343
                        for (size_t j = 0; j < display_columns; j++) {
218,021✔
2344
                                TableData *d;
167,464✔
2345
                                size_t req_width, req_height;
167,464✔
2346

2347
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
167,464✔
2348

2349
                                r = table_data_requested_width_height(t, d,
167,690✔
2350
                                                                      width ? width[j] : SIZE_MAX,
226✔
2351
                                                                      &req_width, &req_height, &any_soft);
2352
                                if (r < 0)
167,464✔
2353
                                        return r;
×
2354
                                if (r > 0) { /* Truncated because too many lines? */
167,464✔
2355
                                        _cleanup_free_ char *last = NULL;
25✔
2356
                                        const char *field;
25✔
2357

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

2363
                                        field = table_data_format(t, d, false,
28✔
2364
                                                                  width ? width[j] : SIZE_MAX,
3✔
2365
                                                                  &any_soft);
2366
                                        if (!field)
25✔
2367
                                                return -ENOMEM;
2368

2369
                                        assert_se(t->cell_height_max > 0);
25✔
2370
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2371
                                        if (r < 0)
25✔
2372
                                                return r;
2373

2374
                                        req_width = MAX(req_width,
25✔
2375
                                                        utf8_console_width(last) +
2376
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2377
                                }
2378

2379
                                /* Determine the biggest width that any cell in this column would like to have */
2380
                                if (requested_width[j] == SIZE_MAX ||
167,464✔
2381
                                    requested_width[j] < req_width)
157,521✔
2382
                                        requested_width[j] = req_width;
22,421✔
2383

2384
                                /* Determine the minimum width any cell in this column needs */
2385
                                if (minimum_width[j] < d->minimum_width)
167,464✔
2386
                                        minimum_width[j] = d->minimum_width;
38✔
2387

2388
                                /* Determine the maximum width any cell in this column needs */
2389
                                if (d->maximum_width != SIZE_MAX &&
167,464✔
2390
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2391
                                     maximum_width[j] > d->maximum_width))
2392
                                        maximum_width[j] = d->maximum_width;
20✔
2393

2394
                                /* Determine the full columns weight */
2395
                                column_weight[j] += d->weight;
167,464✔
2396
                        }
2397
                }
2398

2399
                /* One space between each column */
2400
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,528✔
2401

2402
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2403
                weight_sum = 0;
3,528✔
2404
                for (size_t j = 0; j < display_columns; j++) {
13,503✔
2405
                        weight_sum += column_weight[j];
9,975✔
2406

2407
                        table_minimum_width += minimum_width[j];
9,975✔
2408

2409
                        if (maximum_width[j] == SIZE_MAX)
9,975✔
2410
                                table_maximum_width = SIZE_MAX;
2411
                        else
2412
                                table_maximum_width += maximum_width[j];
20✔
2413

2414
                        table_requested_width += requested_width[j];
9,975✔
2415
                }
2416

2417
                /* Calculate effective table width */
2418
                if (t->width != 0 && t->width != SIZE_MAX)
3,528✔
2419
                        table_effective_width = t->width;
2420
                else if (t->width == 0 ||
3,521✔
2421
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,450✔
2422
                        table_effective_width = table_requested_width;
2423
                else
2424
                        table_effective_width = MIN(table_requested_width, columns());
36✔
2425

2426
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,528✔
2427
                        table_effective_width = table_maximum_width;
×
2428

2429
                if (table_effective_width < table_minimum_width)
3,528✔
2430
                        table_effective_width = table_minimum_width;
2✔
2431

2432
                if (!width)
3,528✔
2433
                        width = newa(size_t, display_columns);
3,510✔
2434

2435
                if (table_effective_width >= table_requested_width) {
3,528✔
2436
                        size_t extra;
3,506✔
2437

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

2441
                        extra = table_effective_width - table_requested_width;
3,506✔
2442

2443
                        for (size_t j = 0; j < display_columns; j++) {
13,425✔
2444
                                size_t delta;
9,919✔
2445

2446
                                if (weight_sum == 0)
9,919✔
2447
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2448
                                else
2449
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
9,887✔
2450

2451
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
9,919✔
2452
                                        width[j] = maximum_width[j];
2✔
2453

2454
                                if (width[j] < minimum_width[j])
9,919✔
2455
                                        width[j] = minimum_width[j];
×
2456

2457
                                delta = LESS_BY(width[j], requested_width[j]);
9,919✔
2458

2459
                                /* Subtract what we just added from the rest */
2460
                                if (extra > delta)
9,919✔
2461
                                        extra -= delta;
13✔
2462
                                else
2463
                                        extra = 0;
2464

2465
                                assert(weight_sum >= column_weight[j]);
9,919✔
2466
                                weight_sum -= column_weight[j];
9,919✔
2467
                        }
2468

2469
                        break; /* Every column should be happy, no need to repeat calculations. */
3,510✔
2470
                } else {
2471
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2472
                         * with the minimum they need, and then distribute anything left. */
2473
                        bool finalize = false;
22✔
2474
                        size_t extra;
22✔
2475

2476
                        extra = table_effective_width - table_minimum_width;
22✔
2477

2478
                        for (size_t j = 0; j < display_columns; j++)
78✔
2479
                                width[j] = SIZE_MAX;
56✔
2480

2481
                        for (;;) {
64✔
2482
                                bool restart = false;
64✔
2483

2484
                                for (size_t j = 0; j < display_columns; j++) {
186✔
2485
                                        size_t delta, w;
142✔
2486

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

2491
                                        if (weight_sum == 0)
95✔
2492
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2493
                                        else
2494
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
95✔
2495

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

2502
                                                w = requested_width[j];
2503
                                                restart = true;
2504

2505
                                        } else if (!finalize)
74✔
2506
                                                continue;
39✔
2507

2508
                                        width[j] = w;
56✔
2509

2510
                                        assert(w >= minimum_width[j]);
56✔
2511
                                        delta = w - minimum_width[j];
56✔
2512

2513
                                        assert(delta <= extra);
56✔
2514
                                        extra -= delta;
56✔
2515

2516
                                        assert(weight_sum >= column_weight[j]);
56✔
2517
                                        weight_sum -= column_weight[j];
56✔
2518

2519
                                        if (restart && !finalize)
56✔
2520
                                                break;
2521
                                }
2522

2523
                                if (finalize)
64✔
2524
                                        break;
2525

2526
                                if (!restart)
42✔
2527
                                        finalize = true;
22✔
2528
                        }
2529

2530
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
22✔
2531
                                        * let's try to reformat them with the new widths. Otherwise, let's
2532
                                        * move on. */
2533
                                break;
2534
                }
2535
        }
2536

2537
        /* Second pass: show output */
2538
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
53,980✔
2539
                size_t n_subline = 0;
50,470✔
2540
                bool more_sublines;
50,470✔
2541
                TableData **row;
50,470✔
2542

2543
                if (sorted)
50,470✔
2544
                        row = t->data + sorted[i];
3,070✔
2545
                else
2546
                        row = t->data + i * t->n_columns;
47,400✔
2547

2548
                do {
53,699✔
2549
                        const char *gap_color = NULL, *gap_underline = NULL;
53,699✔
2550
                        more_sublines = false;
53,699✔
2551

2552
                        for (size_t j = 0; j < display_columns; j++) {
227,397✔
2553
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
173,698✔
2554
                                bool lines_truncated = false;
173,698✔
2555
                                const char *field, *color = NULL, *underline = NULL;
173,698✔
2556
                                TableData *d;
173,698✔
2557
                                size_t l;
173,698✔
2558

2559
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
173,698✔
2560

2561
                                field = table_data_format(t, d, false, width[j], NULL);
173,698✔
2562
                                if (!field)
173,698✔
2563
                                        return -ENOMEM;
2564

2565
                                r = string_extract_line(field, n_subline, &extracted);
173,698✔
2566
                                if (r < 0)
173,698✔
2567
                                        return r;
2568
                                if (r > 0) {
173,698✔
2569
                                        /* There are more lines to come */
2570
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,266✔
2571
                                                more_sublines = true; /* There are more lines to come */
2572
                                        else
2573
                                                lines_truncated = true;
25✔
2574
                                }
2575
                                if (extracted)
173,698✔
2576
                                        field = extracted;
7,917✔
2577

2578
                                l = utf8_console_width(field);
173,698✔
2579
                                if (l > width[j]) {
173,698✔
2580
                                        /* Field is wider than allocated space. Let's ellipsize */
2581

2582
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
33✔
2583
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2584
                                        if (!buffer)
33✔
2585
                                                return -ENOMEM;
2586

2587
                                        field = buffer;
2588
                                } else {
2589
                                        if (lines_truncated) {
173,665✔
2590
                                                _cleanup_free_ char *padded = NULL;
25✔
2591

2592
                                                /* We truncated more lines of this cell, let's add an
2593
                                                 * ellipsis. We first append it, but that might make our
2594
                                                 * string grow above what we have space for, hence ellipsize
2595
                                                 * right after. This will truncate the ellipsis and add a new
2596
                                                 * one. */
2597

2598
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2599
                                                if (!padded)
25✔
2600
                                                        return -ENOMEM;
2601

2602
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2603
                                                if (!buffer)
25✔
2604
                                                        return -ENOMEM;
2605

2606
                                                field = buffer;
25✔
2607
                                                l = utf8_console_width(field);
25✔
2608
                                        }
2609

2610
                                        if (l < width[j]) {
173,665✔
2611
                                                _cleanup_free_ char *aligned = NULL;
×
2612
                                                /* Field is shorter than allocated space. Let's align with spaces */
2613

2614
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
126,875✔
2615
                                                if (!aligned)
126,875✔
2616
                                                        return -ENOMEM;
×
2617

2618
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2619
                                                if (j == display_columns - 1 &&
174,808✔
2620
                                                    (!colors_enabled() || !table_data_color(d)) &&
47,987✔
2621
                                                    (!underline_enabled() || !table_data_underline(d)) &&
95,918✔
2622
                                                    (!urlify_enabled() || !d->url))
47,983✔
2623
                                                        delete_trailing_chars(aligned, NULL);
47,931✔
2624

2625
                                                free_and_replace(buffer, aligned);
126,875✔
2626
                                                field = buffer;
126,875✔
2627
                                        }
2628
                                }
2629

2630
                                if (l >= width[j] && d->url) {
173,698✔
2631
                                        _cleanup_free_ char *clickable = NULL;
×
2632

2633
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2634
                                        if (r < 0)
22✔
2635
                                                return r;
×
2636

2637
                                        free_and_replace(buffer, clickable);
22✔
2638
                                        field = buffer;
22✔
2639
                                }
2640

2641
                                if (colors_enabled() && gap_color)
173,698✔
2642
                                        fputs(gap_color, f);
×
2643
                                if (underline_enabled() && gap_underline)
173,698✔
2644
                                        fputs(gap_underline, f);
16✔
2645

2646
                                if (j > 0)
173,698✔
2647
                                        fputc(' ', f); /* column separator left of cell */
119,999✔
2648

2649
                                /* Undo gap color/underline */
2650
                                if ((colors_enabled() && gap_color) ||
347,396✔
2651
                                    (underline_enabled() && gap_underline))
174,409✔
2652
                                        fputs(ANSI_NORMAL, f);
16✔
2653

2654
                                if (colors_enabled()) {
173,698✔
2655
                                        color = table_data_color(d);
711✔
2656
                                        if (color)
711✔
2657
                                                fputs(color, f);
295✔
2658
                                }
2659

2660
                                if (underline_enabled()) {
173,698✔
2661
                                        underline = table_data_underline(d);
711✔
2662
                                        if (underline)
711✔
2663
                                                fputs(underline, f);
18✔
2664
                                }
2665

2666
                                fputs(field, f);
173,698✔
2667

2668
                                if (color || underline)
173,698✔
2669
                                        fputs(ANSI_NORMAL, f);
313✔
2670

2671
                                gap_color = d->rgap_color;
173,698✔
2672
                                gap_underline = table_data_rgap_underline(d);
173,698✔
2673
                        }
2674

2675
                        fputc('\n', f);
53,699✔
2676
                        n_subline++;
53,699✔
2677
                } while (more_sublines);
53,699✔
2678
        }
2679

2680
        return fflush_and_check(f);
3,510✔
2681
}
2682

2683
int table_format(Table *t, char **ret) {
39✔
2684
        _cleanup_(memstream_done) MemStream m = {};
39✔
2685
        FILE *f;
39✔
2686
        int r;
39✔
2687

2688
        assert(t);
39✔
2689
        assert(ret);
39✔
2690

2691
        f = memstream_init(&m);
39✔
2692
        if (!f)
39✔
2693
                return -ENOMEM;
2694

2695
        r = table_print(t, f);
39✔
2696
        if (r < 0)
39✔
2697
                return r;
2698

2699
        return memstream_finalize(&m, ret, NULL);
39✔
2700
}
2701

2702
size_t table_get_rows(Table *t) {
1,748✔
2703
        if (!t)
1,748✔
2704
                return 0;
2705

2706
        assert(t->n_columns > 0);
1,748✔
2707
        return t->n_cells / t->n_columns;
1,748✔
2708
}
2709

2710
size_t table_get_columns(Table *t) {
34✔
2711
        if (!t)
34✔
2712
                return 0;
2713

2714
        assert(t->n_columns > 0);
34✔
2715
        return t->n_columns;
2716
}
2717

2718
size_t table_get_current_column(Table *t) {
80✔
2719
        if (!t)
80✔
2720
                return 0;
2721

2722
        assert(t->n_columns > 0);
80✔
2723
        return t->n_cells % t->n_columns;
80✔
2724
}
2725

2726
int table_set_reverse(Table *t, size_t column, bool b) {
81✔
2727
        assert(t);
81✔
2728
        assert(column < t->n_columns);
81✔
2729

2730
        if (!t->reverse_map) {
81✔
2731
                if (!b)
81✔
2732
                        return 0;
2733

2734
                t->reverse_map = new0(bool, t->n_columns);
27✔
2735
                if (!t->reverse_map)
27✔
2736
                        return -ENOMEM;
2737
        }
2738

2739
        t->reverse_map[column] = b;
27✔
2740
        return 0;
27✔
2741
}
2742

2743
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,179✔
2744
        size_t i;
8,179✔
2745

2746
        assert(t);
8,179✔
2747

2748
        if (column >= t->n_columns)
8,179✔
2749
                return NULL;
2750

2751
        i = row * t->n_columns + column;
8,179✔
2752
        if (i >= t->n_cells)
8,179✔
2753
                return NULL;
2754

2755
        return TABLE_INDEX_TO_CELL(i);
8,179✔
2756
}
2757

2758
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2759
        TableData *d;
4,374✔
2760

2761
        assert(t);
4,374✔
2762

2763
        d = table_get_data(t, cell);
4,374✔
2764
        if (!d)
4,374✔
2765
                return NULL;
2766

2767
        return d->data;
4,374✔
2768
}
2769

2770
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2771
        TableCell *cell;
4,374✔
2772

2773
        cell = table_get_cell(t, row, column);
4,374✔
2774
        if (!cell)
4,374✔
2775
                return NULL;
2776

2777
        return table_get(t, cell);
4,374✔
2778
}
2779

2780
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
4,057✔
2781

2782
        switch (d->type) {
4,057✔
2783

2784
        case TABLE_EMPTY:
327✔
2785
                return sd_json_variant_new_null(ret);
327✔
2786

2787
        case TABLE_STRING:
2,059✔
2788
        case TABLE_PATH:
2789
        case TABLE_PATH_BASENAME:
2790
        case TABLE_FIELD:
2791
        case TABLE_HEADER:
2792
        case TABLE_VERSION:
2793
                return sd_json_variant_new_string(ret, d->string);
2,059✔
2794

2795
        case TABLE_STRV:
3✔
2796
        case TABLE_STRV_WRAPPED:
2797
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2798

2799
        case TABLE_BOOLEAN_CHECKMARK:
152✔
2800
        case TABLE_BOOLEAN:
2801
                return sd_json_variant_new_boolean(ret, d->boolean);
152✔
2802

2803
        case TABLE_TIMESTAMP:
155✔
2804
        case TABLE_TIMESTAMP_UTC:
2805
        case TABLE_TIMESTAMP_RELATIVE:
2806
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2807
        case TABLE_TIMESTAMP_LEFT:
2808
        case TABLE_TIMESTAMP_DATE:
2809
                if (d->timestamp == USEC_INFINITY)
155✔
2810
                        return sd_json_variant_new_null(ret);
×
2811

2812
                return sd_json_variant_new_unsigned(ret, d->timestamp);
155✔
2813

2814
        case TABLE_TIMESPAN:
×
2815
        case TABLE_TIMESPAN_MSEC:
2816
        case TABLE_TIMESPAN_DAY:
2817
                if (d->timespan == USEC_INFINITY)
×
2818
                        return sd_json_variant_new_null(ret);
×
2819

2820
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2821

2822
        case TABLE_SIZE:
36✔
2823
        case TABLE_BPS:
2824
                if (d->size == UINT64_MAX)
36✔
2825
                        return sd_json_variant_new_null(ret);
16✔
2826

2827
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2828

2829
        case TABLE_INT:
27✔
2830
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2831

2832
        case TABLE_INT8:
6✔
2833
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2834

2835
        case TABLE_INT16:
6✔
2836
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2837

2838
        case TABLE_INT32:
6✔
2839
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2840

2841
        case TABLE_INT64:
46✔
2842
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2843

2844
        case TABLE_UINT:
43✔
2845
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2846

2847
        case TABLE_UINT8:
4✔
2848
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2849

2850
        case TABLE_UINT16:
4✔
2851
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2852

2853
        case TABLE_UINT32:
111✔
2854
        case TABLE_UINT32_HEX:
2855
        case TABLE_UINT32_HEX_0x:
2856
                return sd_json_variant_new_unsigned(ret, d->uint32);
111✔
2857

2858
        case TABLE_UINT64:
386✔
2859
        case TABLE_UINT64_HEX:
2860
        case TABLE_UINT64_HEX_0x:
2861
                return sd_json_variant_new_unsigned(ret, d->uint64);
386✔
2862

2863
        case TABLE_PERCENT:
×
2864
                return sd_json_variant_new_integer(ret, d->percent);
×
2865

2866
        case TABLE_IFINDEX:
×
2867
                if (d->ifindex <= 0)
×
2868
                        return sd_json_variant_new_null(ret);
×
2869

2870
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2871

2872
        case TABLE_IN_ADDR:
2873
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2874

2875
        case TABLE_IN6_ADDR:
2876
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2877

2878
        case TABLE_ID128:
502✔
2879
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2880

2881
        case TABLE_UUID:
91✔
2882
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2883

2884
        case TABLE_UID:
8✔
2885
                if (!uid_is_valid(d->uid))
8✔
2886
                        return sd_json_variant_new_null(ret);
×
2887

2888
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2889

2890
        case TABLE_GID:
8✔
2891
                if (!gid_is_valid(d->gid))
8✔
2892
                        return sd_json_variant_new_null(ret);
×
2893

2894
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2895

2896
        case TABLE_PID:
14✔
2897
                if (!pid_is_valid(d->pid))
14✔
2898
                        return sd_json_variant_new_null(ret);
×
2899

2900
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2901

2902
        case TABLE_SIGNAL:
8✔
2903
                if (!SIGNAL_VALID(d->int_val))
8✔
2904
                        return sd_json_variant_new_null(ret);
×
2905

2906
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2907

2908
        case TABLE_MODE:
51✔
2909
        case TABLE_MODE_INODE_TYPE:
2910
                if (d->mode == MODE_INVALID)
51✔
2911
                        return sd_json_variant_new_null(ret);
×
2912

2913
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2914

2915
        case TABLE_DEVNUM:
4✔
2916
                if (devnum_is_zero(d->devnum))
4✔
2917
                        return sd_json_variant_new_null(ret);
2✔
2918

2919
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2920
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2921
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2922

2923
        default:
2924
                return -EINVAL;
2925
        }
2926
}
2927

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

2937
        bool new_word = true;
703✔
2938
        char *c;
703✔
2939

2940
        assert(str);
703✔
2941

2942
        c = strdup(str);
703✔
2943
        if (!c)
703✔
2944
                return NULL;
2945

2946
        for (char *x = c; *x; x++) {
5,282✔
2947
                if (!strchr(ALPHANUMERICAL, *x)) {
4,579✔
2948
                        *x = '_';
179✔
2949
                        new_word = true;
179✔
2950
                        continue;
179✔
2951
                }
2952

2953
                if (new_word) {
4,400✔
2954
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
877✔
2955
                                                                  * then we assume the whole word is all-caps
2956
                                                                  * and avoid lowercasing it. */
2957
                                *x = ascii_tolower(*x);
876✔
2958
                        new_word = false;
2959
                }
2960
        }
2961

2962
        return c;
2963
}
2964

2965
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
689✔
2966
        _cleanup_free_ char *mangled = NULL;
1,378✔
2967
        const char *n;
689✔
2968

2969
        assert(t);
689✔
2970
        assert(d);
689✔
2971
        assert(ret);
689✔
2972

2973
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
689✔
2974
                n = d->string;
689✔
2975
        else {
2976
                n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
×
2977
                if (!n)
×
2978
                        return -ENOMEM;
2979
        }
2980

2981
        mangled = table_mangle_to_json_field_name(n);
689✔
2982
        if (!mangled)
689✔
2983
                return -ENOMEM;
2984

2985
        *ret = TAKE_PTR(mangled);
689✔
2986
        return 0;
689✔
2987
}
2988

2989
static const char *table_get_json_field_name(Table *t, size_t idx) {
755✔
2990
        assert(t);
755✔
2991

2992
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
755✔
2993
}
2994

2995
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
95✔
2996
        sd_json_variant **rows = NULL, **elements = NULL;
95✔
2997
        _cleanup_free_ size_t *sorted = NULL;
95✔
2998
        size_t n_rows, display_columns;
95✔
2999
        int r;
95✔
3000

3001
        assert(t);
95✔
3002
        assert(!t->vertical);
95✔
3003

3004
        /* Ensure we have no incomplete rows */
3005
        assert(t->n_columns > 0);
95✔
3006
        assert(t->n_cells % t->n_columns == 0);
95✔
3007

3008
        n_rows = t->n_cells / t->n_columns;
95✔
3009
        assert(n_rows > 0); /* at least the header row must be complete */
95✔
3010

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

3014
                sorted = new(size_t, n_rows);
31✔
3015
                if (!sorted)
31✔
3016
                        return -ENOMEM;
3017

3018
                for (size_t i = 0; i < n_rows; i++)
174✔
3019
                        sorted[i] = i * t->n_columns;
143✔
3020

3021
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
31✔
3022
        }
3023

3024
        if (t->display_map)
95✔
3025
                display_columns = t->n_display_map;
43✔
3026
        else
3027
                display_columns = t->n_columns;
52✔
3028
        assert(display_columns > 0);
95✔
3029

3030
        elements = new0(sd_json_variant*, display_columns * 2);
95✔
3031
        if (!elements)
95✔
3032
                return -ENOMEM;
3033

3034
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
95✔
3035

3036
        for (size_t j = 0; j < display_columns; j++) {
846✔
3037
                _cleanup_free_ char *mangled = NULL;
751✔
3038
                const char *n;
751✔
3039
                size_t c;
751✔
3040

3041
                c = t->display_map ? t->display_map[j] : j;
751✔
3042

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

3050
                        n = mangled;
687✔
3051
                }
3052

3053
                r = sd_json_variant_new_string(elements + j*2, n);
751✔
3054
                if (r < 0)
751✔
3055
                        return r;
3056
        }
3057

3058
        rows = new0(sd_json_variant*, n_rows-1);
95✔
3059
        if (!rows)
95✔
3060
                return -ENOMEM;
3061

3062
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
95✔
3063

3064
        for (size_t i = 1; i < n_rows; i++) {
1,040✔
3065
                TableData **row;
945✔
3066

3067
                if (sorted)
945✔
3068
                        row = t->data + sorted[i];
112✔
3069
                else
3070
                        row = t->data + i * t->n_columns;
833✔
3071

3072
                for (size_t j = 0; j < display_columns; j++) {
4,998✔
3073
                        TableData *d;
4,053✔
3074
                        size_t k;
4,053✔
3075

3076
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
4,053✔
3077

3078
                        k = j*2+1;
4,053✔
3079
                        elements[k] = sd_json_variant_unref(elements[k]);
4,053✔
3080

3081
                        r = table_data_to_json(d, elements + k);
4,053✔
3082
                        if (r < 0)
4,053✔
3083
                                return r;
3084
                }
3085

3086
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
945✔
3087
                if (r < 0)
945✔
3088
                        return r;
3089
        }
3090

3091
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
95✔
3092
}
3093

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

3099
        assert(t);
1✔
3100
        assert(t->vertical);
1✔
3101

3102
        if (t->n_columns != 2)
1✔
3103
                return -EINVAL;
1✔
3104

3105
        /* Ensure we have no incomplete rows */
3106
        assert(t->n_cells % t->n_columns == 0);
1✔
3107

3108
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3109
        if (!elements)
1✔
3110
                return -ENOMEM;
3111

3112
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3113

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

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

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

3126
                                n = mangled;
2✔
3127
                        }
3128

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

3135
                n_elements++;
8✔
3136
        }
3137

3138
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3139
}
3140

3141
int table_to_json(Table *t, sd_json_variant **ret) {
96✔
3142
        assert(t);
96✔
3143

3144
        if (t->vertical)
96✔
3145
                return table_to_json_vertical(t, ret);
1✔
3146

3147
        return table_to_json_regular(t, ret);
95✔
3148
}
3149

3150
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
428✔
3151
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
428✔
3152
        int r;
428✔
3153

3154
        assert(t);
428✔
3155

3156
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
428✔
3157
                return table_print(t, f);
359✔
3158

3159
        if (!f)
69✔
3160
                f = stdout;
2✔
3161

3162
        r = table_to_json(t, &v);
69✔
3163
        if (r < 0)
69✔
3164
                return r;
3165

3166
        sd_json_variant_dump(v, flags, f, NULL);
69✔
3167

3168
        return fflush_and_check(f);
69✔
3169
}
3170

3171
int table_print_with_pager(
426✔
3172
                Table *t,
3173
                sd_json_format_flags_t json_format_flags,
3174
                PagerFlags pager_flags,
3175
                bool show_header) {
3176

3177
        bool saved_header;
426✔
3178
        int r;
426✔
3179

3180
        assert(t);
426✔
3181

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

3185
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
426✔
3186
                pager_open(pager_flags);
400✔
3187

3188
        saved_header = t->header;
426✔
3189
        t->header = show_header;
426✔
3190
        r = table_print_json(t, stdout, json_format_flags);
426✔
3191
        t->header = saved_header;
426✔
3192
        if (r < 0)
426✔
3193
                return table_log_print_error(r);
×
3194

3195
        return 0;
3196
}
3197

3198
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
526✔
3199
        int r;
526✔
3200

3201
        assert(t);
526✔
3202

3203
        if (name) {
526✔
3204
                size_t m;
524✔
3205

3206
                m = MAX(idx + 1, t->n_json_fields);
524✔
3207
                if (!GREEDY_REALLOC0(t->json_fields, m))
524✔
3208
                        return -ENOMEM;
3209

3210
                r = free_and_strdup(t->json_fields + idx, name);
524✔
3211
                if (r < 0)
524✔
3212
                        return r;
3213

3214
                t->n_json_fields = m;
524✔
3215
                return r;
524✔
3216
        } else {
3217
                if (idx >= t->n_json_fields)
2✔
3218
                        return 0;
3219

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