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

systemd / systemd / 15101432274

17 May 2025 08:33PM UTC coverage: 72.25% (+0.02%) from 72.228%
15101432274

push

github

yuwata
core/manager: do not pop gc_unit_queue before unit_gc_sweep()

Follow-up for 52e3671bf

unit_gc_sweep() might try to add the unit to gc queue again.
While that becomes no-op as Unit.in_gc_queue is not cleared
yet, it induces minor inconsistency of states.

3 of 3 new or added lines in 1 file covered. (100.0%)

295 existing lines in 26 files now uncovered.

298460 of 413093 relevant lines covered (72.25%)

705153.7 hits per line

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

89.93
/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 "strv.h"
34
#include "strxcpyx.h"
35
#include "terminal-util.h"
36
#include "time-util.h"
37
#include "user-util.h"
38
#include "utf8.h"
39

40
#define DEFAULT_WEIGHT 100
41

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

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

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

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

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

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

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

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

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

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

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

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

123
static size_t TABLE_CELL_TO_INDEX(TableCell *cell) {
282,384✔
124
        size_t i;
282,384✔
125

126
        assert(cell);
282,384✔
127

128
        i = PTR_TO_SIZE(cell);
282,384✔
129
        assert(i > 0);
282,384✔
130

131
        return i-1;
282,384✔
132
}
133

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

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

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

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

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

152
        TableData **data;
153

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

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

160
        char **json_fields;
161
        size_t n_json_fields;
162

163
        bool *reverse_map;
164
};
165

166
Table *table_new_raw(size_t n_columns) {
3,202✔
167
        _cleanup_(table_unrefp) Table *t = NULL;
3,202✔
168

169
        assert(n_columns > 0);
3,202✔
170

171
        t = new(Table, 1);
3,202✔
172
        if (!t)
3,202✔
173
                return NULL;
174

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

183
        return TAKE_PTR(t);
3,202✔
184
}
185

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

192
        assert(first_header);
606✔
193

194
        va_start(ap, first_header);
606✔
195
        for (;;) {
8,844✔
196
                if (!va_arg(ap, const char*))
4,725✔
197
                        break;
198

199
                n_columns++;
4,119✔
200
        }
201
        va_end(ap);
606✔
202

203
        t = table_new_raw(n_columns);
606✔
204
        if (!t)
606✔
205
                return NULL;
206

207
        va_start(ap, first_header);
606✔
208
        for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
5,331✔
209
                TableCell *cell;
4,725✔
210

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

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

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

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

231
        t->vertical = true;
2,553✔
232
        t->header = false;
2,553✔
233

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

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

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

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

246
        return TAKE_PTR(t);
2,553✔
247
}
248

249
static TableData *table_data_free(TableData *d) {
145,405✔
250
        assert(d);
145,405✔
251

252
        free(d->formatted);
145,405✔
253
        free(d->url);
145,405✔
254

255
        if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
145,405✔
256
                strv_free(d->strv);
5,613✔
257

258
        return mfree(d);
145,405✔
259
}
260

261
DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
243,327✔
262
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
158,905✔
263

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

268
        for (size_t i = 0; i < t->n_cells; i++)
162,119✔
269
                table_data_unref(t->data[i]);
158,917✔
270

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

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

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

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

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

286
        switch (type) {
387,175✔
287

288
        case TABLE_EMPTY:
289
                return 0;
290

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

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

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

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

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

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

329
        case TABLE_INT16:
100✔
330
        case TABLE_UINT16:
331
                return sizeof(uint16_t);
100✔
332

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

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

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

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

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

354
        case TABLE_UID:
1,242✔
355
                return sizeof(uid_t);
1,242✔
356
        case TABLE_GID:
1,551✔
357
                return sizeof(gid_t);
1,551✔
358
        case TABLE_PID:
574✔
359
                return sizeof(pid_t);
574✔
360

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

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

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

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

384
        size_t k, l;
148,652✔
385
        assert(d);
148,652✔
386

387
        if (d->type != type)
148,652✔
388
                return false;
389

390
        if (d->minimum_width != minimum_width)
125,794✔
391
                return false;
392

393
        if (d->maximum_width != maximum_width)
125,794✔
394
                return false;
395

396
        if (d->weight != weight)
124,437✔
397
                return false;
398

399
        if (d->align_percent != align_percent)
124,437✔
400
                return false;
401

402
        if (d->ellipsize_percent != ellipsize_percent)
124,437✔
403
                return false;
404

405
        if (d->uppercase != uppercase)
124,437✔
406
                return false;
407

408
        /* If a color/url is set, refuse to merge */
409
        if (d->color || d->rgap_color || d->underline || d->rgap_underline)
124,436✔
410
                return false;
411
        if (d->url)
122,469✔
412
                return false;
413

414
        k = table_data_size(type, data);
120,885✔
415
        l = table_data_size(d->type, d->data);
120,885✔
416
        if (k != l)
120,885✔
417
                return false;
418

419
        return memcmp_safe(data, d->data, l) == 0;
70,371✔
420
}
421

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

432
        _cleanup_free_ TableData *d = NULL;
145,405✔
433
        size_t data_size;
145,405✔
434

435
        data_size = table_data_size(type, data);
145,405✔
436

437
        d = malloc0(offsetof(TableData, data) + data_size);
145,405✔
438
        if (!d)
145,405✔
439
                return NULL;
440

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

450
        if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
145,405✔
451
                d->strv = strv_copy(data);
5,613✔
452
                if (!d->strv)
5,613✔
453
                        return NULL;
×
454
        } else
455
                memcpy_safe(d->data, data, data_size);
139,792✔
456

457
        return TAKE_PTR(d);
458
}
459

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

471
        _cleanup_(table_data_unrefp) TableData *d = NULL;
158,905✔
472
        bool uppercase;
158,905✔
473
        TableData *p;
158,905✔
474

475
        assert(t);
158,905✔
476
        assert(type >= 0);
158,905✔
477
        assert(type < _TABLE_DATA_TYPE_MAX);
158,905✔
478

479
        /* Special rule: patch NULL data fields to the empty field */
480
        if (!data)
158,905✔
481
                type = TABLE_EMPTY;
8,726✔
482

483
        /* Determine the cell adjacent to the current one, but one row up */
484
        if (t->n_cells >= t->n_columns)
158,905✔
485
                assert_se(p = t->data[t->n_cells - t->n_columns]);
148,652✔
486
        else
487
                p = NULL;
488

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

493
        if (weight == UINT_MAX)
158,905✔
494
                weight = p ? p->weight : DEFAULT_WEIGHT;
158,905✔
495

496
        if (align_percent == UINT_MAX)
158,905✔
497
                align_percent = p ? p->align_percent : 0;
158,905✔
498

499
        if (ellipsize_percent == UINT_MAX)
158,905✔
500
                ellipsize_percent = p ? p->ellipsize_percent : 100;
158,905✔
501

502
        assert(align_percent <= 100);
158,905✔
503
        assert(ellipsize_percent <= 100);
158,905✔
504

505
        uppercase = type == TABLE_HEADER;
158,905✔
506

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

510
        if (p && table_data_matches(p, type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase))
158,905✔
511
                d = table_data_ref(p);
48,949✔
512
        else {
513
                d = table_data_new(type, data, minimum_width, maximum_width, weight, align_percent, ellipsize_percent, uppercase);
109,956✔
514
                if (!d)
109,956✔
515
                        return -ENOMEM;
516
        }
517

518
        if (!GREEDY_REALLOC(t->data, MAX(t->n_cells + 1, t->n_columns)))
158,905✔
519
                return -ENOMEM;
520

521
        if (ret_cell)
158,905✔
522
                *ret_cell = TABLE_INDEX_TO_CELL(t->n_cells);
141,902✔
523

524
        t->data[t->n_cells++] = TAKE_PTR(d);
158,905✔
525

526
        return 0;
158,905✔
527
}
528

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

534
        assert(t);
6,752✔
535
        assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER));
6,752✔
536

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

543
        return table_add_cell(t, ret_cell, dt, buffer);
6,752✔
544
}
545

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

549
        assert(t);
52✔
550

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

554
        if (until_column >= t->n_columns)
52✔
555
                return -EINVAL;
556

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

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

564
        return 0;
565
}
566

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

570
        assert(t);
12✔
571

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

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

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

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

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

590
        assert(t);
138,436✔
591

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

595
        i = TABLE_CELL_TO_INDEX(cell);
138,436✔
596
        if (i >= t->n_cells)
138,436✔
597
                return -ENXIO;
598

599
        assert_se(od = t->data[i]);
138,436✔
600
        if (od->n_ref == 1)
138,436✔
601
                return 0;
602

603
        assert(od->n_ref > 1);
34,326✔
604

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

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

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

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

632
        assert(nd->n_ref == 1);
34,326✔
633

634
        return 1;
635
}
636

637
static TableData *table_get_data(Table *t, TableCell *cell) {
142,810✔
638
        size_t i;
142,810✔
639

640
        assert(t);
142,810✔
641
        assert(cell);
142,810✔
642

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

645
        i = TABLE_CELL_TO_INDEX(cell);
142,810✔
646
        if (i >= t->n_cells)
142,810✔
647
                return NULL;
648

649
        assert(t->data[i]);
142,810✔
650
        assert(t->data[i]->n_ref > 0);
142,810✔
651

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

703
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent) {
12,525✔
704
        int r;
12,525✔
705

706
        assert(t);
12,525✔
707
        assert(cell);
12,525✔
708

709
        if (percent == UINT_MAX)
12,525✔
710
                percent = 0;
711

712
        assert(percent <= 100);
12,525✔
713

714
        r = table_dedup_cell(t, cell);
12,525✔
715
        if (r < 0)
12,525✔
716
                return r;
717

718
        table_get_data(t, cell)->align_percent = percent;
12,525✔
719
        return 0;
12,525✔
720
}
721

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

725
        assert(t);
2,944✔
726
        assert(cell);
2,944✔
727

728
        if (percent == UINT_MAX)
2,944✔
729
                percent = 100;
730

731
        assert(percent <= 100);
2,944✔
732

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

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

741
int table_set_color(Table *t, TableCell *cell, const char *color) {
41,544✔
742
        int r;
41,544✔
743

744
        assert(t);
41,544✔
745
        assert(cell);
41,544✔
746

747
        r = table_dedup_cell(t, cell);
41,544✔
748
        if (r < 0)
41,544✔
749
                return r;
750

751
        table_get_data(t, cell)->color = empty_to_null(color);
55,638✔
752
        return 0;
41,544✔
753
}
754

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

758
        assert(t);
3,222✔
759
        assert(cell);
3,222✔
760

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

765
        table_get_data(t, cell)->rgap_color = empty_to_null(color);
6,444✔
766
        return 0;
3,222✔
767
}
768

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

773
        assert(t);
36,848✔
774
        assert(cell);
36,848✔
775

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

780
        assert_se(d = table_get_data(t, cell));
36,848✔
781

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

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

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

793
        assert(t);
36,848✔
794
        assert(cell);
36,848✔
795

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

800
        assert_se(d = table_get_data(t, cell));
36,848✔
801

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

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

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

813
        assert(t);
1,651✔
814
        assert(cell);
1,651✔
815

816
        if (url) {
1,651✔
817
                copy = strdup(url);
1,639✔
818
                if (!copy)
1,639✔
819
                        return -ENOMEM;
820
        }
821

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

891
        return 0;
1,123✔
892
}
893

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

899
        assert(t);
39,157✔
900
        assert(first_type >= 0);
39,157✔
901
        assert(first_type < _TABLE_DATA_TYPE_MAX);
39,157✔
902

903
        va_start(ap, first_type);
39,157✔
904

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

932
                switch (type) {
261,830✔
933

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1204
                case _TABLE_DATA_TYPE_MAX:
39,157✔
1205
                        /* Used as end marker */
1206
                        va_end(ap);
39,157✔
1207
                        return 0;
39,157✔
1208

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

1213
                r = table_add_cell(t, &last_cell, type, data);
132,071✔
1214
        check:
222,673✔
1215
                if (r < 0) {
222,673✔
1216
                        va_end(ap);
×
1217
                        return r;
×
1218
                }
1219
        }
1220
}
1221

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

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

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

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

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

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

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

1245
        t->ersatz = ersatz;
602✔
1246
}
602✔
1247

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

1263
static int table_set_display_all(Table *t) {
291✔
1264
        size_t *d;
291✔
1265

1266
        assert(t);
291✔
1267

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

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

1274
        for (size_t i = 0; i < t->n_columns; i++)
3,700✔
1275
                d[i] = i;
3,409✔
1276

1277
        t->display_map = d;
291✔
1278
        t->n_display_map = t->n_columns;
291✔
1279

1280
        return 0;
291✔
1281
}
1282

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

1287
        assert(t);
31✔
1288

1289
        column = first_column;
31✔
1290

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

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

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

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

1306
        }
1307
        va_end(ap);
31✔
1308

1309
        return 0;
31✔
1310
}
1311

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

1316
        assert(t);
218✔
1317

1318
        column = first_column;
218✔
1319

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

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

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

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

1337
        return 0;
218✔
1338
}
1339

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

1344
        assert(t);
575✔
1345

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

1353
        FOREACH_ARRAY(i, t->display_map, t->n_display_map) {
7,669✔
1354
                bool listed = false;
7,094✔
1355
                va_list ap;
7,094✔
1356

1357
                va_start(ap, t);
7,094✔
1358
                for (;;) {
13,735✔
1359
                        size_t column;
13,735✔
1360

1361
                        column = va_arg(ap, size_t);
13,735✔
1362
                        if (column == SIZE_MAX)
13,735✔
1363
                                break;
1364
                        if (column == *i) {
7,229✔
1365
                                listed = true;
1366
                                break;
1367
                        }
1368
                }
1369
                va_end(ap);
7,094✔
1370

1371
                if (listed)
7,094✔
1372
                        continue;
588✔
1373

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

1377
        t->n_display_map = cur;
575✔
1378

1379
        return 0;
575✔
1380
}
1381

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

1385
        assert(a);
11,294✔
1386
        assert(b);
11,294✔
1387

1388
        if (a->type == b->type) {
11,294✔
1389

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

1393
                switch (a->type) {
11,288✔
1394

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1513
        assert(t);
11,493✔
1514
        assert(t->sort_map);
11,493✔
1515

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

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

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

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

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

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

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

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

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

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

1569
        return buf;
116✔
1570
}
1571

1572
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
272,491✔
1573
        assert(d);
272,491✔
1574

1575
        if (d->formatted &&
272,491✔
1576
            /* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
1577
            (d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
55,310✔
1578
                return d->formatted;
1579

1580
        switch (d->type) {
217,276✔
1581
        case TABLE_EMPTY:
12,182✔
1582
                return table_ersatz_string(t);
12,182✔
1583

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

1592
                if (d->type == TABLE_PATH_BASENAME)
179,414✔
1593
                        s = path_extract_filename(d->string, &bn) < 0 ? d->string : bn;
337✔
1594
                else
1595
                        s = d->string;
179,077✔
1596

1597
                if (d->uppercase && !avoid_uppercasing) {
179,414✔
1598
                        d->formatted = new(char, strlen(s) + (d->type == TABLE_FIELD) + 1);
3,248✔
1599
                        if (!d->formatted)
3,248✔
1600
                                return NULL;
1601

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

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

1609
                        *q = 0;
3,248✔
1610
                        return d->formatted;
3,248✔
1611
                } else if (d->type == TABLE_FIELD) {
176,166✔
1612
                        d->formatted = strjoin(s, ":");
27,826✔
1613
                        if (!d->formatted)
27,826✔
1614
                                return NULL;
1615

1616
                        return d->formatted;
27,826✔
1617
                }
1618

1619
                if (bn) {
148,340✔
1620
                        d->formatted = TAKE_PTR(bn);
335✔
1621
                        return d->formatted;
335✔
1622
                }
1623

1624
                return d->string;
148,005✔
1625
        }
1626

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

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

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

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

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

1649
                break;
14,774✔
1650
        }
1651

1652
        case TABLE_BOOLEAN:
3,834✔
1653
                return yes_no(d->boolean);
274,002✔
1654

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

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

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

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

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

1692
        case TABLE_TIMESPAN:
1,193✔
1693
        case TABLE_TIMESPAN_MSEC:
1694
        case TABLE_TIMESPAN_DAY: {
1695
                _cleanup_free_ char *p = NULL;
×
1696

1697
                p = new(char, FORMAT_TIMESPAN_MAX);
1,193✔
1698
                if (!p)
1,193✔
1699
                        return NULL;
1700

1701
                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
2,379✔
1702
                                     d->type == TABLE_TIMESPAN ? 0 :
1,186✔
1703
                                     d->type == TABLE_TIMESPAN_MSEC ? USEC_PER_MSEC : USEC_PER_DAY))
1704
                        return "-";
1705

1706
                d->formatted = TAKE_PTR(p);
1,193✔
1707
                break;
1,193✔
1708
        }
1709

1710
        case TABLE_SIZE: {
258✔
1711
                _cleanup_free_ char *p = NULL;
140✔
1712

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1826
        case TABLE_UINT16: {
89✔
1827
                _cleanup_free_ char *p = NULL;
×
1828

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1931
        case TABLE_UUID: {
1932
                char *p;
292✔
1933

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

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

1942
        case TABLE_UID: {
390✔
1943
                char *p;
390✔
1944

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

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

1953
                d->formatted = p;
390✔
1954
                break;
390✔
1955
        }
1956

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

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

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

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

1972
        case TABLE_PID: {
217✔
1973
                char *p;
217✔
1974

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

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

1983
                d->formatted = p;
217✔
1984
                break;
217✔
1985
        }
1986

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

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

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

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

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

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

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

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

2018
        case TABLE_MODE_INODE_TYPE:
2✔
2019

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

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

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

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

2032
                break;
2033

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

2038
        return d->formatted;
14,774✔
2039
}
2040

2041
static int console_width_height(
133,309✔
2042
                const char *s,
2043
                size_t *ret_width,
2044
                size_t *ret_height) {
2045

2046
        size_t max_width = 0, height = 0;
133,309✔
2047
        const char *p;
133,309✔
2048

2049
        assert(s);
133,309✔
2050

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

2053
        do {
136,280✔
2054
                size_t k;
136,280✔
2055

2056
                p = strchr(s, '\n');
136,280✔
2057
                if (p) {
136,280✔
2058
                        _cleanup_free_ char *c = NULL;
2,974✔
2059

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

2064
                        k = utf8_console_width(c);
2,974✔
2065
                        s = p + 1;
2,974✔
2066
                } else {
2067
                        k = utf8_console_width(s);
133,306✔
2068
                        s = NULL;
133,306✔
2069
                }
2070
                if (k == SIZE_MAX)
136,280✔
2071
                        return -EINVAL;
2072
                if (k > max_width)
136,280✔
2073
                        max_width = k;
132,310✔
2074

2075
                height++;
136,280✔
2076
        } while (!isempty(s));
139,254✔
2077

2078
        if (ret_width)
133,309✔
2079
                *ret_width = max_width;
133,309✔
2080

2081
        if (ret_height)
133,309✔
2082
                *ret_height = height;
133,309✔
2083

2084
        return 0;
2085
}
2086

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

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

2102
        t = table_data_format(table, d, false, available_width, &soft);
133,309✔
2103
        if (!t)
133,309✔
2104
                return -ENOMEM;
2105

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

2113
                t = truncated;
162✔
2114
        }
2115

2116
        r = console_width_height(t, &width, &height);
133,309✔
2117
        if (r < 0)
133,309✔
2118
                return r;
2119

2120
        if (d->maximum_width != SIZE_MAX && width > d->maximum_width)
133,309✔
2121
                width = d->maximum_width;
×
2122

2123
        if (width < d->minimum_width)
133,309✔
2124
                width = d->minimum_width;
2,478✔
2125

2126
        if (ret_width)
133,309✔
2127
                *ret_width = width;
133,309✔
2128
        if (ret_height)
133,309✔
2129
                *ret_height = height;
133,309✔
2130
        if (have_soft && soft)
133,309✔
2131
                *have_soft = true;
72✔
2132

2133
        return truncation_applied;
133,309✔
2134
}
2135

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

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

2145
        assert(str);
104,790✔
2146
        assert(percent <= 100);
104,790✔
2147

2148
        old_length = strlen(str);
104,790✔
2149

2150
        if (url) {
104,790✔
2151
                r = terminal_urlify(url, str, &clickable);
1,549✔
2152
                if (r < 0)
1,549✔
2153
                        return NULL;
2154

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

2159
        /* Determine current width on screen */
2160
        p = str;
104,790✔
2161
        while (p < str + old_length) {
1,445,321✔
2162
                char32_t c;
1,340,531✔
2163

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

2169
                p = utf8_next_char(p);
1,340,531✔
2170
                w += unichar_iswide(c) ? 2 : 1;
2,681,055✔
2171
        }
2172

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

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

2181
        ret = new(char, space + clickable_length + 1);
104,790✔
2182
        if (!ret)
104,790✔
2183
                return NULL;
2184

2185
        for (size_t i = 0; i < lspace; i++)
638,276✔
2186
                ret[i] = ' ';
533,486✔
2187
        memcpy(ret + lspace, clickable ?: str, clickable_length);
104,790✔
2188
        for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
2,878,036✔
2189
                ret[i] = ' ';
2,773,246✔
2190

2191
        ret[space + clickable_length] = 0;
104,790✔
2192
        return ret;
104,790✔
2193
}
2194

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

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

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

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

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

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

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

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

2222
        return NULL;
2223
}
2224

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

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

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

2234
        return NULL;
2235
}
2236

2237
static const char* table_data_rgap_underline(const TableData *d) {
139,159✔
2238
        assert(d);
139,159✔
2239

2240
        if (d->rgap_underline)
139,159✔
2241
                return ansi_add_underline_grey();
300✔
2242

2243
        if (d->type == TABLE_HEADER)
138,859✔
2244
                return ansi_add_underline();
6,618✔
2245

2246
        return NULL;
2247
}
2248

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

2257
        assert(t);
3,116✔
2258

2259
        if (!f)
3,116✔
2260
                f = stdout;
2,312✔
2261

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

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

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

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

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

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

2281
        if (t->display_map)
3,116✔
2282
                display_columns = t->n_display_map;
279✔
2283
        else
2284
                display_columns = t->n_columns;
2,837✔
2285

2286
        assert(display_columns > 0);
3,116✔
2287

2288
        minimum_width = newa(size_t, display_columns);
3,116✔
2289
        maximum_width = newa(size_t, display_columns);
3,116✔
2290
        requested_width = newa(size_t, display_columns);
3,116✔
2291
        column_weight = newa0(uint64_t, display_columns);
3,116✔
2292

2293
        for (size_t j = 0; j < display_columns; j++) {
11,957✔
2294
                minimum_width[j] = 1;
8,841✔
2295
                maximum_width[j] = SIZE_MAX;
8,841✔
2296
        }
2297

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

2301
                for (size_t j = 0; j < display_columns; j++)
11,984✔
2302
                        requested_width[j] = SIZE_MAX;
8,859✔
2303

2304
                bool any_soft = false;
3,125✔
2305

2306
                for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
44,687✔
2307
                        TableData **row;
41,562✔
2308

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

2313
                        for (size_t j = 0; j < display_columns; j++) {
174,871✔
2314
                                TableData *d;
133,309✔
2315
                                size_t req_width, req_height;
133,309✔
2316

2317
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
133,309✔
2318

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

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

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

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

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

2349
                                /* Determine the biggest width that any cell in this column would like to have */
2350
                                if (requested_width[j] == SIZE_MAX ||
133,309✔
2351
                                    requested_width[j] < req_width)
124,482✔
2352
                                        requested_width[j] = req_width;
19,741✔
2353

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

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

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

2369
                /* One space between each column */
2370
                table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
3,125✔
2371

2372
                /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
2373
                weight_sum = 0;
3,125✔
2374
                for (size_t j = 0; j < display_columns; j++) {
11,984✔
2375
                        weight_sum += column_weight[j];
8,859✔
2376

2377
                        table_minimum_width += minimum_width[j];
8,859✔
2378

2379
                        if (maximum_width[j] == SIZE_MAX)
8,859✔
2380
                                table_maximum_width = SIZE_MAX;
2381
                        else
2382
                                table_maximum_width += maximum_width[j];
20✔
2383

2384
                        table_requested_width += requested_width[j];
8,859✔
2385
                }
2386

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

2396
                if (table_maximum_width != SIZE_MAX && table_effective_width > table_maximum_width)
3,125✔
2397
                        table_effective_width = table_maximum_width;
×
2398

2399
                if (table_effective_width < table_minimum_width)
3,125✔
2400
                        table_effective_width = table_minimum_width;
2✔
2401

2402
                if (!width)
3,125✔
2403
                        width = newa(size_t, display_columns);
3,116✔
2404

2405
                if (table_effective_width >= table_requested_width) {
3,125✔
2406
                        size_t extra;
3,112✔
2407

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

2411
                        extra = table_effective_width - table_requested_width;
3,112✔
2412

2413
                        for (size_t j = 0; j < display_columns; j++) {
11,935✔
2414
                                size_t delta;
8,823✔
2415

2416
                                if (weight_sum == 0)
8,823✔
2417
                                        width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
32✔
2418
                                else
2419
                                        width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
8,791✔
2420

2421
                                if (maximum_width[j] != SIZE_MAX && width[j] > maximum_width[j])
8,823✔
2422
                                        width[j] = maximum_width[j];
2✔
2423

2424
                                if (width[j] < minimum_width[j])
8,823✔
2425
                                        width[j] = minimum_width[j];
×
2426

2427
                                delta = LESS_BY(width[j], requested_width[j]);
8,823✔
2428

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

2435
                                assert(weight_sum >= column_weight[j]);
8,823✔
2436
                                weight_sum -= column_weight[j];
8,823✔
2437
                        }
2438

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

2446
                        extra = table_effective_width - table_minimum_width;
13✔
2447

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

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

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

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

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

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

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

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

2478
                                        width[j] = w;
36✔
2479

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

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

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

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

2493
                                if (finalize)
36✔
2494
                                        break;
2495

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

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

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

2513
                if (sorted)
41,528✔
2514
                        row = t->data + sorted[i];
2,880✔
2515
                else
2516
                        row = t->data + i * t->n_columns;
38,648✔
2517

2518
                do {
44,487✔
2519
                        const char *gap_color = NULL, *gap_underline = NULL;
44,487✔
2520
                        more_sublines = false;
44,487✔
2521

2522
                        for (size_t j = 0; j < display_columns; j++) {
183,646✔
2523
                                _cleanup_free_ char *buffer = NULL, *extracted = NULL;
139,159✔
2524
                                bool lines_truncated = false;
139,159✔
2525
                                const char *field, *color = NULL, *underline = NULL;
139,159✔
2526
                                TableData *d;
139,159✔
2527
                                size_t l;
139,159✔
2528

2529
                                assert_se(d = row[t->display_map ? t->display_map[j] : j]);
139,159✔
2530

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

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

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

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

2557
                                        field = buffer;
2558
                                } else {
2559
                                        if (lines_truncated) {
139,126✔
2560
                                                _cleanup_free_ char *padded = NULL;
23✔
2561

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

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

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

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

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

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

2588
                                                /* Drop trailing white spaces of last column when no cosmetics is set. */
2589
                                                if (j == display_columns - 1 &&
143,976✔
2590
                                                    (!colors_enabled() || !table_data_color(d)) &&
39,238✔
2591
                                                    (!underline_enabled() || !table_data_underline(d)) &&
78,422✔
2592
                                                    (!urlify_enabled() || !d->url))
39,234✔
2593
                                                        delete_trailing_chars(aligned, NULL);
39,184✔
2594

2595
                                                free_and_replace(buffer, aligned);
104,790✔
2596
                                                field = buffer;
104,790✔
2597
                                        }
2598
                                }
2599

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

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

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

2611
                                if (colors_enabled() && gap_color)
139,159✔
2612
                                        fputs(gap_color, f);
×
2613
                                if (underline_enabled() && gap_underline)
139,159✔
2614
                                        fputs(gap_underline, f);
16✔
2615

2616
                                if (j > 0)
139,159✔
2617
                                        fputc(' ', f); /* column separator left of cell */
94,672✔
2618

2619
                                /* Undo gap color/underline */
2620
                                if ((colors_enabled() && gap_color) ||
278,318✔
2621
                                    (underline_enabled() && gap_underline))
139,852✔
2622
                                        fputs(ANSI_NORMAL, f);
16✔
2623

2624
                                if (colors_enabled()) {
139,159✔
2625
                                        color = table_data_color(d);
693✔
2626
                                        if (color)
693✔
2627
                                                fputs(color, f);
288✔
2628
                                }
2629

2630
                                if (underline_enabled()) {
139,159✔
2631
                                        underline = table_data_underline(d);
693✔
2632
                                        if (underline)
693✔
2633
                                                fputs(underline, f);
18✔
2634
                                }
2635

2636
                                fputs(field, f);
139,159✔
2637

2638
                                if (color || underline)
139,159✔
2639
                                        fputs(ANSI_NORMAL, f);
306✔
2640

2641
                                gap_color = d->rgap_color;
139,159✔
2642
                                gap_underline = table_data_rgap_underline(d);
139,159✔
2643
                        }
2644

2645
                        fputc('\n', f);
44,487✔
2646
                        n_subline++;
44,487✔
2647
                } while (more_sublines);
44,487✔
2648
        }
2649

2650
        return fflush_and_check(f);
3,116✔
2651
}
2652

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2716
        assert(t);
8,017✔
2717

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

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

2725
        return TABLE_INDEX_TO_CELL(i);
8,017✔
2726
}
2727

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

2731
        assert(t);
4,374✔
2732

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2907
        assert(str);
710✔
2908

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

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

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

2929
        return c;
2930
}
2931

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3017
                        n = mangled;
694✔
3018
                }
3019

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3093
                                n = mangled;
2✔
3094
                        }
3095

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

3102
                n_elements++;
8✔
3103
        }
3104

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

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

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

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

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

3121
        assert(t);
396✔
3122

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

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

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

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

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

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

3144
        bool saved_header;
394✔
3145
        int r;
394✔
3146

3147
        assert(t);
394✔
3148

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

3152
        if (json_format_flags & (SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO))
394✔
3153
                pager_open(pager_flags);
369✔
3154

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

3162
        return 0;
3163
}
3164

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

3168
        assert(t);
380✔
3169

3170
        if (name) {
380✔
3171
                size_t m;
378✔
3172

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

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

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

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