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

systemd / systemd / 14630481637

23 Apr 2025 07:04PM UTC coverage: 72.178% (-0.002%) from 72.18%
14630481637

push

github

DaanDeMeyer
mkosi: Run clangd within the tools tree instead of the build container

Running within the build sandbox has a number of disadvantages:
- We have a separate clangd cache for each distribution/release combo
- It requires to build the full image before clangd can be used
- It breaks every time the image becomes out of date and requires a
  rebuild
- We can't look at system headers as we don't have the knowledge to map
  them from inside the build sandbox to the corresponding path on the host

Instead, let's have mkosi.clangd run clangd within the tools tree. We
already require building systemd for both the host and the target anyway,
and all the dependencies to build systemd are installed in the tools tree
already for that, as well as clangd since it's installed together with the
other clang tooling we install in the tools tree. Unlike the previous approach,
this approach only requires the mkosi tools tree to be built upfront, which has
a much higher chance of not invalidating its cache. We can also trivially map
system header lookups from within the sandbox to the path within mkosi.tools
on the host so that starts working as well.

297054 of 411557 relevant lines covered (72.18%)

686269.58 hits per line

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

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

3
#include <ctype.h>
4
#include <net/if.h>
5
#include <unistd.h>
6

7
#include "sd-id128.h"
8

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

39
#define DEFAULT_WEIGHT 100
40

41
/*
42
   A few notes on implementation details:
43

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

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

52
 - The Table object stores a simple one-dimensional array of references to TableData objects, one row after the
53
   previous one.
54

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

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

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

71
typedef struct TableData {
72
        unsigned n_ref;
73
        TableDataType type;
74

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

82
        bool uppercase:1;           /* Uppercase string on display */
83
        bool underline:1;
84
        bool rgap_underline:1;
85

86
        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 */
87
        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 */
88
        char *url;                  /* A URL to use for a clickable hyperlink */
89
        char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
90

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

122
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
278,991✔
123
        size_t i;
278,991✔
124

125
        assert(cell);
278,991✔
126

127
        i = PTR_TO_SIZE(cell);
278,991✔
128
        assert(i > 0);
278,991✔
129

130
        return i-1;
278,991✔
131
}
132

133
static TableCell* TABLE_INDEX_TO_CELL(size_t index) {
145,869✔
134
        assert(index != SIZE_MAX);
145,869✔
135
        return SIZE_TO_PTR(index + 1);
145,869✔
136
}
137

138
struct Table {
139
        size_t n_columns;
140
        size_t n_cells;
141

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

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

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

151
        TableData **data;
152

153
        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 */
154
        size_t n_display_map;
155

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

159
        char **json_fields;
160
        size_t n_json_fields;
161

162
        bool *reverse_map;
163
};
164

165
Table *table_new_raw(size_t n_columns) {
2,770✔
166
        _cleanup_(table_unrefp) Table *t = NULL;
2,770✔
167

168
        assert(n_columns > 0);
2,770✔
169

170
        t = new(Table, 1);
2,770✔
171
        if (!t)
2,770✔
172
                return NULL;
173

174
        *t = (Table) {
2,770✔
175
                .n_columns = n_columns,
176
                .header = true,
177
                .width = SIZE_MAX,
178
                .cell_height_max = SIZE_MAX,
179
                .ersatz = TABLE_ERSATZ_EMPTY,
180
        };
181

182
        return TAKE_PTR(t);
2,770✔
183
}
184

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

191
        assert(first_header);
605✔
192

193
        va_start(ap, first_header);
605✔
194
        for (;;) {
8,811✔
195
                if (!va_arg(ap, const char*))
4,708✔
196
                        break;
197

198
                n_columns++;
4,103✔
199
        }
200
        va_end(ap);
605✔
201

202
        t = table_new_raw(n_columns);
605✔
203
        if (!t)
605✔
204
                return NULL;
205

206
        va_start(ap, first_header);
605✔
207
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,313✔
208
                TableCell *cell;
4,708✔
209

210
                r = table_add_cell(t, &cell, TABLE_HEADER, h);
4,708✔
211
                if (r < 0) {
4,708✔
212
                        va_end(ap);
×
213
                        return NULL;
×
214
                }
215
        }
216
        va_end(ap);
605✔
217

218
        assert(t->n_columns == t->n_cells);
605✔
219
        return TAKE_PTR(t);
605✔
220
}
221

222
Table *table_new_vertical(void) {
2,122✔
223
        _cleanup_(table_unrefp) Table *t = NULL;
2,122✔
224
        TableCell *cell;
2,122✔
225

226
        t = table_new_raw(2);
2,122✔
227
        if (!t)
2,122✔
228
                return NULL;
229

230
        t->vertical = true;
2,122✔
231
        t->header = false;
2,122✔
232

233
        if (table_add_cell(t, &cell, TABLE_HEADER, "key") < 0)
2,122✔
234
                return NULL;
235

236
        if (table_set_align_percent(t, cell, 100) < 0)
2,122✔
237
                return NULL;
238

239
        if (table_add_cell(t, &cell, TABLE_HEADER, "value") < 0)
2,122✔
240
                return NULL;
241

242
        if (table_set_align_percent(t, cell, 0) < 0)
2,122✔
243
                return NULL;
244

245
        return TAKE_PTR(t);
2,122✔
246
}
247

248
static TableData *table_data_free(TableData *d) {
139,643✔
249
        assert(d);
139,643✔
250

251
        free(d->formatted);
139,643✔
252
        free(d->url);
139,643✔
253

254
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
139,643✔
255
                strv_free(d->strv);
5,564✔
256

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

260
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
237,333✔
261
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
153,118✔
262

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

267
        for (size_t i = 0; i < t->n_cells; i++)
155,900✔
268
                table_data_unref(t->data[i]);
153,130✔
269

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

275
        for (size_t i = 0; i < t->n_json_fields; i++)
5,077✔
276
                free(t->json_fields[i]);
2,307✔
277

278
        free(t->json_fields);
2,770✔
279

280
        return mfree(t);
2,770✔
281
}
282

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

285
        switch (type) {
375,191✔
286

287
        case TABLE_EMPTY:
288
                return 0;
289

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

297
        case TABLE_STRV:
11,396✔
298
        case TABLE_STRV_WRAPPED:
299
                return sizeof(char **);
11,396✔
300

301
        case TABLE_BOOLEAN_CHECKMARK:
9,123✔
302
        case TABLE_BOOLEAN:
303
                return sizeof(bool);
9,123✔
304

305
        case TABLE_TIMESTAMP:
3,830✔
306
        case TABLE_TIMESTAMP_UTC:
307
        case TABLE_TIMESTAMP_RELATIVE:
308
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
309
        case TABLE_TIMESTAMP_LEFT:
310
        case TABLE_TIMESTAMP_DATE:
311
        case TABLE_TIMESPAN:
312
        case TABLE_TIMESPAN_MSEC:
313
        case TABLE_TIMESPAN_DAY:
314
                return sizeof(usec_t);
3,830✔
315

316
        case TABLE_SIZE:
18,279✔
317
        case TABLE_INT64:
318
        case TABLE_UINT64:
319
        case TABLE_UINT64_HEX:
320
        case TABLE_BPS:
321
                return sizeof(uint64_t);
18,279✔
322

323
        case TABLE_INT32:
4,382✔
324
        case TABLE_UINT32:
325
        case TABLE_UINT32_HEX:
326
                return sizeof(uint32_t);
4,382✔
327

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

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

336
        case TABLE_INT:
3,674✔
337
        case TABLE_UINT:
338
        case TABLE_PERCENT:
339
        case TABLE_IFINDEX:
340
        case TABLE_SIGNAL:
341
                return sizeof(int);
3,674✔
342

343
        case TABLE_IN_ADDR:
211✔
344
                return sizeof(struct in_addr);
211✔
345

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

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

353
        case TABLE_UID:
1,243✔
354
                return sizeof(uid_t);
1,243✔
355
        case TABLE_GID:
1,551✔
356
                return sizeof(gid_t);
1,551✔
357
        case TABLE_PID:
592✔
358
                return sizeof(pid_t);
592✔
359

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

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

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

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

383
        size_t k, l;
143,744✔
384
        assert(d);
143,744✔
385

386
        if (d->type != type)
143,744✔
387
                return false;
388

389
        if (d->minimum_width != minimum_width)
122,683✔
390
                return false;
391

392
        if (d->maximum_width != maximum_width)
122,683✔
393
                return false;
394

395
        if (d->weight != weight)
121,326✔
396
                return false;
397

398
        if (d->align_percent != align_percent)
121,326✔
399
                return false;
400

401
        if (d->ellipsize_percent != ellipsize_percent)
121,326✔
402
                return false;
403

404
        if (d->uppercase != uppercase)
121,326✔
405
                return false;
406

407
        /* If a color/url is set, refuse to merge */
408
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
121,325✔
409
                return false;
410
        if (d->url)
119,358✔
411
                return false;
412

413
        k = table_data_size(type, data);
117,774✔
414
        l = table_data_size(d->type, d->data);
117,774✔
415
        if (k != l)
117,774✔
416
                return false;
417

418
        return memcmp_safe(data, d->data, l) == 0;
69,718✔
419
}
420

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

431
        _cleanup_free_ TableData *d = NULL;
139,643✔
432
        size_t data_size;
139,643✔
433

434
        data_size = table_data_size(type, data);
139,643✔
435

436
        d = malloc0(offsetof(TableData, data) + data_size);
139,643✔
437
        if (!d)
139,643✔
438
                return NULL;
439

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

449
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
139,643✔
450
                d->strv = strv_copy(data);
5,564✔
451
                if (!d->strv)
5,564✔
452
                        return NULL;
×
453
        } else
454
                memcpy_safe(d->data, data, data_size);
134,079✔
455

456
        return TAKE_PTR(d);
457
}
458

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

470
        _cleanup_(table_data_unrefp) TableData *d = NULL;
153,118✔
471
        bool uppercase;
153,118✔
472
        TableData *p;
153,118✔
473

474
        assert(t);
153,118✔
475
        assert(type >= 0);
153,118✔
476
        assert(type < _TABLE_DATA_TYPE_MAX);
153,118✔
477

478
        /* Special rule: patch NULL data fields to the empty field */
479
        if (!data)
153,118✔
480
                type = TABLE_EMPTY;
8,720✔
481

482
        /* Determine the cell adjacent to the current one, but one row up */
483
        if (t->n_cells >= t->n_columns)
153,118✔
484
                assert_se(p = t->data[t->n_cells - t->n_columns]);
143,744✔
485
        else
486
                p = NULL;
487

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

492
        if (weight == UINT_MAX)
153,118✔
493
                weight = p ? p->weight : DEFAULT_WEIGHT;
153,118✔
494

495
        if (align_percent == UINT_MAX)
153,118✔
496
                align_percent = p ? p->align_percent : 0;
153,118✔
497

498
        if (ellipsize_percent == UINT_MAX)
153,118✔
499
                ellipsize_percent = p ? p->ellipsize_percent : 100;
153,118✔
500

501
        assert(align_percent <= 100);
153,118✔
502
        assert(ellipsize_percent <= 100);
153,118✔
503

504
        uppercase = type == TABLE_HEADER;
153,118✔
505

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

509
        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
153,118✔
510
                d = table_data_ref(p);
48,833✔
511
        else {
512
                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
104,285✔
513
                if (!d)
104,285✔
514
                        return -ENOMEM;
515
        }
516

517
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
153,118✔
518
                return -ENOMEM;
519

520
        if (ret_cell)
153,118✔
521
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
137,857✔
522

523
        t->data[t->n_cells++] = TAKE_PTR(d);
153,118✔
524

525
        return 0;
153,118✔
526
}
527

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

533
        assert(t);
5,875✔
534
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER));
5,875✔
535

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

542
        return table_add_cell(t, ret_cell, dt, buffer);
5,875✔
543
}
544

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

548
        assert(t);
54✔
549

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

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

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

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

563
        return 0;
564
}
565

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

569
        assert(t);
12✔
570

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

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

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

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

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

589
        assert(t);
136,740✔
590

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

594
        i = TABLE_CELL_TO_INDEX(cell);
136,740✔
595
        if (i >= t->n_cells)
136,740✔
596
                return -ENXIO;
597

598
        assert_se(od = t->data[i]);
136,740✔
599
        if (od->n_ref == 1)
136,740✔
600
                return 0;
601

602
        assert(od->n_ref > 1);
34,235✔
603

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

610
        nd = table_data_new(
68,470✔
611
                        od->type,
612
                        od->data,
34,235✔
613
                        od->minimum_width,
614
                        od->maximum_width,
615
                        od->weight,
616
                        od->align_percent,
617
                        od->ellipsize_percent,
618
                        od->uppercase);
34,235✔
619
        if (!nd)
34,235✔
620
                return -ENOMEM;
621

622
        nd->color = od->color;
34,235✔
623
        nd->rgap_color = od->rgap_color;
34,235✔
624
        nd->underline = od->underline;
34,235✔
625
        nd->rgap_underline = od->rgap_underline;
34,235✔
626
        nd->url = TAKE_PTR(curl);
34,235✔
627

628
        table_data_unref(od);
34,235✔
629
        t->data[i] = nd;
34,235✔
630

631
        assert(nd->n_ref == 1);
34,235✔
632

633
        return 1;
634
}
635

636
static TableData *table_get_data(Table *t, TableCell *cell) {
141,114✔
637
        size_t i;
141,114✔
638

639
        assert(t);
141,114✔
640
        assert(cell);
141,114✔
641

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

644
        i = TABLE_CELL_TO_INDEX(cell);
141,114✔
645
        if (i >= t->n_cells)
141,114✔
646
                return NULL;
647

648
        assert(t->data[i]);
141,114✔
649
        assert(t->data[i]->n_ref > 0);
141,114✔
650

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

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

657
        assert(t);
1,434✔
658
        assert(cell);
1,434✔
659

660
        if (minimum_width == SIZE_MAX)
1,434✔
661
                minimum_width = 1;
×
662

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

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

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

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

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

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

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

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

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

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

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

702
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
11,656✔
703
        int r;
11,656✔
704

705
        assert(t);
11,656✔
706
        assert(cell);
11,656✔
707

708
        if (percent == UINT_MAX)
11,656✔
709
                percent = 0;
710

711
        assert(percent <= 100);
11,656✔
712

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

717
        table_get_data(t, cell)->align_percent = percent;
11,656✔
718
        return 0;
11,656✔
719
}
720

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

724
        assert(t);
2,946✔
725
        assert(cell);
2,946✔
726

727
        if (percent == UINT_MAX)
2,946✔
728
                percent = 100;
729

730
        assert(percent <= 100);
2,946✔
731

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

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

740
int table_set_color(Table *t, TableCell *cell, const char *color) {
40,960✔
741
        int r;
40,960✔
742

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

746
        r = table_dedup_cell(t, cell);
40,960✔
747
        if (r < 0)
40,960✔
748
                return r;
749

750
        table_get_data(t, cell)->color = empty_to_null(color);
54,594✔
751
        return 0;
40,960✔
752
}
753

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

757
        assert(t);
3,204✔
758
        assert(cell);
3,204✔
759

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

764
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,408✔
765
        return 0;
3,204✔
766
}
767

768
int table_set_underline(Table *t, TableCell *cell, bool b) {
36,736✔
769
        TableData *d;
36,736✔
770
        int r;
36,736✔
771

772
        assert(t);
36,736✔
773
        assert(cell);
36,736✔
774

775
        r = table_dedup_cell(t, cell);
36,736✔
776
        if (r < 0)
36,736✔
777
                return r;
778

779
        assert_se(d = table_get_data(t, cell));
36,736✔
780

781
        if (d->underline == b)
36,736✔
782
                return 0;
783

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

788
int table_set_rgap_underline(Table *t, TableCell *cell, bool b) {
36,736✔
789
        TableData *d;
36,736✔
790
        int r;
36,736✔
791

792
        assert(t);
36,736✔
793
        assert(cell);
36,736✔
794

795
        r = table_dedup_cell(t, cell);
36,736✔
796
        if (r < 0)
36,736✔
797
                return r;
798

799
        assert_se(d = table_get_data(t, cell));
36,736✔
800

801
        if (d->rgap_underline == b)
36,736✔
802
                return 0;
803

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

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

812
        assert(t);
1,648✔
813
        assert(cell);
1,648✔
814

815
        if (url) {
1,648✔
816
                copy = strdup(url);
1,636✔
817
                if (!copy)
1,636✔
818
                        return -ENOMEM;
819
        }
820

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

890
        return 0;
1,123✔
891
}
892

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

898
        assert(t);
37,743✔
899
        assert(first_type >= 0);
37,743✔
900
        assert(first_type < _TABLE_DATA_TYPE_MAX);
37,743✔
901

902
        va_start(ap, first_type);
37,743✔
903

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

931
                switch (type) {
256,551✔
932

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

937
                case TABLE_STRING:
97,855✔
938
                case TABLE_PATH:
939
                case TABLE_PATH_BASENAME:
940
                case TABLE_FIELD:
941
                case TABLE_HEADER:
942
                        data = va_arg(ap, const char *);
97,855✔
943
                        break;
97,855✔
944

945
                case TABLE_STRV:
5,598✔
946
                case TABLE_STRV_WRAPPED:
947
                        data = va_arg(ap, char * const *);
5,598✔
948
                        break;
5,598✔
949

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1164
                case TABLE_SET_BOTH_UNDERLINES: {
36,736✔
1165
                        int u = va_arg(ap, int);
36,736✔
1166

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

1173
                        r = table_set_rgap_underline(t, last_cell, u);
36,736✔
1174
                        goto check;
36,736✔
1175
                }
1176

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

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

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

1203
                case _TABLE_DATA_TYPE_MAX:
37,743✔
1204
                        /* Used as end marker */
1205
                        va_end(ap);
37,743✔
1206
                        return 0;
37,743✔
1207

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

1212
                r = table_add_cell(t, &last_cell, type, data);
128,905✔
1213
        check:
218,808✔
1214
                if (r < 0) {
218,808✔
1215
                        va_end(ap);
×
1216
                        return r;
×
1217
                }
1218
        }
1219
}
1220

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

1224
        t->header = b;
178✔
1225
}
178✔
1226

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

1230
        t->width = width;
98✔
1231
}
98✔
1232

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

1237
        t->cell_height_max = height;
27✔
1238
}
27✔
1239

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

1244
        t->ersatz = ersatz;
604✔
1245
}
604✔
1246

1247
static const char* table_ersatz_string(const Table *t) {
12,254✔
1248
        switch (t->ersatz) {
12,254✔
1249
        case TABLE_ERSATZ_EMPTY:
1250
                return "";
1251
        case TABLE_ERSATZ_DASH:
8,950✔
1252
                return "-";
8,950✔
1253
        case TABLE_ERSATZ_UNSET:
414✔
1254
                return "(unset)";
414✔
1255
        case TABLE_ERSATZ_NA:
×
1256
                return "n/a";
×
1257
        default:
×
1258
                assert_not_reached();
×
1259
        }
1260
}
1261

1262
static int table_set_display_all(Table *t) {
290✔
1263
        size_t *d;
290✔
1264

1265
        assert(t);
290✔
1266

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

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

1273
        for (size_t i = 0; i < t->n_columns; i++)
3,682✔
1274
                d[i] = i;
3,392✔
1275

1276
        t->display_map = d;
290✔
1277
        t->n_display_map = t->n_columns;
290✔
1278

1279
        return 0;
290✔
1280
}
1281

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

1286
        assert(t);
31✔
1287

1288
        column = first_column;
31✔
1289

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

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

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

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

1305
        }
1306
        va_end(ap);
31✔
1307

1308
        return 0;
31✔
1309
}
1310

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

1315
        assert(t);
218✔
1316

1317
        column = first_column;
218✔
1318

1319
        va_start(ap, first_column);
218✔
1320
        for (;;) {
288✔
1321
                assert(column < t->n_columns);
288✔
1322

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

1328
                t->sort_map[t->n_sort_map++] = column;
288✔
1329

1330
                column = va_arg(ap, size_t);
288✔
1331
                if (column == SIZE_MAX)
288✔
1332
                        break;
1333
        }
1334
        va_end(ap);
218✔
1335

1336
        return 0;
218✔
1337
}
1338

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

1343
        assert(t);
578✔
1344

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

1352
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
7,714✔
1353
                bool listed = false;
7,136✔
1354
                va_list ap;
7,136✔
1355

1356
                va_start(ap, t);
7,136✔
1357
                for (;;) {
13,816✔
1358
                        size_t column;
13,816✔
1359

1360
                        column = va_arg(ap, size_t);
13,816✔
1361
                        if (column == SIZE_MAX)
13,816✔
1362
                                break;
1363
                        if (column == *i) {
7,271✔
1364
                                listed = true;
1365
                                break;
1366
                        }
1367
                }
1368
                va_end(ap);
7,136✔
1369

1370
                if (listed)
7,136✔
1371
                        continue;
591✔
1372

1373
                t->display_map[cur++] = *i;
6,545✔
1374
        }
1375

1376
        t->n_display_map = cur;
578✔
1377

1378
        return 0;
578✔
1379
}
1380

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

1384
        assert(a);
11,283✔
1385
        assert(b);
11,283✔
1386

1387
        if (a->type == b->type) {
11,283✔
1388

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

1392
                switch (a->type) {
11,277✔
1393

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

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

1403
                case TABLE_STRV:
×
1404
                case TABLE_STRV_WRAPPED:
1405
                        return strv_compare(a->strv, b->strv);
×
1406

1407
                case TABLE_BOOLEAN:
2✔
1408
                        if (!a->boolean && b->boolean)
2✔
1409
                                return -1;
1410
                        if (a->boolean && !b->boolean)
2✔
1411
                                return 1;
1412
                        return 0;
×
1413

1414
                case TABLE_TIMESTAMP:
×
1415
                case TABLE_TIMESTAMP_UTC:
1416
                case TABLE_TIMESTAMP_RELATIVE:
1417
                case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1418
                case TABLE_TIMESTAMP_LEFT:
1419
                case TABLE_TIMESTAMP_DATE:
1420
                        return CMP(a->timestamp, b->timestamp);
×
1421

1422
                case TABLE_TIMESPAN:
648✔
1423
                case TABLE_TIMESPAN_MSEC:
1424
                case TABLE_TIMESPAN_DAY:
1425
                        return CMP(a->timespan, b->timespan);
648✔
1426

1427
                case TABLE_SIZE:
×
1428
                case TABLE_BPS:
1429
                        return CMP(a->size, b->size);
×
1430

1431
                case TABLE_INT:
13✔
1432
                case TABLE_SIGNAL:
1433
                        return CMP(a->int_val, b->int_val);
13✔
1434

1435
                case TABLE_INT8:
×
1436
                        return CMP(a->int8, b->int8);
×
1437

1438
                case TABLE_INT16:
×
1439
                        return CMP(a->int16, b->int16);
×
1440

1441
                case TABLE_INT32:
×
1442
                        return CMP(a->int32, b->int32);
×
1443

1444
                case TABLE_INT64:
188✔
1445
                        return CMP(a->int64, b->int64);
188✔
1446

1447
                case TABLE_UINT:
12✔
1448
                        return CMP(a->uint_val, b->uint_val);
12✔
1449

1450
                case TABLE_UINT8:
×
1451
                        return CMP(a->uint8, b->uint8);
×
1452

1453
                case TABLE_UINT16:
×
1454
                        return CMP(a->uint16, b->uint16);
×
1455

1456
                case TABLE_UINT32:
27✔
1457
                case TABLE_UINT32_HEX:
1458
                        return CMP(a->uint32, b->uint32);
27✔
1459

1460
                case TABLE_UINT64:
×
1461
                case TABLE_UINT64_HEX:
1462
                        return CMP(a->uint64, b->uint64);
×
1463

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

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

1470
                case TABLE_IN_ADDR:
×
1471
                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
×
1472

1473
                case TABLE_IN6_ADDR:
1474
                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
×
1475

1476
                case TABLE_UUID:
×
1477
                case TABLE_ID128:
1478
                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
×
1479

1480
                case TABLE_UID:
970✔
1481
                        return CMP(a->uid, b->uid);
970✔
1482

1483
                case TABLE_GID:
1,035✔
1484
                        return CMP(a->gid, b->gid);
1,035✔
1485

1486
                case TABLE_PID:
×
1487
                        return CMP(a->pid, b->pid);
×
1488

1489
                case TABLE_MODE:
×
1490
                case TABLE_MODE_INODE_TYPE:
1491
                        return CMP(a->mode, b->mode);
×
1492

1493
                case TABLE_DEVNUM:
×
1494
                        r = CMP(major(a->devnum), major(b->devnum));
×
1495
                        if (r != 0)
×
1496
                                return r;
×
1497

1498
                        return CMP(minor(a->devnum), minor(b->devnum));
×
1499

1500
                default:
6✔
1501
                        ;
6✔
1502
                }
1503
        }
1504

1505
        /* Generic fallback using the original order in which the cells where added. */
1506
        return CMP(index_a, index_b);
6✔
1507
}
1508

1509
static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
11,485✔
1510
        int r;
11,485✔
1511

1512
        assert(t);
11,485✔
1513
        assert(t->sort_map);
11,485✔
1514

1515
        /* Make sure the header stays at the beginning */
1516
        if (*a < t->n_columns && *b < t->n_columns)
11,485✔
1517
                return 0;
1518
        if (*a < t->n_columns)
11,485✔
1519
                return -1;
1520
        if (*b < t->n_columns)
11,018✔
1521
                return 1;
1522

1523
        /* Order other lines by the sorting map */
1524
        for (size_t i = 0; i < t->n_sort_map; i++) {
11,288✔
1525
                TableData *d, *dd;
11,283✔
1526

1527
                d = t->data[*a + t->sort_map[i]];
11,283✔
1528
                dd = t->data[*b + t->sort_map[i]];
11,283✔
1529

1530
                r = cell_data_compare(d, *a, dd, *b);
11,283✔
1531
                if (r != 0)
11,283✔
1532
                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
11,013✔
1533
        }
1534

1535
        /* Order identical lines by the order there were originally added in */
1536
        return CMP(*a, *b);
5✔
1537
}
1538

1539
static char* format_strv_width(char **strv, size_t column_width) {
116✔
1540
        _cleanup_(memstream_done) MemStream m = {};
116✔
1541
        FILE *f;
116✔
1542

1543
        f = memstream_init(&m);
116✔
1544
        if (!f)
116✔
1545
                return NULL;
1546

1547
        size_t position = 0;
1548
        STRV_FOREACH(p, strv) {
476✔
1549
                size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
360✔
1550
                                                          * If that happens, we'll just print one item per line. */
1551

1552
                if (position == 0) {
360✔
1553
                        fputs(*p, f);
116✔
1554
                        position = our_len;
1555
                } else if (size_add(size_add(position, 1), our_len) <= column_width) {
488✔
1556
                        fprintf(f, " %s", *p);
210✔
1557
                        position = size_add(size_add(position, 1), our_len);
570✔
1558
                } else {
1559
                        fprintf(f, "\n%s", *p);
34✔
1560
                        position = our_len;
1561
                }
1562
        }
1563

1564
        char *buf;
116✔
1565
        if (memstream_finalize(&m, &buf, NULL) < 0)
116✔
1566
                return NULL;
1567

1568
        return buf;
116✔
1569
}
1570

1571
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
262,523✔
1572
        assert(d);
262,523✔
1573

1574
        if (d->formatted &&
262,523✔
1575
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1576
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
51,851✔
1577
                return d->formatted;
1578

1579
        switch (d->type) {
210,767✔
1580
        case TABLE_EMPTY:
12,112✔
1581
                return table_ersatz_string(t);
12,112✔
1582

1583
        case TABLE_STRING:
173,953✔
1584
        case TABLE_PATH:
1585
        case TABLE_PATH_BASENAME:
1586
        case TABLE_FIELD:
1587
        case TABLE_HEADER: {
1588
                _cleanup_free_ char *bn = NULL;
173,953✔
1589
                const char *s;
173,953✔
1590

1591
                if (d->type == TABLE_PATH_BASENAME)
173,953✔
1592
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
326✔
1593
                else
1594
                        s = d->string;
173,627✔
1595

1596
                if (d->uppercase && !avoid_uppercasing) {
173,953✔
1597
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,228✔
1598
                        if (!d->formatted)
3,228✔
1599
                                return NULL;
1600

1601
                        char *q = d->formatted;
1602
                        for (const char *p = s; *p; p++)
22,765✔
1603
                                *(q++) = (char) toupper((unsigned char) *p);
19,537✔
1604

1605
                        if (d->type == TABLE_FIELD)
3,228✔
1606
                                *(q++) = ':';
×
1607

1608
                        *q = 0;
3,228✔
1609
                        return d->formatted;
3,228✔
1610
                } else if (d->type == TABLE_FIELD) {
170,725✔
1611
                        d->formatted = strjoin(s, ":");
25,525✔
1612
                        if (!d->formatted)
25,525✔
1613
                                return NULL;
1614

1615
                        return d->formatted;
25,525✔
1616
                }
1617

1618
                if (bn) {
145,200✔
1619
                        d->formatted = TAKE_PTR(bn);
324✔
1620
                        return d->formatted;
324✔
1621
                }
1622

1623
                return d->string;
144,876✔
1624
        }
1625

1626
        case TABLE_STRV:
5,276✔
1627
                if (strv_isempty(d->strv))
5,276✔
1628
                        return table_ersatz_string(t);
×
1629

1630
                d->formatted = strv_join(d->strv, "\n");
5,276✔
1631
                if (!d->formatted)
5,276✔
1632
                        return NULL;
1633
                break;
1634

1635
        case TABLE_STRV_WRAPPED: {
116✔
1636
                if (strv_isempty(d->strv))
116✔
1637
                        return table_ersatz_string(t);
×
1638

1639
                char *buf = format_strv_width(d->strv, column_width);
116✔
1640
                if (!buf)
116✔
1641
                        return NULL;
1642

1643
                free_and_replace(d->formatted, buf);
116✔
1644
                d->formatted_for_width = column_width;
116✔
1645
                if (have_soft)
116✔
1646
                        *have_soft = true;
72✔
1647

1648
                break;
13,824✔
1649
        }
1650

1651
        case TABLE_BOOLEAN:
3,806✔
1652
                return yes_no(d->boolean);
264,024✔
1653

1654
        case TABLE_BOOLEAN_CHECKMARK:
6,852✔
1655
                return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
9,596✔
1656

1657
        case TABLE_TIMESTAMP:
1,888✔
1658
        case TABLE_TIMESTAMP_UTC:
1659
        case TABLE_TIMESTAMP_RELATIVE:
1660
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
1661
        case TABLE_TIMESTAMP_LEFT:
1662
        case TABLE_TIMESTAMP_DATE: {
1663
                _cleanup_free_ char *p = NULL;
76✔
1664
                char *ret;
1,888✔
1665

1666
                p = new(char,
1,888✔
1667
                        IN_SET(d->type, TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, TABLE_TIMESTAMP_LEFT) ?
1668
                                FORMAT_TIMESTAMP_RELATIVE_MAX : FORMAT_TIMESTAMP_MAX);
1669
                if (!p)
1,888✔
1670
                        return NULL;
1671

1672
                if (d->type == TABLE_TIMESTAMP)
1,888✔
1673
                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
1,204✔
1674
                else if (d->type == TABLE_TIMESTAMP_UTC)
684✔
1675
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_UTC);
×
1676
                else if (d->type == TABLE_TIMESTAMP_DATE)
684✔
1677
                        ret = format_timestamp_style(p, FORMAT_TIMESTAMP_MAX, d->timestamp, TIMESTAMP_DATE);
1✔
1678
                else if (d->type == TABLE_TIMESTAMP_RELATIVE_MONOTONIC)
683✔
1679
                        ret = format_timestamp_relative_monotonic(p, FORMAT_TIMESTAMP_RELATIVE_MAX, d->timestamp);
6✔
1680
                else
1681
                        ret = format_timestamp_relative_full(p, FORMAT_TIMESTAMP_RELATIVE_MAX,
677✔
1682
                                                             d->timestamp, CLOCK_REALTIME,
1683
                                                             /* implicit_left = */ d->type == TABLE_TIMESTAMP_LEFT);
1684
                if (!ret)
1,888✔
1685
                        return "-";
1686

1687
                d->formatted = TAKE_PTR(p);
1,812✔
1688
                break;
1,812✔
1689
        }
1690

1691
        case TABLE_TIMESPAN:
329✔
1692
        case TABLE_TIMESPAN_MSEC:
1693
        case TABLE_TIMESPAN_DAY: {
1694
                _cleanup_free_ char *p = NULL;
×
1695

1696
                p = new(char, FORMAT_TIMESPAN_MAX);
329✔
1697
                if (!p)
329✔
1698
                        return NULL;
1699

1700
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
651✔
1701
                                     d->type == TABLE_TIMESPAN ? 0 :
1702
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
322✔
1703
                        return "-";
1704

1705
                d->formatted = TAKE_PTR(p);
329✔
1706
                break;
329✔
1707
        }
1708

1709
        case TABLE_SIZE: {
252✔
1710
                _cleanup_free_ char *p = NULL;
140✔
1711

1712
                p = new(char, FORMAT_BYTES_MAX);
252✔
1713
                if (!p)
252✔
1714
                        return NULL;
1715

1716
                if (!format_bytes(p, FORMAT_BYTES_MAX, d->size))
252✔
1717
                        return table_ersatz_string(t);
140✔
1718

1719
                d->formatted = TAKE_PTR(p);
112✔
1720
                break;
112✔
1721
        }
1722

1723
        case TABLE_BPS: {
360✔
1724
                _cleanup_free_ char *p = NULL;
×
1725
                size_t n;
360✔
1726

1727
                p = new(char, FORMAT_BYTES_MAX+2);
360✔
1728
                if (!p)
360✔
1729
                        return NULL;
1730

1731
                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT))
360✔
1732
                        return table_ersatz_string(t);
×
1733

1734
                n = strlen(p);
360✔
1735
                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
360✔
1736

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

1741
        case TABLE_INT: {
259✔
1742
                _cleanup_free_ char *p = NULL;
×
1743

1744
                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
734✔
1745
                if (!p)
259✔
1746
                        return NULL;
×
1747

1748
                sprintf(p, "%i", d->int_val);
259✔
1749
                d->formatted = TAKE_PTR(p);
259✔
1750
                break;
259✔
1751
        }
1752

1753
        case TABLE_INT8: {
3✔
1754
                _cleanup_free_ char *p = NULL;
×
1755

1756
                p = new(char, DECIMAL_STR_WIDTH(d->int8) + 1);
10✔
1757
                if (!p)
3✔
1758
                        return NULL;
×
1759

1760
                sprintf(p, "%" PRIi8, d->int8);
3✔
1761
                d->formatted = TAKE_PTR(p);
3✔
1762
                break;
3✔
1763
        }
1764

1765
        case TABLE_INT16: {
3✔
1766
                _cleanup_free_ char *p = NULL;
×
1767

1768
                p = new(char, DECIMAL_STR_WIDTH(d->int16) + 1);
14✔
1769
                if (!p)
3✔
1770
                        return NULL;
×
1771

1772
                sprintf(p, "%" PRIi16, d->int16);
3✔
1773
                d->formatted = TAKE_PTR(p);
3✔
1774
                break;
3✔
1775
        }
1776

1777
        case TABLE_INT32: {
3✔
1778
                _cleanup_free_ char *p = NULL;
×
1779

1780
                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
24✔
1781
                if (!p)
3✔
1782
                        return NULL;
×
1783

1784
                sprintf(p, "%" PRIi32, d->int32);
3✔
1785
                d->formatted = TAKE_PTR(p);
3✔
1786
                break;
3✔
1787
        }
1788

1789
        case TABLE_INT64: {
155✔
1790
                _cleanup_free_ char *p = NULL;
×
1791

1792
                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
319✔
1793
                if (!p)
155✔
1794
                        return NULL;
×
1795

1796
                sprintf(p, "%" PRIi64, d->int64);
155✔
1797
                d->formatted = TAKE_PTR(p);
155✔
1798
                break;
155✔
1799
        }
1800

1801
        case TABLE_UINT: {
285✔
1802
                _cleanup_free_ char *p = NULL;
×
1803

1804
                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
409✔
1805
                if (!p)
285✔
1806
                        return NULL;
×
1807

1808
                sprintf(p, "%u", d->uint_val);
285✔
1809
                d->formatted = TAKE_PTR(p);
285✔
1810
                break;
285✔
1811
        }
1812

1813
        case TABLE_UINT8: {
43✔
1814
                _cleanup_free_ char *p = NULL;
×
1815

1816
                p = new(char, DECIMAL_STR_WIDTH(d->uint8) + 1);
46✔
1817
                if (!p)
43✔
1818
                        return NULL;
×
1819

1820
                sprintf(p, "%" PRIu8, d->uint8);
43✔
1821
                d->formatted = TAKE_PTR(p);
43✔
1822
                break;
43✔
1823
        }
1824

1825
        case TABLE_UINT16: {
88✔
1826
                _cleanup_free_ char *p = NULL;
×
1827

1828
                p = new(char, DECIMAL_STR_WIDTH(d->uint16) + 1);
302✔
1829
                if (!p)
88✔
1830
                        return NULL;
×
1831

1832
                sprintf(p, "%" PRIu16, d->uint16);
88✔
1833
                d->formatted = TAKE_PTR(p);
88✔
1834
                break;
88✔
1835
        }
1836

1837
        case TABLE_UINT32: {
1,003✔
1838
                _cleanup_free_ char *p = NULL;
×
1839

1840
                p = new(char, DECIMAL_STR_WIDTH(d->uint32) + 1);
1,520✔
1841
                if (!p)
1,003✔
1842
                        return NULL;
×
1843

1844
                sprintf(p, "%" PRIu32, d->uint32);
1,003✔
1845
                d->formatted = TAKE_PTR(p);
1,003✔
1846
                break;
1,003✔
1847
        }
1848

1849
        case TABLE_UINT32_HEX: {
2✔
1850
                _cleanup_free_ char *p = NULL;
×
1851

1852
                p = new(char, 8 + 1);
2✔
1853
                if (!p)
2✔
1854
                        return NULL;
×
1855

1856
                sprintf(p, "%" PRIx32, d->uint32);
2✔
1857
                d->formatted = TAKE_PTR(p);
2✔
1858
                break;
2✔
1859
        }
1860

1861
        case TABLE_UINT64: {
1,093✔
1862
                _cleanup_free_ char *p = NULL;
×
1863

1864
                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
5,255✔
1865
                if (!p)
1,093✔
1866
                        return NULL;
×
1867

1868
                sprintf(p, "%" PRIu64, d->uint64);
1,093✔
1869
                d->formatted = TAKE_PTR(p);
1,093✔
1870
                break;
1,093✔
1871
        }
1872

1873
        case TABLE_UINT64_HEX: {
14✔
1874
                _cleanup_free_ char *p = NULL;
×
1875

1876
                p = new(char, 16 + 1);
14✔
1877
                if (!p)
14✔
1878
                        return NULL;
×
1879

1880
                sprintf(p, "%" PRIx64, d->uint64);
14✔
1881
                d->formatted = TAKE_PTR(p);
14✔
1882
                break;
14✔
1883
        }
1884

1885
        case TABLE_PERCENT: {
2✔
1886
                _cleanup_free_ char *p = NULL;
×
1887

1888
                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
5✔
1889
                if (!p)
2✔
1890
                        return NULL;
×
1891

1892
                sprintf(p, "%i%%" , d->percent);
2✔
1893
                d->formatted = TAKE_PTR(p);
2✔
1894
                break;
2✔
1895
        }
1896

1897
        case TABLE_IFINDEX: {
72✔
1898
                _cleanup_free_ char *p = NULL;
×
1899

1900
                if (format_ifname_full_alloc(d->ifindex, FORMAT_IFNAME_IFINDEX, &p) < 0)
72✔
1901
                        return NULL;
×
1902

1903
                d->formatted = TAKE_PTR(p);
72✔
1904
                break;
72✔
1905
        }
1906

1907
        case TABLE_IN_ADDR:
153✔
1908
        case TABLE_IN6_ADDR: {
1909
                _cleanup_free_ char *p = NULL;
×
1910

1911
                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
153✔
1912
                                      &d->address, &p) < 0)
153✔
1913
                        return NULL;
×
1914

1915
                d->formatted = TAKE_PTR(p);
153✔
1916
                break;
153✔
1917
        }
1918

1919
        case TABLE_ID128: {
1920
                char *p;
1,095✔
1921

1922
                p = new(char, SD_ID128_STRING_MAX);
1,095✔
1923
                if (!p)
1,095✔
1924
                        return NULL;
1925

1926
                d->formatted = sd_id128_to_string(d->id128, p);
1,095✔
1927
                break;
1,095✔
1928
        }
1929

1930
        case TABLE_UUID: {
1931
                char *p;
286✔
1932

1933
                p = new(char, SD_ID128_UUID_STRING_MAX);
286✔
1934
                if (!p)
286✔
1935
                        return NULL;
1936

1937
                d->formatted = sd_id128_to_uuid_string(d->id128, p);
286✔
1938
                break;
286✔
1939
        }
1940

1941
        case TABLE_UID: {
383✔
1942
                char *p;
383✔
1943

1944
                if (!uid_is_valid(d->uid))
383✔
1945
                        return table_ersatz_string(t);
×
1946

1947
                p = new(char, DECIMAL_STR_WIDTH(d->uid) + 1);
1,299✔
1948
                if (!p)
383✔
1949
                        return NULL;
1950
                sprintf(p, UID_FMT, d->uid);
383✔
1951

1952
                d->formatted = p;
383✔
1953
                break;
383✔
1954
        }
1955

1956
        case TABLE_GID: {
509✔
1957
                char *p;
509✔
1958

1959
                if (!gid_is_valid(d->gid))
509✔
1960
                        return table_ersatz_string(t);
×
1961

1962
                p = new(char, DECIMAL_STR_WIDTH(d->gid) + 1);
1,509✔
1963
                if (!p)
509✔
1964
                        return NULL;
1965
                sprintf(p, GID_FMT, d->gid);
509✔
1966

1967
                d->formatted = p;
509✔
1968
                break;
509✔
1969
        }
1970

1971
        case TABLE_PID: {
220✔
1972
                char *p;
220✔
1973

1974
                if (!pid_is_valid(d->pid))
220✔
1975
                        return table_ersatz_string(t);
×
1976

1977
                p = new(char, DECIMAL_STR_WIDTH(d->pid) + 1);
605✔
1978
                if (!p)
220✔
1979
                        return NULL;
1980
                sprintf(p, PID_FMT, d->pid);
220✔
1981

1982
                d->formatted = p;
220✔
1983
                break;
220✔
1984
        }
1985

1986
        case TABLE_SIGNAL: {
123✔
1987
                const char *suffix;
123✔
1988
                char *p;
123✔
1989

1990
                suffix = signal_to_string(d->int_val);
123✔
1991
                if (!suffix)
123✔
1992
                        return table_ersatz_string(t);
×
1993

1994
                p = strjoin("SIG", suffix);
123✔
1995
                if (!p)
123✔
1996
                        return NULL;
1997

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

2002
        case TABLE_MODE: {
24✔
2003
                char *p;
24✔
2004

2005
                if (d->mode == MODE_INVALID)
24✔
2006
                        return table_ersatz_string(t);
×
2007

2008
                p = new(char, 4 + 1);
24✔
2009
                if (!p)
24✔
2010
                        return NULL;
2011

2012
                sprintf(p, "%04o", d->mode & 07777);
24✔
2013
                d->formatted = p;
24✔
2014
                break;
24✔
2015
        }
2016

2017
        case TABLE_MODE_INODE_TYPE:
2✔
2018

2019
                if (d->mode == MODE_INVALID)
2✔
2020
                        return table_ersatz_string(t);
×
2021

2022
                return inode_type_to_string(d->mode) ?: table_ersatz_string(t);
2✔
2023

2024
        case TABLE_DEVNUM:
3✔
2025
                if (devnum_is_zero(d->devnum))
3✔
2026
                        return table_ersatz_string(t);
2✔
2027

2028
                if (asprintf(&d->formatted, DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(d->devnum)) < 0)
1✔
2029
                        return NULL;
2030

2031
                break;
2032

2033
        default:
×
2034
                assert_not_reached();
×
2035
        }
2036

2037
        return d->formatted;
13,824✔
2038
}
2039

2040
static int console_width_height(
128,386✔
2041
                const char *s,
2042
                size_t *ret_width,
2043
                size_t *ret_height) {
2044

2045
        size_t max_width = 0, height = 0;
128,386✔
2046
        const char *p;
128,386✔
2047

2048
        assert(s);
128,386✔
2049

2050
        /* Determine the width and height in console character cells the specified string needs. */
2051

2052
        do {
131,297✔
2053
                size_t k;
131,297✔
2054

2055
                p = strchr(s, '\n');
131,297✔
2056
                if (p) {
131,297✔
2057
                        _cleanup_free_ char *c = NULL;
2,914✔
2058

2059
                        c = strndup(s, p - s);
2,914✔
2060
                        if (!c)
2,914✔
2061
                                return -ENOMEM;
×
2062

2063
                        k = utf8_console_width(c);
2,914✔
2064
                        s = p + 1;
2,914✔
2065
                } else {
2066
                        k = utf8_console_width(s);
128,383✔
2067
                        s = NULL;
128,383✔
2068
                }
2069
                if (k == SIZE_MAX)
131,297✔
2070
                        return -EINVAL;
2071
                if (k > max_width)
131,297✔
2072
                        max_width = k;
127,378✔
2073

2074
                height++;
131,297✔
2075
        } while (!isempty(s));
134,211✔
2076

2077
        if (ret_width)
128,386✔
2078
                *ret_width = max_width;
128,386✔
2079

2080
        if (ret_height)
128,386✔
2081
                *ret_height = height;
128,386✔
2082

2083
        return 0;
2084
}
2085

2086
static int table_data_requested_width_height(
128,386✔
2087
                Table *table,
2088
                TableData *d,
2089
                size_t available_width,
2090
                size_t *ret_width,
2091
                size_t *ret_height,
2092
                bool *have_soft) {
2093

2094
        _cleanup_free_ char *truncated = NULL;
128,386✔
2095
        bool truncation_applied = false;
128,386✔
2096
        size_t width, height;
128,386✔
2097
        const char *t;
128,386✔
2098
        int r;
128,386✔
2099
        bool soft = false;
128,386✔
2100

2101
        t = table_data_format(table, d, false, available_width, &soft);
128,386✔
2102
        if (!t)
128,386✔
2103
                return -ENOMEM;
2104

2105
        if (table->cell_height_max != SIZE_MAX) {
128,386✔
2106
                r = string_truncate_lines(t, table->cell_height_max, &truncated);
162✔
2107
                if (r < 0)
162✔
2108
                        return r;
2109
                if (r > 0)
162✔
2110
                        truncation_applied = true;
23✔
2111

2112
                t = truncated;
162✔
2113
        }
2114

2115
        r = console_width_height(t, &width, &height);
128,386✔
2116
        if (r < 0)
128,386✔
2117
                return r;
2118

2119
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
128,386✔
2120
                width = d->maximum_width;
×
2121

2122
        if (width < d->minimum_width)
128,386✔
2123
                width = d->minimum_width;
2,430✔
2124

2125
        if (ret_width)
128,386✔
2126
                *ret_width = width;
128,386✔
2127
        if (ret_height)
128,386✔
2128
                *ret_height = height;
128,386✔
2129
        if (have_soft && soft)
128,386✔
2130
                *have_soft = true;
72✔
2131

2132
        return truncation_applied;
128,386✔
2133
}
2134

2135
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
100,686✔
2136
        size_t w = 0, space, lspace, old_length, clickable_length;
100,686✔
2137
        _cleanup_free_ char *clickable = NULL;
100,686✔
2138
        const char *p;
100,686✔
2139
        char *ret;
100,686✔
2140
        int r;
100,686✔
2141

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

2144
        assert(str);
100,686✔
2145
        assert(percent <= 100);
100,686✔
2146

2147
        old_length = strlen(str);
100,686✔
2148

2149
        if (url) {
100,686✔
2150
                r = terminal_urlify(url, str, &clickable);
1,546✔
2151
                if (r < 0)
1,546✔
2152
                        return NULL;
2153

2154
                clickable_length = strlen(clickable);
1,546✔
2155
        } else
2156
                clickable_length = old_length;
2157

2158
        /* Determine current width on screen */
2159
        p = str;
100,686✔
2160
        while (p < str + old_length) {
1,392,499✔
2161
                char32_t c;
1,291,813✔
2162

2163
                if (utf8_encoded_to_unichar(p, &c) < 0) {
1,291,813✔
2164
                        p++, w++; /* count invalid chars as 1 */
×
2165
                        continue;
×
2166
                }
2167

2168
                p = utf8_next_char(p);
1,291,813✔
2169
                w += unichar_iswide(c) ? 2 : 1;
2,583,619✔
2170
        }
2171

2172
        /* Already wider than the target, if so, don't do anything */
2173
        if (w >= new_length)
100,686✔
2174
                return clickable ? TAKE_PTR(clickable) : strdup(str);
×
2175

2176
        /* How much spaces shall we add? An how much on the left side? */
2177
        space = new_length - w;
100,686✔
2178
        lspace = space * percent / 100U;
100,686✔
2179

2180
        ret = new(char, space + clickable_length + 1);
100,686✔
2181
        if (!ret)
100,686✔
2182
                return NULL;
2183

2184
        for (size_t i = 0; i < lspace; i++)
604,855✔
2185
                ret[i] = ' ';
504,169✔
2186
        memcpy(ret + lspace, clickable ?: str, clickable_length);
100,686✔
2187
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
2,828,360✔
2188
                ret[i] = ' ';
2,727,674✔
2189

2190
        ret[space + clickable_length] = 0;
100,686✔
2191
        return ret;
100,686✔
2192
}
2193

2194
static bool table_data_isempty(const TableData *d) {
537✔
2195
        assert(d);
537✔
2196

2197
        if (d->type == TABLE_EMPTY)
537✔
2198
                return true;
2199

2200
        /* Let's also consider an empty strv as truly empty. */
2201
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
457✔
2202
                return strv_isempty(d->strv);
×
2203

2204
        /* Note that an empty string we do not consider empty here! */
2205
        return false;
2206
}
2207

2208
static const char* table_data_color(const TableData *d) {
745✔
2209
        assert(d);
745✔
2210

2211
        if (d->color)
745✔
2212
                return d->color;
2213

2214
        /* Let's implicitly color all "empty" cells in grey, in case an "empty_string" is set that is not empty */
2215
        if (table_data_isempty(d))
537✔
2216
                return ansi_grey();
80✔
2217

2218
        if (d->type == TABLE_FIELD)
457✔
2219
                return ansi_bright_blue();
×
2220

2221
        return NULL;
2222
}
2223

2224
static const char* table_data_underline(const TableData *d) {
745✔
2225
        assert(d);
745✔
2226

2227
        if (d->underline)
745✔
2228
                return ansi_add_underline_grey();
×
2229

2230
        if (d->type == TABLE_HEADER)
745✔
2231
                return ansi_add_underline();
40✔
2232

2233
        return NULL;
2234
}
2235

2236
static const char* table_data_rgap_underline(const TableData *d) {
134,114✔
2237
        assert(d);
134,114✔
2238

2239
        if (d->rgap_underline)
134,114✔
2240
                return ansi_add_underline_grey();
300✔
2241

2242
        if (d->type == TABLE_HEADER)
133,814✔
2243
                return ansi_add_underline();
6,578✔
2244

2245
        return NULL;
2246
}
2247

2248
int table_print(Table *t, FILE *f) {
2,684✔
2249
        size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
2,684✔
2250
                table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
2251
                *width = NULL;
2,684✔
2252
        _cleanup_free_ size_t *sorted = NULL;
2,684✔
2253
        uint64_t *column_weight, weight_sum;
2,684✔
2254
        int r;
2,684✔
2255

2256
        assert(t);
2,684✔
2257

2258
        if (!f)
2,684✔
2259
                f = stdout;
2,301✔
2260

2261
        /* Ensure we have no incomplete rows */
2262
        assert(t->n_cells % t->n_columns == 0);
2,684✔
2263

2264
        n_rows = t->n_cells / t->n_columns;
2,684✔
2265
        assert(n_rows > 0); /* at least the header row must be complete */
2,684✔
2266

2267
        if (t->sort_map) {
2,684✔
2268
                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
2269

2270
                sorted = new(size_t, n_rows);
180✔
2271
                if (!sorted)
180✔
2272
                        return -ENOMEM;
2273

2274
                for (size_t i = 0; i < n_rows; i++)
3,071✔
2275
                        sorted[i] = i * t->n_columns;
2,891✔
2276

2277
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
180✔
2278
        }
2279

2280
        if (t->display_map)
2,684✔
2281
                display_columns = t->n_display_map;
278✔
2282
        else
2283
                display_columns = t->n_columns;
2,406✔
2284

2285
        assert(display_columns > 0);
2,684✔
2286

2287
        minimum_width = newa(size_t, display_columns);
2,684✔
2288
        maximum_width = newa(size_t, display_columns);
2,684✔
2289
        requested_width = newa(size_t, display_columns);
2,684✔
2290
        column_weight = newa0(uint64_t, display_columns);
2,684✔
2291

2292
        for (size_t j = 0; j < display_columns; j++) {
10,643✔
2293
                minimum_width[j] = 1;
7,959✔
2294
                maximum_width[j] = SIZE_MAX;
7,959✔
2295
        }
2296

2297
        for (unsigned pass = 0; pass < 2; pass++) {
2,693✔
2298
                /* First pass: determine column sizes */
2299

2300
                for (size_t j = 0; j < display_columns; j++)
10,670✔
2301
                        requested_width[j] = SIZE_MAX;
7,977✔
2302

2303
                bool any_soft = false;
2,693✔
2304

2305
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
41,917✔
2306
                        TableData **row;
39,224✔
2307

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

2312
                        for (size_t j = 0; j < display_columns; j++) {
167,610✔
2313
                                TableData *d;
128,386✔
2314
                                size_t req_width, req_height;
128,386✔
2315

2316
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
128,386✔
2317

2318
                                r = table_data_requested_width_height(t, d,
128,456✔
2319
                                                                      width ? width[j] : SIZE_MAX,
70✔
2320
                                                                      &req_width, &req_height, &any_soft);
2321
                                if (r < 0)
128,386✔
2322
                                        return r;
×
2323
                                if (r > 0) { /* Truncated because too many lines? */
128,386✔
2324
                                        _cleanup_free_ char *last = NULL;
23✔
2325
                                        const char *field;
23✔
2326

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

2332
                                        field = table_data_format(t, d, false,
26✔
2333
                                                                  width ? width[j] : SIZE_MAX,
3✔
2334
                                                                  &any_soft);
2335
                                        if (!field)
23✔
2336
                                                return -ENOMEM;
2337

2338
                                        assert_se(t->cell_height_max > 0);
23✔
2339
                                        r = string_extract_line(field, t->cell_height_max-1, &last);
23✔
2340
                                        if (r < 0)
23✔
2341
                                                return r;
2342

2343
                                        req_width = MAX(req_width,
23✔
2344
                                                        utf8_console_width(last) +
2345
                                                        utf8_console_width(glyph(GLYPH_ELLIPSIS)));
2346
                                }
2347

2348
                                /* Determine the biggest width that any cell in this column would like to have */
2349
                                if (requested_width[j] == SIZE_MAX ||
128,386✔
2350
                                    requested_width[j] < req_width)
120,441✔
2351
                                        requested_width[j] = req_width;
17,921✔
2352

2353
                                /* Determine the minimum width any cell in this column needs */
2354
                                if (minimum_width[j] < d->minimum_width)
128,386✔
2355
                                        minimum_width[j] = d->minimum_width;
22✔
2356

2357
                                /* Determine the maximum width any cell in this column needs */
2358
                                if (d->maximum_width != SIZE_MAX &&
128,386✔
2359
                                    (maximum_width[j] == SIZE_MAX ||
1,414✔
2360
                                     maximum_width[j] > d->maximum_width))
2361
                                        maximum_width[j] = d->maximum_width;
20✔
2362

2363
                                /* Determine the full columns weight */
2364
                                column_weight[j] += d->weight;
128,386✔
2365
                        }
2366
                }
2367

2368
                /* One space between each column */
2369
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
2,693✔
2370

2371
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2372
                weight_sum = 0;
2,693✔
2373
                for (size_t j = 0; j < display_columns; j++) {
10,670✔
2374
                        weight_sum += column_weight[j];
7,977✔
2375

2376
                        table_minimum_width += minimum_width[j];
7,977✔
2377

2378
                        if (maximum_width[j] == SIZE_MAX)
7,977✔
2379
                                table_maximum_width = SIZE_MAX;
2380
                        else
2381
                                table_maximum_width += maximum_width[j];
20✔
2382

2383
                        table_requested_width += requested_width[j];
7,977✔
2384
                }
2385

2386
                /* Calculate effective table width */
2387
                if (t->width != 0 && t->width != SIZE_MAX)
2,693✔
2388
                        table_effective_width = t->width;
2389
                else if (t->width == 0 ||
2,686✔
2390
                         ((pass > 0 || !any_soft) && (pager_have() || !isatty_safe(STDOUT_FILENO))))
2,615✔
2391
                        table_effective_width = table_requested_width;
2392
                else
2393
                        table_effective_width = MIN(table_requested_width, columns());
19✔
2394

2395
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
2,693✔
2396
                        table_effective_width = table_maximum_width;
×
2397

2398
                if (table_effective_width < table_minimum_width)
2,693✔
2399
                        table_effective_width = table_minimum_width;
2✔
2400

2401
                if (!width)
2,693✔
2402
                        width = newa(size_t, display_columns);
2,684✔
2403

2404
                if (table_effective_width >= table_requested_width) {
2,693✔
2405
                        size_t extra;
2,680✔
2406

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

2410
                        extra = table_effective_width - table_requested_width;
2,680✔
2411

2412
                        for (size_t j = 0; j < display_columns; j++) {
10,621✔
2413
                                size_t delta;
7,941✔
2414

2415
                                if (weight_sum == 0)
7,941✔
2416
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2417
                                else
2418
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
7,909✔
2419

2420
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
7,941✔
2421
                                        width[j] = maximum_width[j];
2✔
2422

2423
                                if (width[j] < minimum_width[j])
7,941✔
2424
                                        width[j] = minimum_width[j];
×
2425

2426
                                delta = LESS_BY(width[j], requested_width[j]);
7,941✔
2427

2428
                                /* Subtract what we just added from the rest */
2429
                                if (extra > delta)
7,941✔
2430
                                        extra -= delta;
13✔
2431
                                else
2432
                                        extra = 0;
2433

2434
                                assert(weight_sum >= column_weight[j]);
7,941✔
2435
                                weight_sum -= column_weight[j];
7,941✔
2436
                        }
2437

2438
                        break; /* Every column should be happy, no need to repeat calculations. */
2,684✔
2439
                } else {
2440
                        /* We need to compress the table, columns can't get what they asked for. We first provide each column
2441
                         * with the minimum they need, and then distribute anything left. */
2442
                        bool finalize = false;
13✔
2443
                        size_t extra;
13✔
2444

2445
                        extra = table_effective_width - table_minimum_width;
13✔
2446

2447
                        for (size_t j = 0; j < display_columns; j++)
49✔
2448
                                width[j] = SIZE_MAX;
36✔
2449

2450
                        for (;;) {
36✔
2451
                                bool restart = false;
36✔
2452

2453
                                for (size_t j = 0; j < display_columns; j++) {
115✔
2454
                                        size_t delta, w;
89✔
2455

2456
                                        /* Did this column already get something assigned? If so, let's skip to the next */
2457
                                        if (width[j] != SIZE_MAX)
89✔
2458
                                                continue;
26✔
2459

2460
                                        if (weight_sum == 0)
63✔
2461
                                                w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
×
2462
                                        else
2463
                                                w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
63✔
2464

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

2471
                                                w = requested_width[j];
2472
                                                restart = true;
2473

2474
                                        } else if (!finalize)
52✔
2475
                                                continue;
27✔
2476

2477
                                        width[j] = w;
36✔
2478

2479
                                        assert(w >= minimum_width[j]);
36✔
2480
                                        delta = w - minimum_width[j];
36✔
2481

2482
                                        assert(delta <= extra);
36✔
2483
                                        extra -= delta;
36✔
2484

2485
                                        assert(weight_sum >= column_weight[j]);
36✔
2486
                                        weight_sum -= column_weight[j];
36✔
2487

2488
                                        if (restart && !finalize)
36✔
2489
                                                break;
2490
                                }
2491

2492
                                if (finalize)
36✔
2493
                                        break;
2494

2495
                                if (!restart)
23✔
2496
                                        finalize = true;
13✔
2497
                        }
2498

2499
                        if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
13✔
2500
                                        * let's try to reformat them with the new widths. Otherwise, let's
2501
                                        * move on. */
2502
                                break;
2503
                }
2504
        }
2505

2506
        /* Second pass: show output */
2507
        for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
41,873✔
2508
                size_t n_subline = 0;
39,189✔
2509
                bool more_sublines;
39,189✔
2510
                TableData **row;
39,189✔
2511

2512
                if (sorted)
39,189✔
2513
                        row = t->data + sorted[i];
2,879✔
2514
                else
2515
                        row = t->data + i * t->n_columns;
36,310✔
2516

2517
                do {
42,088✔
2518
                        const char *gap_color = NULL, *gap_underline = NULL;
42,088✔
2519
                        more_sublines = false;
42,088✔
2520

2521
                        for (size_t j = 0; j < display_columns; j++) {
176,202✔
2522
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
134,114✔
2523
                                bool lines_truncated = false;
134,114✔
2524
                                const char *field, *color = NULL, *underline = NULL;
134,114✔
2525
                                TableData *d;
134,114✔
2526
                                size_t l;
134,114✔
2527

2528
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
134,114✔
2529

2530
                                field = table_data_format(t, d, false, width[j], NULL);
134,114✔
2531
                                if (!field)
134,114✔
2532
                                        return -ENOMEM;
2533

2534
                                r = string_extract_line(field, n_subline, &extracted);
134,114✔
2535
                                if (r < 0)
134,114✔
2536
                                        return r;
2537
                                if (r > 0) {
134,114✔
2538
                                        /* There are more lines to come */
2539
                                        if ((t->cell_height_max == SIZE_MAX || n_subline + 1 < t->cell_height_max))
2,934✔
2540
                                                more_sublines = true; /* There are more lines to come */
2541
                                        else
2542
                                                lines_truncated = true;
23✔
2543
                                }
2544
                                if (extracted)
134,114✔
2545
                                        field = extracted;
7,065✔
2546

2547
                                l = utf8_console_width(field);
134,114✔
2548
                                if (l > width[j]) {
134,114✔
2549
                                        /* Field is wider than allocated space. Let's ellipsize */
2550

2551
                                        buffer = ellipsize(field, width[j], /* ellipsize at the end if we truncated coming lines, otherwise honour configuration */
33✔
2552
                                                           lines_truncated ? 100 : d->ellipsize_percent);
2553
                                        if (!buffer)
33✔
2554
                                                return -ENOMEM;
2555

2556
                                        field = buffer;
2557
                                } else {
2558
                                        if (lines_truncated) {
134,081✔
2559
                                                _cleanup_free_ char *padded = NULL;
23✔
2560

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

2567
                                                padded = strjoin(field, glyph(GLYPH_ELLIPSIS));
23✔
2568
                                                if (!padded)
23✔
2569
                                                        return -ENOMEM;
2570

2571
                                                buffer = ellipsize(padded, width[j], 100);
23✔
2572
                                                if (!buffer)
23✔
2573
                                                        return -ENOMEM;
2574

2575
                                                field = buffer;
23✔
2576
                                                l = utf8_console_width(field);
23✔
2577
                                        }
2578

2579
                                        if (l < width[j]) {
134,081✔
2580
                                                _cleanup_free_ char *aligned = NULL;
×
2581
                                                /* Field is shorter than allocated space. Let's align with spaces */
2582

2583
                                                aligned = align_string_mem(field, d->url, width[j], d->align_percent);
100,686✔
2584
                                                if (!aligned)
100,686✔
2585
                                                        return -ENOMEM;
×
2586

2587
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2588
                                                if (j == display_columns - 1 &&
137,910✔
2589
                                                    (!colors_enabled() || !table_data_color(d)) &&
37,276✔
2590
                                                    (!underline_enabled() || !table_data_underline(d)) &&
74,498✔
2591
                                                    (!urlify_enabled() || !d->url))
37,272✔
2592
                                                        delete_trailing_chars(aligned, NULL);
37,222✔
2593

2594
                                                free_and_replace(buffer, aligned);
100,686✔
2595
                                                field = buffer;
100,686✔
2596
                                        }
2597
                                }
2598

2599
                                if (l >= width[j] && d->url) {
134,114✔
2600
                                        _cleanup_free_ char *clickable = NULL;
×
2601

2602
                                        r = terminal_urlify(d->url, field, &clickable);
22✔
2603
                                        if (r < 0)
22✔
2604
                                                return r;
×
2605

2606
                                        free_and_replace(buffer, clickable);
22✔
2607
                                        field = buffer;
22✔
2608
                                }
2609

2610
                                if (colors_enabled() && gap_color)
134,114✔
2611
                                        fputs(gap_color, f);
×
2612
                                if (underline_enabled() && gap_underline)
134,114✔
2613
                                        fputs(gap_underline, f);
16✔
2614

2615
                                if (j > 0)
134,114✔
2616
                                        fputc(' ', f); /* column separator left of cell */
92,026✔
2617

2618
                                /* Undo gap color/underline */
2619
                                if ((colors_enabled() && gap_color) ||
268,228✔
2620
                                    (underline_enabled() && gap_underline))
134,807✔
2621
                                        fputs(ANSI_NORMAL, f);
16✔
2622

2623
                                if (colors_enabled()) {
134,114✔
2624
                                        color = table_data_color(d);
693✔
2625
                                        if (color)
693✔
2626
                                                fputs(color, f);
288✔
2627
                                }
2628

2629
                                if (underline_enabled()) {
134,114✔
2630
                                        underline = table_data_underline(d);
693✔
2631
                                        if (underline)
693✔
2632
                                                fputs(underline, f);
18✔
2633
                                }
2634

2635
                                fputs(field, f);
134,114✔
2636

2637
                                if (color || underline)
134,114✔
2638
                                        fputs(ANSI_NORMAL, f);
306✔
2639

2640
                                gap_color = d->rgap_color;
134,114✔
2641
                                gap_underline = table_data_rgap_underline(d);
134,114✔
2642
                        }
2643

2644
                        fputc('\n', f);
42,088✔
2645
                        n_subline++;
42,088✔
2646
                } while (more_sublines);
42,088✔
2647
        }
2648

2649
        return fflush_and_check(f);
2,684✔
2650
}
2651

2652
int table_format(Table *t, char **ret) {
39✔
2653
        _cleanup_(memstream_done) MemStream m = {};
39✔
2654
        FILE *f;
39✔
2655
        int r;
39✔
2656

2657
        assert(t);
39✔
2658
        assert(ret);
39✔
2659

2660
        f = memstream_init(&m);
39✔
2661
        if (!f)
39✔
2662
                return -ENOMEM;
2663

2664
        r = table_print(t, f);
39✔
2665
        if (r < 0)
39✔
2666
                return r;
2667

2668
        return memstream_finalize(&m, ret, NULL);
39✔
2669
}
2670

2671
size_t table_get_rows(Table *t) {
1,710✔
2672
        if (!t)
1,710✔
2673
                return 0;
2674

2675
        assert(t->n_columns > 0);
1,710✔
2676
        return t->n_cells / t->n_columns;
1,710✔
2677
}
2678

2679
size_t table_get_columns(Table *t) {
34✔
2680
        if (!t)
34✔
2681
                return 0;
2682

2683
        assert(t->n_columns > 0);
34✔
2684
        return t->n_columns;
2685
}
2686

2687
size_t table_get_current_column(Table *t) {
80✔
2688
        if (!t)
80✔
2689
                return 0;
2690

2691
        assert(t->n_columns > 0);
80✔
2692
        return t->n_cells % t->n_columns;
80✔
2693
}
2694

2695
int table_set_reverse(Table *t, size_t column, bool b) {
80✔
2696
        assert(t);
80✔
2697
        assert(column < t->n_columns);
80✔
2698

2699
        if (!t->reverse_map) {
80✔
2700
                if (!b)
80✔
2701
                        return 0;
2702

2703
                t->reverse_map = new0(bool, t->n_columns);
27✔
2704
                if (!t->reverse_map)
27✔
2705
                        return -ENOMEM;
2706
        }
2707

2708
        t->reverse_map[column] = b;
27✔
2709
        return 0;
27✔
2710
}
2711

2712
TableCell *table_get_cell(Table *t, size_t row, size_t column) {
8,012✔
2713
        size_t i;
8,012✔
2714

2715
        assert(t);
8,012✔
2716

2717
        if (column >= t->n_columns)
8,012✔
2718
                return NULL;
2719

2720
        i = row * t->n_columns + column;
8,012✔
2721
        if (i >= t->n_cells)
8,012✔
2722
                return NULL;
2723

2724
        return TABLE_INDEX_TO_CELL(i);
8,012✔
2725
}
2726

2727
const void *table_get(Table *t, TableCell *cell) {
4,374✔
2728
        TableData *d;
4,374✔
2729

2730
        assert(t);
4,374✔
2731

2732
        d = table_get_data(t, cell);
4,374✔
2733
        if (!d)
4,374✔
2734
                return NULL;
2735

2736
        return d->data;
4,374✔
2737
}
2738

2739
const void* table_get_at(Table *t, size_t row, size_t column) {
4,374✔
2740
        TableCell *cell;
4,374✔
2741

2742
        cell = table_get_cell(t, row, column);
4,374✔
2743
        if (!cell)
4,374✔
2744
                return NULL;
2745

2746
        return table_get(t, cell);
4,374✔
2747
}
2748

2749
static int table_data_to_json(TableData *d, sd_json_variant **ret) {
3,963✔
2750

2751
        switch (d->type) {
3,963✔
2752

2753
        case TABLE_EMPTY:
319✔
2754
                return sd_json_variant_new_null(ret);
319✔
2755

2756
        case TABLE_STRING:
2,012✔
2757
        case TABLE_PATH:
2758
        case TABLE_PATH_BASENAME:
2759
        case TABLE_FIELD:
2760
        case TABLE_HEADER:
2761
                return sd_json_variant_new_string(ret, d->string);
2,012✔
2762

2763
        case TABLE_STRV:
3✔
2764
        case TABLE_STRV_WRAPPED:
2765
                return sd_json_variant_new_array_strv(ret, d->strv);
3✔
2766

2767
        case TABLE_BOOLEAN_CHECKMARK:
150✔
2768
        case TABLE_BOOLEAN:
2769
                return sd_json_variant_new_boolean(ret, d->boolean);
150✔
2770

2771
        case TABLE_TIMESTAMP:
153✔
2772
        case TABLE_TIMESTAMP_UTC:
2773
        case TABLE_TIMESTAMP_RELATIVE:
2774
        case TABLE_TIMESTAMP_RELATIVE_MONOTONIC:
2775
        case TABLE_TIMESTAMP_LEFT:
2776
        case TABLE_TIMESTAMP_DATE:
2777
                if (d->timestamp == USEC_INFINITY)
153✔
2778
                        return sd_json_variant_new_null(ret);
×
2779

2780
                return sd_json_variant_new_unsigned(ret, d->timestamp);
153✔
2781

2782
        case TABLE_TIMESPAN:
×
2783
        case TABLE_TIMESPAN_MSEC:
2784
        case TABLE_TIMESPAN_DAY:
2785
                if (d->timespan == USEC_INFINITY)
×
2786
                        return sd_json_variant_new_null(ret);
×
2787

2788
                return sd_json_variant_new_unsigned(ret, d->timespan);
×
2789

2790
        case TABLE_SIZE:
36✔
2791
        case TABLE_BPS:
2792
                if (d->size == UINT64_MAX)
36✔
2793
                        return sd_json_variant_new_null(ret);
16✔
2794

2795
                return sd_json_variant_new_unsigned(ret, d->size);
20✔
2796

2797
        case TABLE_INT:
27✔
2798
                return sd_json_variant_new_integer(ret, d->int_val);
27✔
2799

2800
        case TABLE_INT8:
6✔
2801
                return sd_json_variant_new_integer(ret, d->int8);
6✔
2802

2803
        case TABLE_INT16:
6✔
2804
                return sd_json_variant_new_integer(ret, d->int16);
6✔
2805

2806
        case TABLE_INT32:
6✔
2807
                return sd_json_variant_new_integer(ret, d->int32);
6✔
2808

2809
        case TABLE_INT64:
45✔
2810
                return sd_json_variant_new_integer(ret, d->int64);
45✔
2811

2812
        case TABLE_UINT:
15✔
2813
                return sd_json_variant_new_unsigned(ret, d->uint_val);
15✔
2814

2815
        case TABLE_UINT8:
4✔
2816
                return sd_json_variant_new_unsigned(ret, d->uint8);
4✔
2817

2818
        case TABLE_UINT16:
4✔
2819
                return sd_json_variant_new_unsigned(ret, d->uint16);
4✔
2820

2821
        case TABLE_UINT32:
107✔
2822
        case TABLE_UINT32_HEX:
2823
                return sd_json_variant_new_unsigned(ret, d->uint32);
107✔
2824

2825
        case TABLE_UINT64:
385✔
2826
        case TABLE_UINT64_HEX:
2827
                return sd_json_variant_new_unsigned(ret, d->uint64);
385✔
2828

2829
        case TABLE_PERCENT:
×
2830
                return sd_json_variant_new_integer(ret, d->percent);
×
2831

2832
        case TABLE_IFINDEX:
×
2833
                if (d->ifindex <= 0)
×
2834
                        return sd_json_variant_new_null(ret);
×
2835

2836
                return sd_json_variant_new_integer(ret, d->ifindex);
×
2837

2838
        case TABLE_IN_ADDR:
2839
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
×
2840

2841
        case TABLE_IN6_ADDR:
2842
                return sd_json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
×
2843

2844
        case TABLE_ID128:
501✔
2845
                return sd_json_variant_new_id128(ret, d->id128);
501✔
2846

2847
        case TABLE_UUID:
91✔
2848
                return sd_json_variant_new_uuid(ret, d->id128);
91✔
2849

2850
        case TABLE_UID:
8✔
2851
                if (!uid_is_valid(d->uid))
8✔
2852
                        return sd_json_variant_new_null(ret);
×
2853

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

2856
        case TABLE_GID:
8✔
2857
                if (!gid_is_valid(d->gid))
8✔
2858
                        return sd_json_variant_new_null(ret);
×
2859

2860
                return sd_json_variant_new_integer(ret, d->gid);
8✔
2861

2862
        case TABLE_PID:
14✔
2863
                if (!pid_is_valid(d->pid))
14✔
2864
                        return sd_json_variant_new_null(ret);
×
2865

2866
                return sd_json_variant_new_integer(ret, d->pid);
14✔
2867

2868
        case TABLE_SIGNAL:
8✔
2869
                if (!SIGNAL_VALID(d->int_val))
8✔
2870
                        return sd_json_variant_new_null(ret);
×
2871

2872
                return sd_json_variant_new_integer(ret, d->int_val);
8✔
2873

2874
        case TABLE_MODE:
51✔
2875
        case TABLE_MODE_INODE_TYPE:
2876
                if (d->mode == MODE_INVALID)
51✔
2877
                        return sd_json_variant_new_null(ret);
×
2878

2879
                return sd_json_variant_new_unsigned(ret, d->mode);
51✔
2880

2881
        case TABLE_DEVNUM:
4✔
2882
                if (devnum_is_zero(d->devnum))
4✔
2883
                        return sd_json_variant_new_null(ret);
2✔
2884

2885
                return sd_json_build(ret, SD_JSON_BUILD_ARRAY(
2✔
2886
                                                  SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
2887
                                                  SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
2888

2889
        default:
2890
                return -EINVAL;
2891
        }
2892
}
2893

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

2903
        bool new_word = true;
710✔
2904
        char *c;
710✔
2905

2906
        assert(str);
710✔
2907

2908
        c = strdup(str);
710✔
2909
        if (!c)
710✔
2910
                return NULL;
2911

2912
        for (char *x = c; *x; x++) {
5,315✔
2913
                if (!strchr(ALPHANUMERICAL, *x)) {
4,605✔
2914
                        *x = '_';
176✔
2915
                        new_word = true;
176✔
2916
                        continue;
176✔
2917
                }
2918

2919
                if (new_word) {
4,429✔
2920
                        if (ascii_tolower(*(x + 1)) == *(x + 1)) /* Heuristic: if next char is upper-case
881✔
2921
                                                                  * then we assume the whole word is all-caps
2922
                                                                  * and avoid lowercasing it. */
2923
                                *x = ascii_tolower(*x);
880✔
2924
                        new_word = false;
2925
                }
2926
        }
2927

2928
        return c;
2929
}
2930

2931
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
696✔
2932
        _cleanup_free_ char *mangled = NULL;
1,392✔
2933
        const char *n;
696✔
2934

2935
        assert(t);
696✔
2936
        assert(d);
696✔
2937
        assert(ret);
696✔
2938

2939
        if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
696✔
2940
                n = d->string;
696✔
2941
        else {
2942
                n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
×
2943
                if (!n)
×
2944
                        return -ENOMEM;
2945
        }
2946

2947
        mangled = table_mangle_to_json_field_name(n);
696✔
2948
        if (!mangled)
696✔
2949
                return -ENOMEM;
2950

2951
        *ret = TAKE_PTR(mangled);
696✔
2952
        return 0;
696✔
2953
}
2954

2955
static const char *table_get_json_field_name(Table *t, size_t idx) {
737✔
2956
        assert(t);
737✔
2957

2958
        return idx < t->n_json_fields ? t->json_fields[idx] : NULL;
737✔
2959
}
2960

2961
static int table_to_json_regular(Table *t, sd_json_variant **ret) {
89✔
2962
        sd_json_variant **rows = NULL, **elements = NULL;
89✔
2963
        _cleanup_free_ size_t *sorted = NULL;
89✔
2964
        size_t n_rows, display_columns;
89✔
2965
        int r;
89✔
2966

2967
        assert(t);
89✔
2968
        assert(!t->vertical);
89✔
2969

2970
        /* Ensure we have no incomplete rows */
2971
        assert(t->n_columns > 0);
89✔
2972
        assert(t->n_cells % t->n_columns == 0);
89✔
2973

2974
        n_rows = t->n_cells / t->n_columns;
89✔
2975
        assert(n_rows > 0); /* at least the header row must be complete */
89✔
2976

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

2980
                sorted = new(size_t, n_rows);
25✔
2981
                if (!sorted)
25✔
2982
                        return -ENOMEM;
2983

2984
                for (size_t i = 0; i < n_rows; i++)
126✔
2985
                        sorted[i] = i * t->n_columns;
101✔
2986

2987
                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
25✔
2988
        }
2989

2990
        if (t->display_map)
89✔
2991
                display_columns = t->n_display_map;
43✔
2992
        else
2993
                display_columns = t->n_columns;
46✔
2994
        assert(display_columns > 0);
89✔
2995

2996
        elements = new0(sd_json_variant*, display_columns * 2);
89✔
2997
        if (!elements)
89✔
2998
                return -ENOMEM;
2999

3000
        CLEANUP_ARRAY(elements, (size_t) { display_columns * 2 }, sd_json_variant_unref_many);
89✔
3001

3002
        for (size_t j = 0; j < display_columns; j++) {
822✔
3003
                _cleanup_free_ char *mangled = NULL;
733✔
3004
                const char *n;
733✔
3005
                size_t c;
733✔
3006

3007
                c = t->display_map ? t->display_map[j] : j;
733✔
3008

3009
                /* Use explicitly set JSON field name, if we have one. Otherwise mangle the column field value. */
3010
                n = table_get_json_field_name(t, c);
733✔
3011
                if (!n) {
733✔
3012
                        r = table_make_json_field_name(t, ASSERT_PTR(t->data[c]), &mangled);
694✔
3013
                        if (r < 0)
694✔
3014
                                return r;
3015

3016
                        n = mangled;
694✔
3017
                }
3018

3019
                r = sd_json_variant_new_string(elements + j*2, n);
733✔
3020
                if (r < 0)
733✔
3021
                        return r;
3022
        }
3023

3024
        rows = new0(sd_json_variant*, n_rows-1);
89✔
3025
        if (!rows)
89✔
3026
                return -ENOMEM;
3027

3028
        CLEANUP_ARRAY(rows, (size_t) { n_rows - 1 }, sd_json_variant_unref_many);
89✔
3029

3030
        for (size_t i = 1; i < n_rows; i++) {
998✔
3031
                TableData **row;
909✔
3032

3033
                if (sorted)
909✔
3034
                        row = t->data + sorted[i];
76✔
3035
                else
3036
                        row = t->data + i * t->n_columns;
833✔
3037

3038
                for (size_t j = 0; j < display_columns; j++) {
4,868✔
3039
                        TableData *d;
3,959✔
3040
                        size_t k;
3,959✔
3041

3042
                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
3,959✔
3043

3044
                        k = j*2+1;
3,959✔
3045
                        elements[k] = sd_json_variant_unref(elements[k]);
3,959✔
3046

3047
                        r = table_data_to_json(d, elements + k);
3,959✔
3048
                        if (r < 0)
3,959✔
3049
                                return r;
3050
                }
3051

3052
                r = sd_json_variant_new_object(rows + i - 1, elements, display_columns * 2);
909✔
3053
                if (r < 0)
909✔
3054
                        return r;
3055
        }
3056

3057
        return sd_json_variant_new_array(ret, rows, n_rows - 1);
89✔
3058
}
3059

3060
static int table_to_json_vertical(Table *t, sd_json_variant **ret) {
1✔
3061
        sd_json_variant **elements = NULL;
1✔
3062
        size_t n_elements = 0;
1✔
3063
        int r;
1✔
3064

3065
        assert(t);
1✔
3066
        assert(t->vertical);
1✔
3067

3068
        if (t->n_columns != 2)
1✔
3069
                return -EINVAL;
1✔
3070

3071
        /* Ensure we have no incomplete rows */
3072
        assert(t->n_cells % t->n_columns == 0);
1✔
3073

3074
        elements = new0(sd_json_variant *, t->n_cells);
1✔
3075
        if (!elements)
1✔
3076
                return -ENOMEM;
3077

3078
        CLEANUP_ARRAY(elements, n_elements, sd_json_variant_unref_many);
1✔
3079

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

3082
                if (i % t->n_columns == 0) {
8✔
3083
                        _cleanup_free_ char *mangled = NULL;
4✔
3084
                        const char *n;
4✔
3085

3086
                        n = table_get_json_field_name(t, i / t->n_columns - 1);
4✔
3087
                        if (!n) {
4✔
3088
                                r = table_make_json_field_name(t, ASSERT_PTR(t->data[i]), &mangled);
2✔
3089
                                if (r < 0)
2✔
3090
                                        return r;
×
3091

3092
                                n = mangled;
2✔
3093
                        }
3094

3095
                        r = sd_json_variant_new_string(elements + n_elements, n);
4✔
3096
                } else
3097
                        r = table_data_to_json(t->data[i], elements + n_elements);
4✔
3098
                if (r < 0)
8✔
3099
                        return r;
3100

3101
                n_elements++;
8✔
3102
        }
3103

3104
        return sd_json_variant_new_object(ret, elements, n_elements);
1✔
3105
}
3106

3107
int table_to_json(Table *t, sd_json_variant **ret) {
90✔
3108
        assert(t);
90✔
3109

3110
        if (t->vertical)
90✔
3111
                return table_to_json_vertical(t, ret);
1✔
3112

3113
        return table_to_json_regular(t, ret);
89✔
3114
}
3115

3116
int table_print_json(Table *t, FILE *f, sd_json_format_flags_t flags) {
395✔
3117
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
395✔
3118
        int r;
395✔
3119

3120
        assert(t);
395✔
3121

3122
        if (!sd_json_format_enabled(flags)) /* If JSON output is turned off, use regular output */
395✔
3123
                return table_print(t, f);
332✔
3124

3125
        if (!f)
63✔
3126
                f = stdout;
2✔
3127

3128
        r = table_to_json(t, &v);
63✔
3129
        if (r < 0)
63✔
3130
                return r;
3131

3132
        sd_json_variant_dump(v, flags, f, NULL);
63✔
3133

3134
        return fflush_and_check(f);
63✔
3135
}
3136

3137
int table_print_with_pager(
393✔
3138
                Table *t,
3139
                sd_json_format_flags_t json_format_flags,
3140
                PagerFlags pager_flags,
3141
                bool show_header) {
3142

3143
        bool saved_header;
393✔
3144
        int r;
393✔
3145

3146
        assert(t);
393✔
3147

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

3151
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
393✔
3152
                pager_open(pager_flags);
368✔
3153

3154
        saved_header = t->header;
393✔
3155
        t->header = show_header;
393✔
3156
        r = table_print_json(t, stdout, json_format_flags);
393✔
3157
        t->header = saved_header;
393✔
3158
        if (r < 0)
393✔
3159
                return table_log_print_error(r);
×
3160

3161
        return 0;
3162
}
3163

3164
int table_set_json_field_name(Table *t, size_t idx, const char *name) {
379✔
3165
        int r;
379✔
3166

3167
        assert(t);
379✔
3168

3169
        if (name) {
379✔
3170
                size_t m;
377✔
3171

3172
                m = MAX(idx + 1, t->n_json_fields);
377✔
3173
                if (!GREEDY_REALLOC0(t->json_fields, m))
377✔
3174
                        return -ENOMEM;
3175

3176
                r = free_and_strdup(t->json_fields + idx, name);
377✔
3177
                if (r < 0)
377✔
3178
                        return r;
3179

3180
                t->n_json_fields = m;
377✔
3181
                return r;
377✔
3182
        } else {
3183
                if (idx >= t->n_json_fields)
2✔
3184
                        return 0;
3185

3186
                t->json_fields[idx] = mfree(t->json_fields[idx]);
2✔
3187
                return 1;
2✔
3188
        }
3189
}
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