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

systemd / systemd / 23825567702

31 Mar 2026 12:42PM UTC coverage: 72.404% (+0.006%) from 72.398%
23825567702

push

github

daandemeyer
terminal-util: fix boot hang from ANSI terminal size queries

Since v257, terminal_fix_size() is called during early boot via
console_setup() → reset_dev_console_fd() to query terminal dimensions
via ANSI escape sequences. This has caused intermittent boot hangs
where the system gets stuck with a blinking cursor and requires a
keypress to continue (see systemd/systemd#35499).

The function tries CSI 18 first, then falls back to DSR if that fails.
Previously, each method independently opened a non-blocking fd, disabled
echo/icanon, ran its query, restored termios, and closed its fd. This
created two problems:

1. Echo window between CSI 18 and DSR fallback: After CSI 18 times out
   and restores termios (re-enabling ECHO and ICANON), there is a brief
   window before DSR disables them again. If the terminal's CSI 18
   response arrives during this window, it is echoed back to the
   terminal — where the terminal interprets \e[8;rows;cols t as a
   "resize text area" command — and the response bytes land in the
   canonical line buffer as stale input that can confuse the DSR
   response parser.

2. Cursor left at bottom-right on DSR timeout: The DSR method worked by
   sending two DSR queries — one to save the cursor position, then
   moving the cursor to (32766,32766) and sending another to read the
   clamped position. If neither response was received (timeout), the
   cursor restore was skipped (conditional on saved_row > 0), leaving
   the cursor at the bottom-right corner of the terminal. The
   subsequent terminal_reset_ansi_seq() then moved it to the beginning
   of the last line via \e[1G, making boot output appear at the bottom
   of the screen — giving the appearance of a hang even when the system
   was still booting.

This commit fixes both issues:

- terminal_fix_size() now opens the non-blocking fd and configures
  termios once for both query methods, so echo stays disabled for the
  entire CSI 18 → DSR fallback sequence with no gap. tcflu... (continued)

22 of 57 new or added lines in 3 files covered. (38.6%)

834 existing lines in 52 files now uncovered.

318485 of 439872 relevant lines covered (72.4%)

1162379.76 hits per line

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

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

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

6
#include "sd-id128.h"
7

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

35
#define DEFAULT_WEIGHT 100
36

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

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

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

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

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

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

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

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

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

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

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

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

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

122
        assert(cell);
422,630✔
123

124
        i = PTR_TO_SIZE(cell);
422,630✔
125
        assert(i > 0);
422,630✔
126

127
        return i-1;
422,630✔
128
}
129

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

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

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

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

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

148
        TableData **data;
149

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

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

156
        char **json_fields;
157
        size_t n_json_fields;
158

159
        bool *reverse_map;
160
};
161

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

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

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

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

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

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

188
        assert(first_header);
831✔
189

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

195
                n_columns++;
5,947✔
196
        }
197
        va_end(ap);
831✔
198

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

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

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

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

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

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

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

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

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

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

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

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

245
static TableData* table_data_free(TableData *d) {
195,643✔
246
        assert(d);
195,643✔
247

248
        free(d->formatted);
195,643✔
249
        free(d->url);
195,643✔
250

251
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
195,643✔
252
                strv_free(d->strv);
6,411✔
253

254
        if (d->type == TABLE_JSON)
195,643✔
255
                sd_json_variant_unref(d->json);
2,664✔
256

257
        return mfree(d);
195,643✔
258
}
259

260
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
352,979✔
261
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
217,355✔
262

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

267
        for (size_t i = 0; i < t->n_cells; i++)
221,187✔
268
                table_data_unref(t->data[i]);
217,367✔
269

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

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

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

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

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

285
        switch (type) {
536,665✔
286

287
        case TABLE_EMPTY:
288
                return 0;
289

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

390
        size_t k, l;
204,221✔
391
        assert(d);
204,221✔
392

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

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

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

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

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

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

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

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

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

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

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

438
        _cleanup_free_ TableData *d = NULL;
195,643✔
439
        size_t data_size;
195,643✔
440

441
        data_size = table_data_size(type, data);
195,643✔
442

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

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

456
        switch (type) {
195,643✔
457

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

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

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

473
        return TAKE_PTR(d);
474
}
475

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

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

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

495
        /* Special rule: patch NULL data fields to the empty field */
496
        if (!data)
217,355✔
497
                dt = TABLE_EMPTY;
15,733✔
498

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

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

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

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

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

518
        assert(align_percent <= 100);
217,355✔
519
        assert(ellipsize_percent <= 100);
217,355✔
520

521
        uppercase = dt == TABLE_HEADER;
217,355✔
522

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

526
        if (p && table_data_matches(p, dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
217,355✔
527
                d = table_data_ref(p);
78,656✔
528
        else {
529
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
138,699✔
530
                if (!d)
138,699✔
531
                        return -ENOMEM;
532
        }
533

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

537
        if (ret_cell)
217,355✔
538
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
193,255✔
539

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

542
        return 0;
217,355✔
543
}
544

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

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

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

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

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

565
        assert(t);
54✔
566

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

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

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

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

580
        return 0;
581
}
582

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

586
        assert(t);
12✔
587

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

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

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

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

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

606
        assert(t);
208,538✔
607

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

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

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

619
        assert(od->n_ref > 1);
55,821✔
620

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

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

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

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

648
        assert(nd->n_ref == 1);
55,821✔
649

650
        return 1;
651
}
652

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

656
        assert(t);
212,950✔
657
        assert(cell);
212,950✔
658

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

760
        assert(t);
59,430✔
761
        assert(cell);
59,430✔
762

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

767
        table_get_data(t, cell)->color = empty_to_null(color);
74,732✔
768
        return 0;
59,430✔
769
}
770

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

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

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

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

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

789
        assert(t);
59,563✔
790
        assert(cell);
59,563✔
791

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

796
        assert_se(d = table_get_data(t, cell));
59,563✔
797

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

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

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

809
        assert(t);
59,563✔
810
        assert(cell);
59,563✔
811

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

816
        assert_se(d = table_get_data(t, cell));
59,563✔
817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

907
        return 0;
1,123✔
908
}
909

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

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

919
        va_start(ap, first_type);
50,431✔
920

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

948
                switch (type) {
367,503✔
949

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1290
        assert(t);
405✔
1291

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

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

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

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

1304
        return 0;
405✔
1305
}
1306

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

1311
        assert(t);
31✔
1312

1313
        column = first_column;
31✔
1314

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

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

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

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

1330
        }
1331
        va_end(ap);
31✔
1332

1333
        return 0;
31✔
1334
}
1335

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

1340
        assert(t);
272✔
1341

1342
        column = first_column;
272✔
1343

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

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

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

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

1361
        return 0;
272✔
1362
}
1363

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

1368
        assert(t);
1,015✔
1369

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

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

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

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

1395
                if (listed)
12,278✔
1396
                        continue;
1,064✔
1397

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

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

1403
        return 0;
1,015✔
1404
}
1405

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

1409
        assert(a);
34,018✔
1410
        assert(b);
34,018✔
1411

1412
        if (a->type == b->type) {
34,018✔
1413

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

1417
                switch (a->type) {
34,012✔
1418

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1546
        assert(t);
24,374✔
1547
        assert(t->sort_map);
24,374✔
1548

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

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

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

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

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

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

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

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

1586
                if (position == 0) {
1,436✔
1587
                        fputs(*p, f);
400✔
1588
                        position = our_len;
1589
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
2,072✔
1590
                        fprintf(f, " %s", *p);
970✔
1591
                        position = size_add(size_add(position, 1), our_len);
2,406✔
1592
                } else {
1593
                        fprintf(f, "\n%s", *p);
66✔
1594
                        position = our_len;
1595
                }
1596
        }
1597

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

1602
        return buf;
400✔
1603
}
1604

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

1612
        assert(d);
372,687✔
1613

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

1619
        d->formatted = mfree(d->formatted);
303,918✔
1620

1621
        switch (d->type) {
303,918✔
1622
        case TABLE_EMPTY:
20,782✔
1623
                return table_ersatz_string(t);
20,782✔
1624

1625
        case TABLE_STRING:
250,077✔
1626
        case TABLE_STRING_WITH_ANSI:
1627
        case TABLE_PATH:
1628
        case TABLE_PATH_BASENAME:
1629
        case TABLE_FIELD:
1630
        case TABLE_HEADER:
1631
        case TABLE_VERSION: {
1632
                _cleanup_free_ char *bn = NULL;
250,077✔
1633
                const char *s;
250,077✔
1634

1635
                if (d->type == TABLE_PATH_BASENAME)
250,077✔
1636
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
443✔
1637
                else
1638
                        s = d->string;
249,634✔
1639

1640
                if (d->uppercase && !avoid_uppercasing) {
250,077✔
1641
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
4,527✔
1642
                        if (!d->formatted)
4,527✔
1643
                                return NULL;
1644

1645
                        char *q = d->formatted;
1646
                        for (const char *p = s; *p; p++)
31,628✔
1647
                                *(q++) = (char) toupper((unsigned char) *p);
27,101✔
1648

1649
                        if (d->type == TABLE_FIELD)
4,527✔
1650
                                *(q++) = ':';
×
1651

1652
                        *q = 0;
4,527✔
1653
                        return d->formatted;
4,527✔
1654
                }
1655

1656
                if (d->type == TABLE_FIELD)
245,550✔
1657
                        return (d->formatted = strjoin(s, ":"));
33,097✔
1658

1659
                if (bn)
212,453✔
1660
                        return (d->formatted = TAKE_PTR(bn));
441✔
1661

1662
                return d->string;
212,012✔
1663
        }
1664

1665
        case TABLE_STRV:
6,278✔
1666
                if (strv_isempty(d->strv))
6,278✔
1667
                        return table_ersatz_string(t);
×
1668

1669
                return (d->formatted = strv_join(d->strv, "\n"));
6,278✔
1670

1671
        case TABLE_STRV_WRAPPED:
400✔
1672
                if (strv_isempty(d->strv))
400✔
1673
                        return table_ersatz_string(t);
×
1674

1675
                d->formatted = format_strv_width(d->strv, column_width);
400✔
1676
                if (!d->formatted)
400✔
1677
                        return NULL;
1678

1679
                d->formatted_for_width = column_width;
400✔
1680
                if (have_soft)
400✔
1681
                        *have_soft = true;
261✔
1682
                return d->formatted;
400✔
1683

1684
        case TABLE_BOOLEAN:
4,797✔
1685
                return yes_no(d->boolean);
374,642✔
1686

1687
        case TABLE_BOOLEAN_CHECKMARK:
7,332✔
1688
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
10,556✔
1689

1690
        case TABLE_TIMESTAMP:
2,116✔
1691
        case TABLE_TIMESTAMP_UTC:
1692
        case TABLE_TIMESTAMP_RELATIVE:
1693
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1694
        case TABLE_TIMESTAMP_LEFT:
1695
        case TABLE_TIMESTAMP_DATE: {
1696
                char *ret;
2,116✔
1697

1698
                _cleanup_free_ char *p = new(
4,232✔
1699
                                char,
1700
                                IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1701
                                        FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1702
                if (!p)
2,116✔
1703
                        return NULL;
1704

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

1720
                return (d->formatted = TAKE_PTR(p));
1,964✔
1721
        }
1722

1723
        case TABLE_TIMESPAN:
1724
        case TABLE_TIMESPAN_MSEC:
1725
        case TABLE_TIMESPAN_DAY: {
1726
                _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX);
3,014✔
1727
                if (!p)
1,507✔
1728
                        return NULL;
1729

1730
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
3,007✔
1731
                                     d->type == TABLE_TIMESPAN ? 0 :
1,500✔
1732
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1733
                        return "-";
1734

1735
                return (d->formatted = TAKE_PTR(p));
1,507✔
1736
        }
1737

1738
        case TABLE_SIZE: {
1739
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX);
720✔
1740
                if (!p)
360✔
1741
                        return NULL;
1742

1743
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
360✔
1744
                        return table_ersatz_string(t);
238✔
1745

1746
                return (d->formatted = TAKE_PTR(p));
122✔
1747
        }
1748

1749
        case TABLE_BPS: {
1750
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2);
824✔
1751
                if (!p)
412✔
1752
                        return NULL;
1753

1754
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
412✔
1755
                        return table_ersatz_string(t);
×
1756

1757
                size_t n = strlen(p);
412✔
1758
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
412✔
1759

1760
                return (d->formatted = TAKE_PTR(p));
412✔
1761
        }
1762

1763
        case TABLE_INT:
260✔
1764
                return (d->formatted = asprintf_safe("%i", d->int_val));
260✔
1765

1766
        case TABLE_INT8:
3✔
1767
                return (d->formatted = asprintf_safe("%" PRIi8, d->int8));
3✔
1768

1769
        case TABLE_INT16:
3✔
1770
                return (d->formatted = asprintf_safe("%" PRIi16, d->int16));
3✔
1771

1772
        case TABLE_INT32:
3✔
1773
                return (d->formatted = asprintf_safe("%" PRIi32, d->int32));
3✔
1774

1775
        case TABLE_INT64:
155✔
1776
                return (d->formatted = asprintf_safe("%" PRIi64, d->int64));
155✔
1777

1778
        case TABLE_UINT:
285✔
1779
                return (d->formatted = asprintf_safe("%u", d->uint_val));
285✔
1780

1781
        case TABLE_UINT8:
43✔
1782
                return (d->formatted = asprintf_safe("%" PRIu8, d->uint8));
43✔
1783

1784
        case TABLE_UINT16:
91✔
1785
                return (d->formatted = asprintf_safe("%" PRIu16, d->uint16));
91✔
1786

1787
        case TABLE_UINT32:
1,204✔
1788
                return (d->formatted = asprintf_safe("%" PRIu32, d->uint32));
1,204✔
1789

1790
        case TABLE_UINT32_HEX:
2✔
1791
                return (d->formatted = asprintf_safe("%" PRIx32, d->uint32));
2✔
1792

1793
        case TABLE_UINT32_HEX_0x:
7✔
1794
                return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32));
7✔
1795

1796
        case TABLE_UINT64:
1,613✔
1797
                return (d->formatted = asprintf_safe("%" PRIu64, d->uint64));
1,613✔
1798

1799
        case TABLE_UINT64_HEX:
50✔
1800
                return (d->formatted = asprintf_safe("%" PRIx64, d->uint64));
50✔
1801

1802
        case TABLE_UINT64_HEX_0x:
×
1803
                return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64));
×
1804

1805
        case TABLE_PERCENT:
2✔
1806
                return (d->formatted = asprintf_safe("%i%%" , d->percent));
2✔
1807

1808
        case TABLE_IFINDEX:
227✔
1809
                (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted);
227✔
1810
                return d->formatted;
227✔
1811

1812
        case TABLE_IN_ADDR:
205✔
1813
        case TABLE_IN6_ADDR:
1814
                (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1815
                                         &d->address,
205✔
1816
                                         &d->formatted);
1817
                return d->formatted;
205✔
1818

1819
        case TABLE_ID128:
1820
                d->formatted = new(char, SD_ID128_STRING_MAX);
1,096✔
1821
                if (!d->formatted)
1,096✔
1822
                        return NULL;
1823

1824
                return sd_id128_to_string(d->id128, d->formatted);
1,096✔
1825

1826
        case TABLE_UUID:
1827
                d->formatted = new(char, SD_ID128_UUID_STRING_MAX);
440✔
1828
                if (!d->formatted)
440✔
1829
                        return NULL;
1830

1831
                return sd_id128_to_uuid_string(d->id128, d->formatted);
440✔
1832

1833
        case TABLE_UID:
444✔
1834
                if (!uid_is_valid(d->uid))
444✔
1835
                        return table_ersatz_string(t);
×
1836

1837
                return (d->formatted = asprintf_safe(UID_FMT, d->uid));
444✔
1838

1839
        case TABLE_GID:
622✔
1840
                if (!gid_is_valid(d->gid))
622✔
1841
                        return table_ersatz_string(t);
×
1842

1843
                return (d->formatted = asprintf_safe(GID_FMT, d->gid));
622✔
1844

1845
        case TABLE_PID:
214✔
1846
                if (!pid_is_valid(d->pid))
214✔
1847
                        return table_ersatz_string(t);
×
1848

1849
                return (d->formatted = asprintf_safe(PID_FMT, d->pid));
214✔
1850

1851
        case TABLE_SIGNAL: {
123✔
1852
                const char *suffix = signal_to_string(d->int_val);
123✔
1853
                if (!suffix)
123✔
1854
                        return table_ersatz_string(t);
×
1855

1856
                return (d->formatted = strjoin("SIG", suffix));
123✔
1857
        }
1858

1859
        case TABLE_MODE:
96✔
1860
                if (d->mode == MODE_INVALID)
96✔
1861
                        return table_ersatz_string(t);
×
1862

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

1865
        case TABLE_MODE_INODE_TYPE:
2✔
1866
                if (d->mode == MODE_INVALID)
2✔
1867
                        return table_ersatz_string(t);
×
1868

1869
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
1870

1871
        case TABLE_DEVNUM:
3✔
1872
                if (devnum_is_zero(d->devnum))
3✔
1873
                        return table_ersatz_string(t);
2✔
1874

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

1877
        case TABLE_JSON:
2,664✔
1878
                if (!d->json)
2,664✔
1879
                        return table_ersatz_string(t);
×
1880

1881
                (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted);
2,664✔
1882
                return d->formatted;
2,664✔
1883

1884
        default:
×
1885
                assert_not_reached();
×
1886
        }
1887
}
1888

1889
static const char* table_data_format_strip_ansi(
371,905✔
1890
                Table *t,
1891
                TableData *d,
1892
                bool avoid_uppercasing,
1893
                size_t column_width,
1894
                bool *have_soft,
1895
                char **ret_buffer) {
1896

1897
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
1898

1899
        assert(ret_buffer);
371,905✔
1900

1901
        const char *c;
371,905✔
1902

1903
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
371,905✔
1904
        if (!c)
371,905✔
1905
                return NULL;
371,905✔
1906

1907
        if (d->type != TABLE_STRING_WITH_ANSI) {
371,905✔
1908
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
1909
                 * original string as-is */
1910
                *ret_buffer = NULL;
371,890✔
1911
                return c;
371,890✔
1912
        }
1913

1914
        _cleanup_free_ char *s = strdup(c);
15✔
1915
        if (!s)
15✔
1916
                return NULL;
1917

1918
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
1919
                return NULL;
1920

1921
        *ret_buffer = TAKE_PTR(s);
15✔
1922
        return *ret_buffer;
15✔
1923
}
1924

1925
static int console_width_height(
183,256✔
1926
                const char *s,
1927
                size_t *ret_width,
1928
                size_t *ret_height) {
1929

1930
        size_t max_width = 0, height = 0;
183,256✔
1931
        const char *p;
183,256✔
1932

1933
        assert(s);
183,256✔
1934

1935
        /* Determine the width and height in console character cells the specified string needs. */
1936

1937
        do {
186,521✔
1938
                size_t k;
186,521✔
1939

1940
                p = strchr(s, '\n');
186,521✔
1941
                if (p) {
186,521✔
1942
                        _cleanup_free_ char *c = NULL;
3,268✔
1943

1944
                        c = strndup(s, p - s);
3,268✔
1945
                        if (!c)
3,268✔
1946
                                return -ENOMEM;
×
1947

1948
                        k = utf8_console_width(c);
3,268✔
1949
                        s = p + 1;
3,268✔
1950
                } else {
1951
                        k = utf8_console_width(s);
183,253✔
1952
                        s = NULL;
183,253✔
1953
                }
1954
                if (k == SIZE_MAX)
186,521✔
1955
                        return -EINVAL;
1956
                if (k > max_width)
186,521✔
1957
                        max_width = k;
181,580✔
1958

1959
                height++;
186,521✔
1960
        } while (!isempty(s));
189,789✔
1961

1962
        if (ret_width)
183,256✔
1963
                *ret_width = max_width;
183,256✔
1964

1965
        if (ret_height)
183,256✔
1966
                *ret_height = height;
183,256✔
1967

1968
        return 0;
1969
}
1970

1971
static int table_data_requested_width_height(
183,256✔
1972
                Table *table,
1973
                TableData *d,
1974
                size_t available_width,
1975
                size_t *ret_width,
1976
                size_t *ret_height,
1977
                bool *have_soft) {
1978

1979
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
183,256✔
1980
        bool truncation_applied = false;
183,256✔
1981
        size_t width, height;
183,256✔
1982
        bool soft = false;
183,256✔
1983
        const char *t;
183,256✔
1984
        int r;
183,256✔
1985

1986
        t = table_data_format_strip_ansi(
183,256✔
1987
                        table,
1988
                        d,
1989
                        /* avoid_uppercasing= */ false,
1990
                        available_width,
1991
                        &soft,
1992
                        &buffer);
1993
        if (!t)
183,256✔
1994
                return -ENOMEM;
1995

1996
        if (table->cell_height_max != SIZE_MAX) {
183,256✔
1997
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
252✔
1998
                if (r < 0)
252✔
1999
                        return r;
2000
                if (r > 0)
252✔
2001
                        truncation_applied = true;
25✔
2002

2003
                t = truncated;
252✔
2004
        }
2005

2006
        r = console_width_height(t, &width, &height);
183,256✔
2007
        if (r < 0)
183,256✔
2008
                return r;
2009

2010
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
183,256✔
2011
                width = d->maximum_width;
×
2012

2013
        if (width < d->minimum_width)
183,256✔
2014
                width = d->minimum_width;
3,419✔
2015

2016
        if (ret_width)
183,256✔
2017
                *ret_width = width;
183,256✔
2018
        if (ret_height)
183,256✔
2019
                *ret_height = height;
183,218✔
2020
        if (have_soft && soft)
183,256✔
2021
                *have_soft = true;
261✔
2022

2023
        return truncation_applied;
183,256✔
2024
}
2025

2026
static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
141,169✔
2027
        size_t w = 0, space, lspace, old_length, clickable_length;
141,169✔
2028
        _cleanup_free_ char *clickable = NULL;
141,169✔
2029
        const char *p;
141,169✔
2030
        char *ret;
141,169✔
2031
        int r;
141,169✔
2032

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

2035
        assert(str);
141,169✔
2036
        assert(percent <= 100);
141,169✔
2037

2038
        old_length = strlen(str);
141,169✔
2039

2040
        if (url) {
141,169✔
2041
                r = terminal_urlify(url, str, &clickable);
1,559✔
2042
                if (r < 0)
1,559✔
2043
                        return NULL;
2044

2045
                clickable_length = strlen(clickable);
1,559✔
2046
        } else
2047
                clickable_length = old_length;
2048

2049
        /* Determine current width on screen */
2050
        p = str;
141,169✔
2051
        while (p < str + old_length) {
2,093,610✔
2052
                char32_t c;
1,952,441✔
2053

2054
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,952,441✔
2055
                        p++, w++; /* count invalid chars as 1 */
×
2056
                        continue;
×
2057
                }
2058

2059
                p = utf8_next_char(p);
1,952,441✔
2060
                w += unichar_iswide(c) ? 2 : 1;
3,904,870✔
2061
        }
2062

2063
        /* Already wider than the target, if so, don't do anything */
2064
        if (w >= new_length)
141,169✔
2065
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2066

2067
        /* How much spaces shall we add? An how much on the left side? */
2068
        space = new_length - w;
141,169✔
2069
        lspace = space * percent / 100U;
141,169✔
2070

2071
        ret = new(char, space + clickable_length + 1);
141,169✔
2072
        if (!ret)
141,169✔
2073
                return NULL;
2074

2075
        for (size_t i = 0; i < lspace; i++)
775,815✔
2076
                ret[i] = ' ';
634,646✔
2077
        memcpy(ret + lspace, clickable ?: str, clickable_length);
141,169✔
2078
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,836,908✔
2079
                ret[i] = ' ';
3,695,739✔
2080

2081
        ret[space + clickable_length] = 0;
141,169✔
2082
        return ret;
141,169✔
2083
}
2084

2085
static bool table_data_isempty(const TableData *d) {
617✔
2086
        assert(d);
617✔
2087

2088
        if (d->type == TABLE_EMPTY)
617✔
2089
                return true;
2090

2091
        /* Let's also consider an empty strv as truly empty. */
2092
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
528✔
2093
                return strv_isempty(d->strv);
×
2094

2095
        if (d->type == TABLE_JSON)
528✔
2096
                return sd_json_variant_is_null(d->json);
×
2097

2098
        /* Note that an empty string we do not consider empty here! */
2099
        return false;
2100
}
2101

2102
static const char* table_data_color(const TableData *d) {
844✔
2103
        assert(d);
844✔
2104

2105
        if (d->color)
844✔
2106
                return d->color;
2107

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

2112
        if (d->type == TABLE_FIELD)
528✔
2113
                return ansi_bright_blue();
×
2114

2115
        return NULL;
2116
}
2117

2118
static const char* table_data_underline(const TableData *d) {
844✔
2119
        assert(d);
844✔
2120

2121
        if (d->underline)
844✔
2122
                return ansi_add_underline_grey();
×
2123

2124
        if (d->type == TABLE_HEADER)
844✔
2125
                return ansi_add_underline();
50✔
2126

2127
        return NULL;
2128
}
2129

2130
static const char* table_data_rgap_underline(const TableData *d) {
189,406✔
2131
        assert(d);
189,406✔
2132

2133
        if (d->rgap_underline)
189,406✔
2134
                return ansi_add_underline_grey();
300✔
2135

2136
        if (d->type == TABLE_HEADER)
189,106✔
2137
                return ansi_add_underline();
9,184✔
2138

2139
        return NULL;
2140
}
2141

2142
int table_data_requested_width(Table *table, size_t column, size_t *ret) {
4✔
2143
        size_t width = 0;
4✔
2144
        int r;
4✔
2145

2146
        assert(table);
4✔
2147
        assert(ret);
4✔
2148

2149
        for (size_t row = 0; row < table_get_rows(table); row++) {
42✔
2150
                TableCell *cell = table_get_cell(table, row, column);
38✔
2151
                if (!cell)
38✔
2152
                        continue;
×
2153

2154
                TableData *data = table_get_data(table, cell);
38✔
2155
                if (!data)
38✔
2156
                        continue;
×
2157

2158
                size_t w;
38✔
2159

2160
                r = table_data_requested_width_height(
38✔
2161
                                table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL);
2162
                if (r < 0)
38✔
2163
                        return r;
×
2164

2165
                width = MAX(width, w);
38✔
2166
        }
2167

2168
        *ret = width;
4✔
2169
        return 0;
4✔
2170
}
2171

2172
int table_set_column_width(Table *t, size_t column, size_t width) {
4✔
2173
        int r = 0;
4✔
2174

2175
        assert(t);
4✔
2176

2177
        for (size_t row = 0; row < table_get_rows(t); row++) {
42✔
2178
                TableCell *cell = table_get_cell(t, row, column);
38✔
2179
                if (!cell)
38✔
2180
                        continue;
×
2181

2182
                RET_GATHER(r, table_set_minimum_width(t, cell, width));
38✔
2183
        }
2184

2185
        return r;
4✔
2186
}
2187

2188
int _table_sync_column_widths(size_t column, Table *a, ...) {
2✔
2189
        size_t max = 0;
2✔
2190
        va_list ap;
2✔
2191
        int r = 0;
2✔
2192

2193
        assert(a);
2✔
2194

2195
        /* Make the specified column have the same width in the tables. */
2196

2197
        va_start(ap, a);
2✔
2198
        for (Table *t = a; t; t = va_arg(ap, Table*)) {
6✔
2199
                size_t w;
4✔
2200

2201
                r = table_data_requested_width(t, column, &w);
4✔
2202
                if (r < 0)
4✔
2203
                        break;
2204

2205
                max = MAX(max, w);
4✔
2206
        }
2207
        va_end(ap);
2✔
2208
        if (r < 0)
2✔
UNCOV
2209
                return log_error_errno(r, "Failed to query table column width: %m");
×
2210

2211
        r = 0;
2✔
2212
        va_start(ap, a);
2✔
2213
        for (Table *t = a; t; t = va_arg(ap, Table*))
6✔
2214
                RET_GATHER(r, table_set_column_width(t, column, max));
4✔
2215
        va_end(ap);
2✔
2216

2217
        return r;
2✔
2218
}
2219

2220
int table_print(Table *t, FILE *f) {
3,678✔
2221
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,678✔
2222
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2223
                *width = NULL;
3,678✔
2224
        _cleanup_free_ size_t *sorted = NULL;
3,678✔
2225
        uint64_t *column_weight, weight_sum;
3,678✔
2226
        int r;
3,678✔
2227

2228
        assert(t);
3,678✔
2229

2230
        if (!f)
3,678✔
2231
                f = stdout;
2,620✔
2232

2233
        /* Ensure we have no incomplete rows */
2234
        assert(t->n_cells % t->n_columns == 0);
3,678✔
2235

2236
        n_rows = t->n_cells / t->n_columns;
3,678✔
2237
        assert(n_rows > 0); /* at least the header row must be complete */
3,678✔
2238

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

2242
                sorted = new(size_t, n_rows);
221✔
2243
                if (!sorted)
221✔
2244
                        return -ENOMEM;
2245

2246
                for (size_t i = 0; i < n_rows; i++)
6,097✔
2247
                        sorted[i] = i * t->n_columns;
5,876✔
2248

2249
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
221✔
2250
        }
2251

2252
        if (t->display_map)
3,678✔
2253
                display_columns = t->n_display_map;
357✔
2254
        else
2255
                display_columns = t->n_columns;
3,321✔
2256

2257
        assert(display_columns > 0);
3,678✔
2258

2259
        minimum_width = newa(size_t, display_columns);
3,678✔
2260
        maximum_width = newa(size_t, display_columns);
3,678✔
2261
        requested_width = newa(size_t, display_columns);
3,678✔
2262
        column_weight = newa0(uint64_t, display_columns);
3,678✔
2263

2264
        for (size_t j = 0; j < display_columns; j++) {
14,602✔
2265
                minimum_width[j] = 1;
10,924✔
2266
                maximum_width[j] = SIZE_MAX;
10,924✔
2267
        }
2268

2269
        for (unsigned pass = 0; pass < 2; pass++) {
3,699✔
2270
                /* First pass: determine column sizes */
2271

2272
                for (size_t j = 0; j < display_columns; j++)
14,667✔
2273
                        requested_width[j] = SIZE_MAX;
10,968✔
2274

2275
                bool any_soft = false;
3,699✔
2276

2277
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
57,752✔
2278
                        TableData **row;
54,053✔
2279

2280
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2281
                         * hence we don't care for sorted[] during the first pass. */
2282
                        row = t->data + i * t->n_columns;
54,053✔
2283

2284
                        for (size_t j = 0; j < display_columns; j++) {
237,271✔
2285
                                TableData *d;
183,218✔
2286
                                size_t req_width, req_height;
183,218✔
2287

2288
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
183,218✔
2289

2290
                                r = table_data_requested_width_height(t, d,
183,540✔
2291
                                                                      width ? width[j] : SIZE_MAX,
322✔
2292
                                                                      &req_width, &req_height, &any_soft);
2293
                                if (r < 0)
183,218✔
UNCOV
2294
                                        return r;
×
2295
                                if (r > 0) { /* Truncated because too many lines? */
183,218✔
2296
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2297
                                        const char *field;
25✔
2298

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

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

2314
                                        assert_se(t->cell_height_max > 0);
25✔
2315
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2316
                                        if (r < 0)
25✔
2317
                                                return r;
2318

2319
                                        req_width = MAX(req_width,
25✔
2320
                                                        utf8_console_width(last) +
2321
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2322
                                }
2323

2324
                                /* Determine the biggest width that any cell in this column would like to have */
2325
                                if (requested_width[j] == SIZE_MAX ||
183,218✔
2326
                                    requested_width[j] < req_width)
172,282✔
2327
                                        requested_width[j] = req_width;
24,346✔
2328

2329
                                /* Determine the minimum width any cell in this column needs */
2330
                                if (minimum_width[j] < d->minimum_width)
183,218✔
2331
                                        minimum_width[j] = d->minimum_width;
31✔
2332

2333
                                /* Determine the maximum width any cell in this column needs */
2334
                                if (d->maximum_width != SIZE_MAX &&
183,218✔
2335
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2336
                                     maximum_width[j] > d->maximum_width))
2337
                                        maximum_width[j] = d->maximum_width;
20✔
2338

2339
                                /* Determine the full columns weight */
2340
                                column_weight[j] += d->weight;
183,218✔
2341
                        }
2342
                }
2343

2344
                /* One space between each column */
2345
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,699✔
2346

2347
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2348
                weight_sum = 0;
3,699✔
2349
                for (size_t j = 0; j < display_columns; j++) {
14,667✔
2350
                        weight_sum += column_weight[j];
10,968✔
2351

2352
                        table_minimum_width += minimum_width[j];
10,968✔
2353

2354
                        if (maximum_width[j] == SIZE_MAX)
10,968✔
2355
                                table_maximum_width = SIZE_MAX;
2356
                        else
2357
                                table_maximum_width += maximum_width[j];
20✔
2358

2359
                        table_requested_width += requested_width[j];
10,968✔
2360
                }
2361

2362
                /* Calculate effective table width */
2363
                if (t->width != 0 && t->width != SIZE_MAX)
3,699✔
2364
                        table_effective_width = t->width;
2365
                else if (t->width == 0 ||
3,692✔
2366
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,621✔
2367
                        table_effective_width = table_requested_width;
2368
                else
2369
                        table_effective_width = MIN(table_requested_width, columns());
47✔
2370

2371
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,699✔
UNCOV
2372
                        table_effective_width = table_maximum_width;
×
2373

2374
                if (table_effective_width < table_minimum_width)
3,699✔
2375
                        table_effective_width = table_minimum_width;
2✔
2376

2377
                if (!width)
3,699✔
2378
                        width = newa(size_t, display_columns);
3,678✔
2379

2380
                if (table_effective_width >= table_requested_width) {
3,699✔
2381
                        size_t extra;
3,674✔
2382

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

2386
                        extra = table_effective_width - table_requested_width;
3,674✔
2387

2388
                        for (size_t j = 0; j < display_columns; j++) {
14,580✔
2389
                                size_t delta;
10,906✔
2390

2391
                                if (weight_sum == 0)
10,906✔
2392
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2393
                                else
2394
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
10,874✔
2395

2396
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
10,906✔
2397
                                        width[j] = maximum_width[j];
2✔
2398

2399
                                if (width[j] < minimum_width[j])
10,906✔
UNCOV
2400
                                        width[j] = minimum_width[j];
×
2401

2402
                                delta = LESS_BY(width[j], requested_width[j]);
10,906✔
2403

2404
                                /* Subtract what we just added from the rest */
2405
                                if (extra > delta)
10,906✔
2406
                                        extra -= delta;
13✔
2407
                                else
2408
                                        extra = 0;
2409

2410
                                assert(weight_sum >= column_weight[j]);
10,906✔
2411
                                weight_sum -= column_weight[j];
10,906✔
2412
                        }
2413

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

2421
                        extra = table_effective_width - table_minimum_width;
25✔
2422

2423
                        for (size_t j = 0; j < display_columns; j++)
87✔
2424
                                width[j] = SIZE_MAX;
62✔
2425

2426
                        for (;;) {
73✔
2427
                                bool restart = false;
73✔
2428

2429
                                for (size_t j = 0; j < display_columns; j++) {
207✔
2430
                                        size_t delta, w;
157✔
2431

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

2436
                                        if (weight_sum == 0)
104✔
UNCOV
2437
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2438
                                        else
2439
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
104✔
2440

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

2447
                                                w = requested_width[j];
2448
                                                restart = true;
2449

2450
                                        } else if (!finalize)
80✔
2451
                                                continue;
42✔
2452

2453
                                        width[j] = w;
62✔
2454

2455
                                        assert(w >= minimum_width[j]);
62✔
2456
                                        delta = w - minimum_width[j];
62✔
2457

2458
                                        assert(delta <= extra);
62✔
2459
                                        extra -= delta;
62✔
2460

2461
                                        assert(weight_sum >= column_weight[j]);
62✔
2462
                                        weight_sum -= column_weight[j];
62✔
2463

2464
                                        if (restart && !finalize)
62✔
2465
                                                break;
2466
                                }
2467

2468
                                if (finalize)
73✔
2469
                                        break;
2470

2471
                                if (!restart)
48✔
2472
                                        finalize = true;
25✔
2473
                        }
2474

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

2482
        /* Second pass: show output */
2483
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
57,598✔
2484
                size_t n_subline = 0;
53,920✔
2485
                bool more_sublines;
53,920✔
2486
                TableData **row;
53,920✔
2487

2488
                if (sorted)
53,920✔
2489
                        row = t->data + sorted[i];
5,860✔
2490
                else
2491
                        row = t->data + i * t->n_columns;
48,060✔
2492

2493
                do {
57,173✔
2494
                        const char *gap_color = NULL, *gap_underline = NULL;
57,173✔
2495
                        more_sublines = false;
57,173✔
2496

2497
                        for (size_t j = 0; j < display_columns; j++) {
246,579✔
2498
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
189,406✔
2499
                                bool lines_truncated = false;
189,406✔
2500
                                const char *field, *color = NULL, *underline = NULL;
189,406✔
2501
                                TableData *d;
189,406✔
2502
                                size_t l;
189,406✔
2503

2504
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
189,406✔
2505

2506
                                if (colors_enabled())
189,406✔
2507
                                        field = table_data_format(
782✔
2508
                                                        t,
2509
                                                        d,
2510
                                                        /* avoid_uppercasing= */ false,
2511
                                                        width[j],
782✔
2512
                                                        /* have_soft= */ NULL);
2513
                                else
2514
                                        field = table_data_format_strip_ansi(
188,624✔
2515
                                                        t,
2516
                                                        d,
2517
                                                        /* avoid_uppercasing= */ false,
2518
                                                        width[j],
188,624✔
2519
                                                        /* have_soft= */ NULL,
2520
                                                        &stripped_ansi_buffer);
2521
                                if (!field)
189,406✔
2522
                                        return -ENOMEM;
2523

2524
                                r = string_extract_line(field, n_subline, &extracted);
189,406✔
2525
                                if (r < 0)
189,406✔
2526
                                        return r;
2527
                                if (r > 0) {
189,406✔
2528
                                        /* There are more lines to come */
2529
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,290✔
2530
                                                more_sublines = true; /* There are more lines to come */
2531
                                        else
2532
                                                lines_truncated = true;
25✔
2533
                                }
2534
                                if (extracted)
189,406✔
2535
                                        field = extracted;
7,981✔
2536

2537
                                l = utf8_console_width(field);
189,406✔
2538
                                if (l > width[j]) {
189,406✔
2539
                                        /* Field is wider than allocated space. Let's ellipsize */
2540

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

2546
                                        field = buffer;
2547
                                } else {
2548
                                        if (lines_truncated) {
189,371✔
2549
                                                _cleanup_free_ char *padded = NULL;
25✔
2550

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

2557
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2558
                                                if (!padded)
25✔
2559
                                                        return -ENOMEM;
2560

2561
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2562
                                                if (!buffer)
25✔
2563
                                                        return -ENOMEM;
2564

2565
                                                field = buffer;
25✔
2566
                                                l = utf8_console_width(field);
25✔
2567
                                        }
2568

2569
                                        if (l < width[j]) {
189,371✔
UNCOV
2570
                                                _cleanup_free_ char *aligned = NULL;
×
2571
                                                /* Field is shorter than allocated space. Let's align with spaces */
2572

2573
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
141,169✔
2574
                                                if (!aligned)
141,169✔
UNCOV
2575
                                                        return -ENOMEM;
×
2576

2577
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2578
                                                if (j == display_columns - 1 &&
192,080✔
2579
                                                    (!colors_enabled() || !table_data_color(d)) &&
50,973✔
2580
                                                    (!underline_enabled() || !table_data_underline(d)) &&
101,881✔
2581
                                                    (!urlify_enabled() || !d->url))
50,967✔
2582
                                                        delete_trailing_chars(aligned, NULL);
50,908✔
2583

2584
                                                free_and_replace(buffer, aligned);
141,169✔
2585
                                                field = buffer;
141,169✔
2586
                                        }
2587
                                }
2588

2589
                                if (l >= width[j] && d->url) {
189,406✔
UNCOV
2590
                                        _cleanup_free_ char *clickable = NULL;
×
2591

2592
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2593
                                        if (r < 0)
22✔
UNCOV
2594
                                                return r;
×
2595

2596
                                        free_and_replace(buffer, clickable);
22✔
2597
                                        field = buffer;
22✔
2598
                                }
2599

2600
                                if (colors_enabled() && gap_color)
189,406✔
UNCOV
2601
                                        fputs(gap_color, f);
×
2602
                                if (underline_enabled() && gap_underline)
189,406✔
2603
                                        fputs(gap_underline, f);
19✔
2604

2605
                                if (j > 0)
189,406✔
2606
                                        fputc(' ', f); /* column separator left of cell */
132,233✔
2607

2608
                                /* Undo gap color/underline */
2609
                                if ((colors_enabled() && gap_color) ||
378,812✔
2610
                                    (underline_enabled() && gap_underline))
190,188✔
2611
                                        fputs(ANSI_NORMAL, f);
19✔
2612

2613
                                if (colors_enabled()) {
189,406✔
2614
                                        color = table_data_color(d);
782✔
2615
                                        if (color)
782✔
2616
                                                fputs(color, f);
316✔
2617
                                }
2618

2619
                                if (underline_enabled()) {
189,406✔
2620
                                        underline = table_data_underline(d);
782✔
2621
                                        if (underline)
782✔
2622
                                                fputs(underline, f);
22✔
2623
                                }
2624

2625
                                fputs(field, f);
189,406✔
2626

2627
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2628
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
189,415✔
2629
                                        fputs(ANSI_NORMAL, f);
341✔
2630

2631
                                gap_color = d->rgap_color;
189,406✔
2632
                                gap_underline = table_data_rgap_underline(d);
189,406✔
2633
                        }
2634

2635
                        fputc('\n', f);
57,173✔
2636
                        n_subline++;
57,173✔
2637
                } while (more_sublines);
57,173✔
2638
        }
2639

2640
        return fflush_and_check(f);
3,678✔
2641
}
2642

2643
int table_format(Table *t, char **ret) {
41✔
2644
        _cleanup_(memstream_done) MemStream m = {};
41✔
2645
        FILE *f;
41✔
2646
        int r;
41✔
2647

2648
        assert(t);
41✔
2649
        assert(ret);
41✔
2650

2651
        f = memstream_init(&m);
41✔
2652
        if (!f)
41✔
2653
                return -ENOMEM;
2654

2655
        r = table_print(t, f);
41✔
2656
        if (r < 0)
41✔
2657
                return r;
2658

2659
        return memstream_finalize(&m, ret, NULL);
41✔
2660
}
2661

2662
size_t table_get_rows(Table *t) {
1,912✔
2663
        if (!t)
1,912✔
2664
                return 0;
2665

2666
        assert(t->n_columns > 0);
1,912✔
2667
        return t->n_cells / t->n_columns;
1,912✔
2668
}
2669

2670
size_t table_get_columns(Table *t) {
34✔
2671
        if (!t)
34✔
2672
                return 0;
2673

2674
        assert(t->n_columns > 0);
34✔
2675
        return t->n_columns;
2676
}
2677

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

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

2686
int table_set_reverse(Table *t, size_t column, bool b) {
87✔
2687
        assert(t);
87✔
2688
        assert(column < t->n_columns);
87✔
2689

2690
        if (!t->reverse_map) {
87✔
2691
                if (!b)
87✔
2692
                        return 0;
2693

2694
                t->reverse_map = new0(bool, t->n_columns);
33✔
2695
                if (!t->reverse_map)
33✔
2696
                        return -ENOMEM;
2697
        }
2698

2699
        t->reverse_map[column] = b;
33✔
2700
        return 0;
33✔
2701
}
2702

2703
TableCell* table_get_cell(Table *t, size_t row, size_t column) {
8,801✔
2704
        size_t i;
8,801✔
2705

2706
        assert(t);
8,801✔
2707

2708
        if (column >= t->n_columns)
8,801✔
2709
                return NULL;
2710

2711
        i = row * t->n_columns + column;
8,801✔
2712
        if (i >= t->n_cells)
8,801✔
2713
                return NULL;
2714

2715
        return TABLE_INDEX_TO_CELL(i);
8,801✔
2716
}
2717

2718
const void* table_get(Table *t, TableCell *cell) {
4,374✔
2719
        TableData *d;
4,374✔
2720

2721
        assert(t);
4,374✔
2722

2723
        d = table_get_data(t, cell);
4,374✔
2724
        if (!d)
4,374✔
2725
                return NULL;
2726

2727
        return d->data;
4,374✔
2728
}
2729

2730
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2731
        TableCell *cell;
4,374✔
2732

2733
        cell = table_get_cell(t, row, column);
4,374✔
2734
        if (!cell)
4,374✔
2735
                return NULL;
2736

2737
        return table_get(t, cell);
4,374✔
2738
}
2739

2740
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
5,425✔
2741

2742
        switch (d->type) {
5,425✔
2743

2744
        case TABLE_EMPTY:
718✔
2745
                return sd_json_variant_new_null(ret);
718✔
2746

2747
        case TABLE_STRING:
2,560✔
2748
        case TABLE_PATH:
2749
        case TABLE_PATH_BASENAME:
2750
        case TABLE_FIELD:
2751
        case TABLE_HEADER:
2752
        case TABLE_VERSION:
2753
                return sd_json_variant_new_string(ret, d->string);
2,560✔
2754

2755
        case TABLE_STRV:
3✔
2756
        case TABLE_STRV_WRAPPED:
2757
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2758

2759
        case TABLE_BOOLEAN_CHECKMARK:
204✔
2760
        case TABLE_BOOLEAN:
2761
                return sd_json_variant_new_boolean(ret, d->boolean);
204✔
2762

2763
        case TABLE_TIMESTAMP:
299✔
2764
        case TABLE_TIMESTAMP_UTC:
2765
        case TABLE_TIMESTAMP_RELATIVE:
2766
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2767
        case TABLE_TIMESTAMP_LEFT:
2768
        case TABLE_TIMESTAMP_DATE:
2769
                if (d->timestamp == USEC_INFINITY)
299✔
UNCOV
2770
                        return sd_json_variant_new_null(ret);
×
2771

2772
                return sd_json_variant_new_unsigned(ret, d->timestamp);
299✔
2773

UNCOV
2774
        case TABLE_TIMESPAN:
×
2775
        case TABLE_TIMESPAN_MSEC:
2776
        case TABLE_TIMESPAN_DAY:
UNCOV
2777
                if (d->timespan == USEC_INFINITY)
×
UNCOV
2778
                        return sd_json_variant_new_null(ret);
×
2779

UNCOV
2780
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2781

2782
        case TABLE_SIZE:
36✔
2783
        case TABLE_BPS:
2784
                if (d->size == UINT64_MAX)
36✔
2785
                        return sd_json_variant_new_null(ret);
16✔
2786

2787
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2788

2789
        case TABLE_INT:
36✔
2790
                return sd_json_variant_new_integer(ret, d->int_val);
36✔
2791

2792
        case TABLE_INT8:
6✔
2793
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2794

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

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

2801
        case TABLE_INT64:
46✔
2802
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2803

2804
        case TABLE_UINT:
43✔
2805
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2806

2807
        case TABLE_UINT8:
4✔
2808
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2809

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

2813
        case TABLE_UINT32:
118✔
2814
        case TABLE_UINT32_HEX:
2815
        case TABLE_UINT32_HEX_0x:
2816
                return sd_json_variant_new_unsigned(ret, d->uint32);
118✔
2817

2818
        case TABLE_UINT64:
458✔
2819
        case TABLE_UINT64_HEX:
2820
        case TABLE_UINT64_HEX_0x:
2821
                return sd_json_variant_new_unsigned(ret, d->uint64);
458✔
2822

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2873
                return sd_json_variant_new_unsigned(ret, d->mode);
195✔
2874

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

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

UNCOV
2883
        case TABLE_JSON:
×
UNCOV
2884
                if (!d->json)
×
UNCOV
2885
                        return sd_json_variant_new_null(ret);
×
2886

UNCOV
2887
                if (ret)
×
UNCOV
2888
                        *ret = sd_json_variant_ref(d->json);
×
2889

2890
                return 0;
2891

2892
        case TABLE_STRING_WITH_ANSI: {
3✔
2893
                _cleanup_free_ char *s = strdup(d->string);
3✔
2894
                if (!s)
3✔
2895
                        return -ENOMEM;
2896

2897
                /* We strip the ANSI data when outputting to JSON */
2898
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
2899
                        return -ENOMEM;
2900

2901
                return sd_json_variant_new_string(ret, s);
3✔
2902
        }
2903

2904
        default:
2905
                return -EINVAL;
2906
        }
2907
}
2908

2909
char* table_mangle_to_json_field_name(const char *str) {
939✔
2910
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2911
         * field name can be hence this is a bit vague and black magic. Here's what we do:
2912
         *  - Convert spaces to underscores
2913
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
2914
         *  - Convert most other symbols to underscores (for similar reasons)
2915
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
2916
         */
2917

2918
        bool new_word = true;
939✔
2919
        char *c;
939✔
2920

2921
        assert(str);
939✔
2922

2923
        c = strdup(str);
939✔
2924
        if (!c)
939✔
2925
                return NULL;
2926

2927
        for (char *x = c; *x; x++) {
6,687✔
2928
                if (!strchr(ALPHANUMERICAL, *x)) {
5,748✔
2929
                        *x = '_';
185✔
2930
                        new_word = true;
185✔
2931
                        continue;
185✔
2932
                }
2933

2934
                if (new_word) {
5,563✔
2935
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
1,119✔
2936
                                                                  * then we assume the whole word is all-caps
2937
                                                                  * and avoid lowercasing it. */
2938
                                *x = ascii_tolower(*x);
1,118✔
2939
                        new_word = false;
2940
                }
2941
        }
2942

2943
        return c;
2944
}
2945

2946
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
925✔
2947
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
925✔
2948
        const char *n;
925✔
2949

2950
        assert(t);
925✔
2951
        assert(d);
925✔
2952
        assert(ret);
925✔
2953

2954
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
925✔
2955
                n = d->string;
925✔
2956
        else {
UNCOV
2957
                n = table_data_format_strip_ansi(
×
2958
                                t,
2959
                                d,
2960
                                /* avoid_uppercasing= */ true,
2961
                                /* column_width= */ SIZE_MAX,
2962
                                /* have_soft= */ NULL,
2963
                                &buffer);
UNCOV
2964
                if (!n)
×
2965
                        return -ENOMEM;
2966
        }
2967

2968
        mangled = table_mangle_to_json_field_name(n);
925✔
2969
        if (!mangled)
925✔
2970
                return -ENOMEM;
2971

2972
        *ret = TAKE_PTR(mangled);
925✔
2973
        return 0;
925✔
2974
}
2975

2976
static const char* table_get_json_field_name(Table *t, size_t idx) {
1,027✔
2977
        assert(t);
1,027✔
2978

2979
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
1,027✔
2980
}
2981

2982
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
137✔
2983
        sd_json_variant **rows = NULL, **elements = NULL;
137✔
2984
        _cleanup_free_ size_t *sorted = NULL;
137✔
2985
        size_t n_rows, display_columns;
137✔
2986
        int r;
137✔
2987

2988
        assert(t);
137✔
2989
        assert(!t->vertical);
137✔
2990

2991
        /* Ensure we have no incomplete rows */
2992
        assert(t->n_columns > 0);
137✔
2993
        assert(t->n_cells % t->n_columns == 0);
137✔
2994

2995
        n_rows = t->n_cells / t->n_columns;
137✔
2996
        assert(n_rows > 0); /* at least the header row must be complete */
137✔
2997

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

3001
                sorted = new(size_t, n_rows);
32✔
3002
                if (!sorted)
32✔
3003
                        return -ENOMEM;
3004

3005
                for (size_t i = 0; i < n_rows; i++)
198✔
3006
                        sorted[i] = i * t->n_columns;
166✔
3007

3008
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
32✔
3009
        }
3010

3011
        if (t->display_map)
137✔
3012
                display_columns = t->n_display_map;
79✔
3013
        else
3014
                display_columns = t->n_columns;
58✔
3015
        assert(display_columns > 0);
137✔
3016

3017
        elements = new0(sd_json_variant*, display_columns * 2);
137✔
3018
        if (!elements)
137✔
3019
                return -ENOMEM;
3020

3021
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
137✔
3022

3023
        for (size_t j = 0; j < display_columns; j++) {
1,160✔
3024
                _cleanup_free_ char *mangled = NULL;
1,023✔
3025
                const char *n;
1,023✔
3026
                size_t c;
1,023✔
3027

3028
                c = t->display_map ? t->display_map[j] : j;
1,023✔
3029

3030
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3031
                n = table_get_json_field_name(t, c);
1,023✔
3032
                if (!n) {
1,023✔
3033
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
923✔
3034
                        if (r < 0)
923✔
3035
                                return r;
3036

3037
                        n = mangled;
923✔
3038
                }
3039

3040
                r = sd_json_variant_new_string(elements + j*2, n);
1,023✔
3041
                if (r < 0)
1,023✔
3042
                        return r;
3043
        }
3044

3045
        rows = new0(sd_json_variant*, n_rows-1);
137✔
3046
        if (!rows)
137✔
3047
                return -ENOMEM;
3048

3049
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
137✔
3050

3051
        for (size_t i = 1; i < n_rows; i++) {
1,303✔
3052
                TableData **row;
1,166✔
3053

3054
                if (sorted)
1,166✔
3055
                        row = t->data + sorted[i];
134✔
3056
                else
3057
                        row = t->data + i * t->n_columns;
1,032✔
3058

3059
                for (size_t j = 0; j < display_columns; j++) {
6,587✔
3060
                        TableData *d;
5,421✔
3061
                        size_t k;
5,421✔
3062

3063
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
5,421✔
3064

3065
                        k = j*2+1;
5,421✔
3066
                        elements[k] = sd_json_variant_unref(elements[k]);
5,421✔
3067

3068
                        r = table_data_to_json(d, elements + k);
5,421✔
3069
                        if (r < 0)
5,421✔
3070
                                return r;
3071
                }
3072

3073
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1,166✔
3074
                if (r < 0)
1,166✔
3075
                        return r;
3076
        }
3077

3078
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
137✔
3079
}
3080

3081
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3082
        sd_json_variant **elements = NULL;
1✔
3083
        size_t n_elements = 0;
1✔
3084
        int r;
1✔
3085

3086
        assert(t);
1✔
3087
        assert(t->vertical);
1✔
3088

3089
        if (t->n_columns != 2)
1✔
3090
                return -EINVAL;
1✔
3091

3092
        /* Ensure we have no incomplete rows */
3093
        assert(t->n_cells % t->n_columns == 0);
1✔
3094

3095
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3096
        if (!elements)
1✔
3097
                return -ENOMEM;
3098

3099
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3100

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

3103
                if (i % t->n_columns == 0) {
8✔
3104
                        _cleanup_free_ char *mangled = NULL;
4✔
3105
                        const char *n;
4✔
3106

3107
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3108
                        if (!n) {
4✔
3109
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3110
                                if (r < 0)
2✔
UNCOV
3111
                                        return r;
×
3112

3113
                                n = mangled;
2✔
3114
                        }
3115

3116
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3117
                } else
3118
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3119
                if (r < 0)
8✔
3120
                        return r;
3121

3122
                n_elements++;
8✔
3123
        }
3124

3125
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3126
}
3127

3128
int table_to_json(Table *t, sd_json_variant **ret) {
138✔
3129
        assert(t);
138✔
3130

3131
        if (t->vertical)
138✔
3132
                return table_to_json_vertical(t, ret);
1✔
3133

3134
        return table_to_json_regular(t, ret);
137✔
3135
}
3136

3137
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
556✔
3138
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
556✔
3139
        int r;
556✔
3140

3141
        assert(t);
556✔
3142

3143
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
556✔
3144
                return table_print(t, f);
485✔
3145

3146
        if (!f)
71✔
3147
                f = stdout;
2✔
3148

3149
        r = table_to_json(t, &v);
71✔
3150
        if (r < 0)
71✔
3151
                return r;
3152

3153
        sd_json_variant_dump(v, flags, f, NULL);
71✔
3154

3155
        return fflush_and_check(f);
71✔
3156
}
3157

3158
int table_print_with_pager(
554✔
3159
                Table *t,
3160
                sd_json_format_flags_t json_format_flags,
3161
                PagerFlags pager_flags,
3162
                bool show_header) {
3163

3164
        bool saved_header;
554✔
3165
        int r;
554✔
3166

3167
        assert(t);
554✔
3168

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

3172
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
554✔
3173
                pager_open(pager_flags);
528✔
3174

3175
        saved_header = t->header;
554✔
3176
        t->header = show_header;
554✔
3177
        r = table_print_json(t, stdout, json_format_flags);
554✔
3178
        t->header = saved_header;
554✔
3179
        if (r < 0)
554✔
UNCOV
3180
                return table_log_print_error(r);
×
3181

3182
        return 0;
3183
}
3184

3185
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
704✔
3186
        int r;
704✔
3187

3188
        assert(t);
704✔
3189

3190
        if (name) {
704✔
3191
                size_t m;
702✔
3192

3193
                m = MAX(idx + 1, t->n_json_fields);
702✔
3194
                if (!GREEDY_REALLOC0(t->json_fields, m))
702✔
3195
                        return -ENOMEM;
3196

3197
                r = free_and_strdup(t->json_fields + idx, name);
702✔
3198
                if (r < 0)
702✔
3199
                        return r;
3200

3201
                t->n_json_fields = m;
702✔
3202
                return r;
702✔
3203
        } else {
3204
                if (idx >= t->n_json_fields)
2✔
3205
                        return 0;
3206

3207
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3208
                return 1;
2✔
3209
        }
3210
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc