]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/format-table.c
table: add TABLE_IN_ADDR and TABLE_IN6_ADDR
[thirdparty/systemd.git] / src / shared / format-table.c
index 959bfc53c4f1d441bd64ed22c4112f72c500125a..6de7da0df0c97d42fc41f0678fb0c6be4034a206 100644 (file)
@@ -1,16 +1,22 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#include <stdio_ext.h>
+#include <ctype.h>
+#include <net/if.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
+#include "format-util.h"
 #include "gunicode.h"
+#include "in-addr-util.h"
+#include "memory-util.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "pretty-print.h"
+#include "sort-util.h"
 #include "string-util.h"
+#include "strxcpyx.h"
 #include "terminal-util.h"
 #include "time-util.h"
 #include "utf8.h"
@@ -58,6 +64,8 @@ typedef struct TableData {
         unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */
         unsigned align_percent;     /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
 
+        bool uppercase;             /* Uppercase string on display */
+
         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 */
         char *url;                  /* A URL to use for a clickable hyperlink */
         char *formatted;            /* A cached textual representation of the cell data, before ellipsation/alignment */
@@ -69,7 +77,15 @@ typedef struct TableData {
                 usec_t timespan;
                 uint64_t size;
                 char string[0];
+                int int_val;
+                int32_t int32;
+                int64_t int64;
+                unsigned uint_val;
                 uint32_t uint32;
+                uint64_t uint64;
+                int percent;        /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
+                int ifindex;
+                union in_addr_union address;
                 /* … add more here as we start supporting more cell data types … */
         };
 } TableData;
@@ -105,6 +121,8 @@ struct Table {
 
         size_t *sort_map;     /* The columns to order rows by, in order of preference. */
         size_t n_sort_map;
+
+        bool *reverse_map;
 };
 
 Table *table_new_raw(size_t n_columns) {
@@ -128,6 +146,7 @@ Table *table_new_raw(size_t n_columns) {
 Table *table_new_internal(const char *first_header, ...) {
         _cleanup_(table_unrefp) Table *t = NULL;
         size_t n_columns = 1;
+        const char *h;
         va_list ap;
         int r;
 
@@ -135,8 +154,6 @@ Table *table_new_internal(const char *first_header, ...) {
 
         va_start(ap, first_header);
         for (;;) {
-                const char *h;
-
                 h = va_arg(ap, const char*);
                 if (!h)
                         break;
@@ -149,19 +166,18 @@ Table *table_new_internal(const char *first_header, ...) {
         if (!t)
                 return NULL;
 
-        r = table_add_cell(t, NULL, TABLE_STRING, first_header);
-        if (r < 0)
-                return NULL;
-
         va_start(ap, first_header);
-        for (;;) {
-                const char *h;
+        for (h = first_header; h; h = va_arg(ap, const char*)) {
+                TableCell *cell;
 
-                h = va_arg(ap, const char*);
-                if (!h)
-                        break;
+                r = table_add_cell(t, &cell, TABLE_STRING, h);
+                if (r < 0) {
+                        va_end(ap);
+                        return NULL;
+                }
 
-                r = table_add_cell(t, NULL, TABLE_STRING, h);
+                /* Make the table header uppercase */
+                r = table_set_uppercase(t, cell, true);
                 if (r < 0) {
                         va_end(ap);
                         return NULL;
@@ -197,6 +213,7 @@ Table *table_unref(Table *t) {
         free(t->data);
         free(t->display_map);
         free(t->sort_map);
+        free(t->reverse_map);
 
         return mfree(t);
 }
@@ -215,15 +232,34 @@ static size_t table_data_size(TableDataType type, const void *data) {
                 return sizeof(bool);
 
         case TABLE_TIMESTAMP:
+        case TABLE_TIMESTAMP_UTC:
+        case TABLE_TIMESTAMP_RELATIVE:
         case TABLE_TIMESPAN:
+        case TABLE_TIMESPAN_MSEC:
                 return sizeof(usec_t);
 
         case TABLE_SIZE:
+        case TABLE_INT64:
+        case TABLE_UINT64:
+        case TABLE_BPS:
                 return sizeof(uint64_t);
 
+        case TABLE_INT32:
         case TABLE_UINT32:
                 return sizeof(uint32_t);
 
+        case TABLE_INT:
+        case TABLE_UINT:
+        case TABLE_PERCENT:
+        case TABLE_IFINDEX:
+                return sizeof(int);
+
+        case TABLE_IN_ADDR:
+                return sizeof(struct in_addr);
+
+        case TABLE_IN6_ADDR:
+                return sizeof(struct in6_addr);
+
         default:
                 assert_not_reached("Uh? Unexpected cell type");
         }
@@ -260,13 +296,21 @@ static bool table_data_matches(
         if (d->ellipsize_percent != ellipsize_percent)
                 return false;
 
+        /* If a color/url/uppercase flag is set, refuse to merge */
+        if (d->color)
+                return false;
+        if (d->url)
+                return false;
+        if (d->uppercase)
+                return false;
+
         k = table_data_size(type, data);
         l = table_data_size(d->type, d->data);
 
         if (k != l)
                 return false;
 
-        return memcmp(data, d->data, l) == 0;
+        return memcmp_safe(data, d->data, l) == 0;
 }
 
 static TableData *table_data_new(
@@ -361,6 +405,20 @@ int table_add_cell_full(
         return 0;
 }
 
+int table_add_cell_stringf(Table *t, TableCell **ret_cell, const char *format, ...) {
+        _cleanup_free_ char *buffer = NULL;
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&buffer, format, ap);
+        va_end(ap);
+        if (r < 0)
+                return -ENOMEM;
+
+        return table_add_cell(t, ret_cell, TABLE_STRING, buffer);
+}
+
 int table_dup_cell(Table *t, TableCell *cell) {
         size_t i;
 
@@ -418,6 +476,7 @@ static int table_dedup_cell(Table *t, TableCell *cell) {
 
         nd->color = od->color;
         nd->url = TAKE_PTR(curl);
+        nd->uppercase = od->uppercase;
 
         table_data_unref(od);
         t->data[i] = nd;
@@ -565,9 +624,72 @@ int table_set_url(Table *t, TableCell *cell, const char *url) {
         return free_and_replace(table_get_data(t, cell)->url, copy);
 }
 
+int table_set_uppercase(Table *t, TableCell *cell, bool b) {
+        TableData *d;
+        int r;
+
+        assert(t);
+        assert(cell);
+
+        r = table_dedup_cell(t, cell);
+        if (r < 0)
+                return r;
+
+        assert_se(d = table_get_data(t, cell));
+
+        if (d->uppercase == b)
+                return 0;
+
+        d->formatted = mfree(d->formatted);
+        d->uppercase = b;
+        return 1;
+}
+
+int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
+        _cleanup_free_ char *curl = NULL;
+        TableData *nd, *od;
+        size_t i;
+
+        assert(t);
+        assert(cell);
+
+        i = TABLE_CELL_TO_INDEX(cell);
+        if (i >= t->n_cells)
+                return -ENXIO;
+
+        assert_se(od = t->data[i]);
+
+        if (od->url) {
+                curl = strdup(od->url);
+                if (!curl)
+                        return -ENOMEM;
+        }
+
+        nd = table_data_new(
+                        type,
+                        data,
+                        od->minimum_width,
+                        od->maximum_width,
+                        od->weight,
+                        od->align_percent,
+                        od->ellipsize_percent);
+        if (!nd)
+                return -ENOMEM;
+
+        nd->color = od->color;
+        nd->url = TAKE_PTR(curl);
+        nd->uppercase = od->uppercase;
+
+        table_data_unref(od);
+        t->data[i] = nd;
+
+        return 0;
+}
+
 int table_add_many_internal(Table *t, TableDataType first_type, ...) {
         TableDataType type;
         va_list ap;
+        TableCell *last_cell = NULL;
         int r;
 
         assert(t);
@@ -582,8 +704,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                 union {
                         uint64_t size;
                         usec_t usec;
+                        int int_val;
+                        int32_t int32;
+                        int64_t int64;
+                        unsigned uint_val;
                         uint32_t uint32;
+                        uint64_t uint64;
+                        int percent;
+                        int ifindex;
                         bool b;
+                        union in_addr_union address;
                 } buffer;
 
                 switch (type) {
@@ -602,21 +732,119 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         break;
 
                 case TABLE_TIMESTAMP:
+                case TABLE_TIMESTAMP_UTC:
+                case TABLE_TIMESTAMP_RELATIVE:
                 case TABLE_TIMESPAN:
+                case TABLE_TIMESPAN_MSEC:
                         buffer.usec = va_arg(ap, usec_t);
                         data = &buffer.usec;
                         break;
 
                 case TABLE_SIZE:
+                case TABLE_BPS:
                         buffer.size = va_arg(ap, uint64_t);
                         data = &buffer.size;
                         break;
 
+                case TABLE_INT:
+                        buffer.int_val = va_arg(ap, int);
+                        data = &buffer.int_val;
+                        break;
+
+                case TABLE_INT32:
+                        buffer.int32 = va_arg(ap, int32_t);
+                        data = &buffer.int32;
+                        break;
+
+                case TABLE_INT64:
+                        buffer.int64 = va_arg(ap, int64_t);
+                        data = &buffer.int64;
+                        break;
+
+                case TABLE_UINT:
+                        buffer.uint_val = va_arg(ap, unsigned);
+                        data = &buffer.uint_val;
+                        break;
+
                 case TABLE_UINT32:
                         buffer.uint32 = va_arg(ap, uint32_t);
                         data = &buffer.uint32;
                         break;
 
+                case TABLE_UINT64:
+                        buffer.uint64 = va_arg(ap, uint64_t);
+                        data = &buffer.uint64;
+                        break;
+
+                case TABLE_PERCENT:
+                        buffer.percent = va_arg(ap, int);
+                        data = &buffer.percent;
+                        break;
+
+                case TABLE_IFINDEX:
+                        buffer.ifindex = va_arg(ap, int);
+                        data = &buffer.ifindex;
+                        break;
+
+                case TABLE_IN_ADDR:
+                        buffer.address = *va_arg(ap, union in_addr_union *);
+                        data = &buffer.address.in;
+                        break;
+
+                case TABLE_IN6_ADDR:
+                        buffer.address = *va_arg(ap, union in_addr_union *);
+                        data = &buffer.address.in6;
+                        break;
+
+                case TABLE_SET_MINIMUM_WIDTH: {
+                        size_t w = va_arg(ap, size_t);
+
+                        r = table_set_minimum_width(t, last_cell, w);
+                        break;
+                }
+
+                case TABLE_SET_MAXIMUM_WIDTH: {
+                        size_t w = va_arg(ap, size_t);
+                        r = table_set_maximum_width(t, last_cell, w);
+                        break;
+                }
+
+                case TABLE_SET_WEIGHT: {
+                        unsigned w = va_arg(ap, unsigned);
+                        r = table_set_weight(t, last_cell, w);
+                        break;
+                }
+
+                case TABLE_SET_ALIGN_PERCENT: {
+                        unsigned p = va_arg(ap, unsigned);
+                        r = table_set_align_percent(t, last_cell, p);
+                        break;
+                }
+
+                case TABLE_SET_ELLIPSIZE_PERCENT: {
+                        unsigned p = va_arg(ap, unsigned);
+                        r = table_set_ellipsize_percent(t, last_cell, p);
+                        break;
+                }
+
+                case TABLE_SET_COLOR: {
+                        const char *c = va_arg(ap, const char*);
+                        r = table_set_color(t, last_cell, c);
+                        break;
+                }
+
+                case TABLE_SET_URL: {
+                        const char *u = va_arg(ap, const char*);
+                        r = table_set_url(t, last_cell, u);
+                        break;
+                }
+
+                case TABLE_SET_UPPERCASE: {
+                        int u = va_arg(ap, int);
+                        r = table_set_uppercase(t, last_cell, u);
+                        break;
+                }
+
                 case _TABLE_DATA_TYPE_MAX:
                         /* Used as end marker */
                         va_end(ap);
@@ -626,7 +854,9 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         assert_not_reached("Uh? Unexpected data type.");
                 }
 
-                r = table_add_cell(t, NULL, type, data);
+                if (type < _TABLE_DATA_TYPE_MAX)
+                        r = table_add_cell(t, &last_cell, type, data);
+
                 if (r < 0) {
                         va_end(ap);
                         return r;
@@ -729,23 +959,54 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                         return 0;
 
                 case TABLE_TIMESTAMP:
+                case TABLE_TIMESTAMP_UTC:
+                case TABLE_TIMESTAMP_RELATIVE:
                         return CMP(a->timestamp, b->timestamp);
 
                 case TABLE_TIMESPAN:
+                case TABLE_TIMESPAN_MSEC:
                         return CMP(a->timespan, b->timespan);
 
                 case TABLE_SIZE:
+                case TABLE_BPS:
                         return CMP(a->size, b->size);
 
+                case TABLE_INT:
+                        return CMP(a->int_val, b->int_val);
+
+                case TABLE_INT32:
+                        return CMP(a->int32, b->int32);
+
+                case TABLE_INT64:
+                        return CMP(a->int64, b->int64);
+
+                case TABLE_UINT:
+                        return CMP(a->uint_val, b->uint_val);
+
                 case TABLE_UINT32:
                         return CMP(a->uint32, b->uint32);
 
+                case TABLE_UINT64:
+                        return CMP(a->uint64, b->uint64);
+
+                case TABLE_PERCENT:
+                        return CMP(a->percent, b->percent);
+
+                case TABLE_IFINDEX:
+                        return CMP(a->ifindex, b->ifindex);
+
+                case TABLE_IN_ADDR:
+                        return CMP(a->address.in.s_addr, b->address.in.s_addr);
+
+                case TABLE_IN6_ADDR:
+                        return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
+
                 default:
                         ;
                 }
         }
 
-        /* Generic fallback using the orginal order in which the cells where added. */
+        /* Generic fallback using the original order in which the cells where added. */
         return CMP(index_a, index_b);
 }
 
@@ -773,7 +1034,7 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
 
                 r = cell_data_compare(d, *a, dd, *b);
                 if (r != 0)
-                        return r;
+                        return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
         }
 
         /* Order identical lines by the order there were originally added in */
@@ -791,33 +1052,58 @@ static const char *table_data_format(TableData *d) {
                 return "";
 
         case TABLE_STRING:
+                if (d->uppercase) {
+                        char *p, *q;
+
+                        d->formatted = new(char, strlen(d->string) + 1);
+                        if (!d->formatted)
+                                return NULL;
+
+                        for (p = d->string, q = d->formatted; *p; p++, q++)
+                                *q = (char) toupper((unsigned char) *p);
+                        *q = 0;
+
+                        return d->formatted;
+                }
+
                 return d->string;
 
         case TABLE_BOOLEAN:
                 return yes_no(d->boolean);
 
-        case TABLE_TIMESTAMP: {
+        case TABLE_TIMESTAMP:
+        case TABLE_TIMESTAMP_UTC:
+        case TABLE_TIMESTAMP_RELATIVE: {
                 _cleanup_free_ char *p;
+                char *ret;
 
                 p = new(char, FORMAT_TIMESTAMP_MAX);
                 if (!p)
                         return NULL;
 
-                if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp))
+                if (d->type == TABLE_TIMESTAMP)
+                        ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
+                else if (d->type == TABLE_TIMESTAMP_UTC)
+                        ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
+                else
+                        ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp);
+                if (!ret)
                         return "n/a";
 
                 d->formatted = TAKE_PTR(p);
                 break;
         }
 
-        case TABLE_TIMESPAN: {
+        case TABLE_TIMESPAN:
+        case TABLE_TIMESPAN_MSEC: {
                 _cleanup_free_ char *p;
 
                 p = new(char, FORMAT_TIMESPAN_MAX);
                 if (!p)
                         return NULL;
 
-                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timestamp, 0))
+                if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan,
+                                     d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC))
                         return "n/a";
 
                 d->formatted = TAKE_PTR(p);
@@ -838,6 +1124,72 @@ static const char *table_data_format(TableData *d) {
                 break;
         }
 
+        case TABLE_BPS: {
+                _cleanup_free_ char *p;
+                size_t n;
+
+                p = new(char, FORMAT_BYTES_MAX+2);
+                if (!p)
+                        return NULL;
+
+                if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
+                        return "n/a";
+
+                n = strlen(p);
+                strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_INT: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int_val) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%i", d->int_val);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_INT32: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int32) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIi32, d->int32);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_INT64: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->int64) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIi64, d->int64);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_UINT: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->uint_val) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%u", d->uint_val);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
         case TABLE_UINT32: {
                 _cleanup_free_ char *p;
 
@@ -850,6 +1202,59 @@ static const char *table_data_format(TableData *d) {
                 break;
         }
 
+        case TABLE_UINT64: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%" PRIu64, d->uint64);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_PERCENT: {
+                _cleanup_free_ char *p;
+
+                p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
+                if (!p)
+                        return NULL;
+
+                sprintf(p, "%i%%" , d->percent);
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_IFINDEX: {
+                _cleanup_free_ char *p;
+                char name[IF_NAMESIZE + 1];
+
+                if (format_ifname(d->ifindex, name)) {
+                        p = strdup(name);
+                        if (!p)
+                                return NULL;
+                } else {
+                        if (asprintf(&p, "%i" , d->ifindex) < 0)
+                                return NULL;
+                }
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
+        case TABLE_IN_ADDR:
+        case TABLE_IN6_ADDR: {
+                _cleanup_free_ char *p = NULL;
+
+                if (in_addr_to_string(d->type == TABLE_IN_ADDR ? AF_INET : AF_INET6,
+                                      &d->address, &p) < 0)
+                        return NULL;
+
+                d->formatted = TAKE_PTR(p);
+                break;
+        }
+
         default:
                 assert_not_reached("Unexpected type?");
         }
@@ -1206,15 +1611,22 @@ int table_print(Table *t, FILE *f) {
                                 field = buffer;
                         }
 
+                        if (row == t->data) /* underline header line fully, including the column separator */
+                                fputs(ansi_underline(), f);
+
                         if (j > 0)
                                 fputc(' ', f); /* column separator */
 
-                        if (d->color && colors_enabled())
+                        if (d->color && colors_enabled()) {
+                                if (row == t->data) /* first undo header underliner */
+                                        fputs(ANSI_NORMAL, f);
+
                                 fputs(d->color, f);
+                        }
 
                         fputs(field, f);
 
-                        if (d->color && colors_enabled())
+                        if (colors_enabled() && (d->color || row == t->data))
                                 fputs(ANSI_NORMAL, f);
                 }
 
@@ -1230,12 +1642,10 @@ int table_format(Table *t, char **ret) {
         size_t sz = 0;
         int r;
 
-        f = open_memstream(&buf, &sz);
+        f = open_memstream_unlocked(&buf, &sz);
         if (!f)
                 return -ENOMEM;
 
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
         r = table_print(t, f);
         if (r < 0)
                 return r;
@@ -1262,3 +1672,245 @@ size_t table_get_columns(Table *t) {
         assert(t->n_columns > 0);
         return t->n_columns;
 }
+
+int table_set_reverse(Table *t, size_t column, bool b) {
+        assert(t);
+        assert(column < t->n_columns);
+
+        if (!t->reverse_map) {
+                if (!b)
+                        return 0;
+
+                t->reverse_map = new0(bool, t->n_columns);
+                if (!t->reverse_map)
+                        return -ENOMEM;
+        }
+
+        t->reverse_map[column] = b;
+        return 0;
+}
+
+TableCell *table_get_cell(Table *t, size_t row, size_t column) {
+        size_t i;
+
+        assert(t);
+
+        if (column >= t->n_columns)
+                return NULL;
+
+        i = row * t->n_columns + column;
+        if (i >= t->n_cells)
+                return NULL;
+
+        return TABLE_INDEX_TO_CELL(i);
+}
+
+const void *table_get(Table *t, TableCell *cell) {
+        TableData *d;
+
+        assert(t);
+
+        d = table_get_data(t, cell);
+        if (!d)
+                return NULL;
+
+        return d->data;
+}
+
+const void* table_get_at(Table *t, size_t row, size_t column) {
+        TableCell *cell;
+
+        cell = table_get_cell(t, row, column);
+        if (!cell)
+                return NULL;
+
+        return table_get(t, cell);
+}
+
+static int table_data_to_json(TableData *d, JsonVariant **ret) {
+
+        switch (d->type) {
+
+        case TABLE_EMPTY:
+                return json_variant_new_null(ret);
+
+        case TABLE_STRING:
+                return json_variant_new_string(ret, d->string);
+
+        case TABLE_BOOLEAN:
+                return json_variant_new_boolean(ret, d->boolean);
+
+        case TABLE_TIMESTAMP:
+        case TABLE_TIMESTAMP_UTC:
+        case TABLE_TIMESTAMP_RELATIVE:
+                if (d->timestamp == USEC_INFINITY)
+                        return json_variant_new_null(ret);
+
+                return json_variant_new_unsigned(ret, d->timestamp);
+
+        case TABLE_TIMESPAN:
+        case TABLE_TIMESPAN_MSEC:
+                if (d->timespan == USEC_INFINITY)
+                        return json_variant_new_null(ret);
+
+                return json_variant_new_unsigned(ret, d->timespan);
+
+        case TABLE_SIZE:
+        case TABLE_BPS:
+                if (d->size == (size_t) -1)
+                        return json_variant_new_null(ret);
+
+                return json_variant_new_unsigned(ret, d->size);
+
+        case TABLE_INT:
+                return json_variant_new_integer(ret, d->int_val);
+
+        case TABLE_INT32:
+                return json_variant_new_integer(ret, d->int32);
+
+        case TABLE_INT64:
+                return json_variant_new_integer(ret, d->int64);
+
+        case TABLE_UINT:
+                return json_variant_new_unsigned(ret, d->uint_val);
+
+        case TABLE_UINT32:
+                return json_variant_new_unsigned(ret, d->uint32);
+
+        case TABLE_UINT64:
+                return json_variant_new_unsigned(ret, d->uint64);
+
+        case TABLE_PERCENT:
+                return json_variant_new_integer(ret, d->percent);
+
+        case TABLE_IFINDEX:
+                return json_variant_new_integer(ret, d->ifindex);
+
+        case TABLE_IN_ADDR:
+                return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET));
+
+        case TABLE_IN6_ADDR:
+                return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
+
+        default:
+                return -EINVAL;
+        }
+}
+
+int table_to_json(Table *t, JsonVariant **ret) {
+        JsonVariant **rows = NULL, **elements = NULL;
+        _cleanup_free_ size_t *sorted = NULL;
+        size_t n_rows, i, j, display_columns;
+        int r;
+
+        assert(t);
+
+        /* Ensure we have no incomplete rows */
+        assert(t->n_cells % t->n_columns == 0);
+
+        n_rows = t->n_cells / t->n_columns;
+        assert(n_rows > 0); /* at least the header row must be complete */
+
+        if (t->sort_map) {
+                /* If sorting is requested, let's calculate an index table we use to lookup the actual index to display with. */
+
+                sorted = new(size_t, n_rows);
+                if (!sorted) {
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                for (i = 0; i < n_rows; i++)
+                        sorted[i] = i * t->n_columns;
+
+                typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
+        }
+
+        if (t->display_map)
+                display_columns = t->n_display_map;
+        else
+                display_columns = t->n_columns;
+        assert(display_columns > 0);
+
+        elements = new0(JsonVariant*, display_columns * 2);
+        if (!elements) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        for (j = 0; j < display_columns; j++) {
+                TableData *d;
+
+                assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
+
+                r = table_data_to_json(d, elements + j*2);
+                if (r < 0)
+                        goto finish;
+        }
+
+        rows = new0(JsonVariant*, n_rows-1);
+        if (!rows) {
+                r = -ENOMEM;
+                goto finish;
+        }
+
+        for (i = 1; i < n_rows; i++) {
+                TableData **row;
+
+                if (sorted)
+                        row = t->data + sorted[i];
+                else
+                        row = t->data + i * t->n_columns;
+
+                for (j = 0; j < display_columns; j++) {
+                        TableData *d;
+                        size_t k;
+
+                        assert_se(d = row[t->display_map ? t->display_map[j] : j]);
+
+                        k = j*2+1;
+                        elements[k] = json_variant_unref(elements[k]);
+
+                        r = table_data_to_json(d, elements + k);
+                        if (r < 0)
+                                goto finish;
+                }
+
+                r = json_variant_new_object(rows + i - 1, elements, display_columns * 2);
+                if (r < 0)
+                        goto finish;
+        }
+
+        r = json_variant_new_array(ret, rows, n_rows - 1);
+
+finish:
+        if (rows) {
+                json_variant_unref_many(rows, n_rows-1);
+                free(rows);
+        }
+
+        if (elements) {
+                json_variant_unref_many(elements, display_columns*2);
+                free(elements);
+        }
+
+        return r;
+}
+
+int table_print_json(Table *t, FILE *f, JsonFormatFlags flags) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
+        assert(t);
+
+        if (!f)
+                f = stdout;
+
+        r = table_to_json(t, &v);
+        if (r < 0)
+                return r;
+
+        json_variant_dump(v, flags, f, NULL);
+
+        return fflush_and_check(f);
+}