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

systemd / systemd / 25834419893

13 May 2026 10:51PM UTC coverage: 72.584% (+0.07%) from 72.513%
25834419893

push

github

bluca
nsresourced: re-link GID delegation file after atomic UID file write

userns_registry_remove() restores a sub-delegated UID range by writing
the previous owner's data to u<UID>.delegate with WRITE_STRING_FILE_ATOMIC.
Atomic writes go via a temp file and rename, which replaces the directory
entry with a fresh inode and severs the hardlink to g<GID>.delegate. The
stale GID side then keeps pointing at the prior inode with outdated owner
and ancestor data, so subsequent lookups via GID return wrong results.

Re-create the hardlink after the atomic write so the two views stay in
sync, matching what userns_registry_store() already does after writing
a new delegation.

5 of 8 new or added lines in 1 file covered. (62.5%)

4652 existing lines in 93 files now uncovered.

328958 of 453210 relevant lines covered (72.58%)

1305869.89 hits per line

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

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

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

6
#include "sd-id128.h"
7

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

35
#define DEFAULT_WEIGHT 100
36

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

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

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

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

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

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

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

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

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

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

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

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

120
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
491,246✔
121
        size_t i;
491,246✔
122

123
        assert(cell);
491,246✔
124

125
        i = PTR_TO_SIZE(cell);
491,246✔
126
        assert(i > 0);
491,246✔
127

128
        return i-1;
491,246✔
129
}
130

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

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

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

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

145
        size_t width;  /* If == 0 format this as wide as necessary. If SIZE_MAX format this to console
146
                        * width or less wide, but not wider. Otherwise the width to format this table in. */
147
        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.) */
148

149
        TableData **data;
150

151
        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 */
152
        size_t n_display_map;
153

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

157
        char **json_fields;
158
        size_t n_json_fields;
159

160
        bool *reverse_map;
161
};
162

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

166
        assert(n_columns > 0);
3,996✔
167

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

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

180
        return TAKE_PTR(t);
3,996✔
181
}
182

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

189
        assert(first_header);
1,004✔
190

191
        va_start(ap, first_header);
1,004✔
192
        for (;;) {
14,154✔
193
                if (!va_arg(ap, const char*))
7,579✔
194
                        break;
195

196
                n_columns++;
6,575✔
197
        }
198
        va_end(ap);
1,004✔
199

200
        t = table_new_raw(n_columns);
1,004✔
201
        if (!t)
1,004✔
202
                return NULL;
203

204
        va_start(ap, first_header);
1,004✔
205
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
8,583✔
206
                TableCell *cell;
7,579✔
207

208
                r = table_add_cell(t, &cell, TABLE_HEADER, h);
7,579✔
209
                if (r < 0) {
7,579✔
210
                        va_end(ap);
×
211
                        return NULL;
×
212
                }
213
        }
214
        va_end(ap);
1,004✔
215

216
        assert(t->n_columns == t->n_cells);
1,004✔
217
        return TAKE_PTR(t);
1,004✔
218
}
219

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

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

228
        t->vertical = true;
2,943✔
229
        t->header = false;
2,943✔
230

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

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

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

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

243
        return TAKE_PTR(t);
2,943✔
244
}
245

246
static TableData* table_data_free(TableData *d) {
223,335✔
247
        assert(d);
223,335✔
248

249
        free(d->formatted);
223,335✔
250
        free(d->url);
223,335✔
251

252
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
223,335✔
253
                strv_free(d->strv);
7,200✔
254

255
        if (d->type == TABLE_JSON)
223,335✔
256
                sd_json_variant_unref(d->json);
10,332✔
257

258
        return mfree(d);
223,335✔
259
}
260

261
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
414,389✔
262
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
252,759✔
263

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

268
        for (size_t i = 0; i < t->n_cells; i++)
256,767✔
269
                table_data_unref(t->data[i]);
252,771✔
270

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

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

279
        free(t->json_fields);
3,996✔
280

281
        return mfree(t);
3,996✔
282
}
283

284
static size_t table_data_size(TableDataType type, const void *data) {
631,601✔
285

286
        switch (type) {
631,601✔
287

288
        case TABLE_EMPTY:
289
                return 0;
290

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

300
        case TABLE_STRV:
15,348✔
301
        case TABLE_STRV_WRAPPED:
302
                return sizeof(char **);
15,348✔
303

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

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

319
        case TABLE_SIZE:
20,166✔
320
        case TABLE_INT64:
321
        case TABLE_UINT64:
322
        case TABLE_UINT64_HEX:
323
        case TABLE_UINT64_HEX_0x:
324
        case TABLE_BPS:
325
                return sizeof(uint64_t);
20,166✔
326

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

333
        case TABLE_INT16:
101✔
334
        case TABLE_UINT16:
335
                return sizeof(uint16_t);
101✔
336

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

341
        case TABLE_INT:
6,277✔
342
        case TABLE_UINT:
343
        case TABLE_PERCENT:
344
        case TABLE_IFINDEX:
345
        case TABLE_SIGNAL:
346
        case TABLE_TRISTATE:
347
                return sizeof(int);
6,277✔
348

349
        case TABLE_IN_ADDR:
289✔
350
                return sizeof(struct in_addr);
289✔
351

352
        case TABLE_IN6_ADDR:
38✔
353
                return sizeof(struct in6_addr);
38✔
354

355
        case TABLE_UUID:
5,995✔
356
        case TABLE_ID128:
357
                return sizeof(sd_id128_t);
5,995✔
358

359
        case TABLE_UID:
1,385✔
360
                return sizeof(uid_t);
1,385✔
361
        case TABLE_GID:
1,905✔
362
                return sizeof(gid_t);
1,905✔
363
        case TABLE_PID:
567✔
364
                return sizeof(pid_t);
567✔
365

366
        case TABLE_MODE:
732✔
367
        case TABLE_MODE_INODE_TYPE:
368
                return sizeof(mode_t);
732✔
369

370
        case TABLE_DEVNUM:
6✔
371
                return sizeof(dev_t);
6✔
372

373
        case TABLE_JSON:
30,964✔
374
                return sizeof(sd_json_variant*);
30,964✔
375

376
        default:
×
377
                assert_not_reached();
×
378
        }
379
}
380

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

392
        size_t k, l;
238,818✔
393
        assert(d);
238,818✔
394

395
        if (d->type != type)
238,818✔
396
                return false;
397

398
        if (d->minimum_width != minimum_width)
209,286✔
399
                return false;
400

401
        if (d->maximum_width != maximum_width)
209,286✔
402
                return false;
403

404
        if (d->weight != weight)
207,929✔
405
                return false;
406

407
        if (d->align_percent != align_percent)
207,929✔
408
                return false;
409

410
        if (d->ellipsize_percent != ellipsize_percent)
207,929✔
411
                return false;
412

413
        if (d->uppercase != uppercase)
207,929✔
414
                return false;
415

416
        /* If a color/url is set, refuse to merge */
417
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
207,928✔
418
                return false;
419
        if (d->url)
205,714✔
420
                return false;
421

422
        k = table_data_size(type, data);
204,133✔
423
        l = table_data_size(d->type, d->data);
204,133✔
424
        if (k != l)
204,133✔
425
                return false;
426

427
        return memcmp_safe(data, d->data, l) == 0;
136,700✔
428
}
429

430
static TableData* table_data_new(
223,335✔
431
                TableDataType type,
432
                const void *data,
433
                size_t minimum_width,
434
                size_t maximum_width,
435
                unsigned weight,
436
                unsigned align_percent,
437
                unsigned ellipsize_percent,
438
                bool uppercase) {
439

440
        _cleanup_free_ TableData *d = NULL;
223,335✔
441
        size_t data_size;
223,335✔
442

443
        data_size = table_data_size(type, data);
223,335✔
444

445
        d = malloc0(offsetof(TableData, data) + data_size);
223,335✔
446
        if (!d)
223,335✔
447
                return NULL;
448

449
        d->n_ref = 1;
223,335✔
450
        d->type = type;
223,335✔
451
        d->minimum_width = minimum_width;
223,335✔
452
        d->maximum_width = maximum_width;
223,335✔
453
        d->weight = weight;
223,335✔
454
        d->align_percent = align_percent;
223,335✔
455
        d->ellipsize_percent = ellipsize_percent;
223,335✔
456
        d->uppercase = uppercase;
223,335✔
457

458
        switch (type) {
223,335✔
459

460
        case TABLE_STRV:
7,200✔
461
        case TABLE_STRV_WRAPPED:
462
                d->strv = strv_copy(data);
7,200✔
463
                if (!d->strv)
7,200✔
464
                        return NULL;
×
465
                break;
466

467
        case TABLE_JSON:
10,332✔
468
                d->json = sd_json_variant_ref((sd_json_variant*) data);
10,332✔
469
                break;
10,332✔
470

471
        default:
205,803✔
472
                memcpy_safe(d->data, data, data_size);
205,803✔
473
        }
474

475
        return TAKE_PTR(d);
476
}
477

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

489
        _cleanup_(table_data_unrefp) TableData *d = NULL;
252,759✔
490
        bool uppercase;
252,759✔
491
        TableData *p;
252,759✔
492

493
        assert(t);
252,759✔
494
        assert(dt >= 0);
252,759✔
495
        assert(dt < _TABLE_DATA_TYPE_MAX);
252,759✔
496

497
        /* Special rule: patch NULL data fields to the empty field */
498
        if (!data)
252,759✔
499
                dt = TABLE_EMPTY;
19,225✔
500

501
        /* Determine the cell adjacent to the current one, but one row up */
502
        if (t->n_cells >= t->n_columns)
252,759✔
503
                assert_se(p = t->data[t->n_cells - t->n_columns]);
238,818✔
504
        else
505
                p = NULL;
506

507
        /* If formatting parameters are left unspecified, copy from the previous row */
508
        if (minimum_width == SIZE_MAX)
252,759✔
509
                minimum_width = p ? p->minimum_width : 1;
252,759✔
510

511
        if (weight == UINT_MAX)
252,759✔
512
                weight = p ? p->weight : DEFAULT_WEIGHT;
252,759✔
513

514
        if (align_percent == UINT_MAX)
252,759✔
515
                align_percent = p ? p->align_percent : 0;
252,759✔
516

517
        if (ellipsize_percent == UINT_MAX)
252,759✔
518
                ellipsize_percent = p ? p->ellipsize_percent : 100;
252,759✔
519

520
        assert(align_percent <= 100);
252,759✔
521
        assert(ellipsize_percent <= 100);
252,759✔
522

523
        uppercase = dt == TABLE_HEADER;
252,759✔
524

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

528
        if (p && table_data_matches(p, dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
252,759✔
529
                d = table_data_ref(p);
95,515✔
530
        else {
531
                d = table_data_new(dt, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
157,244✔
532
                if (!d)
157,244✔
533
                        return -ENOMEM;
534
        }
535

536
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
252,759✔
537
                return -ENOMEM;
538

539
        if (ret_cell)
252,759✔
540
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
227,791✔
541

542
        t->data[t->n_cells++] = TAKE_PTR(d);
252,759✔
543

544
        return 0;
252,759✔
545
}
546

547
int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt, const char *format, ...) {
8,130✔
548
        _cleanup_free_ char *buffer = NULL;
8,130✔
549
        va_list ap;
8,130✔
550
        int r;
8,130✔
551

552
        assert(t);
8,130✔
553
        assert(IN_SET(dt, TABLE_STRING, TABLE_STRING_WITH_ANSI, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
8,130✔
554

555
        va_start(ap, format);
8,130✔
556
        r = vasprintf(&buffer, format, ap);
8,130✔
557
        va_end(ap);
8,130✔
558
        if (r < 0)
8,130✔
559
                return -ENOMEM;
560

561
        return table_add_cell(t, ret_cell, dt, buffer);
8,130✔
562
}
563

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

567
        assert(t);
53✔
568

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

572
        if (until_column >= t->n_columns)
53✔
573
                return -EINVAL;
574

575
        do {
59✔
576
                r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
59✔
577
                if (r < 0)
59✔
578
                        return r;
579

580
        } while ((t->n_cells % t->n_columns) != until_column);
59✔
581

582
        return 0;
583
}
584

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

588
        assert(t);
12✔
589

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

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

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

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

603
static int table_dedup_cell(Table *t, TableCell *cell) {
242,517✔
604
        _cleanup_free_ char *curl = NULL;
242,517✔
605
        TableData *nd, *od;
242,517✔
606
        size_t i;
242,517✔
607

608
        assert(t);
242,517✔
609

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

613
        i = TABLE_CELL_TO_INDEX(cell);
242,517✔
614
        if (i >= t->n_cells)
242,517✔
615
                return -ENXIO;
616

617
        assert_se(od = t->data[i]);
242,517✔
618
        if (od->n_ref == 1)
242,517✔
619
                return 0;
620

621
        assert(od->n_ref > 1);
64,968✔
622

623
        if (od->url) {
64,968✔
624
                curl = strdup(od->url);
×
625
                if (!curl)
×
626
                        return -ENOMEM;
627
        }
628

629
        nd = table_data_new(
129,936✔
630
                        od->type,
631
                        od->data,
64,968✔
632
                        od->minimum_width,
633
                        od->maximum_width,
634
                        od->weight,
635
                        od->align_percent,
636
                        od->ellipsize_percent,
637
                        od->uppercase);
638
        if (!nd)
64,968✔
639
                return -ENOMEM;
640

641
        nd->color = od->color;
64,968✔
642
        nd->rgap_color = od->rgap_color;
64,968✔
643
        nd->underline = od->underline;
64,968✔
644
        nd->rgap_underline = od->rgap_underline;
64,968✔
645
        nd->url = TAKE_PTR(curl);
64,968✔
646

647
        table_data_unref(od);
64,968✔
648
        t->data[i] = nd;
64,968✔
649

650
        assert(nd->n_ref == 1);
64,968✔
651

652
        return 1;
653
}
654

655
static TableData *table_get_data(Table *t, TableCell *cell) {
247,587✔
656
        size_t i;
247,587✔
657

658
        assert(t);
247,587✔
659
        assert(cell);
247,587✔
660

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

663
        i = TABLE_CELL_TO_INDEX(cell);
247,587✔
664
        if (i >= t->n_cells)
247,587✔
665
                return NULL;
666

667
        assert(t->data[i]);
247,587✔
668
        assert(t->data[i]->n_ref > 0);
247,587✔
669

670
        return t->data[i];
671
}
672

673
int table_set_minimum_width(Table *t, TableCell *cell, size_t minimum_width) {
2,135✔
674
        int r;
2,135✔
675

676
        assert(t);
2,135✔
677
        assert(cell);
2,135✔
678

679
        if (minimum_width == SIZE_MAX)
2,135✔
680
                minimum_width = 1;
×
681

682
        r = table_dedup_cell(t, cell);
2,135✔
683
        if (r < 0)
2,135✔
684
                return r;
685

686
        table_get_data(t, cell)->minimum_width = minimum_width;
2,135✔
687
        return 0;
2,135✔
688
}
689

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

693
        assert(t);
1,414✔
694
        assert(cell);
1,414✔
695

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

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

704
int table_set_weight(Table *t, TableCell *cell, unsigned weight) {
15,753✔
705
        int r;
15,753✔
706

707
        assert(t);
15,753✔
708
        assert(cell);
15,753✔
709

710
        if (weight == UINT_MAX)
15,753✔
711
                weight = DEFAULT_WEIGHT;
×
712

713
        r = table_dedup_cell(t, cell);
15,753✔
714
        if (r < 0)
15,753✔
715
                return r;
716

717
        table_get_data(t, cell)->weight = weight;
15,753✔
718
        return 0;
15,753✔
719
}
720

721
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
14,161✔
722
        int r;
14,161✔
723

724
        assert(t);
14,161✔
725
        assert(cell);
14,161✔
726

727
        if (percent == UINT_MAX)
14,161✔
728
                percent = 0;
729

730
        assert(percent <= 100);
14,161✔
731

732
        r = table_dedup_cell(t, cell);
14,161✔
733
        if (r < 0)
14,161✔
734
                return r;
735

736
        table_get_data(t, cell)->align_percent = percent;
14,161✔
737
        return 0;
14,161✔
738
}
739

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

743
        assert(t);
2,960✔
744
        assert(cell);
2,960✔
745

746
        if (percent == UINT_MAX)
2,960✔
747
                percent = 100;
748

749
        assert(percent <= 100);
2,960✔
750

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

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

759
int table_set_color(Table *t, TableCell *cell, const char *color) {
66,410✔
760
        int r;
66,410✔
761

762
        assert(t);
66,410✔
763
        assert(cell);
66,410✔
764

765
        r = table_dedup_cell(t, cell);
66,410✔
766
        if (r < 0)
66,410✔
767
                return r;
768

769
        table_get_data(t, cell)->color = empty_to_null(color);
86,326✔
770
        return 0;
66,410✔
771
}
772

773
int table_set_rgap_color(Table *t, TableCell *cell, const char *color) {
3,318✔
774
        int r;
3,318✔
775

776
        assert(t);
3,318✔
777
        assert(cell);
3,318✔
778

779
        r = table_dedup_cell(t, cell);
3,318✔
780
        if (r < 0)
3,318✔
781
                return r;
782

783
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,636✔
784
        return 0;
3,318✔
785
}
786

787
int table_set_underline(Table *t, TableCell *cell, bool b) {
67,333✔
788
        TableData *d;
67,333✔
789
        int r;
67,333✔
790

791
        assert(t);
67,333✔
792
        assert(cell);
67,333✔
793

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

798
        assert_se(d = table_get_data(t, cell));
67,333✔
799

800
        if (d->underline == b)
67,333✔
801
                return 0;
802

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

807
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
67,333✔
808
        TableData *d;
67,333✔
809
        int r;
67,333✔
810

811
        assert(t);
67,333✔
812
        assert(cell);
67,333✔
813

814
        r = table_dedup_cell(t, cell);
67,333✔
815
        if (r < 0)
67,333✔
816
                return r;
817

818
        assert_se(d = table_get_data(t, cell));
67,333✔
819

820
        if (d->rgap_underline == b)
67,333✔
821
                return 0;
822

823
        d->rgap_underline = b;
322✔
824
        return 1;
322✔
825
}
826

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

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

834
        if (url) {
1,699✔
835
                copy = strdup(url);
1,651✔
836
                if (!copy)
1,651✔
837
                        return -ENOMEM;
838
        }
839

840
        r = table_dedup_cell(t, cell);
1,699✔
841
        if (r < 0)
1,699✔
842
                return r;
843

844
        return free_and_replace(table_get_data(t, cell)->url, copy);
1,699✔
845
}
846

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

851
        assert(t);
1✔
852
        assert(cell);
1✔
853

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

858
        assert_se(d = table_get_data(t, cell));
1✔
859

860
        if (d->uppercase == b)
1✔
861
                return 0;
862

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

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

873
        assert(t);
1,123✔
874
        assert(cell);
1,123✔
875

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

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

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

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

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

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

909
        return 0;
1,123✔
910
}
911

912
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
59,189✔
913
        TableCell *last_cell = NULL;
59,189✔
914
        va_list ap;
59,189✔
915
        int r;
59,189✔
916

917
        assert(t);
59,189✔
918
        assert(first_type >= 0);
59,189✔
919
        assert(first_type < _TABLE_DATA_TYPE_MAX);
59,189✔
920

921
        va_start(ap, first_type);
59,189✔
922

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

951
                switch (type) {
435,294✔
952

953
                case TABLE_EMPTY:
954
                        data = NULL;
955
                        break;
956

957
                case TABLE_STRING:
160,619✔
958
                case TABLE_STRING_WITH_ANSI:
959
                case TABLE_PATH:
960
                case TABLE_PATH_BASENAME:
961
                case TABLE_FIELD:
962
                case TABLE_HEADER:
963
                case TABLE_VERSION:
964
                        data = va_arg(ap, const char *);
160,619✔
965
                        break;
160,619✔
966

967
                case TABLE_STRV:
7,611✔
968
                case TABLE_STRV_WRAPPED:
969
                        data = va_arg(ap, char * const *);
7,611✔
970
                        break;
7,611✔
971

972
                case TABLE_BOOLEAN_CHECKMARK:
6,104✔
973
                case TABLE_BOOLEAN:
974
                        buffer.b = va_arg(ap, int);
6,104✔
975
                        data = &buffer.b;
6,104✔
976
                        break;
6,104✔
977

978
                case TABLE_TRISTATE:
16✔
979
                        buffer.tristate = va_arg(ap, int);
16✔
980
                        data = &buffer.tristate;
16✔
981
                        break;
16✔
982

983
                case TABLE_TIMESTAMP:
3,666✔
984
                case TABLE_TIMESTAMP_UTC:
985
                case TABLE_TIMESTAMP_RELATIVE:
986
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
987
                case TABLE_TIMESTAMP_LEFT:
988
                case TABLE_TIMESTAMP_DATE:
989
                case TABLE_TIMESPAN:
990
                case TABLE_TIMESPAN_MSEC:
991
                case TABLE_TIMESPAN_DAY:
992
                        buffer.usec = va_arg(ap, usec_t);
3,666✔
993
                        data = &buffer.usec;
3,666✔
994
                        break;
3,666✔
995

996
                case TABLE_SIZE:
766✔
997
                case TABLE_BPS:
998
                        buffer.size = va_arg(ap, uint64_t);
766✔
999
                        data = &buffer.size;
766✔
1000
                        break;
766✔
1001

1002
                case TABLE_INT:
1,269✔
1003
                case TABLE_SIGNAL:
1004
                        buffer.int_val = va_arg(ap, int);
1,269✔
1005
                        data = &buffer.int_val;
1,269✔
1006
                        break;
1,269✔
1007

1008
                case TABLE_INT8: {
3✔
1009
                        int x = va_arg(ap, int);
3✔
1010
                        assert(x >= INT8_MIN && x <= INT8_MAX);
3✔
1011

1012
                        buffer.int8 = x;
3✔
1013
                        data = &buffer.int8;
3✔
1014
                        break;
3✔
1015
                }
1016

1017
                case TABLE_INT16: {
3✔
1018
                        int x = va_arg(ap, int);
3✔
1019
                        assert(x >= INT16_MIN && x <= INT16_MAX);
3✔
1020

1021
                        buffer.int16 = x;
3✔
1022
                        data = &buffer.int16;
3✔
1023
                        break;
3✔
1024
                }
1025

1026
                case TABLE_INT32:
3✔
1027
                        buffer.int32 = va_arg(ap, int32_t);
3✔
1028
                        data = &buffer.int32;
3✔
1029
                        break;
3✔
1030

1031
                case TABLE_INT64:
195✔
1032
                        buffer.int64 = va_arg(ap, int64_t);
195✔
1033
                        data = &buffer.int64;
195✔
1034
                        break;
195✔
1035

1036
                case TABLE_UINT:
737✔
1037
                        buffer.uint_val = va_arg(ap, unsigned);
737✔
1038
                        data = &buffer.uint_val;
737✔
1039
                        break;
737✔
1040

1041
                case TABLE_UINT8: {
43✔
1042
                        unsigned x = va_arg(ap, unsigned);
43✔
1043
                        assert(x <= UINT8_MAX);
43✔
1044

1045
                        buffer.uint8 = x;
43✔
1046
                        data = &buffer.uint8;
43✔
1047
                        break;
43✔
1048
                }
1049

1050
                case TABLE_UINT16: {
90✔
1051
                        unsigned x = va_arg(ap, unsigned);
90✔
1052
                        assert(x <= UINT16_MAX);
90✔
1053

1054
                        buffer.uint16 = x;
90✔
1055
                        data = &buffer.uint16;
90✔
1056
                        break;
90✔
1057
                }
1058

1059
                case TABLE_UINT32:
1,983✔
1060
                case TABLE_UINT32_HEX:
1061
                case TABLE_UINT32_HEX_0x:
1062
                        buffer.uint32 = va_arg(ap, uint32_t);
1,983✔
1063
                        data = &buffer.uint32;
1,983✔
1064
                        break;
1,983✔
1065

1066
                case TABLE_UINT64:
6,892✔
1067
                case TABLE_UINT64_HEX:
1068
                case TABLE_UINT64_HEX_0x:
1069
                        buffer.uint64 = va_arg(ap, uint64_t);
6,892✔
1070
                        data = &buffer.uint64;
6,892✔
1071
                        break;
6,892✔
1072

1073
                case TABLE_PERCENT:
2✔
1074
                        buffer.percent = va_arg(ap, int);
2✔
1075
                        data = &buffer.percent;
2✔
1076
                        break;
2✔
1077

1078
                case TABLE_IFINDEX:
66✔
1079
                        buffer.ifindex = va_arg(ap, int);
66✔
1080
                        data = &buffer.ifindex;
66✔
1081
                        break;
66✔
1082

1083
                case TABLE_IN_ADDR:
179✔
1084
                        buffer.address.in = *va_arg(ap, struct in_addr *);
179✔
1085
                        data = &buffer.address.in;
179✔
1086
                        break;
179✔
1087

1088
                case TABLE_IN6_ADDR:
26✔
1089
                        buffer.address.in6 = *va_arg(ap, struct in6_addr *);
26✔
1090
                        data = &buffer.address.in6;
26✔
1091
                        break;
26✔
1092

1093
                case TABLE_UUID:
2,011✔
1094
                case TABLE_ID128:
1095
                        buffer.id128 = va_arg(ap, sd_id128_t);
2,011✔
1096
                        data = &buffer.id128;
2,011✔
1097
                        break;
2,011✔
1098

1099
                case TABLE_UID:
525✔
1100
                        buffer.uid = va_arg(ap, uid_t);
525✔
1101
                        data = &buffer.uid;
525✔
1102
                        break;
525✔
1103

1104
                case TABLE_GID:
679✔
1105
                        buffer.gid = va_arg(ap, gid_t);
679✔
1106
                        data = &buffer.gid;
679✔
1107
                        break;
679✔
1108

1109
                case TABLE_PID:
235✔
1110
                        buffer.pid = va_arg(ap, pid_t);
235✔
1111
                        data = &buffer.pid;
235✔
1112
                        break;
235✔
1113

1114
                case TABLE_MODE:
4✔
1115
                case TABLE_MODE_INODE_TYPE:
1116
                        buffer.mode = va_arg(ap, mode_t);
4✔
1117
                        data = &buffer.mode;
4✔
1118
                        break;
4✔
1119

1120
                case TABLE_DEVNUM:
6✔
1121
                        buffer.devnum = va_arg(ap, dev_t);
6✔
1122
                        data = &buffer.devnum;
6✔
1123
                        break;
6✔
1124

1125
                case TABLE_JSON:
15,568✔
1126
                        data = va_arg(ap, sd_json_variant*);
15,568✔
1127
                        break;
15,568✔
1128

1129
                case TABLE_SET_MINIMUM_WIDTH: {
1,426✔
1130
                        size_t w = va_arg(ap, size_t);
1,426✔
1131

1132
                        r = table_set_minimum_width(t, last_cell, w);
1,426✔
1133
                        goto check;
1,426✔
1134
                }
1135

1136
                case TABLE_SET_MAXIMUM_WIDTH: {
1,412✔
1137
                        size_t w = va_arg(ap, size_t);
1,412✔
1138
                        r = table_set_maximum_width(t, last_cell, w);
1,412✔
1139
                        goto check;
1,412✔
1140
                }
1141

1142
                case TABLE_SET_WEIGHT: {
15,748✔
1143
                        unsigned w = va_arg(ap, unsigned);
15,748✔
1144
                        r = table_set_weight(t, last_cell, w);
15,748✔
1145
                        goto check;
15,748✔
1146
                }
1147

1148
                case TABLE_SET_ALIGN_PERCENT: {
6,334✔
1149
                        unsigned p = va_arg(ap, unsigned);
6,334✔
1150
                        r = table_set_align_percent(t, last_cell, p);
6,334✔
1151
                        goto check;
6,334✔
1152
                }
1153

1154
                case TABLE_SET_ELLIPSIZE_PERCENT: {
1,412✔
1155
                        unsigned p = va_arg(ap, unsigned);
1,412✔
1156
                        r = table_set_ellipsize_percent(t, last_cell, p);
1,412✔
1157
                        goto check;
1,412✔
1158
                }
1159

1160
                case TABLE_SET_COLOR: {
63,092✔
1161
                        const char *c = va_arg(ap, const char*);
63,092✔
1162
                        r = table_set_color(t, last_cell, c);
63,092✔
1163
                        goto check;
63,092✔
1164
                }
1165

1166
                case TABLE_SET_RGAP_COLOR: {
×
1167
                        const char *c = va_arg(ap, const char*);
×
1168
                        r = table_set_rgap_color(t, last_cell, c);
×
1169
                        goto check;
×
1170
                }
1171

1172
                case TABLE_SET_BOTH_COLORS: {
3,318✔
1173
                        const char *c = va_arg(ap, const char*);
3,318✔
1174

1175
                        r = table_set_color(t, last_cell, c);
3,318✔
1176
                        if (r < 0) {
3,318✔
1177
                                va_end(ap);
×
1178
                                return r;
×
1179
                        }
1180

1181
                        r = table_set_rgap_color(t, last_cell, c);
3,318✔
1182
                        goto check;
3,318✔
1183
                }
1184

1185
                case TABLE_SET_UNDERLINE: {
×
1186
                        int u = va_arg(ap, int);
×
1187
                        r = table_set_underline(t, last_cell, u);
×
1188
                        goto check;
×
1189
                }
1190

1191
                case TABLE_SET_RGAP_UNDERLINE: {
×
1192
                        int u = va_arg(ap, int);
×
1193
                        r = table_set_rgap_underline(t, last_cell, u);
×
1194
                        goto check;
×
1195
                }
1196

1197
                case TABLE_SET_BOTH_UNDERLINES: {
67,333✔
1198
                        int u = va_arg(ap, int);
67,333✔
1199

1200
                        r = table_set_underline(t, last_cell, u);
67,333✔
1201
                        if (r < 0) {
67,333✔
1202
                                va_end(ap);
×
1203
                                return r;
×
1204
                        }
1205

1206
                        r = table_set_rgap_underline(t, last_cell, u);
67,333✔
1207
                        goto check;
67,333✔
1208
                }
1209

1210
                case TABLE_SET_URL: {
1,699✔
1211
                        const char *u = va_arg(ap, const char*);
1,699✔
1212
                        r = table_set_url(t, last_cell, u);
1,699✔
1213
                        goto check;
1,699✔
1214
                }
1215

1216
                case TABLE_SET_UPPERCASE: {
1✔
1217
                        int u = va_arg(ap, int);
1✔
1218
                        r = table_set_uppercase(t, last_cell, u);
1✔
1219
                        goto check;
1✔
1220
                }
1221

1222
                case TABLE_SET_JSON_FIELD_NAME: {
4✔
1223
                        const char *n = va_arg(ap, const char*);
4✔
1224
                        size_t idx;
4✔
1225
                        if (t->vertical) {
4✔
1226
                                assert(TABLE_CELL_TO_INDEX(last_cell) >= t->n_columns);
3✔
1227
                                idx = TABLE_CELL_TO_INDEX(last_cell) / t->n_columns - 1;
3✔
1228
                        } else {
1229
                                idx = TABLE_CELL_TO_INDEX(last_cell);
1✔
1230
                                assert(idx < t->n_columns);
1✔
1231
                        }
1232
                        r = table_set_json_field_name(t, idx, n);
4✔
1233
                        goto check;
4✔
1234
                }
1235

1236
                case _TABLE_DATA_TYPE_MAX:
59,189✔
1237
                        /* Used as end marker */
1238
                        va_end(ap);
59,189✔
1239
                        return 0;
59,189✔
1240

1241
                default:
×
1242
                        assert_not_reached();
×
1243
                }
1244

1245
                r = table_add_cell(t, &last_cell, type, data);
214,326✔
1246
        check:
376,105✔
1247
                if (r < 0) {
376,105✔
1248
                        va_end(ap);
×
1249
                        return r;
×
1250
                }
1251
        }
1252
}
1253

1254
void table_set_header(Table *t, bool b) {
323✔
1255
        assert(t);
323✔
1256

1257
        t->header = b;
323✔
1258
}
323✔
1259

1260
void table_set_width(Table *t, size_t width) {
99✔
1261
        assert(t);
99✔
1262

1263
        t->width = width;
99✔
1264
}
99✔
1265

1266
void table_set_cell_height_max(Table *t, size_t height) {
32✔
1267
        assert(t);
32✔
1268
        assert(height >= 1 || height == SIZE_MAX);
32✔
1269

1270
        t->cell_height_max = height;
32✔
1271
}
32✔
1272

1273
void table_set_ersatz_string(Table *t, TableErsatz ersatz) {
781✔
1274
        assert(t);
781✔
1275
        assert(ersatz >= 0 && ersatz < _TABLE_ERSATZ_MAX);
781✔
1276

1277
        t->ersatz = ersatz;
781✔
1278
}
781✔
1279

1280
static const char* table_ersatz_string(const Table *t) {
27,032✔
1281
        switch (t->ersatz) {
27,032✔
1282
        case TABLE_ERSATZ_EMPTY:
1283
                return "";
1284
        case TABLE_ERSATZ_DASH:
22,080✔
1285
                return "-";
22,080✔
1286
        case TABLE_ERSATZ_UNSET:
424✔
1287
                return "(unset)";
424✔
1288
        case TABLE_ERSATZ_NA:
×
1289
                return "n/a";
×
1290
        default:
×
1291
                assert_not_reached();
×
1292
        }
1293
}
1294

1295
static int table_set_display_all(Table *t) {
452✔
1296
        size_t *d;
452✔
1297

1298
        assert(t);
452✔
1299

1300
        /* Initialize the display map to the identity */
1301

1302
        d = reallocarray(t->display_map, t->n_columns, sizeof(size_t));
452✔
1303
        if (!d)
452✔
1304
                return -ENOMEM;
1305

1306
        for (size_t i = 0; i < t->n_columns; i++)
5,939✔
1307
                d[i] = i;
5,487✔
1308

1309
        t->display_map = d;
452✔
1310
        t->n_display_map = t->n_columns;
452✔
1311

1312
        return 0;
452✔
1313
}
1314

1315
int table_set_display_internal(Table *t, size_t first_column, ...) {
31✔
1316
        size_t column;
31✔
1317
        va_list ap;
31✔
1318

1319
        assert(t);
31✔
1320

1321
        column = first_column;
31✔
1322

1323
        va_start(ap, first_column);
31✔
1324
        for (;;) {
159✔
1325
                assert(column < t->n_columns);
159✔
1326

1327
                if (!GREEDY_REALLOC(t->display_map, MAX(t->n_columns, t->n_display_map+1))) {
159✔
1328
                        va_end(ap);
×
1329
                        return -ENOMEM;
×
1330
                }
1331

1332
                t->display_map[t->n_display_map++] = column;
159✔
1333

1334
                column = va_arg(ap, size_t);
159✔
1335
                if (column == SIZE_MAX)
159✔
1336
                        break;
1337

1338
        }
1339
        va_end(ap);
31✔
1340

1341
        return 0;
31✔
1342
}
1343

1344
int table_set_sort_internal(Table *t, size_t first_column, ...) {
295✔
1345
        size_t column;
295✔
1346
        va_list ap;
295✔
1347

1348
        assert(t);
295✔
1349

1350
        column = first_column;
295✔
1351

1352
        va_start(ap, first_column);
295✔
1353
        for (;;) {
405✔
1354
                assert(column < t->n_columns);
405✔
1355

1356
                if (!GREEDY_REALLOC(t->sort_map, MAX(t->n_columns, t->n_sort_map+1))) {
405✔
1357
                        va_end(ap);
×
1358
                        return -ENOMEM;
×
1359
                }
1360

1361
                t->sort_map[t->n_sort_map++] = column;
405✔
1362

1363
                column = va_arg(ap, size_t);
405✔
1364
                if (column == SIZE_MAX)
405✔
1365
                        break;
1366
        }
1367
        va_end(ap);
295✔
1368

1369
        return 0;
295✔
1370
}
1371

1372
int table_hide_column_from_display_internal(Table *t, ...) {
1,170✔
1373
        size_t cur = 0;
1,170✔
1374
        int r;
1,170✔
1375

1376
        assert(t);
1,170✔
1377

1378
        /* If the display map is empty, initialize it with all available columns */
1379
        if (!t->display_map) {
1,170✔
1380
                r = table_set_display_all(t);
452✔
1381
                if (r < 0)
452✔
1382
                        return r;
1383
        }
1384

1385
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
14,791✔
1386
                bool listed = false;
13,621✔
1387
                va_list ap;
13,621✔
1388

1389
                va_start(ap, t);
13,621✔
1390
                for (;;) {
26,840✔
1391
                        size_t column;
26,840✔
1392

1393
                        column = va_arg(ap, size_t);
26,840✔
1394
                        if (column == SIZE_MAX)
26,840✔
1395
                                break;
1396
                        if (column == *i) {
14,470✔
1397
                                listed = true;
1398
                                break;
1399
                        }
1400
                }
1401
                va_end(ap);
13,621✔
1402

1403
                if (listed)
13,621✔
1404
                        continue;
1,251✔
1405

1406
                t->display_map[cur++] = *i;
12,370✔
1407
        }
1408

1409
        t->n_display_map = cur;
1,170✔
1410

1411
        return 0;
1,170✔
1412
}
1413

1414
static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t index_b) {
87,923✔
1415
        int r;
87,923✔
1416

1417
        assert(a);
87,923✔
1418
        assert(b);
87,923✔
1419

1420
        if (a->type == b->type) {
87,923✔
1421

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

1425
                switch (a->type) {
87,917✔
1426

1427
                case TABLE_STRING:
82,586✔
1428
                case TABLE_STRING_WITH_ANSI:
1429
                case TABLE_FIELD:
1430
                case TABLE_HEADER:
1431
                        return strcmp(a->string, b->string);
82,586✔
1432

1433
                case TABLE_PATH:
57✔
1434
                case TABLE_PATH_BASENAME:
1435
                        return path_compare(a->string, b->string);
57✔
1436

1437
                case TABLE_VERSION:
×
1438
                        return strverscmp_improved(a->string, b->string);
×
1439

1440
                case TABLE_STRV:
×
1441
                case TABLE_STRV_WRAPPED:
1442
                        return strv_compare(a->strv, b->strv);
×
1443

1444
                case TABLE_BOOLEAN:
2✔
1445
                case TABLE_BOOLEAN_CHECKMARK:
1446
                        if (!a->boolean && b->boolean)
2✔
1447
                                return -1;
1448
                        if (a->boolean && !b->boolean)
2✔
1449
                                return 1;
1450
                        return 0;
×
1451

1452
                case TABLE_TRISTATE:
4✔
1453
                        /* NB: we do not use CMP() here, since we want to collapse all negative and all
1454
                         * positive into one bucket each. */
1455
                        if ((a->tristate < 0 && b->tristate >= 0) ||
4✔
1456
                            (a->tristate == 0 && b->tristate > 0))
2✔
1457
                                return -1;
1458

1459
                        if ((b->tristate < 0 && a->tristate >= 0) ||
×
1460
                            (b->tristate == 0 && a->tristate > 0))
×
1461
                                return 1;
1462
                        return 0;
×
1463

1464
                case TABLE_TIMESTAMP:
×
1465
                case TABLE_TIMESTAMP_UTC:
1466
                case TABLE_TIMESTAMP_RELATIVE:
1467
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1468
                case TABLE_TIMESTAMP_LEFT:
1469
                case TABLE_TIMESTAMP_DATE:
1470
                        return CMP(a->timestamp, b->timestamp);
×
1471

1472
                case TABLE_TIMESPAN:
893✔
1473
                case TABLE_TIMESPAN_MSEC:
1474
                case TABLE_TIMESPAN_DAY:
1475
                        return CMP(a->timespan, b->timespan);
893✔
1476

1477
                case TABLE_SIZE:
×
1478
                case TABLE_BPS:
1479
                        return CMP(a->size, b->size);
×
1480

1481
                case TABLE_INT:
16✔
1482
                case TABLE_SIGNAL:
1483
                        return CMP(a->int_val, b->int_val);
16✔
1484

1485
                case TABLE_INT8:
×
1486
                        return CMP(a->int8, b->int8);
×
1487

1488
                case TABLE_INT16:
×
1489
                        return CMP(a->int16, b->int16);
×
1490

1491
                case TABLE_INT32:
×
1492
                        return CMP(a->int32, b->int32);
×
1493

1494
                case TABLE_INT64:
188✔
1495
                        return CMP(a->int64, b->int64);
188✔
1496

1497
                case TABLE_UINT:
75✔
1498
                        return CMP(a->uint_val, b->uint_val);
75✔
1499

1500
                case TABLE_UINT8:
×
1501
                        return CMP(a->uint8, b->uint8);
×
1502

1503
                case TABLE_UINT16:
×
1504
                        return CMP(a->uint16, b->uint16);
×
1505

1506
                case TABLE_UINT32:
27✔
1507
                case TABLE_UINT32_HEX:
1508
                case TABLE_UINT32_HEX_0x:
1509
                        return CMP(a->uint32, b->uint32);
27✔
1510

1511
                case TABLE_UINT64:
×
1512
                case TABLE_UINT64_HEX:
1513
                case TABLE_UINT64_HEX_0x:
1514
                        return CMP(a->uint64, b->uint64);
×
1515

1516
                case TABLE_PERCENT:
×
1517
                        return CMP(a->percent, b->percent);
×
1518

1519
                case TABLE_IFINDEX:
×
1520
                        return CMP(a->ifindex, b->ifindex);
×
1521

1522
                case TABLE_IN_ADDR:
×
1523
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1524

1525
                case TABLE_IN6_ADDR:
1526
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1527

1528
                case TABLE_UUID:
×
1529
                case TABLE_ID128:
1530
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1531

1532
                case TABLE_UID:
1,154✔
1533
                        return CMP(a->uid, b->uid);
1,154✔
1534

1535
                case TABLE_GID:
1,443✔
1536
                        return CMP(a->gid, b->gid);
1,443✔
1537

1538
                case TABLE_PID:
×
1539
                        return CMP(a->pid, b->pid);
×
1540

1541
                case TABLE_MODE:
×
1542
                case TABLE_MODE_INODE_TYPE:
1543
                        return CMP(a->mode, b->mode);
×
1544

1545
                case TABLE_DEVNUM:
×
1546
                        r = CMP(major(a->devnum), major(b->devnum));
×
1547
                        if (r != 0)
×
1548
                                return r;
1549

1550
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1551

1552
                case TABLE_JSON:
1,296✔
1553
                        return json_variant_compare(a->json, b->json);
1,296✔
1554

1555
                default:
6✔
1556
                        ;
182✔
1557
                }
1558
        }
1559

1560
        /* Generic fallback using the original order in which the cells where added. */
1561
        return CMP(index_a, index_b);
182✔
1562
}
1563

1564
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
55,236✔
1565
        int r;
55,236✔
1566

1567
        /* This is called from qsort()s inner loops. Correctly implemented qsort will never pass NULL so we
1568
           just suppress the check via POINTER_MAY_BE_NULL instead of assert() to avoid the runtime cost. */
1569
        POINTER_MAY_BE_NULL(a);
55,236✔
1570
        POINTER_MAY_BE_NULL(b);
55,236✔
1571
        assert(t);
55,236✔
1572
        assert(t->sort_map);
55,236✔
1573

1574
        /* Make sure the header stays at the beginning */
1575
        if (*a < t->n_columns && *b < t->n_columns)
55,236✔
1576
                return 0;
1577
        if (*a < t->n_columns)
55,236✔
1578
                return -1;
1579
        if (*b < t->n_columns)
54,593✔
1580
                return 1;
1581

1582
        /* Order other lines by the sorting map */
1583
        for (size_t i = 0; i < t->n_sort_map; i++) {
87,923✔
1584
                TableData *d, *dd;
87,923✔
1585

1586
                d = t->data[*a + t->sort_map[i]];
87,923✔
1587
                dd = t->data[*b + t->sort_map[i]];
87,923✔
1588

1589
                r = cell_data_compare(d, *a, dd, *b);
87,923✔
1590
                if (r != 0)
87,923✔
1591
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
54,593✔
1592
        }
1593

1594
        /* Order identical lines by the order there were originally added in */
UNCOV
1595
        return CMP(*a, *b);
×
1596
}
1597

1598
static char* format_strv_width(char **strv, size_t column_width) {
2,179✔
1599
        _cleanup_(memstream_done) MemStream m = {};
2,179✔
1600
        FILE *f;
2,179✔
1601

1602
        f = memstream_init(&m);
2,179✔
1603
        if (!f)
2,179✔
1604
                return NULL;
1605

1606
        size_t position = 0;
1607
        STRV_FOREACH(p, strv) {
14,325✔
1608
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
12,146✔
1609
                                                          * If that happens, we'll just print one item per line. */
1610

1611
                if (position == 0) {
12,146✔
1612
                        fputs(*p, f);
2,179✔
1613
                        position = our_len;
1614
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
19,934✔
1615
                        fprintf(f, " %s", *p);
9,452✔
1616
                        position = size_add(size_add(position, 1), our_len);
21,598✔
1617
                } else {
1618
                        fprintf(f, "\n%s", *p);
515✔
1619
                        position = our_len;
1620
                }
1621
        }
1622

1623
        char *buf;
2,179✔
1624
        if (memstream_finalize(&m, &buf, NULL) < 0)
2,179✔
1625
                return NULL;
1626

1627
        return buf;
2,179✔
1628
}
1629

1630
static const char* table_data_format(
439,915✔
1631
                Table *t,
1632
                TableData *d,
1633
                bool avoid_uppercasing,
1634
                size_t column_width,
1635
                bool *have_soft) {
1636

1637
        assert(d);
439,915✔
1638

1639
        if (d->formatted &&
439,915✔
1640
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1641
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
79,488✔
1642
                return d->formatted;
1643

1644
        d->formatted = mfree(d->formatted);
361,676✔
1645

1646
        switch (d->type) {
361,676✔
1647
        case TABLE_EMPTY:
26,786✔
1648
                return table_ersatz_string(t);
26,786✔
1649

1650
        case TABLE_STRING:
291,582✔
1651
        case TABLE_STRING_WITH_ANSI:
1652
        case TABLE_PATH:
1653
        case TABLE_PATH_BASENAME:
1654
        case TABLE_FIELD:
1655
        case TABLE_HEADER:
1656
        case TABLE_VERSION: {
1657
                _cleanup_free_ char *bn = NULL;
291,582✔
1658
                const char *s;
291,582✔
1659

1660
                if (d->type == TABLE_PATH_BASENAME)
291,582✔
1661
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
452✔
1662
                else
1663
                        s = d->string;
291,130✔
1664

1665
                if (d->uppercase && !avoid_uppercasing) {
291,582✔
1666
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
4,776✔
1667
                        if (!d->formatted)
4,776✔
1668
                                return NULL;
1669

1670
                        char *q = d->formatted;
1671
                        for (const char *p = s; *p; p++)
33,153✔
1672
                                *(q++) = (char) toupper((unsigned char) *p);
28,377✔
1673

1674
                        if (d->type == TABLE_FIELD)
4,776✔
1675
                                *(q++) = ':';
×
1676

1677
                        *q = 0;
4,776✔
1678
                        return d->formatted;
4,776✔
1679
                }
1680

1681
                if (d->type == TABLE_FIELD)
286,806✔
1682
                        return (d->formatted = strjoin(s, ":"));
33,066✔
1683

1684
                if (bn)
253,740✔
1685
                        return (d->formatted = TAKE_PTR(bn));
450✔
1686

1687
                return d->string;
253,290✔
1688
        }
1689

1690
        case TABLE_STRV:
6,267✔
1691
                if (strv_isempty(d->strv))
6,267✔
1692
                        return table_ersatz_string(t);
×
1693

1694
                return (d->formatted = strv_join(d->strv, "\n"));
6,267✔
1695

1696
        case TABLE_STRV_WRAPPED:
2,179✔
1697
                if (strv_isempty(d->strv))
2,179✔
1698
                        return table_ersatz_string(t);
×
1699

1700
                d->formatted = format_strv_width(d->strv, column_width);
2,179✔
1701
                if (!d->formatted)
2,179✔
1702
                        return NULL;
1703

1704
                d->formatted_for_width = column_width;
2,179✔
1705
                if (have_soft)
2,179✔
1706
                        *have_soft = true;
1,655✔
1707
                return d->formatted;
2,179✔
1708

1709
        case TABLE_BOOLEAN:
4,825✔
1710
                return yes_no(d->boolean);
4,825✔
1711

1712
        case TABLE_BOOLEAN_CHECKMARK:
7,332✔
1713
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
10,556✔
1714

1715
        case TABLE_TRISTATE:
36✔
1716
                if (d->tristate < 0)
36✔
1717
                        return table_ersatz_string(t);
6✔
1718

1719
                return yes_no(d->tristate);
439,939✔
1720

1721
        case TABLE_TIMESTAMP:
2,125✔
1722
        case TABLE_TIMESTAMP_UTC:
1723
        case TABLE_TIMESTAMP_RELATIVE:
1724
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1725
        case TABLE_TIMESTAMP_LEFT:
1726
        case TABLE_TIMESTAMP_DATE: {
1727
                char *ret;
2,125✔
1728

1729
                _cleanup_free_ char *p = new(
4,250✔
1730
                                char,
1731
                                IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1732
                                        FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1733
                if (!p)
2,125✔
1734
                        return NULL;
1735

1736
                if (d->type == TABLE_TIMESTAMP)
2,125✔
1737
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,440✔
1738
                else if (d->type == TABLE_TIMESTAMP_UTC)
685✔
1739
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1740
                else if (d->type == TABLE_TIMESTAMP_DATE)
685✔
1741
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
2✔
1742
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1743
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1744
                else
1745
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1746
                                                             d->timestamp, CLOCK_REALTIME,
1747
                                                             /* implicit_left= */ d->type == TABLE_TIMESTAMP_LEFT);
1748
                if (!ret)
2,125✔
1749
                        return "-";
1750

1751
                return (d->formatted = TAKE_PTR(p));
1,973✔
1752
        }
1753

1754
        case TABLE_TIMESPAN:
1755
        case TABLE_TIMESPAN_MSEC:
1756
        case TABLE_TIMESPAN_DAY: {
1757
                _cleanup_free_ char *p = new(char, FORMAT_TIMESPAN_MAX);
3,050✔
1758
                if (!p)
1,525✔
1759
                        return NULL;
1760

1761
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
3,043✔
1762
                                     d->type == TABLE_TIMESPAN ? 0 :
1,518✔
1763
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1764
                        return "-";
1765

1766
                return (d->formatted = TAKE_PTR(p));
1,525✔
1767
        }
1768

1769
        case TABLE_SIZE: {
1770
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX);
752✔
1771
                if (!p)
376✔
1772
                        return NULL;
1773

1774
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
376✔
1775
                        return table_ersatz_string(t);
238✔
1776

1777
                return (d->formatted = TAKE_PTR(p));
138✔
1778
        }
1779

1780
        case TABLE_BPS: {
1781
                _cleanup_free_ char *p = new(char, FORMAT_BYTES_MAX+2);
824✔
1782
                if (!p)
412✔
1783
                        return NULL;
1784

1785
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
412✔
1786
                        return table_ersatz_string(t);
×
1787

1788
                size_t n = strlen(p);
412✔
1789
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
412✔
1790

1791
                return (d->formatted = TAKE_PTR(p));
412✔
1792
        }
1793

1794
        case TABLE_INT:
260✔
1795
                return (d->formatted = asprintf_safe("%i", d->int_val));
260✔
1796

1797
        case TABLE_INT8:
3✔
1798
                return (d->formatted = asprintf_safe("%" PRIi8, d->int8));
3✔
1799

1800
        case TABLE_INT16:
3✔
1801
                return (d->formatted = asprintf_safe("%" PRIi16, d->int16));
3✔
1802

1803
        case TABLE_INT32:
3✔
1804
                return (d->formatted = asprintf_safe("%" PRIi32, d->int32));
3✔
1805

1806
        case TABLE_INT64:
155✔
1807
                return (d->formatted = asprintf_safe("%" PRIi64, d->int64));
155✔
1808

1809
        case TABLE_UINT:
895✔
1810
                return (d->formatted = asprintf_safe("%u", d->uint_val));
895✔
1811

1812
        case TABLE_UINT8:
43✔
1813
                return (d->formatted = asprintf_safe("%" PRIu8, d->uint8));
43✔
1814

1815
        case TABLE_UINT16:
90✔
1816
                return (d->formatted = asprintf_safe("%" PRIu16, d->uint16));
90✔
1817

1818
        case TABLE_UINT32:
1,204✔
1819
                return (d->formatted = asprintf_safe("%" PRIu32, d->uint32));
1,204✔
1820

1821
        case TABLE_UINT32_HEX:
2✔
1822
                return (d->formatted = asprintf_safe("%" PRIx32, d->uint32));
2✔
1823

1824
        case TABLE_UINT32_HEX_0x:
7✔
1825
                return (d->formatted = asprintf_safe("0x%" PRIx32, d->uint32));
7✔
1826

1827
        case TABLE_UINT64:
1,663✔
1828
                return (d->formatted = asprintf_safe("%" PRIu64, d->uint64));
1,663✔
1829

1830
        case TABLE_UINT64_HEX:
58✔
1831
                return (d->formatted = asprintf_safe("%" PRIx64, d->uint64));
58✔
1832

1833
        case TABLE_UINT64_HEX_0x:
×
1834
                return (d->formatted = asprintf_safe("0x%" PRIx64, d->uint64));
×
1835

1836
        case TABLE_PERCENT:
2✔
1837
                return (d->formatted = asprintf_safe("%i%%" , d->percent));
2✔
1838

1839
        case TABLE_IFINDEX:
227✔
1840
                (void) format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &d->formatted);
227✔
1841
                return d->formatted;
227✔
1842

1843
        case TABLE_IN_ADDR:
205✔
1844
        case TABLE_IN6_ADDR:
1845
                (void) in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
205✔
1846
                                         &d->address,
205✔
1847
                                         &d->formatted);
1848
                return d->formatted;
205✔
1849

1850
        case TABLE_ID128:
1851
                d->formatted = new(char, SD_ID128_STRING_MAX);
1,097✔
1852
                if (!d->formatted)
1,097✔
1853
                        return NULL;
1854

1855
                return sd_id128_to_string(d->id128, d->formatted);
1,097✔
1856

1857
        case TABLE_UUID:
1858
                d->formatted = new(char, SD_ID128_UUID_STRING_MAX);
466✔
1859
                if (!d->formatted)
466✔
1860
                        return NULL;
1861

1862
                return sd_id128_to_uuid_string(d->id128, d->formatted);
466✔
1863

1864
        case TABLE_UID:
431✔
1865
                if (!uid_is_valid(d->uid))
431✔
1866
                        return table_ersatz_string(t);
×
1867

1868
                return (d->formatted = asprintf_safe(UID_FMT, d->uid));
431✔
1869

1870
        case TABLE_GID:
627✔
1871
                if (!gid_is_valid(d->gid))
627✔
1872
                        return table_ersatz_string(t);
×
1873

1874
                return (d->formatted = asprintf_safe(GID_FMT, d->gid));
627✔
1875

1876
        case TABLE_PID:
218✔
1877
                if (!pid_is_valid(d->pid))
218✔
1878
                        return table_ersatz_string(t);
×
1879

1880
                return (d->formatted = asprintf_safe(PID_FMT, d->pid));
218✔
1881

1882
        case TABLE_SIGNAL: {
123✔
1883
                const char *suffix = signal_to_string(d->int_val);
123✔
1884
                if (!suffix)
123✔
1885
                        return table_ersatz_string(t);
×
1886

1887
                return (d->formatted = strjoin("SIG", suffix));
123✔
1888
        }
1889

1890
        case TABLE_MODE:
112✔
1891
                if (d->mode == MODE_INVALID)
112✔
1892
                        return table_ersatz_string(t);
×
1893

1894
                return (d->formatted = asprintf_safe("%04o", d->mode & 07777));
112✔
1895

1896
        case TABLE_MODE_INODE_TYPE:
2✔
1897
                if (d->mode == MODE_INVALID)
2✔
1898
                        return table_ersatz_string(t);
×
1899

1900
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
1901

1902
        case TABLE_DEVNUM:
3✔
1903
                if (devnum_is_zero(d->devnum))
3✔
1904
                        return table_ersatz_string(t);
2✔
1905

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

1908
        case TABLE_JSON:
10,332✔
1909
                if (!d->json)
10,332✔
1910
                        return table_ersatz_string(t);
×
1911

1912
                (void) sd_json_variant_format(d->json, /* flags= */ 0, &d->formatted);
10,332✔
1913
                return d->formatted;
10,332✔
1914

1915
        default:
×
1916
                assert_not_reached();
×
1917
        }
1918
}
1919

1920
static const char* table_data_format_strip_ansi(
439,133✔
1921
                Table *t,
1922
                TableData *d,
1923
                bool avoid_uppercasing,
1924
                size_t column_width,
1925
                bool *have_soft,
1926
                char **ret_buffer) {
1927

1928
        /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
1929

1930
        assert(ret_buffer);
439,133✔
1931

1932
        const char *c;
439,133✔
1933

1934
        c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
439,133✔
1935
        if (!c)
439,133✔
1936
                return NULL;
439,133✔
1937

1938
        if (d->type != TABLE_STRING_WITH_ANSI) {
439,133✔
1939
                /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
1940
                 * original string as-is */
1941
                *ret_buffer = NULL;
439,118✔
1942
                return c;
439,118✔
1943
        }
1944

1945
        _cleanup_free_ char *s = strdup(c);
15✔
1946
        if (!s)
15✔
1947
                return NULL;
1948

1949
        if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
15✔
1950
                return NULL;
1951

1952
        *ret_buffer = TAKE_PTR(s);
15✔
1953
        return *ret_buffer;
15✔
1954
}
1955

1956
static int console_width_height(
217,424✔
1957
                const char *s,
1958
                size_t *ret_width,
1959
                size_t *ret_height) {
1960

1961
        size_t max_width = 0, height = 0;
217,424✔
1962
        const char *p;
217,424✔
1963

1964
        assert(s);
217,424✔
1965

1966
        /* Determine the width and height in console character cells the specified string needs. */
1967

1968
        do {
221,066✔
1969
                size_t k;
221,066✔
1970

1971
                p = strchr(s, '\n');
221,066✔
1972
                if (p) {
221,066✔
1973
                        _cleanup_free_ char *c = NULL;
3,645✔
1974

1975
                        c = strndup(s, p - s);
3,645✔
1976
                        if (!c)
3,645✔
1977
                                return -ENOMEM;
×
1978

1979
                        k = utf8_console_width(c);
3,645✔
1980
                        s = p + 1;
3,645✔
1981
                } else {
1982
                        k = utf8_console_width(s);
217,421✔
1983
                        s = NULL;
217,421✔
1984
                }
1985
                if (k == SIZE_MAX)
221,066✔
1986
                        return -EINVAL;
1987
                if (k > max_width)
221,066✔
1988
                        max_width = k;
215,738✔
1989

1990
                height++;
221,066✔
1991
        } while (!isempty(s));
224,711✔
1992

1993
        if (ret_width)
217,424✔
1994
                *ret_width = max_width;
217,424✔
1995

1996
        if (ret_height)
217,424✔
1997
                *ret_height = height;
217,424✔
1998

1999
        return 0;
2000
}
2001

2002
static int table_data_requested_width_height(
217,424✔
2003
                Table *table,
2004
                TableData *d,
2005
                size_t available_width,
2006
                size_t *ret_width,
2007
                size_t *ret_height,
2008
                bool *have_soft) {
2009

2010
        _cleanup_free_ char *truncated = NULL, *buffer = NULL;
217,424✔
2011
        bool truncation_applied = false;
217,424✔
2012
        size_t width, height;
217,424✔
2013
        bool soft = false;
217,424✔
2014
        const char *t;
217,424✔
2015
        int r;
217,424✔
2016

2017
        t = table_data_format_strip_ansi(
217,424✔
2018
                        table,
2019
                        d,
2020
                        /* avoid_uppercasing= */ false,
2021
                        available_width,
2022
                        &soft,
2023
                        &buffer);
2024
        if (!t)
217,424✔
2025
                return -ENOMEM;
2026

2027
        if (table->cell_height_max != SIZE_MAX) {
217,424✔
2028
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
252✔
2029
                if (r < 0)
252✔
2030
                        return r;
2031
                if (r > 0)
252✔
2032
                        truncation_applied = true;
25✔
2033

2034
                t = truncated;
252✔
2035
        }
2036

2037
        r = console_width_height(t, &width, &height);
217,424✔
2038
        if (r < 0)
217,424✔
2039
                return r;
2040

2041
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
217,424✔
2042
                width = d->maximum_width;
×
2043

2044
        if (width < d->minimum_width)
217,424✔
2045
                width = d->minimum_width;
4,521✔
2046

2047
        if (ret_width)
217,424✔
2048
                *ret_width = width;
217,424✔
2049
        if (ret_height)
217,424✔
2050
                *ret_height = height;
216,728✔
2051
        if (have_soft && soft)
217,424✔
2052
                *have_soft = true;
1,655✔
2053

2054
        return truncation_applied;
217,424✔
2055
}
2056

2057
static char* align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
165,856✔
2058
        size_t w = 0, space, lspace, old_length, clickable_length;
165,856✔
2059
        _cleanup_free_ char *clickable = NULL;
165,856✔
2060
        const char *p;
165,856✔
2061
        char *ret;
165,856✔
2062
        int r;
165,856✔
2063

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

2066
        assert(str);
165,856✔
2067
        assert(percent <= 100);
165,856✔
2068

2069
        old_length = strlen(str);
165,856✔
2070

2071
        if (url) {
165,856✔
2072
                r = terminal_urlify(url, str, &clickable);
1,563✔
2073
                if (r < 0)
1,563✔
2074
                        return NULL;
2075

2076
                clickable_length = strlen(clickable);
1,563✔
2077
        } else
2078
                clickable_length = old_length;
2079

2080
        /* Determine current width on screen */
2081
        p = str;
165,856✔
2082
        while (p < str + old_length) {
2,552,920✔
2083
                char32_t c;
2,387,064✔
2084

2085
                if (utf8_encoded_to_unichar(p, &c) < 0) {
2,387,064✔
2086
                        p++, w++; /* count invalid chars as 1 */
×
2087
                        continue;
×
2088
                }
2089

2090
                p = utf8_next_char(p);
2,387,064✔
2091
                w += unichar_iswide(c) ? 2 : 1;
4,774,116✔
2092
        }
2093

2094
        /* Already wider than the target, if so, don't do anything */
2095
        if (w >= new_length)
165,856✔
2096
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2097

2098
        /* How much spaces shall we add? An how much on the left side? */
2099
        space = new_length - w;
165,856✔
2100
        lspace = space * percent / 100U;
165,856✔
2101

2102
        ret = new(char, space + clickable_length + 1);
165,856✔
2103
        if (!ret)
165,856✔
2104
                return NULL;
2105

2106
        for (size_t i = 0; i < lspace; i++)
800,508✔
2107
                ret[i] = ' ';
634,652✔
2108
        memcpy(ret + lspace, clickable ?: str, clickable_length);
165,856✔
2109
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
4,524,918✔
2110
                ret[i] = ' ';
4,359,062✔
2111

2112
        ret[space + clickable_length] = 0;
165,856✔
2113
        return ret;
165,856✔
2114
}
2115

2116
static bool table_data_isempty(const TableData *d) {
617✔
2117
        assert(d);
617✔
2118

2119
        if (d->type == TABLE_EMPTY)
617✔
2120
                return true;
2121

2122
        /* Let's also consider an empty strv as truly empty. */
2123
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
528✔
2124
                return strv_isempty(d->strv);
×
2125

2126
        if (d->type == TABLE_JSON)
528✔
2127
                return sd_json_variant_is_null(d->json);
×
2128

2129
        /* Note that an empty string we do not consider empty here! */
2130
        return false;
2131
}
2132

2133
static const char* table_data_color(const TableData *d) {
844✔
2134
        assert(d);
844✔
2135

2136
        if (d->color)
844✔
2137
                return d->color;
2138

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

2143
        if (d->type == TABLE_FIELD)
528✔
2144
                return ansi_bright_blue();
×
2145

2146
        return NULL;
2147
}
2148

2149
static const char* table_data_underline(const TableData *d) {
844✔
2150
        assert(d);
844✔
2151

2152
        if (d->underline)
844✔
2153
                return ansi_add_underline_grey();
×
2154

2155
        if (d->type == TABLE_HEADER)
844✔
2156
                return ansi_add_underline();
25✔
2157

2158
        return NULL;
2159
}
2160

2161
static const char* table_data_rgap_underline(const TableData *d) {
222,466✔
2162
        assert(d);
222,466✔
2163

2164
        if (d->rgap_underline)
222,466✔
2165
                return ansi_add_underline_grey();
300✔
2166

2167
        if (d->type == TABLE_HEADER)
222,166✔
2168
                return ansi_add_underline();
4,779✔
2169

2170
        return NULL;
2171
}
2172

2173
int table_data_requested_width(Table *table, size_t column, size_t *ret) {
70✔
2174
        size_t width = 0;
70✔
2175
        int r;
70✔
2176

2177
        assert(table);
70✔
2178
        assert(ret);
70✔
2179

2180
        for (size_t row = 0; row < table_get_rows(table); row++) {
766✔
2181
                TableCell *cell = table_get_cell(table, row, column);
696✔
2182
                if (!cell)
696✔
2183
                        continue;
×
2184

2185
                TableData *data = table_get_data(table, cell);
696✔
2186
                if (!data)
696✔
2187
                        continue;
×
2188

2189
                size_t w;
696✔
2190

2191
                r = table_data_requested_width_height(
696✔
2192
                                table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL);
2193
                if (r < 0)
696✔
2194
                        return r;
×
2195

2196
                width = MAX(width, w);
696✔
2197
        }
2198

2199
        *ret = width;
70✔
2200
        return 0;
70✔
2201
}
2202

2203
int table_set_column_width(Table *t, size_t column, size_t width) {
70✔
2204
        int r = 0;
70✔
2205

2206
        assert(t);
70✔
2207

2208
        for (size_t row = 0; row < table_get_rows(t); row++) {
766✔
2209
                TableCell *cell = table_get_cell(t, row, column);
696✔
2210
                if (!cell)
696✔
2211
                        continue;
×
2212

2213
                RET_GATHER(r, table_set_minimum_width(t, cell, width));
696✔
2214
        }
2215

2216
        return r;
70✔
2217
}
2218

2219
int _table_sync_column_widths(size_t column, Table *a, ...) {
20✔
2220
        size_t max = 0;
20✔
2221
        va_list ap;
20✔
2222
        int r = 0;
20✔
2223

2224
        assert(a);
20✔
2225

2226
        /* Make the specified column have the same width in the tables. */
2227

2228
        va_start(ap, a);
20✔
2229
        for (Table *t = a; t; t = va_arg(ap, Table*)) {
90✔
2230
                size_t w;
70✔
2231

2232
                r = table_data_requested_width(t, column, &w);
70✔
2233
                if (r < 0)
70✔
2234
                        break;
2235

2236
                max = MAX(max, w);
70✔
2237
        }
2238
        va_end(ap);
20✔
2239
        if (r < 0)
20✔
2240
                return log_error_errno(r, "Failed to query table column width: %m");
×
2241

2242
        r = 0;
20✔
2243
        va_start(ap, a);
20✔
2244
        for (Table *t = a; t; t = va_arg(ap, Table*))
90✔
2245
                RET_GATHER(r, table_set_column_width(t, column, max));
70✔
2246
        va_end(ap);
20✔
2247

2248
        return r;
20✔
2249
}
2250

2251
int table_print_full(Table *t, FILE *f, bool flush) {
3,845✔
2252
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
3,845✔
2253
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2254
                *width = NULL;
3,845✔
2255
        _cleanup_free_ size_t *sorted = NULL;
3,845✔
2256
        uint64_t *column_weight, weight_sum;
3,845✔
2257
        int r;
3,845✔
2258

2259
        assert(t);
3,845✔
2260

2261
        if (!f)
3,845✔
2262
                f = stdout;
2,753✔
2263

2264
        /* Ensure we have no incomplete rows */
2265
        assert(t->n_cells % t->n_columns == 0);
3,845✔
2266

2267
        n_rows = t->n_cells / t->n_columns;
3,845✔
2268
        assert(n_rows > 0); /* at least the header row must be complete */
3,845✔
2269

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

2273
                sorted = new(size_t, n_rows);
236✔
2274
                if (!sorted)
236✔
2275
                        return -ENOMEM;
2276

2277
                for (size_t i = 0; i < n_rows; i++)
11,519✔
2278
                        sorted[i] = i * t->n_columns;
11,283✔
2279

2280
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
236✔
2281
        }
2282

2283
        if (t->display_map)
3,845✔
2284
                display_columns = t->n_display_map;
404✔
2285
        else
2286
                display_columns = t->n_columns;
3,441✔
2287

2288
        assert(display_columns > 0);
3,845✔
2289

2290
        minimum_width = newa(size_t, display_columns);
3,845✔
2291
        maximum_width = newa(size_t, display_columns);
3,845✔
2292
        requested_width = newa(size_t, display_columns);
3,845✔
2293
        column_weight = newa0(uint64_t, display_columns);
3,845✔
2294

2295
        for (size_t j = 0; j < display_columns; j++) {
15,332✔
2296
                minimum_width[j] = 1;
11,487✔
2297
                maximum_width[j] = SIZE_MAX;
11,487✔
2298
        }
2299

2300
        for (unsigned pass = 0; pass < 2; pass++) {
3,921✔
2301
                /* First pass: determine column sizes */
2302

2303
                for (size_t j = 0; j < display_columns; j++)
15,562✔
2304
                        requested_width[j] = SIZE_MAX;
11,641✔
2305

2306
                bool any_soft = false;
3,921✔
2307

2308
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
66,631✔
2309
                        TableData **row;
62,710✔
2310

2311
                        /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
2312
                         * hence we don't care for sorted[] during the first pass. */
2313
                        row = t->data + i * t->n_columns;
62,710✔
2314

2315
                        for (size_t j = 0; j < display_columns; j++) {
279,438✔
2316
                                TableData *d;
216,728✔
2317
                                size_t req_width, req_height;
216,728✔
2318

2319
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
216,728✔
2320

2321
                                r = table_data_requested_width_height(t, d,
218,254✔
2322
                                                                      width ? width[j] : SIZE_MAX,
1,526✔
2323
                                                                      &req_width, &req_height, &any_soft);
2324
                                if (r < 0)
216,728✔
2325
                                        return r;
×
2326
                                if (r > 0) { /* Truncated because too many lines? */
216,728✔
2327
                                        _cleanup_free_ char *last = NULL, *buffer = NULL;
25✔
2328
                                        const char *field;
25✔
2329

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

2335
                                        field = table_data_format_strip_ansi(
28✔
2336
                                                        t,
2337
                                                        d,
2338
                                                        /* avoid_uppercasing= */ false,
2339
                                                        width ? width[j] : SIZE_MAX,
3✔
2340
                                                        &any_soft,
2341
                                                        &buffer);
2342
                                        if (!field)
25✔
2343
                                                return -ENOMEM;
2344

2345
                                        assert_se(t->cell_height_max > 0);
25✔
2346
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
25✔
2347
                                        if (r < 0)
25✔
2348
                                                return r;
2349

2350
                                        req_width = MAX(req_width,
25✔
2351
                                                        utf8_console_width(last) +
2352
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2353
                                }
2354

2355
                                /* Determine the biggest width that any cell in this column would like to have */
2356
                                if (requested_width[j] == SIZE_MAX ||
216,728✔
2357
                                    requested_width[j] < req_width)
205,185✔
2358
                                        requested_width[j] = req_width;
25,592✔
2359

2360
                                /* Determine the minimum width any cell in this column needs */
2361
                                if (minimum_width[j] < d->minimum_width)
216,728✔
2362
                                        minimum_width[j] = d->minimum_width;
97✔
2363

2364
                                /* Determine the maximum width any cell in this column needs */
2365
                                if (d->maximum_width != SIZE_MAX &&
216,728✔
2366
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2367
                                     maximum_width[j] > d->maximum_width))
2368
                                        maximum_width[j] = d->maximum_width;
20✔
2369

2370
                                /* Determine the full columns weight */
2371
                                column_weight[j] += d->weight;
216,728✔
2372
                        }
2373
                }
2374

2375
                /* One space between each column */
2376
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,921✔
2377

2378
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2379
                weight_sum = 0;
3,921✔
2380
                for (size_t j = 0; j < display_columns; j++) {
15,562✔
2381
                        weight_sum += column_weight[j];
11,641✔
2382

2383
                        table_minimum_width += minimum_width[j];
11,641✔
2384

2385
                        if (maximum_width[j] == SIZE_MAX)
11,641✔
2386
                                table_maximum_width = SIZE_MAX;
2387
                        else
2388
                                table_maximum_width += maximum_width[j];
20✔
2389

2390
                        table_requested_width += requested_width[j];
11,641✔
2391
                }
2392

2393
                /* Calculate effective table width */
2394
                if (t->width != 0 && t->width != SIZE_MAX)
3,921✔
2395
                        table_effective_width = t->width;
2396
                else if (t->width == 0 ||
3,914✔
2397
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
3,843✔
2398
                        table_effective_width = table_requested_width;
2399
                else
2400
                        table_effective_width = MIN(table_requested_width, columns());
133✔
2401

2402
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,921✔
2403
                        table_effective_width = table_maximum_width;
×
2404

2405
                if (table_effective_width < table_minimum_width)
3,921✔
2406
                        table_effective_width = table_minimum_width;
2✔
2407

2408
                if (!width)
3,921✔
2409
                        width = newa(size_t, display_columns);
3,845✔
2410

2411
                if (table_effective_width >= table_requested_width) {
3,921✔
2412
                        size_t extra;
3,841✔
2413

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

2417
                        extra = table_effective_width - table_requested_width;
3,841✔
2418

2419
                        for (size_t j = 0; j < display_columns; j++) {
15,310✔
2420
                                size_t delta;
11,469✔
2421

2422
                                if (weight_sum == 0)
11,469✔
2423
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
98✔
2424
                                else
2425
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
11,371✔
2426

2427
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
11,469✔
2428
                                        width[j] = maximum_width[j];
2✔
2429

2430
                                if (width[j] < minimum_width[j])
11,469✔
2431
                                        width[j] = minimum_width[j];
×
2432

2433
                                delta = LESS_BY(width[j], requested_width[j]);
11,469✔
2434

2435
                                /* Subtract what we just added from the rest */
2436
                                if (extra > delta)
11,469✔
2437
                                        extra -= delta;
13✔
2438
                                else
2439
                                        extra = 0;
2440

2441
                                assert(weight_sum >= column_weight[j]);
11,469✔
2442
                                weight_sum -= column_weight[j];
11,469✔
2443
                        }
2444

2445
                        break; /* Every column should be happy, no need to repeat calculations. */
3,845✔
2446
                } else {
2447
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2448
                         * with the minimum they need, and then distribute anything left. */
2449
                        bool finalize = false;
80✔
2450
                        size_t extra;
80✔
2451

2452
                        extra = table_effective_width - table_minimum_width;
80✔
2453

2454
                        for (size_t j = 0; j < display_columns; j++)
252✔
2455
                                width[j] = SIZE_MAX;
172✔
2456

2457
                        for (;;) {
237✔
2458
                                bool restart = false;
237✔
2459

2460
                                for (size_t j = 0; j < display_columns; j++) {
591✔
2461
                                        size_t delta, w;
431✔
2462

2463
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2464
                                        if (width[j] != SIZE_MAX)
431✔
2465
                                                continue;
161✔
2466

2467
                                        if (weight_sum == 0)
270✔
2468
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2469
                                        else
2470
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
270✔
2471

2472
                                        if (w >= requested_width[j]) {
270✔
2473
                                                /* Never give more than requested. If we hit a column like this, there's more
2474
                                                 * space to allocate to other columns which means we need to restart the
2475
                                                 * iteration. However, if we hit a column like this, let's assign it the space
2476
                                                 * it wanted for good early. */
2477

2478
                                                w = requested_width[j];
2479
                                                restart = true;
2480

2481
                                        } else if (!finalize)
192✔
2482
                                                continue;
98✔
2483

2484
                                        width[j] = w;
172✔
2485

2486
                                        assert(w >= minimum_width[j]);
172✔
2487
                                        delta = w - minimum_width[j];
172✔
2488

2489
                                        assert(delta <= extra);
172✔
2490
                                        extra -= delta;
172✔
2491

2492
                                        assert(weight_sum >= column_weight[j]);
172✔
2493
                                        weight_sum -= column_weight[j];
172✔
2494

2495
                                        if (restart && !finalize)
172✔
2496
                                                break;
2497
                                }
2498

2499
                                if (finalize)
237✔
2500
                                        break;
2501

2502
                                if (!restart)
157✔
2503
                                        finalize = true;
80✔
2504
                        }
2505

2506
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
80✔
2507
                                        * let's try to reformat them with the new widths. Otherwise, let's
2508
                                        * move on. */
2509
                                break;
2510
                }
2511
        }
2512

2513
        /* Second pass: show output */
2514
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
65,828✔
2515
                size_t n_subline = 0;
61,983✔
2516
                bool more_sublines;
61,983✔
2517
                TableData **row;
61,983✔
2518

2519
                if (sorted)
61,983✔
2520
                        row = t->data + sorted[i];
11,259✔
2521
                else
2522
                        row = t->data + i * t->n_columns;
50,724✔
2523

2524
                do {
65,613✔
2525
                        const char *gap_color = NULL, *gap_underline = NULL;
65,613✔
2526
                        more_sublines = false;
65,613✔
2527

2528
                        for (size_t j = 0; j < display_columns; j++) {
288,079✔
2529
                                _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
222,466✔
2530
                                bool lines_truncated = false;
222,466✔
2531
                                const char *field, *color = NULL, *underline = NULL;
222,466✔
2532
                                TableData *d;
222,466✔
2533
                                size_t l;
222,466✔
2534

2535
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
222,466✔
2536

2537
                                if (colors_enabled())
222,466✔
2538
                                        field = table_data_format(
782✔
2539
                                                        t,
2540
                                                        d,
2541
                                                        /* avoid_uppercasing= */ false,
2542
                                                        width[j],
782✔
2543
                                                        /* have_soft= */ NULL);
2544
                                else
2545
                                        field = table_data_format_strip_ansi(
221,684✔
2546
                                                        t,
2547
                                                        d,
2548
                                                        /* avoid_uppercasing= */ false,
2549
                                                        width[j],
221,684✔
2550
                                                        /* have_soft= */ NULL,
2551
                                                        &stripped_ansi_buffer);
2552
                                if (!field)
222,466✔
2553
                                        return -ENOMEM;
2554

2555
                                r = string_extract_line(field, n_subline, &extracted);
222,466✔
2556
                                if (r < 0)
222,466✔
2557
                                        return r;
2558
                                if (r > 0) {
222,466✔
2559
                                        /* There are more lines to come */
2560
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
3,667✔
2561
                                                more_sublines = true; /* There are more lines to come */
2562
                                        else
2563
                                                lines_truncated = true;
25✔
2564
                                }
2565
                                if (extracted)
222,466✔
2566
                                        field = extracted;
9,018✔
2567

2568
                                l = utf8_console_width(field);
222,466✔
2569
                                if (l > width[j]) {
222,466✔
2570
                                        /* Field is wider than allocated space. Let's ellipsize */
2571

2572
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
35✔
2573
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2574
                                        if (!buffer)
35✔
2575
                                                return -ENOMEM;
2576

2577
                                        field = buffer;
2578
                                } else {
2579
                                        if (lines_truncated) {
222,431✔
2580
                                                _cleanup_free_ char *padded = NULL;
25✔
2581

2582
                                                /* We truncated more lines of this cell, let's add an
2583
                                                 * ellipsis. We first append it, but that might make our
2584
                                                 * string grow above what we have space for, hence ellipsize
2585
                                                 * right after. This will truncate the ellipsis and add a new
2586
                                                 * one. */
2587

2588
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
25✔
2589
                                                if (!padded)
25✔
2590
                                                        return -ENOMEM;
2591

2592
                                                buffer = ellipsize(padded, width[j], 100);
25✔
2593
                                                if (!buffer)
25✔
2594
                                                        return -ENOMEM;
2595

2596
                                                field = buffer;
25✔
2597
                                                l = utf8_console_width(field);
25✔
2598
                                        }
2599

2600
                                        if (l < width[j]) {
222,431✔
2601
                                                _cleanup_free_ char *aligned = NULL;
×
2602
                                                /* Field is shorter than allocated space. Let's align with spaces */
2603

2604
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
165,856✔
2605
                                                if (!aligned)
165,856✔
2606
                                                        return -ENOMEM;
×
2607

2608
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2609
                                                if (j == display_columns - 1 &&
223,625✔
2610
                                                    (!colors_enabled() || !table_data_color(d)) &&
57,831✔
2611
                                                    (!underline_enabled() || !table_data_underline(d)) &&
115,597✔
2612
                                                    (!urlify_enabled() || !d->url))
57,825✔
2613
                                                        delete_trailing_chars(aligned, NULL);
57,766✔
2614

2615
                                                free_and_replace(buffer, aligned);
165,856✔
2616
                                                field = buffer;
165,856✔
2617
                                        }
2618
                                }
2619

2620
                                if (l >= width[j] && d->url) {
222,466✔
2621
                                        _cleanup_free_ char *clickable = NULL;
×
2622

2623
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2624
                                        if (r < 0)
22✔
2625
                                                return r;
×
2626

2627
                                        free_and_replace(buffer, clickable);
22✔
2628
                                        field = buffer;
22✔
2629
                                }
2630

2631
                                if (colors_enabled() && gap_color)
222,466✔
2632
                                        fputs(gap_color, f);
×
2633
                                if (underline_enabled() && gap_underline)
222,466✔
2634
                                        fputs(gap_underline, f);
19✔
2635

2636
                                if (j > 0)
222,466✔
2637
                                        fputc(' ', f); /* column separator left of cell */
156,853✔
2638

2639
                                /* Undo gap color/underline */
2640
                                if ((colors_enabled() && gap_color) ||
444,932✔
2641
                                    (underline_enabled() && gap_underline))
223,248✔
2642
                                        fputs(ANSI_NORMAL, f);
19✔
2643

2644
                                if (colors_enabled()) {
222,466✔
2645
                                        color = table_data_color(d);
782✔
2646
                                        if (color)
782✔
2647
                                                fputs(color, f);
316✔
2648
                                }
2649

2650
                                if (underline_enabled()) {
222,466✔
2651
                                        underline = table_data_underline(d);
782✔
2652
                                        if (underline)
782✔
2653
                                                fputs(underline, f);
22✔
2654
                                }
2655

2656
                                fputs(field, f);
222,466✔
2657

2658
                                /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
2659
                                if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
222,475✔
2660
                                        fputs(ANSI_NORMAL, f);
341✔
2661

2662
                                gap_color = d->rgap_color;
222,466✔
2663
                                gap_underline = table_data_rgap_underline(d);
222,466✔
2664
                        }
2665

2666
                        fputc('\n', f);
65,613✔
2667
                        n_subline++;
65,613✔
2668
                } while (more_sublines);
65,613✔
2669
        }
2670

2671
        if (!flush)
3,845✔
2672
                return 0;
2673

2674
        return fflush_and_check(f);
1,092✔
2675
}
2676

2677
int table_print_or_warn(Table *t) {
2,600✔
2678
        int r;
2,600✔
2679

2680
        r = table_print(t);
2,600✔
2681
        if (r < 0)
2,600✔
2682
                return table_log_print_error(r);
×
2683
        return 0;
2684
}
2685

2686
int table_format(Table *t, char **ret) {
44✔
2687
        _cleanup_(memstream_done) MemStream m = {};
44✔
2688
        FILE *f;
44✔
2689
        int r;
44✔
2690

2691
        assert(t);
44✔
2692
        assert(ret);
44✔
2693

2694
        f = memstream_init(&m);
44✔
2695
        if (!f)
44✔
2696
                return -ENOMEM;
2697

2698
        r = table_print_full(t, f, /* flush= */ true);
44✔
2699
        if (r < 0)
44✔
2700
                return r;
2701

2702
        return memstream_finalize(&m, ret, NULL);
44✔
2703
}
2704

2705
size_t table_get_rows(Table *t) {
3,457✔
2706
        if (!t)
3,457✔
2707
                return 0;
2708

2709
        assert(t->n_columns > 0);
3,457✔
2710
        return t->n_cells / t->n_columns;
3,457✔
2711
}
2712

2713
size_t table_get_columns(Table *t) {
34✔
2714
        if (!t)
34✔
2715
                return 0;
2716

2717
        assert(t->n_columns > 0);
34✔
2718
        return t->n_columns;
2719
}
2720

2721
size_t table_get_current_column(Table *t) {
80✔
2722
        if (!t)
80✔
2723
                return 0;
2724

2725
        assert(t->n_columns > 0);
80✔
2726
        return t->n_cells % t->n_columns;
80✔
2727
}
2728

2729
int table_set_reverse(Table *t, size_t column, bool b) {
87✔
2730
        assert(t);
87✔
2731
        assert(column < t->n_columns);
87✔
2732

2733
        if (!t->reverse_map) {
87✔
2734
                if (!b)
87✔
2735
                        return 0;
2736

2737
                t->reverse_map = new0(bool, t->n_columns);
33✔
2738
                if (!t->reverse_map)
33✔
2739
                        return -ENOMEM;
2740
        }
2741

2742
        t->reverse_map[column] = b;
33✔
2743
        return 0;
33✔
2744
}
2745

2746
TableCell* table_get_cell(Table *t, size_t row, size_t column) {
10,243✔
2747
        size_t i;
10,243✔
2748

2749
        assert(t);
10,243✔
2750

2751
        if (column >= t->n_columns)
10,243✔
2752
                return NULL;
2753

2754
        i = row * t->n_columns + column;
10,243✔
2755
        if (i >= t->n_cells)
10,243✔
2756
                return NULL;
2757

2758
        return TABLE_INDEX_TO_CELL(i);
10,243✔
2759
}
2760

2761
const void* table_get(Table *t, TableCell *cell) {
4,374✔
2762
        TableData *d;
4,374✔
2763

2764
        assert(t);
4,374✔
2765

2766
        d = table_get_data(t, cell);
4,374✔
2767
        if (!d)
4,374✔
2768
                return NULL;
2769

2770
        return d->data;
4,374✔
2771
}
2772

2773
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2774
        TableCell *cell;
4,374✔
2775

2776
        cell = table_get_cell(t, row, column);
4,374✔
2777
        if (!cell)
4,374✔
2778
                return NULL;
2779

2780
        return table_get(t, cell);
4,374✔
2781
}
2782

2783
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
5,603✔
2784

2785
        switch (d->type) {
5,603✔
2786

2787
        case TABLE_EMPTY:
729✔
2788
                return sd_json_variant_new_null(ret);
729✔
2789

2790
        case TABLE_STRING:
2,675✔
2791
        case TABLE_PATH:
2792
        case TABLE_PATH_BASENAME:
2793
        case TABLE_FIELD:
2794
        case TABLE_HEADER:
2795
        case TABLE_VERSION:
2796
                return sd_json_variant_new_string(ret, d->string);
2,675✔
2797

2798
        case TABLE_STRV:
3✔
2799
        case TABLE_STRV_WRAPPED:
2800
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2801

2802
        case TABLE_BOOLEAN_CHECKMARK:
208✔
2803
        case TABLE_BOOLEAN:
2804
                return sd_json_variant_new_boolean(ret, d->boolean);
208✔
2805

2806
        case TABLE_TRISTATE:
7✔
2807
                if (d->tristate < 0)
7✔
2808
                        return sd_json_variant_new_null(ret);
1✔
2809

2810
                return sd_json_variant_new_boolean(ret, d->tristate);
6✔
2811

2812
        case TABLE_TIMESTAMP:
299✔
2813
        case TABLE_TIMESTAMP_UTC:
2814
        case TABLE_TIMESTAMP_RELATIVE:
2815
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2816
        case TABLE_TIMESTAMP_LEFT:
2817
        case TABLE_TIMESTAMP_DATE:
2818
                if (d->timestamp == USEC_INFINITY)
299✔
2819
                        return sd_json_variant_new_null(ret);
×
2820

2821
                return sd_json_variant_new_unsigned(ret, d->timestamp);
299✔
2822

2823
        case TABLE_TIMESPAN:
×
2824
        case TABLE_TIMESPAN_MSEC:
2825
        case TABLE_TIMESPAN_DAY:
2826
                if (d->timespan == USEC_INFINITY)
×
2827
                        return sd_json_variant_new_null(ret);
×
2828

2829
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2830

2831
        case TABLE_SIZE:
40✔
2832
        case TABLE_BPS:
2833
                if (d->size == UINT64_MAX)
40✔
2834
                        return sd_json_variant_new_null(ret);
16✔
2835

2836
                return sd_json_variant_new_unsigned(ret, d->size);
24✔
2837

2838
        case TABLE_INT:
40✔
2839
                return sd_json_variant_new_integer(ret, d->int_val);
40✔
2840

2841
        case TABLE_INT8:
6✔
2842
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2843

2844
        case TABLE_INT16:
6✔
2845
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2846

2847
        case TABLE_INT32:
6✔
2848
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2849

2850
        case TABLE_INT64:
46✔
2851
                return sd_json_variant_new_integer(ret, d->int64);
46✔
2852

2853
        case TABLE_UINT:
43✔
2854
                return sd_json_variant_new_unsigned(ret, d->uint_val);
43✔
2855

2856
        case TABLE_UINT8:
4✔
2857
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2858

2859
        case TABLE_UINT16:
4✔
2860
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2861

2862
        case TABLE_UINT32:
118✔
2863
        case TABLE_UINT32_HEX:
2864
        case TABLE_UINT32_HEX_0x:
2865
                return sd_json_variant_new_unsigned(ret, d->uint32);
118✔
2866

2867
        case TABLE_UINT64:
483✔
2868
        case TABLE_UINT64_HEX:
2869
        case TABLE_UINT64_HEX_0x:
2870
                return sd_json_variant_new_unsigned(ret, d->uint64);
483✔
2871

2872
        case TABLE_PERCENT:
×
2873
                return sd_json_variant_new_integer(ret, d->percent);
×
2874

2875
        case TABLE_IFINDEX:
×
2876
                if (d->ifindex <= 0)
×
2877
                        return sd_json_variant_new_null(ret);
×
2878

2879
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2880

2881
        case TABLE_IN_ADDR:
2882
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2883

2884
        case TABLE_IN6_ADDR:
2885
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2886

2887
        case TABLE_ID128:
502✔
2888
                return sd_json_variant_new_id128(ret, d->id128);
502✔
2889

2890
        case TABLE_UUID:
144✔
2891
                return sd_json_variant_new_uuid(ret, d->id128);
144✔
2892

2893
        case TABLE_UID:
8✔
2894
                if (!uid_is_valid(d->uid))
8✔
2895
                        return sd_json_variant_new_null(ret);
×
2896

2897
                return sd_json_variant_new_integer(ret, d->uid);
8✔
2898

2899
        case TABLE_GID:
8✔
2900
                if (!gid_is_valid(d->gid))
8✔
2901
                        return sd_json_variant_new_null(ret);
×
2902

2903
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2904

2905
        case TABLE_PID:
14✔
2906
                if (!pid_is_valid(d->pid))
14✔
2907
                        return sd_json_variant_new_null(ret);
×
2908

2909
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2910

2911
        case TABLE_SIGNAL:
8✔
2912
                if (!SIGNAL_VALID(d->int_val))
8✔
2913
                        return sd_json_variant_new_null(ret);
×
2914

2915
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2916

2917
        case TABLE_MODE:
195✔
2918
        case TABLE_MODE_INODE_TYPE:
2919
                if (d->mode == MODE_INVALID)
195✔
2920
                        return sd_json_variant_new_null(ret);
×
2921

2922
                return sd_json_variant_new_unsigned(ret, d->mode);
195✔
2923

2924
        case TABLE_DEVNUM:
4✔
2925
                if (devnum_is_zero(d->devnum))
4✔
2926
                        return sd_json_variant_new_null(ret);
2✔
2927

2928
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2929
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2930
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2931

2932
        case TABLE_JSON:
×
2933
                if (!d->json)
×
2934
                        return sd_json_variant_new_null(ret);
×
2935

2936
                if (ret)
×
2937
                        *ret = sd_json_variant_ref(d->json);
×
2938

2939
                return 0;
2940

2941
        case TABLE_STRING_WITH_ANSI: {
3✔
2942
                _cleanup_free_ char *s = strdup(d->string);
3✔
2943
                if (!s)
3✔
2944
                        return -ENOMEM;
2945

2946
                /* We strip the ANSI data when outputting to JSON */
2947
                if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
3✔
2948
                        return -ENOMEM;
2949

2950
                return sd_json_variant_new_string(ret, s);
3✔
2951
        }
2952

2953
        default:
2954
                return -EINVAL;
2955
        }
2956
}
2957

2958
char* table_mangle_to_json_field_name(const char *str) {
985✔
2959
        /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
2960
         * field name can be hence this is a bit vague and black magic. Here's what we do:
2961
         *  - Convert spaces to underscores
2962
         *  - Convert dashes to underscores (some JSON parsers don't like dealing with dashes)
2963
         *  - Convert most other symbols to underscores (for similar reasons)
2964
         *  - Make the first letter of each word lowercase (unless it looks like the whole word is uppercase)
2965
         */
2966

2967
        bool new_word = true;
985✔
2968
        char *c;
985✔
2969

2970
        assert(str);
985✔
2971

2972
        c = strdup(str);
985✔
2973
        if (!c)
985✔
2974
                return NULL;
2975

2976
        for (char *x = c; *x; x++) {
7,041✔
2977
                if (!strchr(ALPHANUMERICAL, *x)) {
6,056✔
2978
                        *x = '_';
191✔
2979
                        new_word = true;
191✔
2980
                        continue;
191✔
2981
                }
2982

2983
                if (new_word) {
5,865✔
2984
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
1,171✔
2985
                                                                  * then we assume the whole word is all-caps
2986
                                                                  * and avoid lowercasing it. */
2987
                                *x = ascii_tolower(*x);
1,170✔
2988
                        new_word = false;
2989
                }
2990
        }
2991

2992
        return c;
2993
}
2994

2995
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
971✔
2996
        _cleanup_free_ char *mangled = NULL, *buffer = NULL;
971✔
2997
        const char *n;
971✔
2998

2999
        assert(t);
971✔
3000
        assert(d);
971✔
3001
        assert(ret);
971✔
3002

3003
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
971✔
3004
                n = d->string;
971✔
3005
        else {
3006
                n = table_data_format_strip_ansi(
×
3007
                                t,
3008
                                d,
3009
                                /* avoid_uppercasing= */ true,
3010
                                /* column_width= */ SIZE_MAX,
3011
                                /* have_soft= */ NULL,
3012
                                &buffer);
3013
                if (!n)
×
3014
                        return -ENOMEM;
3015
        }
3016

3017
        mangled = table_mangle_to_json_field_name(n);
971✔
3018
        if (!mangled)
971✔
3019
                return -ENOMEM;
3020

3021
        *ret = TAKE_PTR(mangled);
971✔
3022
        return 0;
971✔
3023
}
3024

3025
static const char* table_get_json_field_name(Table *t, size_t idx) {
1,073✔
3026
        assert(t);
1,073✔
3027

3028
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
1,073✔
3029
}
3030

3031
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
146✔
3032
        sd_json_variant **rows = NULL, **elements = NULL;
146✔
3033
        _cleanup_free_ size_t *sorted = NULL;
146✔
3034
        size_t n_rows, display_columns;
146✔
3035
        int r;
146✔
3036

3037
        assert(t);
146✔
3038
        assert(!t->vertical);
146✔
3039

3040
        /* Ensure we have no incomplete rows */
3041
        assert(t->n_columns > 0);
146✔
3042
        assert(t->n_cells % t->n_columns == 0);
146✔
3043

3044
        n_rows = t->n_cells / t->n_columns;
146✔
3045
        assert(n_rows > 0); /* at least the header row must be complete */
146✔
3046

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

3050
                sorted = new(size_t, n_rows);
39✔
3051
                if (!sorted)
39✔
3052
                        return -ENOMEM;
3053

3054
                for (size_t i = 0; i < n_rows; i++)
233✔
3055
                        sorted[i] = i * t->n_columns;
194✔
3056

3057
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
39✔
3058
        }
3059

3060
        if (t->display_map)
146✔
3061
                display_columns = t->n_display_map;
79✔
3062
        else
3063
                display_columns = t->n_columns;
67✔
3064
        assert(display_columns > 0);
146✔
3065

3066
        elements = new0(sd_json_variant*, display_columns * 2);
146✔
3067
        if (!elements)
146✔
3068
                return -ENOMEM;
3069

3070
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
146✔
3071

3072
        for (size_t j = 0; j < display_columns; j++) {
1,215✔
3073
                _cleanup_free_ char *mangled = NULL;
1,069✔
3074
                const char *n;
1,069✔
3075
                size_t c;
1,069✔
3076

3077
                c = t->display_map ? t->display_map[j] : j;
1,069✔
3078

3079
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3080
                n = table_get_json_field_name(t, c);
1,069✔
3081
                if (!n) {
1,069✔
3082
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
969✔
3083
                        if (r < 0)
969✔
3084
                                return r;
3085

3086
                        n = mangled;
969✔
3087
                }
3088

3089
                r = sd_json_variant_new_string(elements + j*2, n);
1,069✔
3090
                if (r < 0)
1,069✔
3091
                        return r;
3092
        }
3093

3094
        rows = new0(sd_json_variant*, n_rows-1);
146✔
3095
        if (!rows)
146✔
3096
                return -ENOMEM;
3097

3098
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
146✔
3099

3100
        for (size_t i = 1; i < n_rows; i++) {
1,343✔
3101
                TableData **row;
1,197✔
3102

3103
                if (sorted)
1,197✔
3104
                        row = t->data + sorted[i];
155✔
3105
                else
3106
                        row = t->data + i * t->n_columns;
1,042✔
3107

3108
                for (size_t j = 0; j < display_columns; j++) {
6,796✔
3109
                        TableData *d;
5,599✔
3110
                        size_t k;
5,599✔
3111

3112
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
5,599✔
3113

3114
                        k = j*2+1;
5,599✔
3115
                        elements[k] = sd_json_variant_unref(elements[k]);
5,599✔
3116

3117
                        r = table_data_to_json(d, elements + k);
5,599✔
3118
                        if (r < 0)
5,599✔
3119
                                return r;
3120
                }
3121

3122
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
1,197✔
3123
                if (r < 0)
1,197✔
3124
                        return r;
3125
        }
3126

3127
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
146✔
3128
}
3129

3130
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3131
        sd_json_variant **elements = NULL;
1✔
3132
        size_t n_elements = 0;
1✔
3133
        int r;
1✔
3134

3135
        assert(t);
1✔
3136
        assert(t->vertical);
1✔
3137

3138
        if (t->n_columns != 2)
1✔
3139
                return -EINVAL;
1✔
3140

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

3144
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3145
        if (!elements)
1✔
3146
                return -ENOMEM;
3147

3148
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3149

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

3152
                if (i % t->n_columns == 0) {
8✔
3153
                        _cleanup_free_ char *mangled = NULL;
4✔
3154
                        const char *n;
4✔
3155

3156
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3157
                        if (!n) {
4✔
3158
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3159
                                if (r < 0)
2✔
3160
                                        return r;
×
3161

3162
                                n = mangled;
2✔
3163
                        }
3164

3165
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3166
                } else
3167
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3168
                if (r < 0)
8✔
3169
                        return r;
3170

3171
                n_elements++;
8✔
3172
        }
3173

3174
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3175
}
3176

3177
int table_to_json(Table *t, sd_json_variant **ret) {
147✔
3178
        assert(t);
147✔
3179

3180
        if (t->vertical)
147✔
3181
                return table_to_json_vertical(t, ret);
1✔
3182

3183
        return table_to_json_regular(t, ret);
146✔
3184
}
3185

3186
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
607✔
3187
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
607✔
3188
        int r;
607✔
3189

3190
        assert(t);
607✔
3191

3192
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
607✔
3193
                return table_print_full(t, f, /* flush= */ true);
530✔
3194

3195
        if (!f)
77✔
3196
                f = stdout;
2✔
3197

3198
        r = table_to_json(t, &v);
77✔
3199
        if (r < 0)
77✔
3200
                return r;
3201

3202
        sd_json_variant_dump(v, flags, f, NULL);
77✔
3203

3204
        return fflush_and_check(f);
77✔
3205
}
3206

3207
int table_print_with_pager(
605✔
3208
                Table *t,
3209
                sd_json_format_flags_t json_format_flags,
3210
                PagerFlags pager_flags,
3211
                bool show_header) {
3212

3213
        bool saved_header;
605✔
3214
        int r;
605✔
3215

3216
        assert(t);
605✔
3217

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

3221
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
605✔
3222
                pager_open(pager_flags);
576✔
3223

3224
        saved_header = t->header;
605✔
3225
        t->header = show_header;
605✔
3226
        r = table_print_json(t, stdout, json_format_flags);
605✔
3227
        t->header = saved_header;
605✔
3228
        if (r < 0)
605✔
3229
                return table_log_print_error(r);
×
3230

3231
        return 0;
3232
}
3233

3234
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
744✔
3235
        int r;
744✔
3236

3237
        assert(t);
744✔
3238

3239
        if (name) {
744✔
3240
                size_t m;
742✔
3241

3242
                m = MAX(idx + 1, t->n_json_fields);
742✔
3243
                if (!GREEDY_REALLOC0(t->json_fields, m))
742✔
3244
                        return -ENOMEM;
3245

3246
                r = free_and_strdup(t->json_fields + idx, name);
742✔
3247
                if (r < 0)
742✔
3248
                        return r;
3249

3250
                t->n_json_fields = m;
742✔
3251
                return r;
742✔
3252
        } else {
3253
                if (idx >= t->n_json_fields)
2✔
3254
                        return 0;
3255

3256
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3257
                return 1;
2✔
3258
        }
3259
}
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