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

systemd / systemd / 21230603853

21 Jan 2026 10:57PM UTC coverage: 72.798% (+0.3%) from 72.524%
21230603853

push

github

web-flow
os-release: add a new FANCY_NAME= field to /etc/os-release, similar to PRETTY_NAME, that may carry ansi sequences + more unicode chars (#40367)

It's sometimes useful include non-ascii unicode chars in an os name, and
give it some ansi coloring. Since we usualy don't want to show that,
introduce a new field for it, and show it at boot and in thostnamectl
only, with safe fallbacks if colors/emojis are not available.

77 of 113 new or added lines in 5 files covered. (68.14%)

2146 existing lines in 53 files now uncovered.

311199 of 427481 relevant lines covered (72.8%)

1155064.07 hits per line

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

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

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

6
#include "sd-id128.h"
7

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

34
#define DEFAULT_WEIGHT 100
35

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

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

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

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

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

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

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

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

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

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

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

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

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

120
        assert(cell);
413,158✔
121

122
        i = PTR_TO_SIZE(cell);
413,158✔
123
        assert(i > 0);
413,158✔
124

125
        return i-1;
413,158✔
126
}
127

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

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

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

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

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

146
        TableData **data;
147

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

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

154
        char **json_fields;
155
        size_t n_json_fields;
156

157
        bool *reverse_map;
158
};
159

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

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

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

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

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

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

186
        assert(first_header);
669✔
187

188
        va_start(ap, first_header);
669✔
189
        for (;;) {
10,171✔
190
                if (!va_arg(ap, const char*))
5,420✔
191
                        break;
192

193
                n_columns++;
4,751✔
194
        }
195
        va_end(ap);
669✔
196

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

201
        va_start(ap, first_header);
669✔
202
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
6,089✔
203
                TableCell *cell;
5,420✔
204

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

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

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

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

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

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

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

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

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

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

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

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

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

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

255
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
322,403✔
256
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
197,604✔
257

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

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

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

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

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

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

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

280
        switch (type) {
491,453✔
281

282
        case TABLE_EMPTY:
283
                return 0;
284

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

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

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

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

313
        case TABLE_SIZE:
19,024✔
314
        case TABLE_INT64:
315
        case TABLE_UINT64:
316
        case TABLE_UINT64_HEX:
317
        case TABLE_UINT64_HEX_0x:
318
        case TABLE_BPS:
319
                return sizeof(uint64_t);
19,024✔
320

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

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

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

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

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

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

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

352
        case TABLE_UID:
1,369✔
353
                return sizeof(uid_t);
1,369✔
354
        case TABLE_GID:
1,866✔
355
                return sizeof(gid_t);
1,866✔
356
        case TABLE_PID:
580✔
357
                return sizeof(pid_t);
580✔
358

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

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

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

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

382
        size_t k, l;
185,924✔
383
        assert(d);
185,924✔
384

385
        if (d->type != type)
185,924✔
386
                return false;
387

388
        if (d->minimum_width != minimum_width)
159,504✔
389
                return false;
390

391
        if (d->maximum_width != maximum_width)
159,504✔
392
                return false;
393

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

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

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

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

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

412
        k = table_data_size(type, data);
154,537✔
413
        l = table_data_size(d->type, d->data);
154,537✔
414
        if (k != l)
154,537✔
415
                return false;
416

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

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

430
        _cleanup_free_ TableData *d = NULL;
182,379✔
431
        size_t data_size;
182,379✔
432

433
        data_size = table_data_size(type, data);
182,379✔
434

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

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

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

455
        return TAKE_PTR(d);
456
}
457

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

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

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

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

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

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

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

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

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

500
        assert(align_percent <= 100);
197,604✔
501
        assert(ellipsize_percent <= 100);
197,604✔
502

503
        uppercase = dt == TABLE_HEADER;
197,604✔
504

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

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

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

519
        if (ret_cell)
197,604✔
520
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
177,925✔
521

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

524
        return 0;
197,604✔
525
}
526

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

532
        assert(t);
7,944✔
533
        assert(IN_SET(dt, TABLE_STRING, TABLE_STRING_WITH_ANSI, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
7,944✔
534

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

541
        return table_add_cell(t, ret_cell, dt, buffer);
7,944✔
542
}
543

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

547
        assert(t);
54✔
548

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

552
        if (until_column >= t->n_columns)
54✔
553
                return -EINVAL;
554

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

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

562
        return 0;
563
}
564

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

568
        assert(t);
12✔
569

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

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

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

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

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

588
        assert(t);
203,823✔
589

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

593
        i = TABLE_CELL_TO_INDEX(cell);
203,823✔
594
        if (i >= t->n_cells)
203,823✔
595
                return -ENXIO;
596

597
        assert_se(od = t->data[i]);
203,823✔
598
        if (od->n_ref == 1)
203,823✔
599
                return 0;
600

601
        assert(od->n_ref > 1);
53,652✔
602

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

609
        nd = table_data_new(
107,304✔
610
                        od->type,
611
                        od->data,
53,652✔
612
                        od->minimum_width,
613
                        od->maximum_width,
614
                        od->weight,
615
                        od->align_percent,
616
                        od->ellipsize_percent,
617
                        od->uppercase);
53,652✔
618
        if (!nd)
53,652✔
619
                return -ENOMEM;
620

621
        nd->color = od->color;
53,652✔
622
        nd->rgap_color = od->rgap_color;
53,652✔
623
        nd->underline = od->underline;
53,652✔
624
        nd->rgap_underline = od->rgap_underline;
53,652✔
625
        nd->url = TAKE_PTR(curl);
53,652✔
626

627
        table_data_unref(od);
53,652✔
628
        t->data[i] = nd;
53,652✔
629

630
        assert(nd->n_ref == 1);
53,652✔
631

632
        return 1;
633
}
634

635
static TableData *table_get_data(Table *t, TableCell *cell) {
208,197✔
636
        size_t i;
208,197✔
637

638
        assert(t);
208,197✔
639
        assert(cell);
208,197✔
640

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

643
        i = TABLE_CELL_TO_INDEX(cell);
208,197✔
644
        if (i >= t->n_cells)
208,197✔
645
                return NULL;
646

647
        assert(t->data[i]);
208,197✔
648
        assert(t->data[i]->n_ref > 0);
208,197✔
649

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

704
        assert(t);
13,473✔
705
        assert(cell);
13,473✔
706

707
        if (percent == UINT_MAX)
13,473✔
708
                percent = 0;
709

710
        assert(percent <= 100);
13,473✔
711

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

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

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

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

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

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

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

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

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

742
        assert(t);
59,170✔
743
        assert(cell);
59,170✔
744

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

749
        table_get_data(t, cell)->color = empty_to_null(color);
73,969✔
750
        return 0;
59,170✔
751
}
752

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

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

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

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

767
int table_set_underline(Table *t, TableCell *cell, bool b) {
60,277✔
768
        TableData *d;
60,277✔
769
        int r;
60,277✔
770

771
        assert(t);
60,277✔
772
        assert(cell);
60,277✔
773

774
        r = table_dedup_cell(t, cell);
60,277✔
775
        if (r < 0)
60,277✔
776
                return r;
777

778
        assert_se(d = table_get_data(t, cell));
60,277✔
779

780
        if (d->underline == b)
60,277✔
781
                return 0;
782

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

787
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
60,277✔
788
        TableData *d;
60,277✔
789
        int r;
60,277✔
790

791
        assert(t);
60,277✔
792
        assert(cell);
60,277✔
793

794
        r = table_dedup_cell(t, cell);
60,277✔
795
        if (r < 0)
60,277✔
796
                return r;
797

798
        assert_se(d = table_get_data(t, cell));
60,277✔
799

800
        if (d->rgap_underline == b)
60,277✔
801
                return 0;
802

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

889
        return 0;
1,123✔
890
}
891

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

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

901
        va_start(ap, first_type);
46,494✔
902

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

930
                switch (type) {
344,835✔
931

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

936
                case TABLE_STRING:
131,308✔
937
                case TABLE_STRING_WITH_ANSI:
938
                case TABLE_PATH:
939
                case TABLE_PATH_BASENAME:
940
                case TABLE_FIELD:
941
                case TABLE_HEADER:
942
                case TABLE_VERSION:
943
                        data = va_arg(ap, const char *);
131,308✔
944
                        break;
131,308✔
945

946
                case TABLE_STRV:
6,684✔
947
                case TABLE_STRV_WRAPPED:
948
                        data = va_arg(ap, char * const *);
6,684✔
949
                        break;
6,684✔
950

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

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

970
                case TABLE_SIZE:
725✔
971
                case TABLE_BPS:
972
                        buffer.size = va_arg(ap, uint64_t);
725✔
973
                        data = &buffer.size;
725✔
974
                        break;
725✔
975

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

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

986
                        buffer.int8 = x;
3✔
987
                        data = &buffer.int8;
3✔
988
                        break;
3✔
989
                }
990

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

995
                        buffer.int16 = x;
3✔
996
                        data = &buffer.int16;
3✔
997
                        break;
3✔
998
                }
999

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

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

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

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

1019
                        buffer.uint8 = x;
43✔
1020
                        data = &buffer.uint8;
43✔
1021
                        break;
43✔
1022
                }
1023

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

1028
                        buffer.uint16 = x;
88✔
1029
                        data = &buffer.uint16;
88✔
1030
                        break;
88✔
1031
                }
1032

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

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

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

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

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

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

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

1073
                case TABLE_UID:
524✔
1074
                        buffer.uid = va_arg(ap, uid_t);
524✔
1075
                        data = &buffer.uid;
524✔
1076
                        break;
524✔
1077

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

1083
                case TABLE_PID:
237✔
1084
                        buffer.pid = va_arg(ap, pid_t);
237✔
1085
                        data = &buffer.pid;
237✔
1086
                        break;
237✔
1087

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1167
                case TABLE_SET_BOTH_UNDERLINES: {
60,277✔
1168
                        int u = va_arg(ap, int);
60,277✔
1169

1170
                        r = table_set_underline(t, last_cell, u);
60,277✔
1171
                        if (r < 0) {
60,277✔
1172
                                va_end(ap);
×
1173
                                return r;
×
1174
                        }
1175

1176
                        r = table_set_rgap_underline(t, last_cell, u);
60,277✔
1177
                        goto check;
60,277✔
1178
                }
1179

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

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

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

1206
                case _TABLE_DATA_TYPE_MAX:
46,494✔
1207
                        /* Used as end marker */
1208
                        va_end(ap);
46,494✔
1209
                        return 0;
46,494✔
1210

1211
                default:
×
1212
                        assert_not_reached();
×
1213
                }
1214

1215
                r = table_add_cell(t, &last_cell, type, data);
166,667✔
1216
        check:
298,341✔
1217
                if (r < 0) {
298,341✔
1218
                        va_end(ap);
×
1219
                        return r;
×
1220
                }
1221
        }
1222
}
1223

1224
void table_set_header(Table *t, bool b) {
194✔
1225
        assert(t);
194✔
1226

1227
        t->header = b;
194✔
1228
}
194✔
1229

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

1233
        t->width = width;
99✔
1234
}
99✔
1235

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

1240
        t->cell_height_max = height;
28✔
1241
}
28✔
1242

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

1247
        t->ersatz = ersatz;
620✔
1248
}
620✔
1249

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

1265
static int table_set_display_all(Table *t) {
325✔
1266
        size_t *d;
325✔
1267

1268
        assert(t);
325✔
1269

1270
        /* Initialize the display map to the identity */
1271

1272
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
325✔
1273
        if (!d)
325✔
1274
                return -ENOMEM;
1275

1276
        for (size_t i = 0; i < t->n_columns; i++)
4,267✔
1277
                d[i] = i;
3,942✔
1278

1279
        t->display_map = d;
325✔
1280
        t->n_display_map = t->n_columns;
325✔
1281

1282
        return 0;
325✔
1283
}
1284

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

1289
        assert(t);
31✔
1290

1291
        column = first_column;
31✔
1292

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

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

1302
                t->display_map[t->n_display_map++] = column;
159✔
1303

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

1308
        }
1309
        va_end(ap);
31✔
1310

1311
        return 0;
31✔
1312
}
1313

1314
int table_set_sort_internal(Table *t, size_t first_column, ...) {
243✔
1315
        size_t column;
243✔
1316
        va_list ap;
243✔
1317

1318
        assert(t);
243✔
1319

1320
        column = first_column;
243✔
1321

1322
        va_start(ap, first_column);
243✔
1323
        for (;;) {
316✔
1324
                assert(column < t->n_columns);
316✔
1325

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

1331
                t->sort_map[t->n_sort_map++] = column;
316✔
1332

1333
                column = va_arg(ap, size_t);
316✔
1334
                if (column == SIZE_MAX)
316✔
1335
                        break;
1336
        }
1337
        va_end(ap);
243✔
1338

1339
        return 0;
243✔
1340
}
1341

1342
int table_hide_column_from_display_internal(Table *t, ...) {
664✔
1343
        size_t cur = 0;
664✔
1344
        int r;
664✔
1345

1346
        assert(t);
664✔
1347

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

1355
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
9,140✔
1356
                bool listed = false;
8,476✔
1357
                va_list ap;
8,476✔
1358

1359
                va_start(ap, t);
8,476✔
1360
                for (;;) {
16,410✔
1361
                        size_t column;
16,410✔
1362

1363
                        column = va_arg(ap, size_t);
16,410✔
1364
                        if (column == SIZE_MAX)
16,410✔
1365
                                break;
1366
                        if (column == *i) {
8,611✔
1367
                                listed = true;
1368
                                break;
1369
                        }
1370
                }
1371
                va_end(ap);
8,476✔
1372

1373
                if (listed)
8,476✔
1374
                        continue;
677✔
1375

1376
                t->display_map[cur++] = *i;
7,799✔
1377
        }
1378

1379
        t->n_display_map = cur;
664✔
1380

1381
        return 0;
664✔
1382
}
1383

1384
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
12,010✔
1385
        int r;
12,010✔
1386

1387
        assert(a);
12,010✔
1388
        assert(b);
12,010✔
1389

1390
        if (a->type == b->type) {
12,010✔
1391

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

1395
                switch (a->type) {
12,004✔
1396

1397
                case TABLE_STRING:
8,385✔
1398
                case TABLE_STRING_WITH_ANSI:
1399
                case TABLE_FIELD:
1400
                case TABLE_HEADER:
1401
                        return strcmp(a->string, b->string);
8,385✔
1402

1403
                case TABLE_PATH:
41✔
1404
                case TABLE_PATH_BASENAME:
1405
                        return path_compare(a->string, b->string);
41✔
1406

1407
                case TABLE_VERSION:
×
1408
                        return strverscmp_improved(a->string, b->string);
×
1409

1410
                case TABLE_STRV:
×
1411
                case TABLE_STRV_WRAPPED:
1412
                        return strv_compare(a->strv, b->strv);
×
1413

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

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

1429
                case TABLE_TIMESPAN:
786✔
1430
                case TABLE_TIMESPAN_MSEC:
1431
                case TABLE_TIMESPAN_DAY:
1432
                        return CMP(a->timespan, b->timespan);
786✔
1433

1434
                case TABLE_SIZE:
×
1435
                case TABLE_BPS:
1436
                        return CMP(a->size, b->size);
×
1437

1438
                case TABLE_INT:
16✔
1439
                case TABLE_SIGNAL:
1440
                        return CMP(a->int_val, b->int_val);
16✔
1441

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

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

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

1451
                case TABLE_INT64:
188✔
1452
                        return CMP(a->int64, b->int64);
188✔
1453

1454
                case TABLE_UINT:
75✔
1455
                        return CMP(a->uint_val, b->uint_val);
75✔
1456

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

1460
                case TABLE_UINT16:
×
1461
                        return CMP(a->uint16, b->uint16);
×
1462

1463
                case TABLE_UINT32:
27✔
1464
                case TABLE_UINT32_HEX:
1465
                case TABLE_UINT32_HEX_0x:
1466
                        return CMP(a->uint32, b->uint32);
27✔
1467

1468
                case TABLE_UINT64:
×
1469
                case TABLE_UINT64_HEX:
1470
                case TABLE_UINT64_HEX_0x:
1471
                        return CMP(a->uint64, b->uint64);
×
1472

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

1476
                case TABLE_IFINDEX:
×
1477
                        return CMP(a->ifindex, b->ifindex);
×
1478

1479
                case TABLE_IN_ADDR:
×
1480
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1481

1482
                case TABLE_IN6_ADDR:
1483
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1484

1485
                case TABLE_UUID:
×
1486
                case TABLE_ID128:
1487
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1488

1489
                case TABLE_UID:
1,154✔
1490
                        return CMP(a->uid, b->uid);
1,154✔
1491

1492
                case TABLE_GID:
1,330✔
1493
                        return CMP(a->gid, b->gid);
1,330✔
1494

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

1498
                case TABLE_MODE:
×
1499
                case TABLE_MODE_INODE_TYPE:
1500
                        return CMP(a->mode, b->mode);
×
1501

1502
                case TABLE_DEVNUM:
×
1503
                        r = CMP(major(a->devnum), major(b->devnum));
×
1504
                        if (r != 0)
×
1505
                                return r;
×
1506

1507
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1508

1509
                default:
6✔
1510
                        ;
6✔
1511
                }
1512
        }
1513

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

1518
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
12,252✔
1519
        int r;
12,252✔
1520

1521
        assert(t);
12,252✔
1522
        assert(t->sort_map);
12,252✔
1523

1524
        /* Make sure the header stays at the beginning */
1525
        if (*a < t->n_columns && *b < t->n_columns)
12,252✔
1526
                return 0;
1527
        if (*a < t->n_columns)
12,252✔
1528
                return -1;
1529
        if (*b < t->n_columns)
11,733✔
1530
                return 1;
1531

1532
        /* Order other lines by the sorting map */
1533
        for (size_t i = 0; i < t->n_sort_map; i++) {
12,015✔
1534
                TableData *d, *dd;
12,010✔
1535

1536
                d = t->data[*a + t->sort_map[i]];
12,010✔
1537
                dd = t->data[*b + t->sort_map[i]];
12,010✔
1538

1539
                r = cell_data_compare(d, *a, dd, *b);
12,010✔
1540
                if (r != 0)
12,010✔
1541
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
11,728✔
1542
        }
1543

1544
        /* Order identical lines by the order there were originally added in */
1545
        return CMP(*a, *b);
5✔
1546
}
1547

1548
static char* format_strv_width(char **strv, size_t column_width) {
246✔
1549
        _cleanup_(memstream_done) MemStream m = {};
246✔
1550
        FILE *f;
246✔
1551

1552
        f = memstream_init(&m);
246✔
1553
        if (!f)
246✔
1554
                return NULL;
1555

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

1561
                if (position == 0) {
682✔
1562
                        fputs(*p, f);
246✔
1563
                        position = our_len;
1564
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
872✔
1565
                        fprintf(f, " %s", *p);
384✔
1566
                        position = size_add(size_add(position, 1), our_len);
1,066✔
1567
                } else {
1568
                        fprintf(f, "\n%s", *p);
52✔
1569
                        position = our_len;
1570
                }
1571
        }
1572

1573
        char *buf;
246✔
1574
        if (memstream_finalize(&m, &buf, NULL) < 0)
246✔
1575
                return NULL;
1576

1577
        return buf;
246✔
1578
}
1579

1580
static const char *table_data_format(
341,181✔
1581
                Table *t,
1582
                TableData *d,
1583
                bool avoid_uppercasing,
1584
                size_t column_width,
1585
                bool *have_soft) {
1586

1587
        assert(d);
341,181✔
1588

1589
        if (d->formatted &&
341,181✔
1590
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1591
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
63,992✔
1592
                return d->formatted;
1593

1594
        switch (d->type) {
277,363✔
1595
        case TABLE_EMPTY:
13,480✔
1596
                return table_ersatz_string(t);
13,480✔
1597

1598
        case TABLE_STRING:
235,032✔
1599
        case TABLE_STRING_WITH_ANSI:
1600
        case TABLE_PATH:
1601
        case TABLE_PATH_BASENAME:
1602
        case TABLE_FIELD:
1603
        case TABLE_HEADER:
1604
        case TABLE_VERSION: {
1605
                _cleanup_free_ char *bn = NULL;
235,032✔
1606
                const char *s;
235,032✔
1607

1608
                if (d->type == TABLE_PATH_BASENAME)
235,032✔
1609
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
384✔
1610
                else
1611
                        s = d->string;
234,648✔
1612

1613
                if (d->uppercase && !avoid_uppercasing) {
235,032✔
1614
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,785✔
1615
                        if (!d->formatted)
3,785✔
1616
                                return NULL;
1617

1618
                        char *q = d->formatted;
1619
                        for (const char *p = s; *p; p++)
26,513✔
1620
                                *(q++) = (char) toupper((unsigned char) *p);
22,728✔
1621

1622
                        if (d->type == TABLE_FIELD)
3,785✔
1623
                                *(q++) = ':';
×
1624

1625
                        *q = 0;
3,785✔
1626
                        return d->formatted;
3,785✔
1627
                } else if (d->type == TABLE_FIELD) {
231,247✔
1628
                        d->formatted = strjoin(s, ":");
32,908✔
1629
                        if (!d->formatted)
32,908✔
1630
                                return NULL;
1631

1632
                        return d->formatted;
32,908✔
1633
                }
1634

1635
                if (bn) {
198,339✔
1636
                        d->formatted = TAKE_PTR(bn);
382✔
1637
                        return d->formatted;
382✔
1638
                }
1639

1640
                return d->string;
197,957✔
1641
        }
1642

1643
        case TABLE_STRV:
6,257✔
1644
                if (strv_isempty(d->strv))
6,257✔
1645
                        return table_ersatz_string(t);
×
1646

1647
                d->formatted = strv_join(d->strv, "\n");
6,257✔
1648
                if (!d->formatted)
6,257✔
1649
                        return NULL;
1650
                break;
1651

1652
        case TABLE_STRV_WRAPPED: {
246✔
1653
                if (strv_isempty(d->strv))
246✔
1654
                        return table_ersatz_string(t);
×
1655

1656
                char *buf = format_strv_width(d->strv, column_width);
246✔
1657
                if (!buf)
246✔
1658
                        return NULL;
1659

1660
                free_and_replace(d->formatted, buf);
246✔
1661
                d->formatted_for_width = column_width;
246✔
1662
                if (have_soft)
246✔
1663
                        *have_soft = true;
165✔
1664

1665
                break;
16,914✔
1666
        }
1667

1668
        case TABLE_BOOLEAN:
4,617✔
1669
                return yes_no(d->boolean);
342,996✔
1670

1671
        case TABLE_BOOLEAN_CHECKMARK:
6,976✔
1672
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,746✔
1673

1674
        case TABLE_TIMESTAMP:
1,996✔
1675
        case TABLE_TIMESTAMP_UTC:
1676
        case TABLE_TIMESTAMP_RELATIVE:
1677
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1678
        case TABLE_TIMESTAMP_LEFT:
1679
        case TABLE_TIMESTAMP_DATE: {
1680
                _cleanup_free_ char *p = NULL;
132✔
1681
                char *ret;
1,996✔
1682

1683
                p = new(char,
1,996✔
1684
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1685
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1686
                if (!p)
1,996✔
1687
                        return NULL;
1688

1689
                if (d->type == TABLE_TIMESTAMP)
1,996✔
1690
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,312✔
1691
                else if (d->type == TABLE_TIMESTAMP_UTC)
684✔
1692
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1693
                else if (d->type == TABLE_TIMESTAMP_DATE)
684✔
1694
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
1✔
1695
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1696
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1697
                else
1698
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1699
                                                             d->timestamp, CLOCK_REALTIME,
1700
                                                             /* implicit_left= */ d->type == TABLE_TIMESTAMP_LEFT);
1701
                if (!ret)
1,996✔
1702
                        return "-";
1703

1704
                d->formatted = TAKE_PTR(p);
1,864✔
1705
                break;
1,864✔
1706
        }
1707

1708
        case TABLE_TIMESPAN:
1,495✔
1709
        case TABLE_TIMESPAN_MSEC:
1710
        case TABLE_TIMESPAN_DAY: {
1711
                _cleanup_free_ char *p = NULL;
×
1712

1713
                p = new(char, FORMAT_TIMESPAN_MAX);
1,495✔
1714
                if (!p)
1,495✔
1715
                        return NULL;
1716

1717
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,983✔
1718
                                     d->type == TABLE_TIMESPAN ? 0 :
1,488✔
1719
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1720
                        return "-";
1721

1722
                d->formatted = TAKE_PTR(p);
1,495✔
1723
                break;
1,495✔
1724
        }
1725

1726
        case TABLE_SIZE: {
336✔
1727
                _cleanup_free_ char *p = NULL;
208✔
1728

1729
                p = new(char, FORMAT_BYTES_MAX);
336✔
1730
                if (!p)
336✔
1731
                        return NULL;
1732

1733
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
336✔
1734
                        return table_ersatz_string(t);
208✔
1735

1736
                d->formatted = TAKE_PTR(p);
128✔
1737
                break;
128✔
1738
        }
1739

1740
        case TABLE_BPS: {
410✔
1741
                _cleanup_free_ char *p = NULL;
×
1742
                size_t n;
410✔
1743

1744
                p = new(char, FORMAT_BYTES_MAX+2);
410✔
1745
                if (!p)
410✔
1746
                        return NULL;
1747

1748
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
410✔
1749
                        return table_ersatz_string(t);
×
1750

1751
                n = strlen(p);
410✔
1752
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
410✔
1753

1754
                d->formatted = TAKE_PTR(p);
410✔
1755
                break;
410✔
1756
        }
1757

1758
        case TABLE_INT: {
260✔
1759
                _cleanup_free_ char *p = NULL;
×
1760

1761
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
738✔
1762
                if (!p)
260✔
1763
                        return NULL;
×
1764

1765
                sprintf(p, "%i", d->int_val);
260✔
1766
                d->formatted = TAKE_PTR(p);
260✔
1767
                break;
260✔
1768
        }
1769

1770
        case TABLE_INT8: {
3✔
1771
                _cleanup_free_ char *p = NULL;
×
1772

1773
                p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
10✔
1774
                if (!p)
3✔
1775
                        return NULL;
×
1776

1777
                sprintf(p, "%" PRIi8, d->int8);
3✔
1778
                d->formatted = TAKE_PTR(p);
3✔
1779
                break;
3✔
1780
        }
1781

1782
        case TABLE_INT16: {
3✔
1783
                _cleanup_free_ char *p = NULL;
×
1784

1785
                p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
14✔
1786
                if (!p)
3✔
1787
                        return NULL;
×
1788

1789
                sprintf(p, "%" PRIi16, d->int16);
3✔
1790
                d->formatted = TAKE_PTR(p);
3✔
1791
                break;
3✔
1792
        }
1793

1794
        case TABLE_INT32: {
3✔
1795
                _cleanup_free_ char *p = NULL;
×
1796

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

1801
                sprintf(p, "%" PRIi32, d->int32);
3✔
1802
                d->formatted = TAKE_PTR(p);
3✔
1803
                break;
3✔
1804
        }
1805

1806
        case TABLE_INT64: {
155✔
1807
                _cleanup_free_ char *p = NULL;
×
1808

1809
                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
319✔
1810
                if (!p)
155✔
1811
                        return NULL;
×
1812

1813
                sprintf(p, "%" PRIi64, d->int64);
155✔
1814
                d->formatted = TAKE_PTR(p);
155✔
1815
                break;
155✔
1816
        }
1817

1818
        case TABLE_UINT: {
285✔
1819
                _cleanup_free_ char *p = NULL;
×
1820

1821
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
410✔
1822
                if (!p)
285✔
1823
                        return NULL;
×
1824

1825
                sprintf(p, "%u", d->uint_val);
285✔
1826
                d->formatted = TAKE_PTR(p);
285✔
1827
                break;
285✔
1828
        }
1829

1830
        case TABLE_UINT8: {
43✔
1831
                _cleanup_free_ char *p = NULL;
×
1832

1833
                p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
46✔
1834
                if (!p)
43✔
1835
                        return NULL;
×
1836

1837
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1838
                d->formatted = TAKE_PTR(p);
43✔
1839
                break;
43✔
1840
        }
1841

1842
        case TABLE_UINT16: {
88✔
1843
                _cleanup_free_ char *p = NULL;
×
1844

1845
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
302✔
1846
                if (!p)
88✔
1847
                        return NULL;
×
1848

1849
                sprintf(p, "%" PRIu16, d->uint16);
88✔
1850
                d->formatted = TAKE_PTR(p);
88✔
1851
                break;
88✔
1852
        }
1853

1854
        case TABLE_UINT32: {
1,040✔
1855
                _cleanup_free_ char *p = NULL;
×
1856

1857
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,563✔
1858
                if (!p)
1,040✔
1859
                        return NULL;
×
1860

1861
                sprintf(p, "%" PRIu32, d->uint32);
1,040✔
1862
                d->formatted = TAKE_PTR(p);
1,040✔
1863
                break;
1,040✔
1864
        }
1865

1866
        case TABLE_UINT32_HEX: {
2✔
1867
                _cleanup_free_ char *p = NULL;
×
1868

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

1873
                sprintf(p, "%" PRIx32, d->uint32);
2✔
1874
                d->formatted = TAKE_PTR(p);
2✔
1875
                break;
2✔
1876
        }
1877

1878
        case TABLE_UINT32_HEX_0x: {
6✔
1879
                _cleanup_free_ char *p = NULL;
×
1880

1881
                p = new(char, 2 + 8 + 1);
6✔
1882
                if (!p)
6✔
1883
                        return NULL;
×
1884

1885
                sprintf(p, "0x%" PRIx32, d->uint32);
6✔
1886
                d->formatted = TAKE_PTR(p);
6✔
1887
                break;
6✔
1888
        }
1889

1890
        case TABLE_UINT64: {
1,352✔
1891
                _cleanup_free_ char *p = NULL;
×
1892

1893
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
6,206✔
1894
                if (!p)
1,352✔
1895
                        return NULL;
×
1896

1897
                sprintf(p, "%" PRIu64, d->uint64);
1,352✔
1898
                d->formatted = TAKE_PTR(p);
1,352✔
1899
                break;
1,352✔
1900
        }
1901

1902
        case TABLE_UINT64_HEX: {
14✔
1903
                _cleanup_free_ char *p = NULL;
×
1904

1905
                p = new(char, 16 + 1);
14✔
1906
                if (!p)
14✔
1907
                        return NULL;
×
1908

1909
                sprintf(p, "%" PRIx64, d->uint64);
14✔
1910
                d->formatted = TAKE_PTR(p);
14✔
1911
                break;
14✔
1912
        }
1913

1914
        case TABLE_UINT64_HEX_0x: {
×
1915
                _cleanup_free_ char *p = NULL;
×
1916

1917
                p = new(char, 2 + 16 + 1);
×
1918
                if (!p)
×
1919
                        return NULL;
×
1920

1921
                sprintf(p, "0x%" PRIx64, d->uint64);
×
1922
                d->formatted = TAKE_PTR(p);
×
1923
                break;
×
1924
        }
1925

1926
        case TABLE_PERCENT: {
2✔
1927
                _cleanup_free_ char *p = NULL;
×
1928

1929
                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
5✔
1930
                if (!p)
2✔
1931
                        return NULL;
×
1932

1933
                sprintf(p, "%i%%" , d->percent);
2✔
1934
                d->formatted = TAKE_PTR(p);
2✔
1935
                break;
2✔
1936
        }
1937

1938
        case TABLE_IFINDEX: {
223✔
1939
                _cleanup_free_ char *p = NULL;
×
1940

1941
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
223✔
1942
                        return NULL;
×
1943

1944
                d->formatted = TAKE_PTR(p);
223✔
1945
                break;
223✔
1946
        }
1947

1948
        case TABLE_IN_ADDR:
205✔
1949
        case TABLE_IN6_ADDR: {
1950
                _cleanup_free_ char *p = NULL;
×
1951

1952
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1953
                                      &d->address, &p) < 0)
205✔
1954
                        return NULL;
×
1955

1956
                d->formatted = TAKE_PTR(p);
205✔
1957
                break;
205✔
1958
        }
1959

1960
        case TABLE_ID128: {
1961
                char *p;
1,096✔
1962

1963
                p = new(char, SD_ID128_STRING_MAX);
1,096✔
1964
                if (!p)
1,096✔
1965
                        return NULL;
1966

1967
                d->formatted = sd_id128_to_string(d->id128, p);
1,096✔
1968
                break;
1,096✔
1969
        }
1970

1971
        case TABLE_UUID: {
1972
                char *p;
335✔
1973

1974
                p = new(char, SD_ID128_UUID_STRING_MAX);
335✔
1975
                if (!p)
335✔
1976
                        return NULL;
1977

1978
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
335✔
1979
                break;
335✔
1980
        }
1981

1982
        case TABLE_UID: {
417✔
1983
                char *p;
417✔
1984

1985
                if (!uid_is_valid(d->uid))
417✔
1986
                        return table_ersatz_string(t);
×
1987

1988
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,451✔
1989
                if (!p)
417✔
1990
                        return NULL;
1991
                sprintf(p, UID_FMT, d->uid);
417✔
1992

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

1997
        case TABLE_GID: {
614✔
1998
                char *p;
614✔
1999

2000
                if (!gid_is_valid(d->gid))
614✔
2001
                        return table_ersatz_string(t);
×
2002

2003
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,805✔
2004
                if (!p)
614✔
2005
                        return NULL;
2006
                sprintf(p, GID_FMT, d->gid);
614✔
2007

2008
                d->formatted = p;
614✔
2009
                break;
614✔
2010
        }
2011

2012
        case TABLE_PID: {
220✔
2013
                char *p;
220✔
2014

2015
                if (!pid_is_valid(d->pid))
220✔
2016
                        return table_ersatz_string(t);
×
2017

2018
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
584✔
2019
                if (!p)
220✔
2020
                        return NULL;
2021
                sprintf(p, PID_FMT, d->pid);
220✔
2022

2023
                d->formatted = p;
220✔
2024
                break;
220✔
2025
        }
2026

2027
        case TABLE_SIGNAL: {
123✔
2028
                const char *suffix;
123✔
2029
                char *p;
123✔
2030

2031
                suffix = signal_to_string(d->int_val);
123✔
2032
                if (!suffix)
123✔
2033
                        return table_ersatz_string(t);
×
2034

2035
                p = strjoin("SIG", suffix);
123✔
2036
                if (!p)
123✔
2037
                        return NULL;
2038

2039
                d->formatted = p;
123✔
2040
                break;
123✔
2041
        }
2042

2043
        case TABLE_MODE: {
24✔
2044
                char *p;
24✔
2045

2046
                if (d->mode == MODE_INVALID)
24✔
2047
                        return table_ersatz_string(t);
×
2048

2049
                p = new(char, 4 + 1);
24✔
2050
                if (!p)
24✔
2051
                        return NULL;
2052

2053
                sprintf(p, "%04o", d->mode & 07777);
24✔
2054
                d->formatted = p;
24✔
2055
                break;
24✔
2056
        }
2057

2058
        case TABLE_MODE_INODE_TYPE:
2✔
2059

2060
                if (d->mode == MODE_INVALID)
2✔
2061
                        return table_ersatz_string(t);
×
2062

2063
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2064

2065
        case TABLE_DEVNUM:
3✔
2066
                if (devnum_is_zero(d->devnum))
3✔
2067
                        return table_ersatz_string(t);
2✔
2068

2069
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2070
                        return NULL;
2071

2072
                break;
2073

2074
        default:
×
2075
                assert_not_reached();
×
2076
        }
2077

2078
        return d->formatted;
16,914✔
2079
}
2080

2081
static const char *table_data_format_strip_ansi(
340,462✔
2082
                Table *t,
2083
                TableData *d,
2084
                bool avoid_uppercasing,
2085
                size_t column_width,
2086
                bool *have_soft,
2087
                char **ret_buffer) {
2088

2089
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
2090

2091
        assert(ret_buffer);
340,462✔
2092

2093
        const char *c;
340,462✔
2094

2095
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
340,462✔
2096
        if (!c)
340,462✔
2097
                return NULL;
340,462✔
2098

2099
        if (d->type != TABLE_STRING_WITH_ANSI) {
340,462✔
2100
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
2101
                 * original string as-is */
2102
                *ret_buffer = NULL;
340,447✔
2103
                return c;
340,447✔
2104
        }
2105

2106
        _cleanup_free_ char *s = strdup(c);
15✔
2107
        if (!s)
15✔
2108
                return NULL;
2109

2110
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
2111
                return NULL;
2112

2113
        *ret_buffer = TAKE_PTR(s);
15✔
2114
        return *ret_buffer;
15✔
2115
}
2116

2117
static int console_width_height(
167,451✔
2118
                const char *s,
2119
                size_t *ret_width,
2120
                size_t *ret_height) {
2121

2122
        size_t max_width = 0, height = 0;
167,451✔
2123
        const char *p;
167,451✔
2124

2125
        assert(s);
167,451✔
2126

2127
        /* Determine the width and height in console character cells the specified string needs. */
2128

2129
        do {
170,703✔
2130
                size_t k;
170,703✔
2131

2132
                p = strchr(s, '\n');
170,703✔
2133
                if (p) {
170,703✔
2134
                        _cleanup_free_ char *c = NULL;
3,255✔
2135

2136
                        c = strndup(s, p - s);
3,255✔
2137
                        if (!c)
3,255✔
2138
                                return -ENOMEM;
×
2139

2140
                        k = utf8_console_width(c);
3,255✔
2141
                        s = p + 1;
3,255✔
2142
                } else {
2143
                        k = utf8_console_width(s);
167,448✔
2144
                        s = NULL;
167,448✔
2145
                }
2146
                if (k == SIZE_MAX)
170,703✔
2147
                        return -EINVAL;
2148
                if (k > max_width)
170,703✔
2149
                        max_width = k;
166,106✔
2150

2151
                height++;
170,703✔
2152
        } while (!isempty(s));
173,958✔
2153

2154
        if (ret_width)
167,451✔
2155
                *ret_width = max_width;
167,451✔
2156

2157
        if (ret_height)
167,451✔
2158
                *ret_height = height;
167,451✔
2159

2160
        return 0;
2161
}
2162

2163
static int table_data_requested_width_height(
167,451✔
2164
                Table *table,
2165
                TableData *d,
2166
                size_t available_width,
2167
                size_t *ret_width,
2168
                size_t *ret_height,
2169
                bool *have_soft) {
2170

2171
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
167,451✔
2172
        bool truncation_applied = false;
167,451✔
2173
        size_t width, height;
167,451✔
2174
        bool soft = false;
167,451✔
2175
        const char *t;
167,451✔
2176
        int r;
167,451✔
2177

2178
        t = table_data_format_strip_ansi(
167,451✔
2179
                        table,
2180
                        d,
2181
                        /* avoid_uppercasing= */ false,
2182
                        available_width,
2183
                        &soft,
2184
                        &buffer);
2185
        if (!t)
167,451✔
2186
                return -ENOMEM;
2187

2188
        if (table->cell_height_max != SIZE_MAX) {
167,451✔
2189
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
180✔
2190
                if (r < 0)
180✔
2191
                        return r;
2192
                if (r > 0)
180✔
2193
                        truncation_applied = true;
25✔
2194

2195
                t = truncated;
180✔
2196
        }
2197

2198
        r = console_width_height(t, &width, &height);
167,451✔
2199
        if (r < 0)
167,451✔
2200
                return r;
2201

2202
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
167,451✔
2203
                width = d->maximum_width;
×
2204

2205
        if (width < d->minimum_width)
167,451✔
2206
                width = d->minimum_width;
3,080✔
2207

2208
        if (ret_width)
167,451✔
2209
                *ret_width = width;
167,451✔
2210
        if (ret_height)
167,451✔
2211
                *ret_height = height;
167,451✔
2212
        if (have_soft && soft)
167,451✔
2213
                *have_soft = true;
165✔
2214

2215
        return truncation_applied;
167,451✔
2216
}
2217

2218
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
126,960✔
2219
        size_t w = 0, space, lspace, old_length, clickable_length;
126,960✔
2220
        _cleanup_free_ char *clickable = NULL;
126,960✔
2221
        const char *p;
126,960✔
2222
        char *ret;
126,960✔
2223
        int r;
126,960✔
2224

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

2227
        assert(str);
126,960✔
2228
        assert(percent <= 100);
126,960✔
2229

2230
        old_length = strlen(str);
126,960✔
2231

2232
        if (url) {
126,960✔
2233
                r = terminal_urlify(url, str, &clickable);
1,555✔
2234
                if (r < 0)
1,555✔
2235
                        return NULL;
2236

2237
                clickable_length = strlen(clickable);
1,555✔
2238
        } else
2239
                clickable_length = old_length;
2240

2241
        /* Determine current width on screen */
2242
        p = str;
126,960✔
2243
        while (p < str + old_length) {
1,902,541✔
2244
                char32_t c;
1,775,581✔
2245

2246
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,775,581✔
2247
                        p++, w++; /* count invalid chars as 1 */
×
2248
                        continue;
×
2249
                }
2250

2251
                p = utf8_next_char(p);
1,775,581✔
2252
                w += unichar_iswide(c) ? 2 : 1;
3,551,150✔
2253
        }
2254

2255
        /* Already wider than the target, if so, don't do anything */
2256
        if (w >= new_length)
126,960✔
2257
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2258

2259
        /* How much spaces shall we add? An how much on the left side? */
2260
        space = new_length - w;
126,960✔
2261
        lspace = space * percent / 100U;
126,960✔
2262

2263
        ret = new(char, space + clickable_length + 1);
126,960✔
2264
        if (!ret)
126,960✔
2265
                return NULL;
2266

2267
        for (size_t i = 0; i < lspace; i++)
750,002✔
2268
                ret[i] = ' ';
623,042✔
2269
        memcpy(ret + lspace, clickable ?: str, clickable_length);
126,960✔
2270
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
3,534,617✔
2271
                ret[i] = ' ';
3,407,657✔
2272

2273
        ret[space + clickable_length] = 0;
126,960✔
2274
        return ret;
126,960✔
2275
}
2276

2277
static bool table_data_isempty(const TableData *d) {
561✔
2278
        assert(d);
561✔
2279

2280
        if (d->type == TABLE_EMPTY)
561✔
2281
                return true;
2282

2283
        /* Let's also consider an empty strv as truly empty. */
2284
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
479✔
2285
                return strv_isempty(d->strv);
×
2286

2287
        /* Note that an empty string we do not consider empty here! */
2288
        return false;
2289
}
2290

2291
static const char* table_data_color(const TableData *d) {
774✔
2292
        assert(d);
774✔
2293

2294
        if (d->color)
774✔
2295
                return d->color;
2296

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

2301
        if (d->type == TABLE_FIELD)
479✔
2302
                return ansi_bright_blue();
×
2303

2304
        return NULL;
2305
}
2306

2307
static const char* table_data_underline(const TableData *d) {
774✔
2308
        assert(d);
774✔
2309

2310
        if (d->underline)
774✔
2311
                return ansi_add_underline_grey();
×
2312

2313
        if (d->type == TABLE_HEADER)
774✔
2314
                return ansi_add_underline();
50✔
2315

2316
        return NULL;
2317
}
2318

2319
static const char* table_data_rgap_underline(const TableData *d) {
173,705✔
2320
        assert(d);
173,705✔
2321

2322
        if (d->rgap_underline)
173,705✔
2323
                return ansi_add_underline_grey();
300✔
2324

2325
        if (d->type == TABLE_HEADER)
173,405✔
2326
                return ansi_add_underline();
7,708✔
2327

2328
        return NULL;
2329
}
2330

2331
int table_print(Table *t, FILE *f) {
3,536✔
2332
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,536✔
2333
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2334
                *width = NULL;
3,536✔
2335
        _cleanup_free_ size_t *sorted = NULL;
3,536✔
2336
        uint64_t *column_weight, weight_sum;
3,536✔
2337
        int r;
3,536✔
2338

2339
        assert(t);
3,536✔
2340

2341
        if (!f)
3,536✔
2342
                f = stdout;
2,603✔
2343

2344
        /* Ensure we have no incomplete rows */
2345
        assert(t->n_cells % t->n_columns == 0);
3,536✔
2346

2347
        n_rows = t->n_cells / t->n_columns;
3,536✔
2348
        assert(n_rows > 0); /* at least the header row must be complete */
3,536✔
2349

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

2353
                sorted = new(size_t, n_rows);
199✔
2354
                if (!sorted)
199✔
2355
                        return -ENOMEM;
2356

2357
                for (size_t i = 0; i < n_rows; i++)
3,312✔
2358
                        sorted[i] = i * t->n_columns;
3,113✔
2359

2360
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
199✔
2361
        }
2362

2363
        if (t->display_map)
3,536✔
2364
                display_columns = t->n_display_map;
313✔
2365
        else
2366
                display_columns = t->n_columns;
3,223✔
2367

2368
        assert(display_columns > 0);
3,536✔
2369

2370
        minimum_width = newa(size_t, display_columns);
3,536✔
2371
        maximum_width = newa(size_t, display_columns);
3,536✔
2372
        requested_width = newa(size_t, display_columns);
3,536✔
2373
        column_weight = newa0(uint64_t, display_columns);
3,536✔
2374

2375
        for (size_t j = 0; j < display_columns; j++) {
13,673✔
2376
                minimum_width[j] = 1;
10,137✔
2377
                maximum_width[j] = SIZE_MAX;
10,137✔
2378
        }
2379

2380
        for (unsigned pass = 0; pass < 2; pass++) {
3,554✔
2381
                /* First pass: determine column sizes */
2382

2383
                for (size_t j = 0; j < display_columns; j++)
13,729✔
2384
                        requested_width[j] = SIZE_MAX;
10,175✔
2385

2386
                bool any_soft = false;
3,554✔
2387

2388
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
54,078✔
2389
                        TableData **row;
50,524✔
2390

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

2395
                        for (size_t j = 0; j < display_columns; j++) {
217,975✔
2396
                                TableData *d;
167,451✔
2397
                                size_t req_width, req_height;
167,451✔
2398

2399
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
167,451✔
2400

2401
                                r = table_data_requested_width_height(t, d,
167,679✔
2402
                                                                      width ? width[j] : SIZE_MAX,
228✔
2403
                                                                      &req_width, &req_height, &any_soft);
2404
                                if (r < 0)
167,451✔
2405
                                        return r;
×
2406
                                if (r > 0) { /* Truncated because too many lines? */
167,451✔
2407
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2408
                                        const char *field;
25✔
2409

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

2415
                                        field = table_data_format_strip_ansi(
28✔
2416
                                                        t,
2417
                                                        d,
2418
                                                        /* avoid_uppercasing= */ false,
2419
                                                        width ? width[j] : SIZE_MAX,
3✔
2420
                                                        &any_soft,
2421
                                                        &buffer);
2422
                                        if (!field)
25✔
2423
                                                return -ENOMEM;
2424

2425
                                        assert_se(t->cell_height_max > 0);
25✔
2426
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2427
                                        if (r < 0)
25✔
2428
                                                return r;
2429

2430
                                        req_width = MAX(req_width,
25✔
2431
                                                        utf8_console_width(last) +
2432
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2433
                                }
2434

2435
                                /* Determine the biggest width that any cell in this column would like to have */
2436
                                if (requested_width[j] == SIZE_MAX ||
167,451✔
2437
                                    requested_width[j] < req_width)
157,308✔
2438
                                        requested_width[j] = req_width;
22,802✔
2439

2440
                                /* Determine the minimum width any cell in this column needs */
2441
                                if (minimum_width[j] < d->minimum_width)
167,451✔
2442
                                        minimum_width[j] = d->minimum_width;
38✔
2443

2444
                                /* Determine the maximum width any cell in this column needs */
2445
                                if (d->maximum_width != SIZE_MAX &&
167,451✔
2446
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2447
                                     maximum_width[j] > d->maximum_width))
2448
                                        maximum_width[j] = d->maximum_width;
20✔
2449

2450
                                /* Determine the full columns weight */
2451
                                column_weight[j] += d->weight;
167,451✔
2452
                        }
2453
                }
2454

2455
                /* One space between each column */
2456
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,554✔
2457

2458
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2459
                weight_sum = 0;
3,554✔
2460
                for (size_t j = 0; j < display_columns; j++) {
13,729✔
2461
                        weight_sum += column_weight[j];
10,175✔
2462

2463
                        table_minimum_width += minimum_width[j];
10,175✔
2464

2465
                        if (maximum_width[j] == SIZE_MAX)
10,175✔
2466
                                table_maximum_width = SIZE_MAX;
2467
                        else
2468
                                table_maximum_width += maximum_width[j];
20✔
2469

2470
                        table_requested_width += requested_width[j];
10,175✔
2471
                }
2472

2473
                /* Calculate effective table width */
2474
                if (t->width != 0 && t->width != SIZE_MAX)
3,554✔
2475
                        table_effective_width = t->width;
2476
                else if (t->width == 0 ||
3,547✔
2477
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,476✔
2478
                        table_effective_width = table_requested_width;
2479
                else
2480
                        table_effective_width = MIN(table_requested_width, columns());
36✔
2481

2482
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,554✔
2483
                        table_effective_width = table_maximum_width;
×
2484

2485
                if (table_effective_width < table_minimum_width)
3,554✔
2486
                        table_effective_width = table_minimum_width;
2✔
2487

2488
                if (!width)
3,554✔
2489
                        width = newa(size_t, display_columns);
3,536✔
2490

2491
                if (table_effective_width >= table_requested_width) {
3,554✔
2492
                        size_t extra;
3,532✔
2493

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

2497
                        extra = table_effective_width - table_requested_width;
3,532✔
2498

2499
                        for (size_t j = 0; j < display_columns; j++) {
13,651✔
2500
                                size_t delta;
10,119✔
2501

2502
                                if (weight_sum == 0)
10,119✔
2503
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2504
                                else
2505
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
10,087✔
2506

2507
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
10,119✔
2508
                                        width[j] = maximum_width[j];
2✔
2509

2510
                                if (width[j] < minimum_width[j])
10,119✔
2511
                                        width[j] = minimum_width[j];
×
2512

2513
                                delta = LESS_BY(width[j], requested_width[j]);
10,119✔
2514

2515
                                /* Subtract what we just added from the rest */
2516
                                if (extra > delta)
10,119✔
2517
                                        extra -= delta;
13✔
2518
                                else
2519
                                        extra = 0;
2520

2521
                                assert(weight_sum >= column_weight[j]);
10,119✔
2522
                                weight_sum -= column_weight[j];
10,119✔
2523
                        }
2524

2525
                        break; /* Every column should be happy, no need to repeat calculations. */
3,536✔
2526
                } else {
2527
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2528
                         * with the minimum they need, and then distribute anything left. */
2529
                        bool finalize = false;
22✔
2530
                        size_t extra;
22✔
2531

2532
                        extra = table_effective_width - table_minimum_width;
22✔
2533

2534
                        for (size_t j = 0; j < display_columns; j++)
78✔
2535
                                width[j] = SIZE_MAX;
56✔
2536

2537
                        for (;;) {
64✔
2538
                                bool restart = false;
64✔
2539

2540
                                for (size_t j = 0; j < display_columns; j++) {
186✔
2541
                                        size_t delta, w;
142✔
2542

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

2547
                                        if (weight_sum == 0)
95✔
2548
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2549
                                        else
2550
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
95✔
2551

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

2558
                                                w = requested_width[j];
2559
                                                restart = true;
2560

2561
                                        } else if (!finalize)
74✔
2562
                                                continue;
39✔
2563

2564
                                        width[j] = w;
56✔
2565

2566
                                        assert(w >= minimum_width[j]);
56✔
2567
                                        delta = w - minimum_width[j];
56✔
2568

2569
                                        assert(delta <= extra);
56✔
2570
                                        extra -= delta;
56✔
2571

2572
                                        assert(weight_sum >= column_weight[j]);
56✔
2573
                                        weight_sum -= column_weight[j];
56✔
2574

2575
                                        if (restart && !finalize)
56✔
2576
                                                break;
2577
                                }
2578

2579
                                if (finalize)
64✔
2580
                                        break;
2581

2582
                                if (!restart)
42✔
2583
                                        finalize = true;
22✔
2584
                        }
2585

2586
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
22✔
2587
                                        * let's try to reformat them with the new widths. Otherwise, let's
2588
                                        * move on. */
2589
                                break;
2590
                }
2591
        }
2592

2593
        /* Second pass: show output */
2594
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
53,973✔
2595
                size_t n_subline = 0;
50,437✔
2596
                bool more_sublines;
50,437✔
2597
                TableData **row;
50,437✔
2598

2599
                if (sorted)
50,437✔
2600
                        row = t->data + sorted[i];
3,099✔
2601
                else
2602
                        row = t->data + i * t->n_columns;
47,338✔
2603

2604
                do {
53,677✔
2605
                        const char *gap_color = NULL, *gap_underline = NULL;
53,677✔
2606
                        more_sublines = false;
53,677✔
2607

2608
                        for (size_t j = 0; j < display_columns; j++) {
227,382✔
2609
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
173,705✔
2610
                                bool lines_truncated = false;
173,705✔
2611
                                const char *field, *color = NULL, *underline = NULL;
173,705✔
2612
                                TableData *d;
173,705✔
2613
                                size_t l;
173,705✔
2614

2615
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
173,705✔
2616

2617
                                if (colors_enabled())
173,705✔
2618
                                        field = table_data_format(
719✔
2619
                                                        t,
2620
                                                        d,
2621
                                                        /* avoid_uppercasing= */ false,
2622
                                                        width[j],
719✔
2623
                                                        /* have_soft= */ NULL);
2624
                                else
2625
                                        field = table_data_format_strip_ansi(
172,986✔
2626
                                                        t,
2627
                                                        d,
2628
                                                        /* avoid_uppercasing= */ false,
2629
                                                        width[j],
172,986✔
2630
                                                        /* have_soft= */ NULL,
2631
                                                        &stripped_ansi_buffer);
2632
                                if (!field)
173,705✔
2633
                                        return -ENOMEM;
2634

2635
                                r = string_extract_line(field, n_subline, &extracted);
173,705✔
2636
                                if (r < 0)
173,705✔
2637
                                        return r;
2638
                                if (r > 0) {
173,705✔
2639
                                        /* There are more lines to come */
2640
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,277✔
2641
                                                more_sublines = true; /* There are more lines to come */
2642
                                        else
2643
                                                lines_truncated = true;
25✔
2644
                                }
2645
                                if (extracted)
173,705✔
2646
                                        field = extracted;
7,939✔
2647

2648
                                l = utf8_console_width(field);
173,705✔
2649
                                if (l > width[j]) {
173,705✔
2650
                                        /* Field is wider than allocated space. Let's ellipsize */
2651

2652
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
35✔
2653
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2654
                                        if (!buffer)
35✔
2655
                                                return -ENOMEM;
2656

2657
                                        field = buffer;
2658
                                } else {
2659
                                        if (lines_truncated) {
173,670✔
2660
                                                _cleanup_free_ char *padded = NULL;
25✔
2661

2662
                                                /* We truncated more lines of this cell, let's add an
2663
                                                 * ellipsis. We first append it, but that might make our
2664
                                                 * string grow above what we have space for, hence ellipsize
2665
                                                 * right after. This will truncate the ellipsis and add a new
2666
                                                 * one. */
2667

2668
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2669
                                                if (!padded)
25✔
2670
                                                        return -ENOMEM;
2671

2672
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2673
                                                if (!buffer)
25✔
2674
                                                        return -ENOMEM;
2675

2676
                                                field = buffer;
25✔
2677
                                                l = utf8_console_width(field);
25✔
2678
                                        }
2679

2680
                                        if (l < width[j]) {
173,670✔
2681
                                                _cleanup_free_ char *aligned = NULL;
×
2682
                                                /* Field is shorter than allocated space. Let's align with spaces */
2683

2684
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
126,960✔
2685
                                                if (!aligned)
126,960✔
2686
                                                        return -ENOMEM;
×
2687

2688
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2689
                                                if (j == display_columns - 1 &&
174,827✔
2690
                                                    (!colors_enabled() || !table_data_color(d)) &&
47,922✔
2691
                                                    (!underline_enabled() || !table_data_underline(d)) &&
95,786✔
2692
                                                    (!urlify_enabled() || !d->url))
47,916✔
2693
                                                        delete_trailing_chars(aligned, NULL);
47,864✔
2694

2695
                                                free_and_replace(buffer, aligned);
126,960✔
2696
                                                field = buffer;
126,960✔
2697
                                        }
2698
                                }
2699

2700
                                if (l >= width[j] && d->url) {
173,705✔
2701
                                        _cleanup_free_ char *clickable = NULL;
×
2702

2703
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2704
                                        if (r < 0)
22✔
2705
                                                return r;
×
2706

2707
                                        free_and_replace(buffer, clickable);
22✔
2708
                                        field = buffer;
22✔
2709
                                }
2710

2711
                                if (colors_enabled() && gap_color)
173,705✔
2712
                                        fputs(gap_color, f);
×
2713
                                if (underline_enabled() && gap_underline)
173,705✔
2714
                                        fputs(gap_underline, f);
19✔
2715

2716
                                if (j > 0)
173,705✔
2717
                                        fputc(' ', f); /* column separator left of cell */
120,028✔
2718

2719
                                /* Undo gap color/underline */
2720
                                if ((colors_enabled() && gap_color) ||
347,410✔
2721
                                    (underline_enabled() && gap_underline))
174,424✔
2722
                                        fputs(ANSI_NORMAL, f);
19✔
2723

2724
                                if (colors_enabled()) {
173,705✔
2725
                                        color = table_data_color(d);
719✔
2726
                                        if (color)
719✔
2727
                                                fputs(color, f);
295✔
2728
                                }
2729

2730
                                if (underline_enabled()) {
173,705✔
2731
                                        underline = table_data_underline(d);
719✔
2732
                                        if (underline)
719✔
2733
                                                fputs(underline, f);
22✔
2734
                                }
2735

2736
                                fputs(field, f);
173,705✔
2737

2738
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2739
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
173,714✔
2740
                                        fputs(ANSI_NORMAL, f);
320✔
2741

2742
                                gap_color = d->rgap_color;
173,705✔
2743
                                gap_underline = table_data_rgap_underline(d);
173,705✔
2744
                        }
2745

2746
                        fputc('\n', f);
53,677✔
2747
                        n_subline++;
53,677✔
2748
                } while (more_sublines);
53,677✔
2749
        }
2750

2751
        return fflush_and_check(f);
3,536✔
2752
}
2753

2754
int table_format(Table *t, char **ret) {
41✔
2755
        _cleanup_(memstream_done) MemStream m = {};
41✔
2756
        FILE *f;
41✔
2757
        int r;
41✔
2758

2759
        assert(t);
41✔
2760
        assert(ret);
41✔
2761

2762
        f = memstream_init(&m);
41✔
2763
        if (!f)
41✔
2764
                return -ENOMEM;
2765

2766
        r = table_print(t, f);
41✔
2767
        if (r < 0)
41✔
2768
                return r;
2769

2770
        return memstream_finalize(&m, ret, NULL);
41✔
2771
}
2772

2773
size_t table_get_rows(Table *t) {
1,770✔
2774
        if (!t)
1,770✔
2775
                return 0;
2776

2777
        assert(t->n_columns > 0);
1,770✔
2778
        return t->n_cells / t->n_columns;
1,770✔
2779
}
2780

2781
size_t table_get_columns(Table *t) {
34✔
2782
        if (!t)
34✔
2783
                return 0;
2784

2785
        assert(t->n_columns > 0);
34✔
2786
        return t->n_columns;
2787
}
2788

2789
size_t table_get_current_column(Table *t) {
80✔
2790
        if (!t)
80✔
2791
                return 0;
2792

2793
        assert(t->n_columns > 0);
80✔
2794
        return t->n_cells % t->n_columns;
80✔
2795
}
2796

2797
int table_set_reverse(Table *t, size_t column, bool b) {
81✔
2798
        assert(t);
81✔
2799
        assert(column < t->n_columns);
81✔
2800

2801
        if (!t->reverse_map) {
81✔
2802
                if (!b)
81✔
2803
                        return 0;
2804

2805
                t->reverse_map = new0(bool, t->n_columns);
27✔
2806
                if (!t->reverse_map)
27✔
2807
                        return -ENOMEM;
2808
        }
2809

2810
        t->reverse_map[column] = b;
27✔
2811
        return 0;
27✔
2812
}
2813

2814
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,242✔
2815
        size_t i;
8,242✔
2816

2817
        assert(t);
8,242✔
2818

2819
        if (column >= t->n_columns)
8,242✔
2820
                return NULL;
2821

2822
        i = row * t->n_columns + column;
8,242✔
2823
        if (i >= t->n_cells)
8,242✔
2824
                return NULL;
2825

2826
        return TABLE_INDEX_TO_CELL(i);
8,242✔
2827
}
2828

2829
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2830
        TableData *d;
4,374✔
2831

2832
        assert(t);
4,374✔
2833

2834
        d = table_get_data(t, cell);
4,374✔
2835
        if (!d)
4,374✔
2836
                return NULL;
2837

2838
        return d->data;
4,374✔
2839
}
2840

2841
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2842
        TableCell *cell;
4,374✔
2843

2844
        cell = table_get_cell(t, row, column);
4,374✔
2845
        if (!cell)
4,374✔
2846
                return NULL;
2847

2848
        return table_get(t, cell);
4,374✔
2849
}
2850

2851
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
4,061✔
2852

2853
        switch (d->type) {
4,061✔
2854

2855
        case TABLE_EMPTY:
327✔
2856
                return sd_json_variant_new_null(ret);
327✔
2857

2858
        case TABLE_STRING:
2,060✔
2859
        case TABLE_PATH:
2860
        case TABLE_PATH_BASENAME:
2861
        case TABLE_FIELD:
2862
        case TABLE_HEADER:
2863
        case TABLE_VERSION:
2864
                return sd_json_variant_new_string(ret, d->string);
2,060✔
2865

2866
        case TABLE_STRV:
3✔
2867
        case TABLE_STRV_WRAPPED:
2868
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2869

2870
        case TABLE_BOOLEAN_CHECKMARK:
152✔
2871
        case TABLE_BOOLEAN:
2872
                return sd_json_variant_new_boolean(ret, d->boolean);
152✔
2873

2874
        case TABLE_TIMESTAMP:
155✔
2875
        case TABLE_TIMESTAMP_UTC:
2876
        case TABLE_TIMESTAMP_RELATIVE:
2877
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2878
        case TABLE_TIMESTAMP_LEFT:
2879
        case TABLE_TIMESTAMP_DATE:
2880
                if (d->timestamp == USEC_INFINITY)
155✔
2881
                        return sd_json_variant_new_null(ret);
×
2882

2883
                return sd_json_variant_new_unsigned(ret, d->timestamp);
155✔
2884

2885
        case TABLE_TIMESPAN:
×
2886
        case TABLE_TIMESPAN_MSEC:
2887
        case TABLE_TIMESPAN_DAY:
2888
                if (d->timespan == USEC_INFINITY)
×
2889
                        return sd_json_variant_new_null(ret);
×
2890

2891
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2892

2893
        case TABLE_SIZE:
36✔
2894
        case TABLE_BPS:
2895
                if (d->size == UINT64_MAX)
36✔
2896
                        return sd_json_variant_new_null(ret);
16✔
2897

2898
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2899

2900
        case TABLE_INT:
27✔
2901
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2902

2903
        case TABLE_INT8:
6✔
2904
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2905

2906
        case TABLE_INT16:
6✔
2907
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2908

2909
        case TABLE_INT32:
6✔
2910
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2911

2912
        case TABLE_INT64:
46✔
2913
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2914

2915
        case TABLE_UINT:
43✔
2916
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2917

2918
        case TABLE_UINT8:
4✔
2919
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2920

2921
        case TABLE_UINT16:
4✔
2922
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2923

2924
        case TABLE_UINT32:
111✔
2925
        case TABLE_UINT32_HEX:
2926
        case TABLE_UINT32_HEX_0x:
2927
                return sd_json_variant_new_unsigned(ret, d->uint32);
111✔
2928

2929
        case TABLE_UINT64:
386✔
2930
        case TABLE_UINT64_HEX:
2931
        case TABLE_UINT64_HEX_0x:
2932
                return sd_json_variant_new_unsigned(ret, d->uint64);
386✔
2933

2934
        case TABLE_PERCENT:
×
2935
                return sd_json_variant_new_integer(ret, d->percent);
×
2936

2937
        case TABLE_IFINDEX:
×
2938
                if (d->ifindex <= 0)
×
2939
                        return sd_json_variant_new_null(ret);
×
2940

2941
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2942

2943
        case TABLE_IN_ADDR:
2944
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2945

2946
        case TABLE_IN6_ADDR:
2947
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2948

2949
        case TABLE_ID128:
502✔
2950
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2951

2952
        case TABLE_UUID:
91✔
2953
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2954

2955
        case TABLE_UID:
8✔
2956
                if (!uid_is_valid(d->uid))
8✔
2957
                        return sd_json_variant_new_null(ret);
×
2958

2959
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2960

2961
        case TABLE_GID:
8✔
2962
                if (!gid_is_valid(d->gid))
8✔
2963
                        return sd_json_variant_new_null(ret);
×
2964

2965
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2966

2967
        case TABLE_PID:
14✔
2968
                if (!pid_is_valid(d->pid))
14✔
2969
                        return sd_json_variant_new_null(ret);
×
2970

2971
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2972

2973
        case TABLE_SIGNAL:
8✔
2974
                if (!SIGNAL_VALID(d->int_val))
8✔
2975
                        return sd_json_variant_new_null(ret);
×
2976

2977
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2978

2979
        case TABLE_MODE:
51✔
2980
        case TABLE_MODE_INODE_TYPE:
2981
                if (d->mode == MODE_INVALID)
51✔
2982
                        return sd_json_variant_new_null(ret);
×
2983

2984
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2985

2986
        case TABLE_DEVNUM:
4✔
2987
                if (devnum_is_zero(d->devnum))
4✔
2988
                        return sd_json_variant_new_null(ret);
2✔
2989

2990
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2991
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2992
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2993

2994
        case TABLE_STRING_WITH_ANSI: {
3✔
2995
                _cleanup_free_ char *s = strdup(d->string);
3✔
2996
                if (!s)
3✔
2997
                        return -ENOMEM;
2998

2999
                /* We strip the ANSI data when outputting to JSON */
3000
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
3001
                        return -ENOMEM;
3002

3003
                return sd_json_variant_new_string(ret, s);
3✔
3004
        }
3005

3006
        default:
3007
                return -EINVAL;
3008
        }
3009
}
3010

3011
char* table_mangle_to_json_field_name(const char *str) {
707✔
3012
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
3013
         * field name can be hence this is a bit vague and black magic. Here's what we do:
3014
         *  - Convert spaces to underscores
3015
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
3016
         *  - Convert most other symbols to underscores (for similar reasons)
3017
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
3018
         */
3019

3020
        bool new_word = true;
707✔
3021
        char *c;
707✔
3022

3023
        assert(str);
707✔
3024

3025
        c = strdup(str);
707✔
3026
        if (!c)
707✔
3027
                return NULL;
3028

3029
        for (char *x = c; *x; x++) {
5,298✔
3030
                if (!strchr(ALPHANUMERICAL, *x)) {
4,591✔
3031
                        *x = '_';
179✔
3032
                        new_word = true;
179✔
3033
                        continue;
179✔
3034
                }
3035

3036
                if (new_word) {
4,412✔
3037
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
881✔
3038
                                                                  * then we assume the whole word is all-caps
3039
                                                                  * and avoid lowercasing it. */
3040
                                *x = ascii_tolower(*x);
880✔
3041
                        new_word = false;
3042
                }
3043
        }
3044

3045
        return c;
3046
}
3047

3048
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
693✔
3049
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
693✔
3050
        const char *n;
693✔
3051

3052
        assert(t);
693✔
3053
        assert(d);
693✔
3054
        assert(ret);
693✔
3055

3056
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
693✔
3057
                n = d->string;
693✔
3058
        else {
NEW
3059
                n = table_data_format_strip_ansi(
×
3060
                                t,
3061
                                d,
3062
                                /* avoid_uppercasing= */ true,
3063
                                /* column_width= */ SIZE_MAX,
3064
                                /* have_soft= */ NULL,
3065
                                &buffer);
UNCOV
3066
                if (!n)
×
3067
                        return -ENOMEM;
3068
        }
3069

3070
        mangled = table_mangle_to_json_field_name(n);
693✔
3071
        if (!mangled)
693✔
3072
                return -ENOMEM;
3073

3074
        *ret = TAKE_PTR(mangled);
693✔
3075
        return 0;
693✔
3076
}
3077

3078
static const char *table_get_json_field_name(Table *t, size_t idx) {
759✔
3079
        assert(t);
759✔
3080

3081
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
759✔
3082
}
3083

3084
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
96✔
3085
        sd_json_variant **rows = NULL, **elements = NULL;
96✔
3086
        _cleanup_free_ size_t *sorted = NULL;
96✔
3087
        size_t n_rows, display_columns;
96✔
3088
        int r;
96✔
3089

3090
        assert(t);
96✔
3091
        assert(!t->vertical);
96✔
3092

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

3097
        n_rows = t->n_cells / t->n_columns;
96✔
3098
        assert(n_rows > 0); /* at least the header row must be complete */
96✔
3099

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

3103
                sorted = new(size_t, n_rows);
31✔
3104
                if (!sorted)
31✔
3105
                        return -ENOMEM;
3106

3107
                for (size_t i = 0; i < n_rows; i++)
174✔
3108
                        sorted[i] = i * t->n_columns;
143✔
3109

3110
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
31✔
3111
        }
3112

3113
        if (t->display_map)
96✔
3114
                display_columns = t->n_display_map;
43✔
3115
        else
3116
                display_columns = t->n_columns;
53✔
3117
        assert(display_columns > 0);
96✔
3118

3119
        elements = new0(sd_json_variant*, display_columns * 2);
96✔
3120
        if (!elements)
96✔
3121
                return -ENOMEM;
3122

3123
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
96✔
3124

3125
        for (size_t j = 0; j < display_columns; j++) {
851✔
3126
                _cleanup_free_ char *mangled = NULL;
755✔
3127
                const char *n;
755✔
3128
                size_t c;
755✔
3129

3130
                c = t->display_map ? t->display_map[j] : j;
755✔
3131

3132
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3133
                n = table_get_json_field_name(t, c);
755✔
3134
                if (!n) {
755✔
3135
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
691✔
3136
                        if (r < 0)
691✔
3137
                                return r;
3138

3139
                        n = mangled;
691✔
3140
                }
3141

3142
                r = sd_json_variant_new_string(elements + j*2, n);
755✔
3143
                if (r < 0)
755✔
3144
                        return r;
3145
        }
3146

3147
        rows = new0(sd_json_variant*, n_rows-1);
96✔
3148
        if (!rows)
96✔
3149
                return -ENOMEM;
3150

3151
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
96✔
3152

3153
        for (size_t i = 1; i < n_rows; i++) {
1,042✔
3154
                TableData **row;
946✔
3155

3156
                if (sorted)
946✔
3157
                        row = t->data + sorted[i];
112✔
3158
                else
3159
                        row = t->data + i * t->n_columns;
834✔
3160

3161
                for (size_t j = 0; j < display_columns; j++) {
5,003✔
3162
                        TableData *d;
4,057✔
3163
                        size_t k;
4,057✔
3164

3165
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
4,057✔
3166

3167
                        k = j*2+1;
4,057✔
3168
                        elements[k] = sd_json_variant_unref(elements[k]);
4,057✔
3169

3170
                        r = table_data_to_json(d, elements + k);
4,057✔
3171
                        if (r < 0)
4,057✔
3172
                                return r;
3173
                }
3174

3175
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
946✔
3176
                if (r < 0)
946✔
3177
                        return r;
3178
        }
3179

3180
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
96✔
3181
}
3182

3183
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3184
        sd_json_variant **elements = NULL;
1✔
3185
        size_t n_elements = 0;
1✔
3186
        int r;
1✔
3187

3188
        assert(t);
1✔
3189
        assert(t->vertical);
1✔
3190

3191
        if (t->n_columns != 2)
1✔
3192
                return -EINVAL;
1✔
3193

3194
        /* Ensure we have no incomplete rows */
3195
        assert(t->n_cells % t->n_columns == 0);
1✔
3196

3197
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3198
        if (!elements)
1✔
3199
                return -ENOMEM;
3200

3201
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3202

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

3205
                if (i % t->n_columns == 0) {
8✔
3206
                        _cleanup_free_ char *mangled = NULL;
4✔
3207
                        const char *n;
4✔
3208

3209
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3210
                        if (!n) {
4✔
3211
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3212
                                if (r < 0)
2✔
3213
                                        return r;
×
3214

3215
                                n = mangled;
2✔
3216
                        }
3217

3218
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3219
                } else
3220
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3221
                if (r < 0)
8✔
3222
                        return r;
3223

3224
                n_elements++;
8✔
3225
        }
3226

3227
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3228
}
3229

3230
int table_to_json(Table *t, sd_json_variant **ret) {
97✔
3231
        assert(t);
97✔
3232

3233
        if (t->vertical)
97✔
3234
                return table_to_json_vertical(t, ret);
1✔
3235

3236
        return table_to_json_regular(t, ret);
96✔
3237
}
3238

3239
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
437✔
3240
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
437✔
3241
        int r;
437✔
3242

3243
        assert(t);
437✔
3244

3245
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
437✔
3246
                return table_print(t, f);
368✔
3247

3248
        if (!f)
69✔
3249
                f = stdout;
2✔
3250

3251
        r = table_to_json(t, &v);
69✔
3252
        if (r < 0)
69✔
3253
                return r;
3254

3255
        sd_json_variant_dump(v, flags, f, NULL);
69✔
3256

3257
        return fflush_and_check(f);
69✔
3258
}
3259

3260
int table_print_with_pager(
435✔
3261
                Table *t,
3262
                sd_json_format_flags_t json_format_flags,
3263
                PagerFlags pager_flags,
3264
                bool show_header) {
3265

3266
        bool saved_header;
435✔
3267
        int r;
435✔
3268

3269
        assert(t);
435✔
3270

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

3274
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
435✔
3275
                pager_open(pager_flags);
409✔
3276

3277
        saved_header = t->header;
435✔
3278
        t->header = show_header;
435✔
3279
        r = table_print_json(t, stdout, json_format_flags);
435✔
3280
        t->header = saved_header;
435✔
3281
        if (r < 0)
435✔
3282
                return table_log_print_error(r);
×
3283

3284
        return 0;
3285
}
3286

3287
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
544✔
3288
        int r;
544✔
3289

3290
        assert(t);
544✔
3291

3292
        if (name) {
544✔
3293
                size_t m;
542✔
3294

3295
                m = MAX(idx + 1, t->n_json_fields);
542✔
3296
                if (!GREEDY_REALLOC0(t->json_fields, m))
542✔
3297
                        return -ENOMEM;
3298

3299
                r = free_and_strdup(t->json_fields + idx, name);
542✔
3300
                if (r < 0)
542✔
3301
                        return r;
3302

3303
                t->n_json_fields = m;
542✔
3304
                return r;
542✔
3305
        } else {
3306
                if (idx >= t->n_json_fields)
2✔
3307
                        return 0;
3308

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

© 2026 Coveralls, Inc