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

systemd / systemd / 17750153392

15 Sep 2025 06:38PM UTC coverage: 72.241% (-0.03%) from 72.266%
17750153392

push

github

bluca
Revert "TEST-55-OOMD: Verify that ExecStopPost= runs on oom-kill"

The test consistently fails on CentOS 9:

TEST-55-OOMD.sh[678]: + test -f /run/testbloat-exec-stop-post
[FAILED] Failed to start TEST-55-OOMD.service.

https://github.com/systemd/systemd/actions/runs/17689186773/job/50293446228?pr=38911

This reverts commit 5bf7438ff.

302589 of 418863 relevant lines covered (72.24%)

1047733.04 hits per line

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

89.99
/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) {
388,824✔
118
        size_t i;
388,824✔
119

120
        assert(cell);
388,824✔
121

122
        i = PTR_TO_SIZE(cell);
388,824✔
123
        assert(i > 0);
388,824✔
124

125
        return i-1;
388,824✔
126
}
127

128
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
178,368✔
129
        assert(index != SIZE_MAX);
178,368✔
130
        return SIZE_TO_PTR(index + 1);
178,368✔
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,527✔
161
        _cleanup_(table_unrefp) Table *t = NULL;
3,527✔
162

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

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

169
        *t = (Table) {
3,527✔
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,527✔
178
}
179

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

186
        assert(first_header);
617✔
187

188
        va_start(ap, first_header);
617✔
189
        for (;;) {
9,027✔
190
                if (!va_arg(ap, const char*))
4,822✔
191
                        break;
192

193
                n_columns++;
4,205✔
194
        }
195
        va_end(ap);
617✔
196

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

201
        va_start(ap, first_header);
617✔
202
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,439✔
203
                TableCell *cell;
4,822✔
204

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

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

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

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

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

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

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

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

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

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

243
static TableData *table_data_free(TableData *d) {
175,043✔
244
        assert(d);
175,043✔
245

246
        free(d->formatted);
175,043✔
247
        free(d->url);
175,043✔
248

249
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
175,043✔
250
                strv_free(d->strv);
6,247✔
251

252
        return mfree(d);
175,043✔
253
}
254

255
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
306,245✔
256
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
189,740✔
257

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

262
        for (size_t i = 0; i < t->n_cells; i++)
193,279✔
263
                table_data_unref(t->data[i]);
189,752✔
264

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

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

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

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

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

280
        switch (type) {
472,307✔
281

282
        case TABLE_EMPTY:
283
                return 0;
284

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

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

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

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

311
        case TABLE_SIZE:
18,597✔
312
        case TABLE_INT64:
313
        case TABLE_UINT64:
314
        case TABLE_UINT64_HEX:
315
        case TABLE_BPS:
316
                return sizeof(uint64_t);
18,597✔
317

318
        case TABLE_INT32:
4,482✔
319
        case TABLE_UINT32:
320
        case TABLE_UINT32_HEX:
321
                return sizeof(uint32_t);
4,482✔
322

323
        case TABLE_INT16:
100✔
324
        case TABLE_UINT16:
325
                return sizeof(uint16_t);
100✔
326

327
        case TABLE_INT8:
54✔
328
        case TABLE_UINT8:
329
                return sizeof(uint8_t);
54✔
330

331
        case TABLE_INT:
4,193✔
332
        case TABLE_UINT:
333
        case TABLE_PERCENT:
334
        case TABLE_IFINDEX:
335
        case TABLE_SIGNAL:
336
                return sizeof(int);
4,193✔
337

338
        case TABLE_IN_ADDR:
289✔
339
                return sizeof(struct in_addr);
289✔
340

341
        case TABLE_IN6_ADDR:
38✔
342
                return sizeof(struct in6_addr);
38✔
343

344
        case TABLE_UUID:
5,542✔
345
        case TABLE_ID128:
346
                return sizeof(sd_id128_t);
5,542✔
347

348
        case TABLE_UID:
1,362✔
349
                return sizeof(uid_t);
1,362✔
350
        case TABLE_GID:
1,809✔
351
                return sizeof(gid_t);
1,809✔
352
        case TABLE_PID:
586✔
353
                return sizeof(pid_t);
586✔
354

355
        case TABLE_MODE:
178✔
356
        case TABLE_MODE_INODE_TYPE:
357
                return sizeof(mode_t);
178✔
358

359
        case TABLE_DEVNUM:
6✔
360
                return sizeof(dev_t);
6✔
361

362
        default:
×
363
                assert_not_reached();
×
364
        }
365
}
366

367
static bool table_data_matches(
178,762✔
368
                TableData *d,
369
                TableDataType type,
370
                const void *data,
371
                size_t minimum_width,
372
                size_t maximum_width,
373
                unsigned weight,
374
                unsigned align_percent,
375
                unsigned ellipsize_percent,
376
                bool uppercase) {
377

378
        size_t k, l;
178,762✔
379
        assert(d);
178,762✔
380

381
        if (d->type != type)
178,762✔
382
                return false;
383

384
        if (d->minimum_width != minimum_width)
153,580✔
385
                return false;
386

387
        if (d->maximum_width != maximum_width)
153,580✔
388
                return false;
389

390
        if (d->weight != weight)
152,223✔
391
                return false;
392

393
        if (d->align_percent != align_percent)
152,223✔
394
                return false;
395

396
        if (d->ellipsize_percent != ellipsize_percent)
152,223✔
397
                return false;
398

399
        if (d->uppercase != uppercase)
152,223✔
400
                return false;
401

402
        /* If a color/url is set, refuse to merge */
403
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
152,222✔
404
                return false;
405
        if (d->url)
150,213✔
406
                return false;
407

408
        k = table_data_size(type, data);
148,632✔
409
        l = table_data_size(d->type, d->data);
148,632✔
410
        if (k != l)
148,632✔
411
                return false;
412

413
        return memcmp_safe(data, d->data, l) == 0;
90,685✔
414
}
415

416
static TableData *table_data_new(
175,043✔
417
                TableDataType type,
418
                const void *data,
419
                size_t minimum_width,
420
                size_t maximum_width,
421
                unsigned weight,
422
                unsigned align_percent,
423
                unsigned ellipsize_percent,
424
                bool uppercase) {
425

426
        _cleanup_free_ TableData *d = NULL;
175,043✔
427
        size_t data_size;
175,043✔
428

429
        data_size = table_data_size(type, data);
175,043✔
430

431
        d = malloc0(offsetof(TableData, data) + data_size);
175,043✔
432
        if (!d)
175,043✔
433
                return NULL;
434

435
        d->n_ref = 1;
175,043✔
436
        d->type = type;
175,043✔
437
        d->minimum_width = minimum_width;
175,043✔
438
        d->maximum_width = maximum_width;
175,043✔
439
        d->weight = weight;
175,043✔
440
        d->align_percent = align_percent;
175,043✔
441
        d->ellipsize_percent = ellipsize_percent;
175,043✔
442
        d->uppercase = uppercase;
175,043✔
443

444
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
175,043✔
445
                d->strv = strv_copy(data);
6,247✔
446
                if (!d->strv)
6,247✔
447
                        return NULL;
×
448
        } else
449
                memcpy_safe(d->data, data, data_size);
168,796✔
450

451
        return TAKE_PTR(d);
452
}
453

454
int table_add_cell_full(
189,740✔
455
                Table *t,
456
                TableCell **ret_cell,
457
                TableDataType type,
458
                const void *data,
459
                size_t minimum_width,
460
                size_t maximum_width,
461
                unsigned weight,
462
                unsigned align_percent,
463
                unsigned ellipsize_percent) {
464

465
        _cleanup_(table_data_unrefp) TableData *d = NULL;
189,740✔
466
        bool uppercase;
189,740✔
467
        TableData *p;
189,740✔
468

469
        assert(t);
189,740✔
470
        assert(type >= 0);
189,740✔
471
        assert(type < _TABLE_DATA_TYPE_MAX);
189,740✔
472

473
        /* Special rule: patch NULL data fields to the empty field */
474
        if (!data)
189,740✔
475
                type = TABLE_EMPTY;
9,258✔
476

477
        /* Determine the cell adjacent to the current one, but one row up */
478
        if (t->n_cells >= t->n_columns)
189,740✔
479
                assert_se(p = t->data[t->n_cells - t->n_columns]);
178,762✔
480
        else
481
                p = NULL;
482

483
        /* If formatting parameters are left unspecified, copy from the previous row */
484
        if (minimum_width == SIZE_MAX)
189,740✔
485
                minimum_width = p ? p->minimum_width : 1;
189,740✔
486

487
        if (weight == UINT_MAX)
189,740✔
488
                weight = p ? p->weight : DEFAULT_WEIGHT;
189,740✔
489

490
        if (align_percent == UINT_MAX)
189,740✔
491
                align_percent = p ? p->align_percent : 0;
189,740✔
492

493
        if (ellipsize_percent == UINT_MAX)
189,740✔
494
                ellipsize_percent = p ? p->ellipsize_percent : 100;
189,740✔
495

496
        assert(align_percent <= 100);
189,740✔
497
        assert(ellipsize_percent <= 100);
189,740✔
498

499
        uppercase = type == TABLE_HEADER;
189,740✔
500

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

504
        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
189,740✔
505
                d = table_data_ref(p);
65,589✔
506
        else {
507
                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
124,151✔
508
                if (!d)
124,151✔
509
                        return -ENOMEM;
510
        }
511

512
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
189,740✔
513
                return -ENOMEM;
514

515
        if (ret_cell)
189,740✔
516
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
170,313✔
517

518
        t->data[t->n_cells++] = TAKE_PTR(d);
189,740✔
519

520
        return 0;
189,740✔
521
}
522

523
int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
7,846✔
524
        _cleanup_free_ char *buffer = NULL;
7,846✔
525
        va_list ap;
7,846✔
526
        int r;
7,846✔
527

528
        assert(t);
7,846✔
529
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER));
7,846✔
530

531
        va_start(ap, format);
7,846✔
532
        r = vasprintf(&buffer, format, ap);
7,846✔
533
        va_end(ap);
7,846✔
534
        if (r < 0)
7,846✔
535
                return -ENOMEM;
536

537
        return table_add_cell(t, ret_cell, dt, buffer);
7,846✔
538
}
539

540
int table_fill_empty(Table *t, size_t until_column) {
54✔
541
        int r;
54✔
542

543
        assert(t);
54✔
544

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

548
        if (until_column >= t->n_columns)
54✔
549
                return -EINVAL;
550

551
        do {
54✔
552
                r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
54✔
553
                if (r < 0)
54✔
554
                        return r;
555

556
        } while ((t->n_cells % t->n_columns) != until_column);
54✔
557

558
        return 0;
559
}
560

561
int table_dup_cell(Table *t, TableCell *cell) {
12✔
562
        size_t i;
12✔
563

564
        assert(t);
12✔
565

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

568
        i = TABLE_CELL_TO_INDEX(cell);
12✔
569
        if (i >= t->n_cells)
12✔
570
                return -ENXIO;
571

572
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
12✔
573
                return -ENOMEM;
574

575
        t->data[t->n_cells++] = table_data_ref(t->data[i]);
12✔
576
        return 0;
12✔
577
}
578

579
static int table_dedup_cell(Table *t, TableCell *cell) {
191,656✔
580
        _cleanup_free_ char *curl = NULL;
191,656✔
581
        TableData *nd, *od;
191,656✔
582
        size_t i;
191,656✔
583

584
        assert(t);
191,656✔
585

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

589
        i = TABLE_CELL_TO_INDEX(cell);
191,656✔
590
        if (i >= t->n_cells)
191,656✔
591
                return -ENXIO;
592

593
        assert_se(od = t->data[i]);
191,656✔
594
        if (od->n_ref == 1)
191,656✔
595
                return 0;
596

597
        assert(od->n_ref > 1);
49,769✔
598

599
        if (od->url) {
49,769✔
600
                curl = strdup(od->url);
×
601
                if (!curl)
×
602
                        return -ENOMEM;
603
        }
604

605
        nd = table_data_new(
99,538✔
606
                        od->type,
607
                        od->data,
49,769✔
608
                        od->minimum_width,
609
                        od->maximum_width,
610
                        od->weight,
611
                        od->align_percent,
612
                        od->ellipsize_percent,
613
                        od->uppercase);
49,769✔
614
        if (!nd)
49,769✔
615
                return -ENOMEM;
616

617
        nd->color = od->color;
49,769✔
618
        nd->rgap_color = od->rgap_color;
49,769✔
619
        nd->underline = od->underline;
49,769✔
620
        nd->rgap_underline = od->rgap_underline;
49,769✔
621
        nd->url = TAKE_PTR(curl);
49,769✔
622

623
        table_data_unref(od);
49,769✔
624
        t->data[i] = nd;
49,769✔
625

626
        assert(nd->n_ref == 1);
49,769✔
627

628
        return 1;
629
}
630

631
static TableData *table_get_data(Table *t, TableCell *cell) {
196,030✔
632
        size_t i;
196,030✔
633

634
        assert(t);
196,030✔
635
        assert(cell);
196,030✔
636

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

639
        i = TABLE_CELL_TO_INDEX(cell);
196,030✔
640
        if (i >= t->n_cells)
196,030✔
641
                return NULL;
642

643
        assert(t->data[i]);
196,030✔
644
        assert(t->data[i]->n_ref > 0);
196,030✔
645

646
        return t->data[i];
647
}
648

649
int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
1,445✔
650
        int r;
1,445✔
651

652
        assert(t);
1,445✔
653
        assert(cell);
1,445✔
654

655
        if (minimum_width == SIZE_MAX)
1,445✔
656
                minimum_width = 1;
×
657

658
        r = table_dedup_cell(t, cell);
1,445✔
659
        if (r < 0)
1,445✔
660
                return r;
661

662
        table_get_data(t, cell)->minimum_width = minimum_width;
1,445✔
663
        return 0;
1,445✔
664
}
665

666
int table_set_maximum_width(Table *t, TableCell *cell, size_t maximum_width) {
1,414✔
667
        int r;
1,414✔
668

669
        assert(t);
1,414✔
670
        assert(cell);
1,414✔
671

672
        r = table_dedup_cell(t, cell);
1,414✔
673
        if (r < 0)
1,414✔
674
                return r;
675

676
        table_get_data(t, cell)->maximum_width = maximum_width;
1,414✔
677
        return 0;
1,414✔
678
}
679

680
int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
5✔
681
        int r;
5✔
682

683
        assert(t);
5✔
684
        assert(cell);
5✔
685

686
        if (weight == UINT_MAX)
5✔
687
                weight = DEFAULT_WEIGHT;
×
688

689
        r = table_dedup_cell(t, cell);
5✔
690
        if (r < 0)
5✔
691
                return r;
692

693
        table_get_data(t, cell)->weight = weight;
5✔
694
        return 0;
5✔
695
}
696

697
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
13,174✔
698
        int r;
13,174✔
699

700
        assert(t);
13,174✔
701
        assert(cell);
13,174✔
702

703
        if (percent == UINT_MAX)
13,174✔
704
                percent = 0;
705

706
        assert(percent <= 100);
13,174✔
707

708
        r = table_dedup_cell(t, cell);
13,174✔
709
        if (r < 0)
13,174✔
710
                return r;
711

712
        table_get_data(t, cell)->align_percent = percent;
13,174✔
713
        return 0;
13,174✔
714
}
715

716
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent) {
2,958✔
717
        int r;
2,958✔
718

719
        assert(t);
2,958✔
720
        assert(cell);
2,958✔
721

722
        if (percent == UINT_MAX)
2,958✔
723
                percent = 100;
724

725
        assert(percent <= 100);
2,958✔
726

727
        r = table_dedup_cell(t, cell);
2,958✔
728
        if (r < 0)
2,958✔
729
                return r;
730

731
        table_get_data(t, cell)->ellipsize_percent = percent;
2,958✔
732
        return 0;
2,958✔
733
}
734

735
int table_set_color(Table *t, TableCell *cell, const char *color) {
55,760✔
736
        int r;
55,760✔
737

738
        assert(t);
55,760✔
739
        assert(cell);
55,760✔
740

741
        r = table_dedup_cell(t, cell);
55,760✔
742
        if (r < 0)
55,760✔
743
                return r;
744

745
        table_get_data(t, cell)->color = empty_to_null(color);
71,726✔
746
        return 0;
55,760✔
747
}
748

749
int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
3,081✔
750
        int r;
3,081✔
751

752
        assert(t);
3,081✔
753
        assert(cell);
3,081✔
754

755
        r = table_dedup_cell(t, cell);
3,081✔
756
        if (r < 0)
3,081✔
757
                return r;
758

759
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,162✔
760
        return 0;
3,081✔
761
}
762

763
int table_set_underline(Table *t, TableCell *cell, bool b) {
56,084✔
764
        TableData *d;
56,084✔
765
        int r;
56,084✔
766

767
        assert(t);
56,084✔
768
        assert(cell);
56,084✔
769

770
        r = table_dedup_cell(t, cell);
56,084✔
771
        if (r < 0)
56,084✔
772
                return r;
773

774
        assert_se(d = table_get_data(t, cell));
56,084✔
775

776
        if (d->underline == b)
56,084✔
777
                return 0;
778

779
        d->underline = b;
322✔
780
        return 1;
322✔
781
}
782

783
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
56,084✔
784
        TableData *d;
56,084✔
785
        int r;
56,084✔
786

787
        assert(t);
56,084✔
788
        assert(cell);
56,084✔
789

790
        r = table_dedup_cell(t, cell);
56,084✔
791
        if (r < 0)
56,084✔
792
                return r;
793

794
        assert_se(d = table_get_data(t, cell));
56,084✔
795

796
        if (d->rgap_underline == b)
56,084✔
797
                return 0;
798

799
        d->rgap_underline = b;
322✔
800
        return 1;
322✔
801
}
802

803
int table_set_url(Table *t, TableCell *cell, const char *url) {
1,650✔
804
        _cleanup_free_ char *copy = NULL;
1,650✔
805
        int r;
1,650✔
806

807
        assert(t);
1,650✔
808
        assert(cell);
1,650✔
809

810
        if (url) {
1,650✔
811
                copy = strdup(url);
1,638✔
812
                if (!copy)
1,638✔
813
                        return -ENOMEM;
814
        }
815

816
        r = table_dedup_cell(t, cell);
1,650✔
817
        if (r < 0)
1,650✔
818
                return r;
819

820
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,650✔
821
}
822

823
int table_set_uppercase(Table *t, TableCell *cell, bool b) {
1✔
824
        TableData *d;
1✔
825
        int r;
1✔
826

827
        assert(t);
1✔
828
        assert(cell);
1✔
829

830
        r = table_dedup_cell(t, cell);
1✔
831
        if (r < 0)
1✔
832
                return r;
833

834
        assert_se(d = table_get_data(t, cell));
1✔
835

836
        if (d->uppercase == b)
1✔
837
                return 0;
838

839
        d->formatted = mfree(d->formatted);
1✔
840
        d->uppercase = b;
1✔
841
        return 1;
1✔
842
}
843

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

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

852
        i = TABLE_CELL_TO_INDEX(cell);
1,123✔
853
        if (i >= t->n_cells)
1,123✔
854
                return -ENXIO;
855

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

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

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

876
        nd->color = od->color;
1,123✔
877
        nd->rgap_color = od->rgap_color;
1,123✔
878
        nd->underline = od->underline;
1,123✔
879
        nd->rgap_underline = od->rgap_underline;
1,123✔
880
        nd->url = TAKE_PTR(curl);
1,123✔
881

882
        table_data_unref(od);
1,123✔
883
        t->data[i] = nd;
1,123✔
884

885
        return 0;
1,123✔
886
}
887

888
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
45,198✔
889
        TableCell *last_cell = NULL;
45,198✔
890
        va_list ap;
45,198✔
891
        int r;
45,198✔
892

893
        assert(t);
45,198✔
894
        assert(first_type >= 0);
45,198✔
895
        assert(first_type < _TABLE_DATA_TYPE_MAX);
45,198✔
896

897
        va_start(ap, first_type);
45,198✔
898

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

926
                switch (type) {
329,016✔
927

928
                case TABLE_EMPTY:
929
                        data = NULL;
930
                        break;
931

932
                case TABLE_STRING:
125,617✔
933
                case TABLE_PATH:
934
                case TABLE_PATH_BASENAME:
935
                case TABLE_FIELD:
936
                case TABLE_HEADER:
937
                        data = va_arg(ap, const char *);
125,617✔
938
                        break;
125,617✔
939

940
                case TABLE_STRV:
6,563✔
941
                case TABLE_STRV_WRAPPED:
942
                        data = va_arg(ap, char * const *);
6,563✔
943
                        break;
6,563✔
944

945
                case TABLE_BOOLEAN_CHECKMARK:
5,816✔
946
                case TABLE_BOOLEAN:
947
                        buffer.b = va_arg(ap, int);
5,816✔
948
                        data = &buffer.b;
5,816✔
949
                        break;
5,816✔
950

951
                case TABLE_TIMESTAMP:
3,459✔
952
                case TABLE_TIMESTAMP_UTC:
953
                case TABLE_TIMESTAMP_RELATIVE:
954
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
955
                case TABLE_TIMESTAMP_LEFT:
956
                case TABLE_TIMESTAMP_DATE:
957
                case TABLE_TIMESPAN:
958
                case TABLE_TIMESPAN_MSEC:
959
                case TABLE_TIMESPAN_DAY:
960
                        buffer.usec = va_arg(ap, usec_t);
3,459✔
961
                        data = &buffer.usec;
3,459✔
962
                        break;
3,459✔
963

964
                case TABLE_SIZE:
665✔
965
                case TABLE_BPS:
966
                        buffer.size = va_arg(ap, uint64_t);
665✔
967
                        data = &buffer.size;
665✔
968
                        break;
665✔
969

970
                case TABLE_INT:
1,234✔
971
                case TABLE_SIGNAL:
972
                        buffer.int_val = va_arg(ap, int);
1,234✔
973
                        data = &buffer.int_val;
1,234✔
974
                        break;
1,234✔
975

976
                case TABLE_INT8: {
3✔
977
                        int x = va_arg(ap, int);
3✔
978
                        assert(x >= INT8_MIN && x <= INT8_MAX);
3✔
979

980
                        buffer.int8 = x;
3✔
981
                        data = &buffer.int8;
3✔
982
                        break;
3✔
983
                }
984

985
                case TABLE_INT16: {
3✔
986
                        int x = va_arg(ap, int);
3✔
987
                        assert(x >= INT16_MIN && x <= INT16_MAX);
3✔
988

989
                        buffer.int16 = x;
3✔
990
                        data = &buffer.int16;
3✔
991
                        break;
3✔
992
                }
993

994
                case TABLE_INT32:
3✔
995
                        buffer.int32 = va_arg(ap, int32_t);
3✔
996
                        data = &buffer.int32;
3✔
997
                        break;
3✔
998

999
                case TABLE_INT64:
195✔
1000
                        buffer.int64 = va_arg(ap, int64_t);
195✔
1001
                        data = &buffer.int64;
195✔
1002
                        break;
195✔
1003

1004
                case TABLE_UINT:
99✔
1005
                        buffer.uint_val = va_arg(ap, unsigned);
99✔
1006
                        data = &buffer.uint_val;
99✔
1007
                        break;
99✔
1008

1009
                case TABLE_UINT8: {
43✔
1010
                        unsigned x = va_arg(ap, unsigned);
43✔
1011
                        assert(x <= UINT8_MAX);
43✔
1012

1013
                        buffer.uint8 = x;
43✔
1014
                        data = &buffer.uint8;
43✔
1015
                        break;
43✔
1016
                }
1017

1018
                case TABLE_UINT16: {
89✔
1019
                        unsigned x = va_arg(ap, unsigned);
89✔
1020
                        assert(x <= UINT16_MAX);
89✔
1021

1022
                        buffer.uint16 = x;
89✔
1023
                        data = &buffer.uint16;
89✔
1024
                        break;
89✔
1025
                }
1026

1027
                case TABLE_UINT32:
1,776✔
1028
                case TABLE_UINT32_HEX:
1029
                        buffer.uint32 = va_arg(ap, uint32_t);
1,776✔
1030
                        data = &buffer.uint32;
1,776✔
1031
                        break;
1,776✔
1032

1033
                case TABLE_UINT64:
6,322✔
1034
                case TABLE_UINT64_HEX:
1035
                        buffer.uint64 = va_arg(ap, uint64_t);
6,322✔
1036
                        data = &buffer.uint64;
6,322✔
1037
                        break;
6,322✔
1038

1039
                case TABLE_PERCENT:
2✔
1040
                        buffer.percent = va_arg(ap, int);
2✔
1041
                        data = &buffer.percent;
2✔
1042
                        break;
2✔
1043

1044
                case TABLE_IFINDEX:
62✔
1045
                        buffer.ifindex = va_arg(ap, int);
62✔
1046
                        data = &buffer.ifindex;
62✔
1047
                        break;
62✔
1048

1049
                case TABLE_IN_ADDR:
179✔
1050
                        buffer.address = *va_arg(ap, union in_addr_union *);
179✔
1051
                        data = &buffer.address.in;
179✔
1052
                        break;
179✔
1053

1054
                case TABLE_IN6_ADDR:
26✔
1055
                        buffer.address = *va_arg(ap, union in_addr_union *);
26✔
1056
                        data = &buffer.address.in6;
26✔
1057
                        break;
26✔
1058

1059
                case TABLE_UUID:
1,916✔
1060
                case TABLE_ID128:
1061
                        buffer.id128 = va_arg(ap, sd_id128_t);
1,916✔
1062
                        data = &buffer.id128;
1,916✔
1063
                        break;
1,916✔
1064

1065
                case TABLE_UID:
516✔
1066
                        buffer.uid = va_arg(ap, uid_t);
516✔
1067
                        data = &buffer.uid;
516✔
1068
                        break;
516✔
1069

1070
                case TABLE_GID:
647✔
1071
                        buffer.gid = va_arg(ap, gid_t);
647✔
1072
                        data = &buffer.gid;
647✔
1073
                        break;
647✔
1074

1075
                case TABLE_PID:
238✔
1076
                        buffer.pid = va_arg(ap, pid_t);
238✔
1077
                        data = &buffer.pid;
238✔
1078
                        break;
238✔
1079

1080
                case TABLE_MODE:
4✔
1081
                case TABLE_MODE_INODE_TYPE:
1082
                        buffer.mode = va_arg(ap, mode_t);
4✔
1083
                        data = &buffer.mode;
4✔
1084
                        break;
4✔
1085

1086
                case TABLE_DEVNUM:
6✔
1087
                        buffer.devnum = va_arg(ap, dev_t);
6✔
1088
                        data = &buffer.devnum;
6✔
1089
                        break;
6✔
1090

1091
                case TABLE_SET_MINIMUM_WIDTH: {
1,432✔
1092
                        size_t w = va_arg(ap, size_t);
1,432✔
1093

1094
                        r = table_set_minimum_width(t, last_cell, w);
1,432✔
1095
                        goto check;
1,432✔
1096
                }
1097

1098
                case TABLE_SET_MAXIMUM_WIDTH: {
1,412✔
1099
                        size_t w = va_arg(ap, size_t);
1,412✔
1100
                        r = table_set_maximum_width(t, last_cell, w);
1,412✔
1101
                        goto check;
1,412✔
1102
                }
1103

1104
                case TABLE_SET_WEIGHT: {
×
1105
                        unsigned w = va_arg(ap, unsigned);
×
1106
                        r = table_set_weight(t, last_cell, w);
×
1107
                        goto check;
×
1108
                }
1109

1110
                case TABLE_SET_ALIGN_PERCENT: {
6,308✔
1111
                        unsigned p = va_arg(ap, unsigned);
6,308✔
1112
                        r = table_set_align_percent(t, last_cell, p);
6,308✔
1113
                        goto check;
6,308✔
1114
                }
1115

1116
                case TABLE_SET_ELLIPSIZE_PERCENT: {
1,412✔
1117
                        unsigned p = va_arg(ap, unsigned);
1,412✔
1118
                        r = table_set_ellipsize_percent(t, last_cell, p);
1,412✔
1119
                        goto check;
1,412✔
1120
                }
1121

1122
                case TABLE_SET_COLOR: {
52,679✔
1123
                        const char *c = va_arg(ap, const char*);
52,679✔
1124
                        r = table_set_color(t, last_cell, c);
52,679✔
1125
                        goto check;
52,679✔
1126
                }
1127

1128
                case TABLE_SET_RGAP_COLOR: {
×
1129
                        const char *c = va_arg(ap, const char*);
×
1130
                        r = table_set_rgap_color(t, last_cell, c);
×
1131
                        goto check;
×
1132
                }
1133

1134
                case TABLE_SET_BOTH_COLORS: {
3,081✔
1135
                        const char *c = va_arg(ap, const char*);
3,081✔
1136

1137
                        r = table_set_color(t, last_cell, c);
3,081✔
1138
                        if (r < 0) {
3,081✔
1139
                                va_end(ap);
×
1140
                                return r;
×
1141
                        }
1142

1143
                        r = table_set_rgap_color(t, last_cell, c);
3,081✔
1144
                        goto check;
3,081✔
1145
                }
1146

1147
                case TABLE_SET_UNDERLINE: {
×
1148
                        int u = va_arg(ap, int);
×
1149
                        r = table_set_underline(t, last_cell, u);
×
1150
                        goto check;
×
1151
                }
1152

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

1159
                case TABLE_SET_BOTH_UNDERLINES: {
56,084✔
1160
                        int u = va_arg(ap, int);
56,084✔
1161

1162
                        r = table_set_underline(t, last_cell, u);
56,084✔
1163
                        if (r < 0) {
56,084✔
1164
                                va_end(ap);
×
1165
                                return r;
×
1166
                        }
1167

1168
                        r = table_set_rgap_underline(t, last_cell, u);
56,084✔
1169
                        goto check;
56,084✔
1170
                }
1171

1172
                case TABLE_SET_URL: {
1,650✔
1173
                        const char *u = va_arg(ap, const char*);
1,650✔
1174
                        r = table_set_url(t, last_cell, u);
1,650✔
1175
                        goto check;
1,650✔
1176
                }
1177

1178
                case TABLE_SET_UPPERCASE: {
1✔
1179
                        int u = va_arg(ap, int);
1✔
1180
                        r = table_set_uppercase(t, last_cell, u);
1✔
1181
                        goto check;
1✔
1182
                }
1183

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

1198
                case _TABLE_DATA_TYPE_MAX:
45,198✔
1199
                        /* Used as end marker */
1200
                        va_end(ap);
45,198✔
1201
                        return 0;
45,198✔
1202

1203
                default:
×
1204
                        assert_not_reached();
×
1205
                }
1206

1207
                r = table_add_cell(t, &last_cell, type, data);
159,757✔
1208
        check:
283,818✔
1209
                if (r < 0) {
283,818✔
1210
                        va_end(ap);
×
1211
                        return r;
×
1212
                }
1213
        }
1214
}
1215

1216
void table_set_header(Table *t, bool b) {
181✔
1217
        assert(t);
181✔
1218

1219
        t->header = b;
181✔
1220
}
181✔
1221

1222
void table_set_width(Table *t, size_t width) {
99✔
1223
        assert(t);
99✔
1224

1225
        t->width = width;
99✔
1226
}
99✔
1227

1228
void table_set_cell_height_max(Table *t, size_t height) {
28✔
1229
        assert(t);
28✔
1230
        assert(height >= 1 || height == SIZE_MAX);
28✔
1231

1232
        t->cell_height_max = height;
28✔
1233
}
28✔
1234

1235
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
612✔
1236
        assert(t);
612✔
1237
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
612✔
1238

1239
        t->ersatz = ersatz;
612✔
1240
}
612✔
1241

1242
static const char* table_ersatz_string(const Table *t) {
12,786✔
1243
        switch (t->ersatz) {
12,786✔
1244
        case TABLE_ERSATZ_EMPTY:
1245
                return "";
1246
        case TABLE_ERSATZ_DASH:
9,198✔
1247
                return "-";
9,198✔
1248
        case TABLE_ERSATZ_UNSET:
420✔
1249
                return "(unset)";
420✔
1250
        case TABLE_ERSATZ_NA:
×
1251
                return "n/a";
×
1252
        default:
×
1253
                assert_not_reached();
×
1254
        }
1255
}
1256

1257
static int table_set_display_all(Table *t) {
298✔
1258
        size_t *d;
298✔
1259

1260
        assert(t);
298✔
1261

1262
        /* Initialize the display map to the identity */
1263

1264
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
298✔
1265
        if (!d)
298✔
1266
                return -ENOMEM;
1267

1268
        for (size_t i = 0; i < t->n_columns; i++)
3,787✔
1269
                d[i] = i;
3,489✔
1270

1271
        t->display_map = d;
298✔
1272
        t->n_display_map = t->n_columns;
298✔
1273

1274
        return 0;
298✔
1275
}
1276

1277
int table_set_display_internal(Table *t, size_t first_column, ...) {
31✔
1278
        size_t column;
31✔
1279
        va_list ap;
31✔
1280

1281
        assert(t);
31✔
1282

1283
        column = first_column;
31✔
1284

1285
        va_start(ap, first_column);
31✔
1286
        for (;;) {
159✔
1287
                assert(column < t->n_columns);
159✔
1288

1289
                if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
159✔
1290
                        va_end(ap);
×
1291
                        return -ENOMEM;
×
1292
                }
1293

1294
                t->display_map[t->n_display_map++] = column;
159✔
1295

1296
                column = va_arg(ap, size_t);
159✔
1297
                if (column == SIZE_MAX)
159✔
1298
                        break;
1299

1300
        }
1301
        va_end(ap);
31✔
1302

1303
        return 0;
31✔
1304
}
1305

1306
int table_set_sort_internal(Table *t, size_t first_column, ...) {
224✔
1307
        size_t column;
224✔
1308
        va_list ap;
224✔
1309

1310
        assert(t);
224✔
1311

1312
        column = first_column;
224✔
1313

1314
        va_start(ap, first_column);
224✔
1315
        for (;;) {
296✔
1316
                assert(column < t->n_columns);
296✔
1317

1318
                if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
296✔
1319
                        va_end(ap);
×
1320
                        return -ENOMEM;
×
1321
                }
1322

1323
                t->sort_map[t->n_sort_map++] = column;
296✔
1324

1325
                column = va_arg(ap, size_t);
296✔
1326
                if (column == SIZE_MAX)
296✔
1327
                        break;
1328
        }
1329
        va_end(ap);
224✔
1330

1331
        return 0;
224✔
1332
}
1333

1334
int table_hide_column_from_display_internal(Table *t, ...) {
588✔
1335
        size_t cur = 0;
588✔
1336
        int r;
588✔
1337

1338
        assert(t);
588✔
1339

1340
        /* If the display map is empty, initialize it with all available columns */
1341
        if (!t->display_map) {
588✔
1342
                r = table_set_display_all(t);
298✔
1343
                if (r < 0)
298✔
1344
                        return r;
1345
        }
1346

1347
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
7,855✔
1348
                bool listed = false;
7,267✔
1349
                va_list ap;
7,267✔
1350

1351
                va_start(ap, t);
7,267✔
1352
                for (;;) {
14,068✔
1353
                        size_t column;
14,068✔
1354

1355
                        column = va_arg(ap, size_t);
14,068✔
1356
                        if (column == SIZE_MAX)
14,068✔
1357
                                break;
1358
                        if (column == *i) {
7,402✔
1359
                                listed = true;
1360
                                break;
1361
                        }
1362
                }
1363
                va_end(ap);
7,267✔
1364

1365
                if (listed)
7,267✔
1366
                        continue;
601✔
1367

1368
                t->display_map[cur++] = *i;
6,666✔
1369
        }
1370

1371
        t->n_display_map = cur;
588✔
1372

1373
        return 0;
588✔
1374
}
1375

1376
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
11,729✔
1377
        int r;
11,729✔
1378

1379
        assert(a);
11,729✔
1380
        assert(b);
11,729✔
1381

1382
        if (a->type == b->type) {
11,729✔
1383

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

1387
                switch (a->type) {
11,723✔
1388

1389
                case TABLE_STRING:
8,352✔
1390
                case TABLE_FIELD:
1391
                case TABLE_HEADER:
1392
                        return strcmp(a->string, b->string);
8,352✔
1393

1394
                case TABLE_PATH:
39✔
1395
                case TABLE_PATH_BASENAME:
1396
                        return path_compare(a->string, b->string);
39✔
1397

1398
                case TABLE_STRV:
×
1399
                case TABLE_STRV_WRAPPED:
1400
                        return strv_compare(a->strv, b->strv);
×
1401

1402
                case TABLE_BOOLEAN:
2✔
1403
                        if (!a->boolean && b->boolean)
2✔
1404
                                return -1;
1405
                        if (a->boolean && !b->boolean)
2✔
1406
                                return 1;
1407
                        return 0;
×
1408

1409
                case TABLE_TIMESTAMP:
×
1410
                case TABLE_TIMESTAMP_UTC:
1411
                case TABLE_TIMESTAMP_RELATIVE:
1412
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1413
                case TABLE_TIMESTAMP_LEFT:
1414
                case TABLE_TIMESTAMP_DATE:
1415
                        return CMP(a->timestamp, b->timestamp);
×
1416

1417
                case TABLE_TIMESPAN:
683✔
1418
                case TABLE_TIMESPAN_MSEC:
1419
                case TABLE_TIMESPAN_DAY:
1420
                        return CMP(a->timespan, b->timespan);
683✔
1421

1422
                case TABLE_SIZE:
×
1423
                case TABLE_BPS:
1424
                        return CMP(a->size, b->size);
×
1425

1426
                case TABLE_INT:
16✔
1427
                case TABLE_SIGNAL:
1428
                        return CMP(a->int_val, b->int_val);
16✔
1429

1430
                case TABLE_INT8:
×
1431
                        return CMP(a->int8, b->int8);
×
1432

1433
                case TABLE_INT16:
×
1434
                        return CMP(a->int16, b->int16);
×
1435

1436
                case TABLE_INT32:
×
1437
                        return CMP(a->int32, b->int32);
×
1438

1439
                case TABLE_INT64:
188✔
1440
                        return CMP(a->int64, b->int64);
188✔
1441

1442
                case TABLE_UINT:
12✔
1443
                        return CMP(a->uint_val, b->uint_val);
12✔
1444

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

1448
                case TABLE_UINT16:
×
1449
                        return CMP(a->uint16, b->uint16);
×
1450

1451
                case TABLE_UINT32:
27✔
1452
                case TABLE_UINT32_HEX:
1453
                        return CMP(a->uint32, b->uint32);
27✔
1454

1455
                case TABLE_UINT64:
×
1456
                case TABLE_UINT64_HEX:
1457
                        return CMP(a->uint64, b->uint64);
×
1458

1459
                case TABLE_PERCENT:
×
1460
                        return CMP(a->percent, b->percent);
×
1461

1462
                case TABLE_IFINDEX:
×
1463
                        return CMP(a->ifindex, b->ifindex);
×
1464

1465
                case TABLE_IN_ADDR:
×
1466
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1467

1468
                case TABLE_IN6_ADDR:
1469
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1470

1471
                case TABLE_UUID:
×
1472
                case TABLE_ID128:
1473
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1474

1475
                case TABLE_UID:
1,125✔
1476
                        return CMP(a->uid, b->uid);
1,125✔
1477

1478
                case TABLE_GID:
1,279✔
1479
                        return CMP(a->gid, b->gid);
1,279✔
1480

1481
                case TABLE_PID:
×
1482
                        return CMP(a->pid, b->pid);
×
1483

1484
                case TABLE_MODE:
×
1485
                case TABLE_MODE_INODE_TYPE:
1486
                        return CMP(a->mode, b->mode);
×
1487

1488
                case TABLE_DEVNUM:
×
1489
                        r = CMP(major(a->devnum), major(b->devnum));
×
1490
                        if (r != 0)
×
1491
                                return r;
×
1492

1493
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1494

1495
                default:
6✔
1496
                        ;
6✔
1497
                }
1498
        }
1499

1500
        /* Generic fallback using the original order in which the cells where added. */
1501
        return CMP(index_a, index_b);
6✔
1502
}
1503

1504
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
11,947✔
1505
        int r;
11,947✔
1506

1507
        assert(t);
11,947✔
1508
        assert(t->sort_map);
11,947✔
1509

1510
        /* Make sure the header stays at the beginning */
1511
        if (*a < t->n_columns && *b < t->n_columns)
11,947✔
1512
                return 0;
1513
        if (*a < t->n_columns)
11,947✔
1514
                return -1;
1515
        if (*b < t->n_columns)
11,461✔
1516
                return 1;
1517

1518
        /* Order other lines by the sorting map */
1519
        for (size_t i = 0; i < t->n_sort_map; i++) {
11,734✔
1520
                TableData *d, *dd;
11,729✔
1521

1522
                d = t->data[*a + t->sort_map[i]];
11,729✔
1523
                dd = t->data[*b + t->sort_map[i]];
11,729✔
1524

1525
                r = cell_data_compare(d, *a, dd, *b);
11,729✔
1526
                if (r != 0)
11,729✔
1527
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
11,456✔
1528
        }
1529

1530
        /* Order identical lines by the order there were originally added in */
1531
        return CMP(*a, *b);
5✔
1532
}
1533

1534
static char* format_strv_width(char **strv, size_t column_width) {
148✔
1535
        _cleanup_(memstream_done) MemStream m = {};
148✔
1536
        FILE *f;
148✔
1537

1538
        f = memstream_init(&m);
148✔
1539
        if (!f)
148✔
1540
                return NULL;
1541

1542
        size_t position = 0;
1543
        STRV_FOREACH(p, strv) {
643✔
1544
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
495✔
1545
                                                          * If that happens, we'll just print one item per line. */
1546

1547
                if (position == 0) {
495✔
1548
                        fputs(*p, f);
148✔
1549
                        position = our_len;
1550
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
694✔
1551
                        fprintf(f, " %s", *p);
307✔
1552
                        position = size_add(size_add(position, 1), our_len);
802✔
1553
                } else {
1554
                        fprintf(f, "\n%s", *p);
40✔
1555
                        position = our_len;
1556
                }
1557
        }
1558

1559
        char *buf;
148✔
1560
        if (memstream_finalize(&m, &buf, NULL) < 0)
148✔
1561
                return NULL;
1562

1563
        return buf;
148✔
1564
}
1565

1566
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
327,467✔
1567
        assert(d);
327,467✔
1568

1569
        if (d->formatted &&
327,467✔
1570
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1571
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
62,360✔
1572
                return d->formatted;
1573

1574
        switch (d->type) {
265,219✔
1575
        case TABLE_EMPTY:
12,640✔
1576
                return table_ersatz_string(t);
12,640✔
1577

1578
        case TABLE_STRING:
224,544✔
1579
        case TABLE_PATH:
1580
        case TABLE_PATH_BASENAME:
1581
        case TABLE_FIELD:
1582
        case TABLE_HEADER: {
1583
                _cleanup_free_ char *bn = NULL;
224,544✔
1584
                const char *s;
224,544✔
1585

1586
                if (d->type == TABLE_PATH_BASENAME)
224,544✔
1587
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
348✔
1588
                else
1589
                        s = d->string;
224,196✔
1590

1591
                if (d->uppercase && !avoid_uppercasing) {
224,544✔
1592
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,309✔
1593
                        if (!d->formatted)
3,309✔
1594
                                return NULL;
1595

1596
                        char *q = d->formatted;
1597
                        for (const char *p = s; *p; p++)
23,359✔
1598
                                *(q++) = (char) toupper((unsigned char) *p);
20,050✔
1599

1600
                        if (d->type == TABLE_FIELD)
3,309✔
1601
                                *(q++) = ':';
×
1602

1603
                        *q = 0;
3,309✔
1604
                        return d->formatted;
3,309✔
1605
                } else if (d->type == TABLE_FIELD) {
221,235✔
1606
                        d->formatted = strjoin(s, ":");
32,521✔
1607
                        if (!d->formatted)
32,521✔
1608
                                return NULL;
1609

1610
                        return d->formatted;
32,521✔
1611
                }
1612

1613
                if (bn) {
188,714✔
1614
                        d->formatted = TAKE_PTR(bn);
346✔
1615
                        return d->formatted;
346✔
1616
                }
1617

1618
                return d->string;
188,368✔
1619
        }
1620

1621
        case TABLE_STRV:
6,208✔
1622
                if (strv_isempty(d->strv))
6,208✔
1623
                        return table_ersatz_string(t);
×
1624

1625
                d->formatted = strv_join(d->strv, "\n");
6,208✔
1626
                if (!d->formatted)
6,208✔
1627
                        return NULL;
1628
                break;
1629

1630
        case TABLE_STRV_WRAPPED: {
148✔
1631
                if (strv_isempty(d->strv))
148✔
1632
                        return table_ersatz_string(t);
×
1633

1634
                char *buf = format_strv_width(d->strv, column_width);
148✔
1635
                if (!buf)
148✔
1636
                        return NULL;
1637

1638
                free_and_replace(d->formatted, buf);
148✔
1639
                d->formatted_for_width = column_width;
148✔
1640
                if (have_soft)
148✔
1641
                        *have_soft = true;
93✔
1642

1643
                break;
16,366✔
1644
        }
1645

1646
        case TABLE_BOOLEAN:
4,517✔
1647
                return yes_no(d->boolean);
329,208✔
1648

1649
        case TABLE_BOOLEAN_CHECKMARK:
6,928✔
1650
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,672✔
1651

1652
        case TABLE_TIMESTAMP:
1,893✔
1653
        case TABLE_TIMESTAMP_UTC:
1654
        case TABLE_TIMESTAMP_RELATIVE:
1655
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1656
        case TABLE_TIMESTAMP_LEFT:
1657
        case TABLE_TIMESTAMP_DATE: {
1658
                _cleanup_free_ char *p = NULL;
76✔
1659
                char *ret;
1,893✔
1660

1661
                p = new(char,
1,893✔
1662
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1663
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1664
                if (!p)
1,893✔
1665
                        return NULL;
1666

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

1682
                d->formatted = TAKE_PTR(p);
1,817✔
1683
                break;
1,817✔
1684
        }
1685

1686
        case TABLE_TIMESPAN:
1,423✔
1687
        case TABLE_TIMESPAN_MSEC:
1688
        case TABLE_TIMESPAN_DAY: {
1689
                _cleanup_free_ char *p = NULL;
×
1690

1691
                p = new(char, FORMAT_TIMESPAN_MAX);
1,423✔
1692
                if (!p)
1,423✔
1693
                        return NULL;
1694

1695
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,839✔
1696
                                     d->type == TABLE_TIMESPAN ? 0 :
1,416✔
1697
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1698
                        return "-";
1699

1700
                d->formatted = TAKE_PTR(p);
1,423✔
1701
                break;
1,423✔
1702
        }
1703

1704
        case TABLE_SIZE: {
256✔
1705
                _cleanup_free_ char *p = NULL;
144✔
1706

1707
                p = new(char, FORMAT_BYTES_MAX);
256✔
1708
                if (!p)
256✔
1709
                        return NULL;
1710

1711
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
256✔
1712
                        return table_ersatz_string(t);
144✔
1713

1714
                d->formatted = TAKE_PTR(p);
112✔
1715
                break;
112✔
1716
        }
1717

1718
        case TABLE_BPS: {
404✔
1719
                _cleanup_free_ char *p = NULL;
×
1720
                size_t n;
404✔
1721

1722
                p = new(char, FORMAT_BYTES_MAX+2);
404✔
1723
                if (!p)
404✔
1724
                        return NULL;
1725

1726
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
404✔
1727
                        return table_ersatz_string(t);
×
1728

1729
                n = strlen(p);
404✔
1730
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
404✔
1731

1732
                d->formatted = TAKE_PTR(p);
404✔
1733
                break;
404✔
1734
        }
1735

1736
        case TABLE_INT: {
259✔
1737
                _cleanup_free_ char *p = NULL;
×
1738

1739
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
734✔
1740
                if (!p)
259✔
1741
                        return NULL;
×
1742

1743
                sprintf(p, "%i", d->int_val);
259✔
1744
                d->formatted = TAKE_PTR(p);
259✔
1745
                break;
259✔
1746
        }
1747

1748
        case TABLE_INT8: {
3✔
1749
                _cleanup_free_ char *p = NULL;
×
1750

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

1755
                sprintf(p, "%" PRIi8, d->int8);
3✔
1756
                d->formatted = TAKE_PTR(p);
3✔
1757
                break;
3✔
1758
        }
1759

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

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

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

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

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

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

1784
        case TABLE_INT64: {
155✔
1785
                _cleanup_free_ char *p = NULL;
×
1786

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

1791
                sprintf(p, "%" PRIi64, d->int64);
155✔
1792
                d->formatted = TAKE_PTR(p);
155✔
1793
                break;
155✔
1794
        }
1795

1796
        case TABLE_UINT: {
285✔
1797
                _cleanup_free_ char *p = NULL;
×
1798

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

1803
                sprintf(p, "%u", d->uint_val);
285✔
1804
                d->formatted = TAKE_PTR(p);
285✔
1805
                break;
285✔
1806
        }
1807

1808
        case TABLE_UINT8: {
43✔
1809
                _cleanup_free_ char *p = NULL;
×
1810

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

1815
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1816
                d->formatted = TAKE_PTR(p);
43✔
1817
                break;
43✔
1818
        }
1819

1820
        case TABLE_UINT16: {
89✔
1821
                _cleanup_free_ char *p = NULL;
×
1822

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

1827
                sprintf(p, "%" PRIu16, d->uint16);
89✔
1828
                d->formatted = TAKE_PTR(p);
89✔
1829
                break;
89✔
1830
        }
1831

1832
        case TABLE_UINT32: {
1,022✔
1833
                _cleanup_free_ char *p = NULL;
×
1834

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

1839
                sprintf(p, "%" PRIu32, d->uint32);
1,022✔
1840
                d->formatted = TAKE_PTR(p);
1,022✔
1841
                break;
1,022✔
1842
        }
1843

1844
        case TABLE_UINT32_HEX: {
2✔
1845
                _cleanup_free_ char *p = NULL;
×
1846

1847
                p = new(char, 8 + 1);
2✔
1848
                if (!p)
2✔
1849
                        return NULL;
×
1850

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

1856
        case TABLE_UINT64: {
1,166✔
1857
                _cleanup_free_ char *p = NULL;
×
1858

1859
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
5,572✔
1860
                if (!p)
1,166✔
1861
                        return NULL;
×
1862

1863
                sprintf(p, "%" PRIu64, d->uint64);
1,166✔
1864
                d->formatted = TAKE_PTR(p);
1,166✔
1865
                break;
1,166✔
1866
        }
1867

1868
        case TABLE_UINT64_HEX: {
14✔
1869
                _cleanup_free_ char *p = NULL;
×
1870

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

1875
                sprintf(p, "%" PRIx64, d->uint64);
14✔
1876
                d->formatted = TAKE_PTR(p);
14✔
1877
                break;
14✔
1878
        }
1879

1880
        case TABLE_PERCENT: {
2✔
1881
                _cleanup_free_ char *p = NULL;
×
1882

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

1887
                sprintf(p, "%i%%" , d->percent);
2✔
1888
                d->formatted = TAKE_PTR(p);
2✔
1889
                break;
2✔
1890
        }
1891

1892
        case TABLE_IFINDEX: {
219✔
1893
                _cleanup_free_ char *p = NULL;
×
1894

1895
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
219✔
1896
                        return NULL;
×
1897

1898
                d->formatted = TAKE_PTR(p);
219✔
1899
                break;
219✔
1900
        }
1901

1902
        case TABLE_IN_ADDR:
205✔
1903
        case TABLE_IN6_ADDR: {
1904
                _cleanup_free_ char *p = NULL;
×
1905

1906
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1907
                                      &d->address, &p) < 0)
205✔
1908
                        return NULL;
×
1909

1910
                d->formatted = TAKE_PTR(p);
205✔
1911
                break;
205✔
1912
        }
1913

1914
        case TABLE_ID128: {
1915
                char *p;
1,096✔
1916

1917
                p = new(char, SD_ID128_STRING_MAX);
1,096✔
1918
                if (!p)
1,096✔
1919
                        return NULL;
1920

1921
                d->formatted = sd_id128_to_string(d->id128, p);
1,096✔
1922
                break;
1,096✔
1923
        }
1924

1925
        case TABLE_UUID: {
1926
                char *p;
299✔
1927

1928
                p = new(char, SD_ID128_UUID_STRING_MAX);
299✔
1929
                if (!p)
299✔
1930
                        return NULL;
1931

1932
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
299✔
1933
                break;
299✔
1934
        }
1935

1936
        case TABLE_UID: {
426✔
1937
                char *p;
426✔
1938

1939
                if (!uid_is_valid(d->uid))
426✔
1940
                        return table_ersatz_string(t);
×
1941

1942
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,467✔
1943
                if (!p)
426✔
1944
                        return NULL;
1945
                sprintf(p, UID_FMT, d->uid);
426✔
1946

1947
                d->formatted = p;
426✔
1948
                break;
426✔
1949
        }
1950

1951
        case TABLE_GID: {
595✔
1952
                char *p;
595✔
1953

1954
                if (!gid_is_valid(d->gid))
595✔
1955
                        return table_ersatz_string(t);
×
1956

1957
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,748✔
1958
                if (!p)
595✔
1959
                        return NULL;
1960
                sprintf(p, GID_FMT, d->gid);
595✔
1961

1962
                d->formatted = p;
595✔
1963
                break;
595✔
1964
        }
1965

1966
        case TABLE_PID: {
220✔
1967
                char *p;
220✔
1968

1969
                if (!pid_is_valid(d->pid))
220✔
1970
                        return table_ersatz_string(t);
×
1971

1972
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
603✔
1973
                if (!p)
220✔
1974
                        return NULL;
1975
                sprintf(p, PID_FMT, d->pid);
220✔
1976

1977
                d->formatted = p;
220✔
1978
                break;
220✔
1979
        }
1980

1981
        case TABLE_SIGNAL: {
123✔
1982
                const char *suffix;
123✔
1983
                char *p;
123✔
1984

1985
                suffix = signal_to_string(d->int_val);
123✔
1986
                if (!suffix)
123✔
1987
                        return table_ersatz_string(t);
×
1988

1989
                p = strjoin("SIG", suffix);
123✔
1990
                if (!p)
123✔
1991
                        return NULL;
1992

1993
                d->formatted = p;
123✔
1994
                break;
123✔
1995
        }
1996

1997
        case TABLE_MODE: {
24✔
1998
                char *p;
24✔
1999

2000
                if (d->mode == MODE_INVALID)
24✔
2001
                        return table_ersatz_string(t);
×
2002

2003
                p = new(char, 4 + 1);
24✔
2004
                if (!p)
24✔
2005
                        return NULL;
2006

2007
                sprintf(p, "%04o", d->mode & 07777);
24✔
2008
                d->formatted = p;
24✔
2009
                break;
24✔
2010
        }
2011

2012
        case TABLE_MODE_INODE_TYPE:
2✔
2013

2014
                if (d->mode == MODE_INVALID)
2✔
2015
                        return table_ersatz_string(t);
×
2016

2017
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2018

2019
        case TABLE_DEVNUM:
3✔
2020
                if (devnum_is_zero(d->devnum))
3✔
2021
                        return table_ersatz_string(t);
2✔
2022

2023
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2024
                        return NULL;
2025

2026
                break;
2027

2028
        default:
×
2029
                assert_not_reached();
×
2030
        }
2031

2032
        return d->formatted;
16,366✔
2033
}
2034

2035
static int console_width_height(
160,560✔
2036
                const char *s,
2037
                size_t *ret_width,
2038
                size_t *ret_height) {
2039

2040
        size_t max_width = 0, height = 0;
160,560✔
2041
        const char *p;
160,560✔
2042

2043
        assert(s);
160,560✔
2044

2045
        /* Determine the width and height in console character cells the specified string needs. */
2046

2047
        do {
163,779✔
2048
                size_t k;
163,779✔
2049

2050
                p = strchr(s, '\n');
163,779✔
2051
                if (p) {
163,779✔
2052
                        _cleanup_free_ char *c = NULL;
3,222✔
2053

2054
                        c = strndup(s, p - s);
3,222✔
2055
                        if (!c)
3,222✔
2056
                                return -ENOMEM;
×
2057

2058
                        k = utf8_console_width(c);
3,222✔
2059
                        s = p + 1;
3,222✔
2060
                } else {
2061
                        k = utf8_console_width(s);
160,557✔
2062
                        s = NULL;
160,557✔
2063
                }
2064
                if (k == SIZE_MAX)
163,779✔
2065
                        return -EINVAL;
2066
                if (k > max_width)
163,779✔
2067
                        max_width = k;
159,573✔
2068

2069
                height++;
163,779✔
2070
        } while (!isempty(s));
167,001✔
2071

2072
        if (ret_width)
160,560✔
2073
                *ret_width = max_width;
160,560✔
2074

2075
        if (ret_height)
160,560✔
2076
                *ret_height = height;
160,560✔
2077

2078
        return 0;
2079
}
2080

2081
static int table_data_requested_width_height(
160,560✔
2082
                Table *table,
2083
                TableData *d,
2084
                size_t available_width,
2085
                size_t *ret_width,
2086
                size_t *ret_height,
2087
                bool *have_soft) {
2088

2089
        _cleanup_free_ char *truncated = NULL;
160,560✔
2090
        bool truncation_applied = false;
160,560✔
2091
        size_t width, height;
160,560✔
2092
        const char *t;
160,560✔
2093
        int r;
160,560✔
2094
        bool soft = false;
160,560✔
2095

2096
        t = table_data_format(table, d, false, available_width, &soft);
160,560✔
2097
        if (!t)
160,560✔
2098
                return -ENOMEM;
2099

2100
        if (table->cell_height_max != SIZE_MAX) {
160,560✔
2101
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
180✔
2102
                if (r < 0)
180✔
2103
                        return r;
2104
                if (r > 0)
180✔
2105
                        truncation_applied = true;
25✔
2106

2107
                t = truncated;
180✔
2108
        }
2109

2110
        r = console_width_height(t, &width, &height);
160,560✔
2111
        if (r < 0)
160,560✔
2112
                return r;
2113

2114
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
160,560✔
2115
                width = d->maximum_width;
×
2116

2117
        if (width < d->minimum_width)
160,560✔
2118
                width = d->minimum_width;
2,688✔
2119

2120
        if (ret_width)
160,560✔
2121
                *ret_width = width;
160,560✔
2122
        if (ret_height)
160,560✔
2123
                *ret_height = height;
160,560✔
2124
        if (have_soft && soft)
160,560✔
2125
                *have_soft = true;
93✔
2126

2127
        return truncation_applied;
160,560✔
2128
}
2129

2130
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
122,931✔
2131
        size_t w = 0, space, lspace, old_length, clickable_length;
122,931✔
2132
        _cleanup_free_ char *clickable = NULL;
122,931✔
2133
        const char *p;
122,931✔
2134
        char *ret;
122,931✔
2135
        int r;
122,931✔
2136

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

2139
        assert(str);
122,931✔
2140
        assert(percent <= 100);
122,931✔
2141

2142
        old_length = strlen(str);
122,931✔
2143

2144
        if (url) {
122,931✔
2145
                r = terminal_urlify(url, str, &clickable);
1,550✔
2146
                if (r < 0)
1,550✔
2147
                        return NULL;
2148

2149
                clickable_length = strlen(clickable);
1,550✔
2150
        } else
2151
                clickable_length = old_length;
2152

2153
        /* Determine current width on screen */
2154
        p = str;
122,931✔
2155
        while (p < str + old_length) {
1,817,259✔
2156
                char32_t c;
1,694,328✔
2157

2158
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,694,328✔
2159
                        p++, w++; /* count invalid chars as 1 */
×
2160
                        continue;
×
2161
                }
2162

2163
                p = utf8_next_char(p);
1,694,328✔
2164
                w += unichar_iswide(c) ? 2 : 1;
3,388,649✔
2165
        }
2166

2167
        /* Already wider than the target, if so, don't do anything */
2168
        if (w >= new_length)
122,931✔
2169
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2170

2171
        /* How much spaces shall we add? An how much on the left side? */
2172
        space = new_length - w;
122,931✔
2173
        lspace = space * percent / 100U;
122,931✔
2174

2175
        ret = new(char, space + clickable_length + 1);
122,931✔
2176
        if (!ret)
122,931✔
2177
                return NULL;
2178

2179
        for (size_t i = 0; i < lspace; i++)
742,237✔
2180
                ret[i] = ' ';
619,306✔
2181
        memcpy(ret + lspace, clickable ?: str, clickable_length);
122,931✔
2182
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,422,779✔
2183
                ret[i] = ' ';
3,299,848✔
2184

2185
        ret[space + clickable_length] = 0;
122,931✔
2186
        return ret;
122,931✔
2187
}
2188

2189
static bool table_data_isempty(const TableData *d) {
553✔
2190
        assert(d);
553✔
2191

2192
        if (d->type == TABLE_EMPTY)
553✔
2193
                return true;
2194

2195
        /* Let's also consider an empty strv as truly empty. */
2196
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
471✔
2197
                return strv_isempty(d->strv);
×
2198

2199
        /* Note that an empty string we do not consider empty here! */
2200
        return false;
2201
}
2202

2203
static const char* table_data_color(const TableData *d) {
765✔
2204
        assert(d);
765✔
2205

2206
        if (d->color)
765✔
2207
                return d->color;
2208

2209
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2210
        if (table_data_isempty(d))
553✔
2211
                return ansi_grey();
82✔
2212

2213
        if (d->type == TABLE_FIELD)
471✔
2214
                return ansi_bright_blue();
×
2215

2216
        return NULL;
2217
}
2218

2219
static const char* table_data_underline(const TableData *d) {
765✔
2220
        assert(d);
765✔
2221

2222
        if (d->underline)
765✔
2223
                return ansi_add_underline_grey();
×
2224

2225
        if (d->type == TABLE_HEADER)
765✔
2226
                return ansi_add_underline();
40✔
2227

2228
        return NULL;
2229
}
2230

2231
static const char* table_data_rgap_underline(const TableData *d) {
166,882✔
2232
        assert(d);
166,882✔
2233

2234
        if (d->rgap_underline)
166,882✔
2235
                return ansi_add_underline_grey();
300✔
2236

2237
        if (d->type == TABLE_HEADER)
166,582✔
2238
                return ansi_add_underline();
6,740✔
2239

2240
        return NULL;
2241
}
2242

2243
int table_print(Table *t, FILE *f) {
3,440✔
2244
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,440✔
2245
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2246
                *width = NULL;
3,440✔
2247
        _cleanup_free_ size_t *sorted = NULL;
3,440✔
2248
        uint64_t *column_weight, weight_sum;
3,440✔
2249
        int r;
3,440✔
2250

2251
        assert(t);
3,440✔
2252

2253
        if (!f)
3,440✔
2254
                f = stdout;
2,567✔
2255

2256
        /* Ensure we have no incomplete rows */
2257
        assert(t->n_cells % t->n_columns == 0);
3,440✔
2258

2259
        n_rows = t->n_cells / t->n_columns;
3,440✔
2260
        assert(n_rows > 0); /* at least the header row must be complete */
3,440✔
2261

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

2265
                sorted = new(size_t, n_rows);
185✔
2266
                if (!sorted)
185✔
2267
                        return -ENOMEM;
2268

2269
                for (size_t i = 0; i < n_rows; i++)
3,209✔
2270
                        sorted[i] = i * t->n_columns;
3,024✔
2271

2272
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
185✔
2273
        }
2274

2275
        if (t->display_map)
3,440✔
2276
                display_columns = t->n_display_map;
286✔
2277
        else
2278
                display_columns = t->n_columns;
3,154✔
2279

2280
        assert(display_columns > 0);
3,440✔
2281

2282
        minimum_width = newa(size_t, display_columns);
3,440✔
2283
        maximum_width = newa(size_t, display_columns);
3,440✔
2284
        requested_width = newa(size_t, display_columns);
3,440✔
2285
        column_weight = newa0(uint64_t, display_columns);
3,440✔
2286

2287
        for (size_t j = 0; j < display_columns; j++) {
12,989✔
2288
                minimum_width[j] = 1;
9,549✔
2289
                maximum_width[j] = SIZE_MAX;
9,549✔
2290
        }
2291

2292
        for (unsigned pass = 0; pass < 2; pass++) {
3,452✔
2293
                /* First pass: determine column sizes */
2294

2295
                for (size_t j = 0; j < display_columns; j++)
13,025✔
2296
                        requested_width[j] = SIZE_MAX;
9,573✔
2297

2298
                bool any_soft = false;
3,452✔
2299

2300
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
52,677✔
2301
                        TableData **row;
49,225✔
2302

2303
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2304
                         * hence we don't care for sorted[] during the first pass. */
2305
                        row = t->data + i * t->n_columns;
49,225✔
2306

2307
                        for (size_t j = 0; j < display_columns; j++) {
209,785✔
2308
                                TableData *d;
160,560✔
2309
                                size_t req_width, req_height;
160,560✔
2310

2311
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
160,560✔
2312

2313
                                r = table_data_requested_width_height(t, d,
160,652✔
2314
                                                                      width ? width[j] : SIZE_MAX,
92✔
2315
                                                                      &req_width, &req_height, &any_soft);
2316
                                if (r < 0)
160,560✔
2317
                                        return r;
×
2318
                                if (r > 0) { /* Truncated because too many lines? */
160,560✔
2319
                                        _cleanup_free_ char *last = NULL;
25✔
2320
                                        const char *field;
25✔
2321

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

2327
                                        field = table_data_format(t, d, false,
28✔
2328
                                                                  width ? width[j] : SIZE_MAX,
3✔
2329
                                                                  &any_soft);
2330
                                        if (!field)
25✔
2331
                                                return -ENOMEM;
2332

2333
                                        assert_se(t->cell_height_max > 0);
25✔
2334
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2335
                                        if (r < 0)
25✔
2336
                                                return r;
2337

2338
                                        req_width = MAX(req_width,
25✔
2339
                                                        utf8_console_width(last) +
2340
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2341
                                }
2342

2343
                                /* Determine the biggest width that any cell in this column would like to have */
2344
                                if (requested_width[j] == SIZE_MAX ||
160,560✔
2345
                                    requested_width[j] < req_width)
151,019✔
2346
                                        requested_width[j] = req_width;
21,713✔
2347

2348
                                /* Determine the minimum width any cell in this column needs */
2349
                                if (minimum_width[j] < d->minimum_width)
160,560✔
2350
                                        minimum_width[j] = d->minimum_width;
33✔
2351

2352
                                /* Determine the maximum width any cell in this column needs */
2353
                                if (d->maximum_width != SIZE_MAX &&
160,560✔
2354
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2355
                                     maximum_width[j] > d->maximum_width))
2356
                                        maximum_width[j] = d->maximum_width;
20✔
2357

2358
                                /* Determine the full columns weight */
2359
                                column_weight[j] += d->weight;
160,560✔
2360
                        }
2361
                }
2362

2363
                /* One space between each column */
2364
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,452✔
2365

2366
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2367
                weight_sum = 0;
3,452✔
2368
                for (size_t j = 0; j < display_columns; j++) {
13,025✔
2369
                        weight_sum += column_weight[j];
9,573✔
2370

2371
                        table_minimum_width += minimum_width[j];
9,573✔
2372

2373
                        if (maximum_width[j] == SIZE_MAX)
9,573✔
2374
                                table_maximum_width = SIZE_MAX;
2375
                        else
2376
                                table_maximum_width += maximum_width[j];
20✔
2377

2378
                        table_requested_width += requested_width[j];
9,573✔
2379
                }
2380

2381
                /* Calculate effective table width */
2382
                if (t->width != 0 && t->width != SIZE_MAX)
3,452✔
2383
                        table_effective_width = t->width;
2384
                else if (t->width == 0 ||
3,445✔
2385
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,374✔
2386
                        table_effective_width = table_requested_width;
2387
                else
2388
                        table_effective_width = MIN(table_requested_width, columns());
30✔
2389

2390
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,452✔
2391
                        table_effective_width = table_maximum_width;
×
2392

2393
                if (table_effective_width < table_minimum_width)
3,452✔
2394
                        table_effective_width = table_minimum_width;
2✔
2395

2396
                if (!width)
3,452✔
2397
                        width = newa(size_t, display_columns);
3,440✔
2398

2399
                if (table_effective_width >= table_requested_width) {
3,452✔
2400
                        size_t extra;
3,436✔
2401

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

2405
                        extra = table_effective_width - table_requested_width;
3,436✔
2406

2407
                        for (size_t j = 0; j < display_columns; j++) {
12,967✔
2408
                                size_t delta;
9,531✔
2409

2410
                                if (weight_sum == 0)
9,531✔
2411
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2412
                                else
2413
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
9,499✔
2414

2415
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
9,531✔
2416
                                        width[j] = maximum_width[j];
2✔
2417

2418
                                if (width[j] < minimum_width[j])
9,531✔
2419
                                        width[j] = minimum_width[j];
×
2420

2421
                                delta = LESS_BY(width[j], requested_width[j]);
9,531✔
2422

2423
                                /* Subtract what we just added from the rest */
2424
                                if (extra > delta)
9,531✔
2425
                                        extra -= delta;
13✔
2426
                                else
2427
                                        extra = 0;
2428

2429
                                assert(weight_sum >= column_weight[j]);
9,531✔
2430
                                weight_sum -= column_weight[j];
9,531✔
2431
                        }
2432

2433
                        break; /* Every column should be happy, no need to repeat calculations. */
3,440✔
2434
                } else {
2435
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2436
                         * with the minimum they need, and then distribute anything left. */
2437
                        bool finalize = false;
16✔
2438
                        size_t extra;
16✔
2439

2440
                        extra = table_effective_width - table_minimum_width;
16✔
2441

2442
                        for (size_t j = 0; j < display_columns; j++)
58✔
2443
                                width[j] = SIZE_MAX;
42✔
2444

2445
                        for (;;) {
45✔
2446
                                bool restart = false;
45✔
2447

2448
                                for (size_t j = 0; j < display_columns; j++) {
136✔
2449
                                        size_t delta, w;
104✔
2450

2451
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2452
                                        if (width[j] != SIZE_MAX)
104✔
2453
                                                continue;
32✔
2454

2455
                                        if (weight_sum == 0)
72✔
2456
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2457
                                        else
2458
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
72✔
2459

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

2466
                                                w = requested_width[j];
2467
                                                restart = true;
2468

2469
                                        } else if (!finalize)
58✔
2470
                                                continue;
30✔
2471

2472
                                        width[j] = w;
42✔
2473

2474
                                        assert(w >= minimum_width[j]);
42✔
2475
                                        delta = w - minimum_width[j];
42✔
2476

2477
                                        assert(delta <= extra);
42✔
2478
                                        extra -= delta;
42✔
2479

2480
                                        assert(weight_sum >= column_weight[j]);
42✔
2481
                                        weight_sum -= column_weight[j];
42✔
2482

2483
                                        if (restart && !finalize)
42✔
2484
                                                break;
2485
                                }
2486

2487
                                if (finalize)
45✔
2488
                                        break;
2489

2490
                                if (!restart)
29✔
2491
                                        finalize = true;
16✔
2492
                        }
2493

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

2501
        /* Second pass: show output */
2502
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
52,619✔
2503
                size_t n_subline = 0;
49,179✔
2504
                bool more_sublines;
49,179✔
2505
                TableData **row;
49,179✔
2506

2507
                if (sorted)
49,179✔
2508
                        row = t->data + sorted[i];
3,010✔
2509
                else
2510
                        row = t->data + i * t->n_columns;
46,169✔
2511

2512
                do {
52,386✔
2513
                        const char *gap_color = NULL, *gap_underline = NULL;
52,386✔
2514
                        more_sublines = false;
52,386✔
2515

2516
                        for (size_t j = 0; j < display_columns; j++) {
219,268✔
2517
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
166,882✔
2518
                                bool lines_truncated = false;
166,882✔
2519
                                const char *field, *color = NULL, *underline = NULL;
166,882✔
2520
                                TableData *d;
166,882✔
2521
                                size_t l;
166,882✔
2522

2523
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
166,882✔
2524

2525
                                field = table_data_format(t, d, false, width[j], NULL);
166,882✔
2526
                                if (!field)
166,882✔
2527
                                        return -ENOMEM;
2528

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

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

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

2551
                                        field = buffer;
2552
                                } else {
2553
                                        if (lines_truncated) {
166,849✔
2554
                                                _cleanup_free_ char *padded = NULL;
25✔
2555

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

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

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

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

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

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

2582
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2583
                                                if (j == display_columns - 1 &&
169,651✔
2584
                                                    (!colors_enabled() || !table_data_color(d)) &&
46,774✔
2585
                                                    (!underline_enabled() || !table_data_underline(d)) &&
93,492✔
2586
                                                    (!urlify_enabled() || !d->url))
46,770✔
2587
                                                        delete_trailing_chars(aligned, NULL);
46,718✔
2588

2589
                                                free_and_replace(buffer, aligned);
122,931✔
2590
                                                field = buffer;
122,931✔
2591
                                        }
2592
                                }
2593

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

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

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

2605
                                if (colors_enabled() && gap_color)
166,882✔
2606
                                        fputs(gap_color, f);
×
2607
                                if (underline_enabled() && gap_underline)
166,882✔
2608
                                        fputs(gap_underline, f);
16✔
2609

2610
                                if (j > 0)
166,882✔
2611
                                        fputc(' ', f); /* column separator left of cell */
114,496✔
2612

2613
                                /* Undo gap color/underline */
2614
                                if ((colors_enabled() && gap_color) ||
333,764✔
2615
                                    (underline_enabled() && gap_underline))
167,593✔
2616
                                        fputs(ANSI_NORMAL, f);
16✔
2617

2618
                                if (colors_enabled()) {
166,882✔
2619
                                        color = table_data_color(d);
711✔
2620
                                        if (color)
711✔
2621
                                                fputs(color, f);
294✔
2622
                                }
2623

2624
                                if (underline_enabled()) {
166,882✔
2625
                                        underline = table_data_underline(d);
711✔
2626
                                        if (underline)
711✔
2627
                                                fputs(underline, f);
18✔
2628
                                }
2629

2630
                                fputs(field, f);
166,882✔
2631

2632
                                if (color || underline)
166,882✔
2633
                                        fputs(ANSI_NORMAL, f);
312✔
2634

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

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

2644
        return fflush_and_check(f);
3,440✔
2645
}
2646

2647
int table_format(Table *t, char **ret) {
39✔
2648
        _cleanup_(memstream_done) MemStream m = {};
39✔
2649
        FILE *f;
39✔
2650
        int r;
39✔
2651

2652
        assert(t);
39✔
2653
        assert(ret);
39✔
2654

2655
        f = memstream_init(&m);
39✔
2656
        if (!f)
39✔
2657
                return -ENOMEM;
2658

2659
        r = table_print(t, f);
39✔
2660
        if (r < 0)
39✔
2661
                return r;
2662

2663
        return memstream_finalize(&m, ret, NULL);
39✔
2664
}
2665

2666
size_t table_get_rows(Table *t) {
1,716✔
2667
        if (!t)
1,716✔
2668
                return 0;
2669

2670
        assert(t->n_columns > 0);
1,716✔
2671
        return t->n_cells / t->n_columns;
1,716✔
2672
}
2673

2674
size_t table_get_columns(Table *t) {
34✔
2675
        if (!t)
34✔
2676
                return 0;
2677

2678
        assert(t->n_columns > 0);
34✔
2679
        return t->n_columns;
2680
}
2681

2682
size_t table_get_current_column(Table *t) {
80✔
2683
        if (!t)
80✔
2684
                return 0;
2685

2686
        assert(t->n_columns > 0);
80✔
2687
        return t->n_cells % t->n_columns;
80✔
2688
}
2689

2690
int table_set_reverse(Table *t, size_t column, bool b) {
81✔
2691
        assert(t);
81✔
2692
        assert(column < t->n_columns);
81✔
2693

2694
        if (!t->reverse_map) {
81✔
2695
                if (!b)
81✔
2696
                        return 0;
2697

2698
                t->reverse_map = new0(bool, t->n_columns);
27✔
2699
                if (!t->reverse_map)
27✔
2700
                        return -ENOMEM;
2701
        }
2702

2703
        t->reverse_map[column] = b;
27✔
2704
        return 0;
27✔
2705
}
2706

2707
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,055✔
2708
        size_t i;
8,055✔
2709

2710
        assert(t);
8,055✔
2711

2712
        if (column >= t->n_columns)
8,055✔
2713
                return NULL;
2714

2715
        i = row * t->n_columns + column;
8,055✔
2716
        if (i >= t->n_cells)
8,055✔
2717
                return NULL;
2718

2719
        return TABLE_INDEX_TO_CELL(i);
8,055✔
2720
}
2721

2722
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2723
        TableData *d;
4,374✔
2724

2725
        assert(t);
4,374✔
2726

2727
        d = table_get_data(t, cell);
4,374✔
2728
        if (!d)
4,374✔
2729
                return NULL;
2730

2731
        return d->data;
4,374✔
2732
}
2733

2734
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2735
        TableCell *cell;
4,374✔
2736

2737
        cell = table_get_cell(t, row, column);
4,374✔
2738
        if (!cell)
4,374✔
2739
                return NULL;
2740

2741
        return table_get(t, cell);
4,374✔
2742
}
2743

2744
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
3,983✔
2745

2746
        switch (d->type) {
3,983✔
2747

2748
        case TABLE_EMPTY:
323✔
2749
                return sd_json_variant_new_null(ret);
323✔
2750

2751
        case TABLE_STRING:
2,019✔
2752
        case TABLE_PATH:
2753
        case TABLE_PATH_BASENAME:
2754
        case TABLE_FIELD:
2755
        case TABLE_HEADER:
2756
                return sd_json_variant_new_string(ret, d->string);
2,019✔
2757

2758
        case TABLE_STRV:
3✔
2759
        case TABLE_STRV_WRAPPED:
2760
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2761

2762
        case TABLE_BOOLEAN_CHECKMARK:
152✔
2763
        case TABLE_BOOLEAN:
2764
                return sd_json_variant_new_boolean(ret, d->boolean);
152✔
2765

2766
        case TABLE_TIMESTAMP:
155✔
2767
        case TABLE_TIMESTAMP_UTC:
2768
        case TABLE_TIMESTAMP_RELATIVE:
2769
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2770
        case TABLE_TIMESTAMP_LEFT:
2771
        case TABLE_TIMESTAMP_DATE:
2772
                if (d->timestamp == USEC_INFINITY)
155✔
2773
                        return sd_json_variant_new_null(ret);
×
2774

2775
                return sd_json_variant_new_unsigned(ret, d->timestamp);
155✔
2776

2777
        case TABLE_TIMESPAN:
×
2778
        case TABLE_TIMESPAN_MSEC:
2779
        case TABLE_TIMESPAN_DAY:
2780
                if (d->timespan == USEC_INFINITY)
×
2781
                        return sd_json_variant_new_null(ret);
×
2782

2783
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2784

2785
        case TABLE_SIZE:
36✔
2786
        case TABLE_BPS:
2787
                if (d->size == UINT64_MAX)
36✔
2788
                        return sd_json_variant_new_null(ret);
16✔
2789

2790
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2791

2792
        case TABLE_INT:
27✔
2793
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2794

2795
        case TABLE_INT8:
6✔
2796
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2797

2798
        case TABLE_INT16:
6✔
2799
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2800

2801
        case TABLE_INT32:
6✔
2802
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2803

2804
        case TABLE_INT64:
46✔
2805
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2806

2807
        case TABLE_UINT:
15✔
2808
                return sd_json_variant_new_unsigned(ret, d->uint_val);
15✔
2809

2810
        case TABLE_UINT8:
4✔
2811
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2812

2813
        case TABLE_UINT16:
4✔
2814
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2815

2816
        case TABLE_UINT32:
109✔
2817
        case TABLE_UINT32_HEX:
2818
                return sd_json_variant_new_unsigned(ret, d->uint32);
109✔
2819

2820
        case TABLE_UINT64:
386✔
2821
        case TABLE_UINT64_HEX:
2822
                return sd_json_variant_new_unsigned(ret, d->uint64);
386✔
2823

2824
        case TABLE_PERCENT:
×
2825
                return sd_json_variant_new_integer(ret, d->percent);
×
2826

2827
        case TABLE_IFINDEX:
×
2828
                if (d->ifindex <= 0)
×
2829
                        return sd_json_variant_new_null(ret);
×
2830

2831
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2832

2833
        case TABLE_IN_ADDR:
2834
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2835

2836
        case TABLE_IN6_ADDR:
2837
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2838

2839
        case TABLE_ID128:
502✔
2840
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2841

2842
        case TABLE_UUID:
91✔
2843
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2844

2845
        case TABLE_UID:
8✔
2846
                if (!uid_is_valid(d->uid))
8✔
2847
                        return sd_json_variant_new_null(ret);
×
2848

2849
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2850

2851
        case TABLE_GID:
8✔
2852
                if (!gid_is_valid(d->gid))
8✔
2853
                        return sd_json_variant_new_null(ret);
×
2854

2855
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2856

2857
        case TABLE_PID:
14✔
2858
                if (!pid_is_valid(d->pid))
14✔
2859
                        return sd_json_variant_new_null(ret);
×
2860

2861
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2862

2863
        case TABLE_SIGNAL:
8✔
2864
                if (!SIGNAL_VALID(d->int_val))
8✔
2865
                        return sd_json_variant_new_null(ret);
×
2866

2867
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2868

2869
        case TABLE_MODE:
51✔
2870
        case TABLE_MODE_INODE_TYPE:
2871
                if (d->mode == MODE_INVALID)
51✔
2872
                        return sd_json_variant_new_null(ret);
×
2873

2874
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2875

2876
        case TABLE_DEVNUM:
4✔
2877
                if (devnum_is_zero(d->devnum))
4✔
2878
                        return sd_json_variant_new_null(ret);
2✔
2879

2880
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2881
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2882
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2883

2884
        default:
2885
                return -EINVAL;
2886
        }
2887
}
2888

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

2898
        bool new_word = true;
713✔
2899
        char *c;
713✔
2900

2901
        assert(str);
713✔
2902

2903
        c = strdup(str);
713✔
2904
        if (!c)
713✔
2905
                return NULL;
2906

2907
        for (char *x = c; *x; x++) {
5,346✔
2908
                if (!strchr(ALPHANUMERICAL, *x)) {
4,633✔
2909
                        *x = '_';
179✔
2910
                        new_word = true;
179✔
2911
                        continue;
179✔
2912
                }
2913

2914
                if (new_word) {
4,454✔
2915
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
887✔
2916
                                                                  * then we assume the whole word is all-caps
2917
                                                                  * and avoid lowercasing it. */
2918
                                *x = ascii_tolower(*x);
886✔
2919
                        new_word = false;
2920
                }
2921
        }
2922

2923
        return c;
2924
}
2925

2926
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
699✔
2927
        _cleanup_free_ char *mangled = NULL;
1,398✔
2928
        const char *n;
699✔
2929

2930
        assert(t);
699✔
2931
        assert(d);
699✔
2932
        assert(ret);
699✔
2933

2934
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
699✔
2935
                n = d->string;
699✔
2936
        else {
2937
                n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
×
2938
                if (!n)
×
2939
                        return -ENOMEM;
2940
        }
2941

2942
        mangled = table_mangle_to_json_field_name(n);
699✔
2943
        if (!mangled)
699✔
2944
                return -ENOMEM;
2945

2946
        *ret = TAKE_PTR(mangled);
699✔
2947
        return 0;
699✔
2948
}
2949

2950
static const char *table_get_json_field_name(Table *t, size_t idx) {
741✔
2951
        assert(t);
741✔
2952

2953
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
741✔
2954
}
2955

2956
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
90✔
2957
        sd_json_variant **rows = NULL, **elements = NULL;
90✔
2958
        _cleanup_free_ size_t *sorted = NULL;
90✔
2959
        size_t n_rows, display_columns;
90✔
2960
        int r;
90✔
2961

2962
        assert(t);
90✔
2963
        assert(!t->vertical);
90✔
2964

2965
        /* Ensure we have no incomplete rows */
2966
        assert(t->n_columns > 0);
90✔
2967
        assert(t->n_cells % t->n_columns == 0);
90✔
2968

2969
        n_rows = t->n_cells / t->n_columns;
90✔
2970
        assert(n_rows > 0); /* at least the header row must be complete */
90✔
2971

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

2975
                sorted = new(size_t, n_rows);
26✔
2976
                if (!sorted)
26✔
2977
                        return -ENOMEM;
2978

2979
                for (size_t i = 0; i < n_rows; i++)
130✔
2980
                        sorted[i] = i * t->n_columns;
104✔
2981

2982
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
26✔
2983
        }
2984

2985
        if (t->display_map)
90✔
2986
                display_columns = t->n_display_map;
43✔
2987
        else
2988
                display_columns = t->n_columns;
47✔
2989
        assert(display_columns > 0);
90✔
2990

2991
        elements = new0(sd_json_variant*, display_columns * 2);
90✔
2992
        if (!elements)
90✔
2993
                return -ENOMEM;
2994

2995
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
90✔
2996

2997
        for (size_t j = 0; j < display_columns; j++) {
827✔
2998
                _cleanup_free_ char *mangled = NULL;
737✔
2999
                const char *n;
737✔
3000
                size_t c;
737✔
3001

3002
                c = t->display_map ? t->display_map[j] : j;
737✔
3003

3004
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3005
                n = table_get_json_field_name(t, c);
737✔
3006
                if (!n) {
737✔
3007
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
697✔
3008
                        if (r < 0)
697✔
3009
                                return r;
3010

3011
                        n = mangled;
697✔
3012
                }
3013

3014
                r = sd_json_variant_new_string(elements + j*2, n);
737✔
3015
                if (r < 0)
737✔
3016
                        return r;
3017
        }
3018

3019
        rows = new0(sd_json_variant*, n_rows-1);
90✔
3020
        if (!rows)
90✔
3021
                return -ENOMEM;
3022

3023
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
90✔
3024

3025
        for (size_t i = 1; i < n_rows; i++) {
1,001✔
3026
                TableData **row;
911✔
3027

3028
                if (sorted)
911✔
3029
                        row = t->data + sorted[i];
78✔
3030
                else
3031
                        row = t->data + i * t->n_columns;
833✔
3032

3033
                for (size_t j = 0; j < display_columns; j++) {
4,890✔
3034
                        TableData *d;
3,979✔
3035
                        size_t k;
3,979✔
3036

3037
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
3,979✔
3038

3039
                        k = j*2+1;
3,979✔
3040
                        elements[k] = sd_json_variant_unref(elements[k]);
3,979✔
3041

3042
                        r = table_data_to_json(d, elements + k);
3,979✔
3043
                        if (r < 0)
3,979✔
3044
                                return r;
3045
                }
3046

3047
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
911✔
3048
                if (r < 0)
911✔
3049
                        return r;
3050
        }
3051

3052
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
90✔
3053
}
3054

3055
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3056
        sd_json_variant **elements = NULL;
1✔
3057
        size_t n_elements = 0;
1✔
3058
        int r;
1✔
3059

3060
        assert(t);
1✔
3061
        assert(t->vertical);
1✔
3062

3063
        if (t->n_columns != 2)
1✔
3064
                return -EINVAL;
1✔
3065

3066
        /* Ensure we have no incomplete rows */
3067
        assert(t->n_cells % t->n_columns == 0);
1✔
3068

3069
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3070
        if (!elements)
1✔
3071
                return -ENOMEM;
3072

3073
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3074

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

3077
                if (i % t->n_columns == 0) {
8✔
3078
                        _cleanup_free_ char *mangled = NULL;
4✔
3079
                        const char *n;
4✔
3080

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

3087
                                n = mangled;
2✔
3088
                        }
3089

3090
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3091
                } else
3092
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3093
                if (r < 0)
8✔
3094
                        return r;
3095

3096
                n_elements++;
8✔
3097
        }
3098

3099
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3100
}
3101

3102
int table_to_json(Table *t, sd_json_variant **ret) {
91✔
3103
        assert(t);
91✔
3104

3105
        if (t->vertical)
91✔
3106
                return table_to_json_vertical(t, ret);
1✔
3107

3108
        return table_to_json_regular(t, ret);
90✔
3109
}
3110

3111
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
404✔
3112
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
404✔
3113
        int r;
404✔
3114

3115
        assert(t);
404✔
3116

3117
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
404✔
3118
                return table_print(t, f);
340✔
3119

3120
        if (!f)
64✔
3121
                f = stdout;
2✔
3122

3123
        r = table_to_json(t, &v);
64✔
3124
        if (r < 0)
64✔
3125
                return r;
3126

3127
        sd_json_variant_dump(v, flags, f, NULL);
64✔
3128

3129
        return fflush_and_check(f);
64✔
3130
}
3131

3132
int table_print_with_pager(
402✔
3133
                Table *t,
3134
                sd_json_format_flags_t json_format_flags,
3135
                PagerFlags pager_flags,
3136
                bool show_header) {
3137

3138
        bool saved_header;
402✔
3139
        int r;
402✔
3140

3141
        assert(t);
402✔
3142

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

3146
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
402✔
3147
                pager_open(pager_flags);
376✔
3148

3149
        saved_header = t->header;
402✔
3150
        t->header = show_header;
402✔
3151
        r = table_print_json(t, stdout, json_format_flags);
402✔
3152
        t->header = saved_header;
402✔
3153
        if (r < 0)
402✔
3154
                return table_log_print_error(r);
×
3155

3156
        return 0;
3157
}
3158

3159
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
384✔
3160
        int r;
384✔
3161

3162
        assert(t);
384✔
3163

3164
        if (name) {
384✔
3165
                size_t m;
382✔
3166

3167
                m = MAX(idx + 1, t->n_json_fields);
382✔
3168
                if (!GREEDY_REALLOC0(t->json_fields, m))
382✔
3169
                        return -ENOMEM;
3170

3171
                r = free_and_strdup(t->json_fields + idx, name);
382✔
3172
                if (r < 0)
382✔
3173
                        return r;
3174

3175
                t->n_json_fields = m;
382✔
3176
                return r;
382✔
3177
        } else {
3178
                if (idx >= t->n_json_fields)
2✔
3179
                        return 0;
3180

3181
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3182
                return 1;
2✔
3183
        }
3184
}
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