1
1
mirror of https://github.com/systemd/systemd-stable.git synced 2025-01-26 10:03:40 +03:00

Merge pull request #17324 from keszybz/resolvectl-compat-output

resolvectl compat output
This commit is contained in:
Lennart Poettering 2020-10-22 14:57:41 +02:00 committed by GitHub
commit e1da60e430
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 570 additions and 301 deletions

View File

@ -138,7 +138,17 @@
and follow the same overriding rules. They are text files with the and follow the same overriding rules. They are text files with the
<filename>.negative</filename> suffix. Empty lines and lines whose first character is <filename>.negative</filename> suffix. Empty lines and lines whose first character is
<literal>;</literal> are ignored. Each line specifies one domain name which is the root of a DNS <literal>;</literal> are ignored. Each line specifies one domain name which is the root of a DNS
subtree where validation shall be disabled.</para> subtree where validation shall be disabled. For example:</para>
<programlisting># Reverse IPv4 mappings
10.in-addr.arpa
16.172.in-addr.arpa
168.192.in-addr.arpa
...
# Some custom domains
prod
stag
</programlisting>
<para>Negative trust anchors are useful to support private DNS <para>Negative trust anchors are useful to support private DNS
subtrees that are not referenced from the Internet DNS hierarchy, subtrees that are not referenced from the Internet DNS hierarchy,

View File

@ -650,4 +650,8 @@ static inline int __coverity_check_and_return__(int condition) {
_copy; \ _copy; \
}) })
static inline size_t size_add(size_t x, size_t y) {
return y >= SIZE_MAX - x ? SIZE_MAX : x + y;
}
#include "log.h" #include "log.h"

View File

@ -59,6 +59,10 @@ static inline const char* true_false(bool b) {
return b ? "true" : "false"; return b ? "true" : "false";
} }
static inline const char* plus_minus(bool b) {
return b ? "+" : "-";
}
static inline const char* one_zero(bool b) { static inline const char* one_zero(bool b) {
return b ? "1" : "0"; return b ? "1" : "0";
} }

View File

@ -35,6 +35,7 @@
#include "string-table.h" #include "string-table.h"
#include "strv.h" #include "strv.h"
#include "terminal-util.h" #include "terminal-util.h"
#include "utf8.h"
#include "verbs.h" #include "verbs.h"
static int arg_family = AF_UNSPEC; static int arg_family = AF_UNSPEC;
@ -1296,24 +1297,46 @@ static int map_link_domains(sd_bus *bus, const char *member, sd_bus_message *m,
if (r < 0) if (r < 0)
return r; return r;
strv_sort(*l);
return 0; return 0;
} }
static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p) { static int status_print_strv_ifindex(int ifindex, const char *ifname, char **p) {
const unsigned indent = strlen("Global: "); /* Use the same indentation everywhere to make things nice */
int pos1, pos2;
if (ifname)
printf("%s%nLink %i (%s)%n%s:", ansi_highlight(), &pos1, ifindex, ifname, &pos2, ansi_normal());
else
printf("%s%nGlobal%n%s:", ansi_highlight(), &pos1, &pos2, ansi_normal());
size_t cols = columns(), position = pos2 - pos1 + 2;
char **i; char **i;
printf("%sLink %i (%s)%s:", STRV_FOREACH(i, p) {
ansi_highlight(), ifindex, ifname, ansi_normal()); size_t our_len = utf8_console_width(*i); /* This returns -1 on invalid utf-8 (which shouldn't happen).
* If that happens, we'll just print one item per line. */
STRV_FOREACH(i, p) if (position <= indent || size_add(size_add(position, 1), our_len) < cols) {
printf(" %s", *i); printf(" %s", *i);
position = size_add(size_add(position, 1), our_len);
} else {
printf("\n%*s%s", indent, "", *i);
position = size_add(our_len, indent);
}
}
printf("\n"); printf("\n");
return 0; return 0;
} }
struct link_info { static int status_print_strv_global(char **p) {
return status_print_strv_ifindex(0, NULL, p);
}
typedef struct LinkInfo {
uint64_t scopes_mask; uint64_t scopes_mask;
const char *llmnr; const char *llmnr;
const char *mdns; const char *mdns;
@ -1327,9 +1350,26 @@ struct link_info {
char **ntas; char **ntas;
bool dnssec_supported; bool dnssec_supported;
bool default_route; bool default_route;
}; } LinkInfo;
static void link_info_clear(struct link_info *p) { typedef struct GlobalInfo {
char *current_dns;
char *current_dns_ex;
char **dns;
char **dns_ex;
char **fallback_dns;
char **fallback_dns_ex;
char **domains;
char **ntas;
const char *llmnr;
const char *mdns;
const char *dns_over_tls;
const char *dnssec;
const char *resolv_conf_mode;
bool dnssec_supported;
} GlobalInfo;
static void link_info_clear(LinkInfo *p) {
free(p->current_dns); free(p->current_dns);
free(p->current_dns_ex); free(p->current_dns_ex);
strv_free(p->dns); strv_free(p->dns);
@ -1338,6 +1378,17 @@ static void link_info_clear(struct link_info *p) {
strv_free(p->ntas); strv_free(p->ntas);
} }
static void global_info_clear(GlobalInfo *p) {
free(p->current_dns);
free(p->current_dns_ex);
strv_free(p->dns);
strv_free(p->dns_ex);
strv_free(p->fallback_dns);
strv_free(p->fallback_dns_ex);
strv_free(p->domains);
strv_free(p->ntas);
}
static int dump_list(Table *table, const char *prefix, char * const *l) { static int dump_list(Table *table, const char *prefix, char * const *l) {
int r; int r;
@ -1346,33 +1397,88 @@ static int dump_list(Table *table, const char *prefix, char * const *l) {
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, prefix, TABLE_STRING, prefix,
TABLE_STRV, l); TABLE_STRV_WRAPPED, l);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
return 0; return 0;
} }
static int strv_extend_extended_bool(char ***strv, const char *name, const char *value) {
int r;
if (value) {
r = parse_boolean(value);
if (r >= 0)
return strv_extendf(strv, "%s%s", plus_minus(r), name);
}
return strv_extendf(strv, "%s=%s", name, value ?: "???");
}
static char** link_protocol_status(const LinkInfo *info) {
_cleanup_strv_free_ char **s = NULL;
if (strv_extendf(&s, "%sDefaultRoute", plus_minus(info->default_route)) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "LLMNR", info->llmnr) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "mDNS", info->mdns) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "DNSOverTLS", info->dns_over_tls) < 0)
return NULL;
if (strv_extendf(&s, "DNSSEC=%s/%s",
info->dnssec ?: "???",
info->dnssec_supported ? "supported" : "unsupported") < 0)
return NULL;
return TAKE_PTR(s);
}
static char** global_protocol_status(const GlobalInfo *info) {
_cleanup_strv_free_ char **s = NULL;
if (strv_extend_extended_bool(&s, "LLMNR", info->llmnr) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "mDNS", info->mdns) < 0)
return NULL;
if (strv_extend_extended_bool(&s, "DNSOverTLS", info->dns_over_tls) < 0)
return NULL;
if (strv_extendf(&s, "DNSSEC=%s/%s",
info->dnssec ?: "???",
info->dnssec_supported ? "supported" : "unsupported") < 0)
return NULL;
return TAKE_PTR(s);
}
static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) { static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode mode, bool *empty_line) {
static const struct bus_properties_map property_map[] = { static const struct bus_properties_map property_map[] = {
{ "ScopesMask", "t", NULL, offsetof(struct link_info, scopes_mask) }, { "ScopesMask", "t", NULL, offsetof(LinkInfo, scopes_mask) },
{ "DNS", "a(iay)", map_link_dns_servers, offsetof(struct link_info, dns) }, { "DNS", "a(iay)", map_link_dns_servers, offsetof(LinkInfo, dns) },
{ "DNSEx", "a(iayqs)", map_link_dns_servers_ex, offsetof(struct link_info, dns_ex) }, { "DNSEx", "a(iayqs)", map_link_dns_servers_ex, offsetof(LinkInfo, dns_ex) },
{ "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(struct link_info, current_dns) }, { "CurrentDNSServer", "(iay)", map_link_current_dns_server, offsetof(LinkInfo, current_dns) },
{ "CurrentDNSServerEx", "(iayqs)", map_link_current_dns_server_ex, offsetof(struct link_info, current_dns_ex) }, { "CurrentDNSServerEx", "(iayqs)", map_link_current_dns_server_ex, offsetof(LinkInfo, current_dns_ex) },
{ "Domains", "a(sb)", map_link_domains, offsetof(struct link_info, domains) }, { "Domains", "a(sb)", map_link_domains, offsetof(LinkInfo, domains) },
{ "DefaultRoute", "b", NULL, offsetof(struct link_info, default_route) }, { "DefaultRoute", "b", NULL, offsetof(LinkInfo, default_route) },
{ "LLMNR", "s", NULL, offsetof(struct link_info, llmnr) }, { "LLMNR", "s", NULL, offsetof(LinkInfo, llmnr) },
{ "MulticastDNS", "s", NULL, offsetof(struct link_info, mdns) }, { "MulticastDNS", "s", NULL, offsetof(LinkInfo, mdns) },
{ "DNSOverTLS", "s", NULL, offsetof(struct link_info, dns_over_tls) }, { "DNSOverTLS", "s", NULL, offsetof(LinkInfo, dns_over_tls) },
{ "DNSSEC", "s", NULL, offsetof(struct link_info, dnssec) }, { "DNSSEC", "s", NULL, offsetof(LinkInfo, dnssec) },
{ "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct link_info, ntas) }, { "DNSSECNegativeTrustAnchors", "as", bus_map_strv_sort, offsetof(LinkInfo, ntas) },
{ "DNSSECSupported", "b", NULL, offsetof(struct link_info, dnssec_supported) }, { "DNSSECSupported", "b", NULL, offsetof(LinkInfo, dnssec_supported) },
{} {}
}; };
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(link_info_clear) struct link_info link_info = {}; _cleanup_(link_info_clear) LinkInfo link_info = {};
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *p = NULL; _cleanup_free_ char *p = NULL;
char ifi[DECIMAL_STR_MAX(int)], ifname[IF_NAMESIZE + 1] = ""; char ifi[DECIMAL_STR_MAX(int)], ifname[IF_NAMESIZE + 1] = "";
@ -1496,19 +1602,13 @@ static int status_ifindex(sd_bus *bus, int ifindex, const char *name, StatusMode
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
_cleanup_strv_free_ char **pstatus = link_protocol_status(&link_info);
if (!pstatus)
return log_oom();
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, "DefaultRoute setting:", TABLE_STRING, "Protocols:",
TABLE_BOOLEAN, link_info.default_route, TABLE_STRV_WRAPPED, pstatus);
TABLE_STRING, "LLMNR setting:",
TABLE_STRING, strna(link_info.llmnr),
TABLE_STRING, "MulticastDNS setting:",
TABLE_STRING, strna(link_info.mdns),
TABLE_STRING, "DNSOverTLS setting:",
TABLE_STRING, strna(link_info.dns_over_tls),
TABLE_STRING, "DNSSEC setting:",
TABLE_STRING, strna(link_info.dnssec),
TABLE_STRING, "DNSSEC supported:",
TABLE_BOOLEAN, link_info.dnssec_supported);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);
@ -1631,71 +1731,32 @@ static int map_global_domains(sd_bus *bus, const char *member, sd_bus_message *m
if (r < 0) if (r < 0)
return r; return r;
return 0; strv_sort(*l);
}
static int status_print_strv_global(char **p) {
char **i;
printf("%sGlobal%s:", ansi_highlight(), ansi_normal());
STRV_FOREACH(i, p)
printf(" %s", *i);
printf("\n");
return 0; return 0;
} }
struct global_info {
char *current_dns;
char *current_dns_ex;
char **dns;
char **dns_ex;
char **fallback_dns;
char **fallback_dns_ex;
char **domains;
char **ntas;
const char *llmnr;
const char *mdns;
const char *dns_over_tls;
const char *dnssec;
const char *resolv_conf_mode;
bool dnssec_supported;
};
static void global_info_clear(struct global_info *p) {
free(p->current_dns);
free(p->current_dns_ex);
strv_free(p->dns);
strv_free(p->dns_ex);
strv_free(p->fallback_dns);
strv_free(p->fallback_dns_ex);
strv_free(p->domains);
strv_free(p->ntas);
}
static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) { static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) {
static const struct bus_properties_map property_map[] = { static const struct bus_properties_map property_map[] = {
{ "DNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, dns) }, { "DNS", "a(iiay)", map_global_dns_servers, offsetof(GlobalInfo, dns) },
{ "DNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(struct global_info, dns_ex) }, { "DNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(GlobalInfo, dns_ex) },
{ "FallbackDNS", "a(iiay)", map_global_dns_servers, offsetof(struct global_info, fallback_dns) }, { "FallbackDNS", "a(iiay)", map_global_dns_servers, offsetof(GlobalInfo, fallback_dns) },
{ "FallbackDNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(struct global_info, fallback_dns_ex) }, { "FallbackDNSEx", "a(iiayqs)", map_global_dns_servers_ex, offsetof(GlobalInfo, fallback_dns_ex) },
{ "CurrentDNSServer", "(iiay)", map_global_current_dns_server, offsetof(struct global_info, current_dns) }, { "CurrentDNSServer", "(iiay)", map_global_current_dns_server, offsetof(GlobalInfo, current_dns) },
{ "CurrentDNSServerEx", "(iiayqs)", map_global_current_dns_server_ex, offsetof(struct global_info, current_dns_ex) }, { "CurrentDNSServerEx", "(iiayqs)", map_global_current_dns_server_ex, offsetof(GlobalInfo, current_dns_ex) },
{ "Domains", "a(isb)", map_global_domains, offsetof(struct global_info, domains) }, { "Domains", "a(isb)", map_global_domains, offsetof(GlobalInfo, domains) },
{ "DNSSECNegativeTrustAnchors", "as", NULL, offsetof(struct global_info, ntas) }, { "DNSSECNegativeTrustAnchors", "as", bus_map_strv_sort, offsetof(GlobalInfo, ntas) },
{ "LLMNR", "s", NULL, offsetof(struct global_info, llmnr) }, { "LLMNR", "s", NULL, offsetof(GlobalInfo, llmnr) },
{ "MulticastDNS", "s", NULL, offsetof(struct global_info, mdns) }, { "MulticastDNS", "s", NULL, offsetof(GlobalInfo, mdns) },
{ "DNSOverTLS", "s", NULL, offsetof(struct global_info, dns_over_tls) }, { "DNSOverTLS", "s", NULL, offsetof(GlobalInfo, dns_over_tls) },
{ "DNSSEC", "s", NULL, offsetof(struct global_info, dnssec) }, { "DNSSEC", "s", NULL, offsetof(GlobalInfo, dnssec) },
{ "DNSSECSupported", "b", NULL, offsetof(struct global_info, dnssec_supported) }, { "DNSSECSupported", "b", NULL, offsetof(GlobalInfo, dnssec_supported) },
{ "ResolvConfMode", "s", NULL, offsetof(struct global_info, resolv_conf_mode) }, { "ResolvConfMode", "s", NULL, offsetof(GlobalInfo, resolv_conf_mode) },
{} {}
}; };
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
_cleanup_(global_info_clear) struct global_info global_info = {}; _cleanup_(global_info_clear) GlobalInfo global_info = {};
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
int r; int r;
@ -1760,18 +1821,14 @@ static int status_global(sd_bus *bus, StatusMode mode, bool *empty_line) {
table_set_header(table, false); table_set_header(table, false);
_cleanup_strv_free_ char **pstatus = global_protocol_status(&global_info);
if (!pstatus)
return log_oom();
r = table_add_many(table, r = table_add_many(table,
TABLE_STRING, "LLMNR setting:", TABLE_STRING, "Protocols:",
TABLE_SET_ALIGN_PERCENT, 100, TABLE_SET_ALIGN_PERCENT, 100,
TABLE_STRING, strna(global_info.llmnr), TABLE_STRV_WRAPPED, pstatus);
TABLE_STRING, "MulticastDNS setting:",
TABLE_STRING, strna(global_info.mdns),
TABLE_STRING, "DNSOverTLS setting:",
TABLE_STRING, strna(global_info.dns_over_tls),
TABLE_STRING, "DNSSEC setting:",
TABLE_STRING, strna(global_info.dnssec),
TABLE_STRING, "DNSSEC supported:",
TABLE_BOOLEAN, global_info.dnssec_supported);
if (r < 0) if (r < 0)
return table_log_add_error(r); return table_log_add_error(r);

View File

@ -25,6 +25,23 @@ int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_err
return 0; return 0;
} }
int bus_map_strv_sort(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
_cleanup_strv_free_ char **l = NULL;
char ***p = userdata;
int r;
r = bus_message_read_strv_extend(m, &l);
if (r < 0)
return r;
r = strv_extend_strv(p, l, false);
if (r < 0)
return r;
strv_sort(*p);
return 0;
}
static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, unsigned flags, sd_bus_error *error, void *userdata) { static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, unsigned flags, sd_bus_error *error, void *userdata) {
char type; char type;
int r; int r;

View File

@ -18,6 +18,7 @@ enum {
}; };
int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
int bus_map_strv_sort(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata);
int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata); int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, unsigned flags, sd_bus_error *error, void *userdata);
int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map,

View File

@ -66,6 +66,7 @@ typedef struct TableData {
size_t minimum_width; /* minimum width for the column */ size_t minimum_width; /* minimum width for the column */
size_t maximum_width; /* maximum width for the column */ size_t maximum_width; /* maximum width for the column */
size_t formatted_for_width; /* the width we tried to format for */
unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */ unsigned weight; /* the horizontal weight for this column, in case the table is expanded/compressed */
unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */ 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 */ unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
@ -164,7 +165,6 @@ Table *table_new_raw(size_t n_columns) {
Table *table_new_internal(const char *first_header, ...) { Table *table_new_internal(const char *first_header, ...) {
_cleanup_(table_unrefp) Table *t = NULL; _cleanup_(table_unrefp) Table *t = NULL;
size_t n_columns = 1; size_t n_columns = 1;
const char *h;
va_list ap; va_list ap;
int r; int r;
@ -172,8 +172,7 @@ Table *table_new_internal(const char *first_header, ...) {
va_start(ap, first_header); va_start(ap, first_header);
for (;;) { for (;;) {
h = va_arg(ap, const char*); if (!va_arg(ap, const char*))
if (!h)
break; break;
n_columns++; n_columns++;
@ -185,7 +184,7 @@ Table *table_new_internal(const char *first_header, ...) {
return NULL; return NULL;
va_start(ap, first_header); va_start(ap, first_header);
for (h = first_header; h; h = va_arg(ap, const char*)) { for (const char *h = first_header; h; h = va_arg(ap, const char*)) {
TableCell *cell; TableCell *cell;
r = table_add_cell(t, &cell, TABLE_STRING, h); r = table_add_cell(t, &cell, TABLE_STRING, h);
@ -213,7 +212,7 @@ static TableData *table_data_free(TableData *d) {
free(d->formatted); free(d->formatted);
free(d->url); free(d->url);
if (d->type == TABLE_STRV) if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
strv_free(d->strv); strv_free(d->strv);
return mfree(d); return mfree(d);
@ -223,12 +222,10 @@ DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(TableData, table_data, table_data_free);
DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(TableData*, table_data_unref);
Table *table_unref(Table *t) { Table *table_unref(Table *t) {
size_t i;
if (!t) if (!t)
return NULL; return NULL;
for (i = 0; i < t->n_cells; i++) for (size_t i = 0; i < t->n_cells; i++)
table_data_unref(t->data[i]); table_data_unref(t->data[i]);
free(t->data); free(t->data);
@ -252,6 +249,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
return strlen(data) + 1; return strlen(data) + 1;
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return sizeof(char **); return sizeof(char **);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -376,7 +374,7 @@ static TableData *table_data_new(
d->align_percent = align_percent; d->align_percent = align_percent;
d->ellipsize_percent = ellipsize_percent; d->ellipsize_percent = ellipsize_percent;
if (type == TABLE_STRV) { if (IN_SET(type, TABLE_STRV, TABLE_STRV_WRAPPED)) {
d->strv = strv_copy(data); d->strv = strv_copy(data);
if (!d->strv) if (!d->strv)
return NULL; return NULL;
@ -817,6 +815,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break; break;
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
data = va_arg(ap, char * const *); data = va_arg(ap, char * const *);
break; break;
@ -1047,11 +1046,9 @@ int table_set_empty_string(Table *t, const char *empty) {
} }
int table_set_display_all(Table *t) { int table_set_display_all(Table *t) {
size_t allocated;
assert(t); assert(t);
allocated = t->n_display_map; size_t allocated = t->n_display_map;
if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, allocated))) if (!GREEDY_REALLOC(t->display_map, allocated, MAX(t->n_columns, allocated)))
return -ENOMEM; return -ENOMEM;
@ -1124,7 +1121,6 @@ int table_set_sort(Table *t, size_t first_column, ...) {
} }
int table_hide_column_from_display(Table *t, size_t column) { int table_hide_column_from_display(Table *t, size_t column) {
size_t allocated, cur = 0;
int r; int r;
assert(t); assert(t);
@ -1137,7 +1133,7 @@ int table_hide_column_from_display(Table *t, size_t column) {
return r; return r;
} }
allocated = t->n_display_map; size_t allocated = t->n_display_map, cur = 0;
for (size_t i = 0; i < allocated; i++) { for (size_t i = 0; i < allocated; i++) {
if (t->display_map[i] == column) if (t->display_map[i] == column)
@ -1169,6 +1165,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
return path_compare(a->string, b->string); return path_compare(a->string, b->string);
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return strv_compare(a->strv, b->strv); return strv_compare(a->strv, b->strv);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -1247,7 +1244,6 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
} }
static int table_data_compare(const size_t *a, const size_t *b, Table *t) { static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
size_t i;
int r; int r;
assert(t); assert(t);
@ -1262,7 +1258,7 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
return 1; return 1;
/* Order other lines by the sorting map */ /* Order other lines by the sorting map */
for (i = 0; i < t->n_sort_map; i++) { for (size_t i = 0; i < t->n_sort_map; i++) {
TableData *d, *dd; TableData *d, *dd;
d = t->data[*a + t->sort_map[i]]; d = t->data[*a + t->sort_map[i]];
@ -1277,10 +1273,46 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
return CMP(*a, *b); return CMP(*a, *b);
} }
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing) { static char* format_strv_width(char **strv, size_t column_width) {
_cleanup_fclose_ FILE *f = NULL;
size_t sz = 0;
_cleanup_free_ char *buf = NULL;
f = open_memstream_unlocked(&buf, &sz);
if (!f)
return NULL;
size_t position = 0;
char **p;
STRV_FOREACH(p, strv) {
size_t our_len = utf8_console_width(*p); /* This returns -1 on invalid utf-8 (which shouldn't happen).
* If that happens, we'll just print one item per line. */
if (position == 0) {
fputs(*p, f);
position = our_len;
} else if (size_add(size_add(position, 1), our_len) <= column_width) {
fprintf(f, " %s", *p);
position = size_add(size_add(position, 1), our_len);
} else {
fprintf(f, "\n%s", *p);
position = our_len;
}
}
if (fflush_and_check(f) < 0)
return NULL;
f = safe_fclose(f);
return TAKE_PTR(buf);
}
static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
assert(d); assert(d);
if (d->formatted) if (d->formatted &&
/* Only TABLE_STRV_WRAPPED adjust based on column_width so far… */
(d->type != TABLE_STRV_WRAPPED || d->formatted_for_width == column_width))
return d->formatted; return d->formatted;
switch (d->type) { switch (d->type) {
@ -1290,13 +1322,12 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
case TABLE_STRING: case TABLE_STRING:
case TABLE_PATH: case TABLE_PATH:
if (d->uppercase && !avoid_uppercasing) { if (d->uppercase && !avoid_uppercasing) {
char *p, *q;
d->formatted = new(char, strlen(d->string) + 1); d->formatted = new(char, strlen(d->string) + 1);
if (!d->formatted) if (!d->formatted)
return NULL; return NULL;
for (p = d->string, q = d->formatted; *p; p++, q++) char *q = d->formatted;
for (char *p = d->string; *p; p++, q++)
*q = (char) toupper((unsigned char) *p); *q = (char) toupper((unsigned char) *p);
*q = 0; *q = 0;
@ -1305,17 +1336,28 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
return d->string; return d->string;
case TABLE_STRV: { case TABLE_STRV:
char *p;
if (strv_isempty(d->strv)) if (strv_isempty(d->strv))
return strempty(t->empty_string); return strempty(t->empty_string);
p = strv_join(d->strv, "\n"); d->formatted = strv_join(d->strv, "\n");
if (!p) if (!d->formatted)
return NULL;
break;
case TABLE_STRV_WRAPPED: {
if (strv_isempty(d->strv))
return strempty(t->empty_string);
char *buf = format_strv_width(d->strv, column_width);
if (!buf)
return NULL; return NULL;
d->formatted = p; free_and_replace(d->formatted, buf);
d->formatted_for_width = column_width;
if (have_soft)
*have_soft = true;
break; break;
} }
@ -1632,16 +1674,19 @@ static int console_width_height(
static int table_data_requested_width_height( static int table_data_requested_width_height(
Table *table, Table *table,
TableData *d, TableData *d,
size_t available_width,
size_t *ret_width, size_t *ret_width,
size_t *ret_height) { size_t *ret_height,
bool *have_soft) {
_cleanup_free_ char *truncated = NULL; _cleanup_free_ char *truncated = NULL;
bool truncation_applied = false; bool truncation_applied = false;
size_t width, height; size_t width, height;
const char *t; const char *t;
int r; int r;
bool soft = false;
t = table_data_format(table, d, false); t = table_data_format(table, d, false, available_width, &soft);
if (!t) if (!t)
return -ENOMEM; return -ENOMEM;
@ -1669,6 +1714,8 @@ static int table_data_requested_width_height(
*ret_width = width; *ret_width = width;
if (ret_height) if (ret_height)
*ret_height = height; *ret_height = height;
if (have_soft && soft)
*have_soft = true;
return truncation_applied; return truncation_applied;
} }
@ -1678,7 +1725,6 @@ static char *align_string_mem(const char *str, const char *url, size_t new_lengt
_cleanup_free_ char *clickable = NULL; _cleanup_free_ char *clickable = NULL;
const char *p; const char *p;
char *ret; char *ret;
size_t i;
int r; int r;
/* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */ /* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
@ -1723,10 +1769,10 @@ static char *align_string_mem(const char *str, const char *url, size_t new_lengt
if (!ret) if (!ret)
return NULL; return NULL;
for (i = 0; i < lspace; i++) for (size_t i = 0; i < lspace; i++)
ret[i] = ' '; ret[i] = ' ';
memcpy(ret + lspace, clickable ?: str, clickable_length); memcpy(ret + lspace, clickable ?: str, clickable_length);
for (i = lspace + clickable_length; i < space + clickable_length; i++) for (size_t i = lspace + clickable_length; i < space + clickable_length; i++)
ret[i] = ' '; ret[i] = ' ';
ret[space + clickable_length] = 0; ret[space + clickable_length] = 0;
@ -1740,7 +1786,7 @@ static bool table_data_isempty(TableData *d) {
return true; return true;
/* Let's also consider an empty strv as truly empty. */ /* Let's also consider an empty strv as truly empty. */
if (d->type == TABLE_STRV) if (IN_SET(d->type, TABLE_STRV, TABLE_STRV_WRAPPED))
return strv_isempty(d->strv); return strv_isempty(d->strv);
/* Note that an empty string we do not consider empty here! */ /* Note that an empty string we do not consider empty here! */
@ -1771,8 +1817,8 @@ static const char* table_data_rgap_color(TableData *d) {
int table_print(Table *t, FILE *f) { int table_print(Table *t, FILE *f) {
size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width, size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
i, j, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width, table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
*width; *width = NULL;
_cleanup_free_ size_t *sorted = NULL; _cleanup_free_ size_t *sorted = NULL;
uint64_t *column_weight, weight_sum; uint64_t *column_weight, weight_sum;
int r; int r;
@ -1795,7 +1841,7 @@ int table_print(Table *t, FILE *f) {
if (!sorted) if (!sorted)
return -ENOMEM; return -ENOMEM;
for (i = 0; i < n_rows; i++) for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns; sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t); typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
@ -1811,205 +1857,225 @@ int table_print(Table *t, FILE *f) {
minimum_width = newa(size_t, display_columns); minimum_width = newa(size_t, display_columns);
maximum_width = newa(size_t, display_columns); maximum_width = newa(size_t, display_columns);
requested_width = newa(size_t, display_columns); requested_width = newa(size_t, display_columns);
width = newa(size_t, display_columns);
column_weight = newa0(uint64_t, display_columns); column_weight = newa0(uint64_t, display_columns);
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
minimum_width[j] = 1; minimum_width[j] = 1;
maximum_width[j] = (size_t) -1; maximum_width[j] = (size_t) -1;
requested_width[j] = (size_t) -1;
} }
/* First pass: determine column sizes */ for (unsigned pass = 0; pass < 2; pass++) {
for (i = t->header ? 0 : 1; i < n_rows; i++) { /* First pass: determine column sizes */
TableData **row;
/* Note that we don't care about ordering at this time, as we just want to determine column sizes, for (size_t j = 0; j < display_columns; j++)
* hence we don't care for sorted[] during the first pass. */ requested_width[j] = (size_t) -1;
row = t->data + i * t->n_columns;
for (j = 0; j < display_columns; j++) { bool any_soft = false;
TableData *d;
size_t req_width, req_height;
assert_se(d = row[t->display_map ? t->display_map[j] : j]); for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
TableData **row;
r = table_data_requested_width_height(t, d, &req_width, &req_height); /* Note that we don't care about ordering at this time, as we just want to determine column sizes,
if (r < 0) * hence we don't care for sorted[] during the first pass. */
return r; row = t->data + i * t->n_columns;
if (r > 0) { /* Truncated because too many lines? */
_cleanup_free_ char *last = NULL;
const char *field;
/* If we are going to show only the first few lines of a cell that has for (size_t j = 0; j < display_columns; j++) {
* multiple make sure that we have enough space horizontally to show an TableData *d;
* ellipsis. Hence, let's figure out the last line, and account for its size_t req_width, req_height;
* length plus ellipsis. */
field = table_data_format(t, d, false); assert_se(d = row[t->display_map ? t->display_map[j] : j]);
if (!field)
return -ENOMEM;
assert_se(t->cell_height_max > 0); r = table_data_requested_width_height(t, d,
r = string_extract_line(field, t->cell_height_max-1, &last); width ? width[j] : SIZE_MAX,
&req_width, &req_height, &any_soft);
if (r < 0) if (r < 0)
return r; return r;
if (r > 0) { /* Truncated because too many lines? */
_cleanup_free_ char *last = NULL;
const char *field;
req_width = MAX(req_width, /* If we are going to show only the first few lines of a cell that has
utf8_console_width(last) + * multiple make sure that we have enough space horizontally to show an
utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS))); * ellipsis. Hence, let's figure out the last line, and account for its
* length plus ellipsis. */
field = table_data_format(t, d, false,
width ? width[j] : SIZE_MAX,
&any_soft);
if (!field)
return -ENOMEM;
assert_se(t->cell_height_max > 0);
r = string_extract_line(field, t->cell_height_max-1, &last);
if (r < 0)
return r;
req_width = MAX(req_width,
utf8_console_width(last) +
utf8_console_width(special_glyph(SPECIAL_GLYPH_ELLIPSIS)));
}
/* Determine the biggest width that any cell in this column would like to have */
if (requested_width[j] == (size_t) -1 ||
requested_width[j] < req_width)
requested_width[j] = req_width;
/* Determine the minimum width any cell in this column needs */
if (minimum_width[j] < d->minimum_width)
minimum_width[j] = d->minimum_width;
/* Determine the maximum width any cell in this column needs */
if (d->maximum_width != (size_t) -1 &&
(maximum_width[j] == (size_t) -1 ||
maximum_width[j] > d->maximum_width))
maximum_width[j] = d->maximum_width;
/* Determine the full columns weight */
column_weight[j] += d->weight;
} }
/* Determine the biggest width that any cell in this column would like to have */
if (requested_width[j] == (size_t) -1 ||
requested_width[j] < req_width)
requested_width[j] = req_width;
/* Determine the minimum width any cell in this column needs */
if (minimum_width[j] < d->minimum_width)
minimum_width[j] = d->minimum_width;
/* Determine the maximum width any cell in this column needs */
if (d->maximum_width != (size_t) -1 &&
(maximum_width[j] == (size_t) -1 ||
maximum_width[j] > d->maximum_width))
maximum_width[j] = d->maximum_width;
/* Determine the full columns weight */
column_weight[j] += d->weight;
} }
}
/* One space between each column */ /* One space between each column */
table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1; table_requested_width = table_minimum_width = table_maximum_width = display_columns - 1;
/* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */ /* Calculate the total weight for all columns, plus the minimum, maximum and requested width for the table. */
weight_sum = 0; weight_sum = 0;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
weight_sum += column_weight[j]; weight_sum += column_weight[j];
table_minimum_width += minimum_width[j]; table_minimum_width += minimum_width[j];
if (maximum_width[j] == (size_t) -1) if (maximum_width[j] == (size_t) -1)
table_maximum_width = (size_t) -1; table_maximum_width = (size_t) -1;
else
table_maximum_width += maximum_width[j];
table_requested_width += requested_width[j];
}
/* Calculate effective table width */
if (t->width != 0 && t->width != (size_t) -1)
table_effective_width = t->width;
else if (t->width == 0 ||
((pass > 0 || !any_soft) && (pager_have() || !isatty(STDOUT_FILENO))))
table_effective_width = table_requested_width;
else else
table_maximum_width += maximum_width[j]; table_effective_width = MIN(table_requested_width, columns());
table_requested_width += requested_width[j]; if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width)
} table_effective_width = table_maximum_width;
/* Calculate effective table width */ if (table_effective_width < table_minimum_width)
if (t->width != 0 && t->width != (size_t) -1) table_effective_width = table_minimum_width;
table_effective_width = t->width;
else if (t->width == 0 || pager_have() || !isatty(STDOUT_FILENO))
table_effective_width = table_requested_width;
else
table_effective_width = MIN(table_requested_width, columns());
if (table_maximum_width != (size_t) -1 && table_effective_width > table_maximum_width) if (!width)
table_effective_width = table_maximum_width; width = newa(size_t, display_columns);
if (table_effective_width < table_minimum_width) if (table_effective_width >= table_requested_width) {
table_effective_width = table_minimum_width; size_t extra;
if (table_effective_width >= table_requested_width) { /* We have extra room, let's distribute it among columns according to their weights. We first provide
size_t extra; * each column with what it asked for and the distribute the rest. */
/* We have extra room, let's distribute it among columns according to their weights. We first provide extra = table_effective_width - table_requested_width;
* each column with what it asked for and the distribute the rest. */
extra = table_effective_width - table_requested_width; for (size_t j = 0; j < display_columns; j++) {
size_t delta;
for (j = 0; j < display_columns; j++) {
size_t delta;
if (weight_sum == 0)
width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
else
width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
width[j] = maximum_width[j];
if (width[j] < minimum_width[j])
width[j] = minimum_width[j];
assert(width[j] >= requested_width[j]);
delta = width[j] - requested_width[j];
/* Subtract what we just added from the rest */
if (extra > delta)
extra -= delta;
else
extra = 0;
assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j];
}
} else {
/* We need to compress the table, columns can't get what they asked for. We first provide each column
* with the minimum they need, and then distribute anything left. */
bool finalize = false;
size_t extra;
extra = table_effective_width - table_minimum_width;
for (j = 0; j < display_columns; j++)
width[j] = (size_t) -1;
for (;;) {
bool restart = false;
for (j = 0; j < display_columns; j++) {
size_t delta, w;
/* Did this column already get something assigned? If so, let's skip to the next */
if (width[j] != (size_t) -1)
continue;
if (weight_sum == 0) if (weight_sum == 0)
w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */ width[j] = requested_width[j] + extra / (display_columns - j); /* Avoid division by zero */
else else
w = minimum_width[j] + (extra * column_weight[j]) / weight_sum; width[j] = requested_width[j] + (extra * column_weight[j]) / weight_sum;
if (w >= requested_width[j]) { if (maximum_width[j] != (size_t) -1 && width[j] > maximum_width[j])
/* Never give more than requested. If we hit a column like this, there's more width[j] = maximum_width[j];
* space to allocate to other columns which means we need to restart the
* iteration. However, if we hit a column like this, let's assign it the space
* it wanted for good early.*/
w = requested_width[j]; if (width[j] < minimum_width[j])
restart = true; width[j] = minimum_width[j];
} else if (!finalize) assert(width[j] >= requested_width[j]);
continue; delta = width[j] - requested_width[j];
width[j] = w; /* Subtract what we just added from the rest */
if (extra > delta)
assert(w >= minimum_width[j]); extra -= delta;
delta = w - minimum_width[j]; else
extra = 0;
assert(delta <= extra);
extra -= delta;
assert(weight_sum >= column_weight[j]); assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j]; weight_sum -= column_weight[j];
if (restart && !finalize)
break;
} }
if (finalize) break; /* Every column should be happy, no need to repeat calculations. */
break; } else {
/* We need to compress the table, columns can't get what they asked for. We first provide each column
* with the minimum they need, and then distribute anything left. */
bool finalize = false;
size_t extra;
if (!restart) extra = table_effective_width - table_minimum_width;
finalize = true;
for (size_t j = 0; j < display_columns; j++)
width[j] = (size_t) -1;
for (;;) {
bool restart = false;
for (size_t j = 0; j < display_columns; j++) {
size_t delta, w;
/* Did this column already get something assigned? If so, let's skip to the next */
if (width[j] != (size_t) -1)
continue;
if (weight_sum == 0)
w = minimum_width[j] + extra / (display_columns - j); /* avoid division by zero */
else
w = minimum_width[j] + (extra * column_weight[j]) / weight_sum;
if (w >= requested_width[j]) {
/* Never give more than requested. If we hit a column like this, there's more
* space to allocate to other columns which means we need to restart the
* iteration. However, if we hit a column like this, let's assign it the space
* it wanted for good early.*/
w = requested_width[j];
restart = true;
} else if (!finalize)
continue;
width[j] = w;
assert(w >= minimum_width[j]);
delta = w - minimum_width[j];
assert(delta <= extra);
extra -= delta;
assert(weight_sum >= column_weight[j]);
weight_sum -= column_weight[j];
if (restart && !finalize)
break;
}
if (finalize)
break;
if (!restart)
finalize = true;
}
if (!any_soft) /* Some columns got less than requested. If some cells were "soft",
* let's try to reformat them with the new widths. Otherwise, let's
* move on. */
break;
} }
} }
/* Second pass: show output */ /* Second pass: show output */
for (i = t->header ? 0 : 1; i < n_rows; i++) { for (size_t i = t->header ? 0 : 1; i < n_rows; i++) {
size_t n_subline = 0; size_t n_subline = 0;
bool more_sublines; bool more_sublines;
TableData **row; TableData **row;
@ -2023,7 +2089,7 @@ int table_print(Table *t, FILE *f) {
const char *gap_color = NULL; const char *gap_color = NULL;
more_sublines = false; more_sublines = false;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *buffer = NULL, *extracted = NULL; _cleanup_free_ char *buffer = NULL, *extracted = NULL;
bool lines_truncated = false; bool lines_truncated = false;
const char *field, *color = NULL; const char *field, *color = NULL;
@ -2032,7 +2098,7 @@ int table_print(Table *t, FILE *f) {
assert_se(d = row[t->display_map ? t->display_map[j] : j]); assert_se(d = row[t->display_map ? t->display_map[j] : j]);
field = table_data_format(t, d, false); field = table_data_format(t, d, false, width[j], NULL);
if (!field) if (!field)
return -ENOMEM; return -ENOMEM;
@ -2247,6 +2313,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
return json_variant_new_string(ret, d->string); return json_variant_new_string(ret, d->string);
case TABLE_STRV: case TABLE_STRV:
case TABLE_STRV_WRAPPED:
return json_variant_new_array_strv(ret, d->strv); return json_variant_new_array_strv(ret, d->strv);
case TABLE_BOOLEAN: case TABLE_BOOLEAN:
@ -2332,17 +2399,15 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
} }
static char* string_to_json_field_name(const char *f) { static char* string_to_json_field_name(const char *f) {
char *c, *x;
/* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a /* Tries to make a string more suitable as JSON field name. There are no strict rules defined what a
* field name can be hence this is a bit vague and black magic. Right now we only convert spaces to * field name can be hence this is a bit vague and black magic. Right now we only convert spaces to
* underscores and leave everything as is. */ * underscores and leave everything as is. */
c = strdup(f); char *c = strdup(f);
if (!c) if (!c)
return NULL; return NULL;
for (x = c; *x; x++) for (char *x = c; *x; x++)
if (isspace(*x)) if (isspace(*x))
*x = '_'; *x = '_';
@ -2352,7 +2417,7 @@ static char* string_to_json_field_name(const char *f) {
int table_to_json(Table *t, JsonVariant **ret) { int table_to_json(Table *t, JsonVariant **ret) {
JsonVariant **rows = NULL, **elements = NULL; JsonVariant **rows = NULL, **elements = NULL;
_cleanup_free_ size_t *sorted = NULL; _cleanup_free_ size_t *sorted = NULL;
size_t n_rows, i, j, display_columns; size_t n_rows, display_columns;
int r; int r;
assert(t); assert(t);
@ -2372,7 +2437,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (i = 0; i < n_rows; i++) for (size_t i = 0; i < n_rows; i++)
sorted[i] = i * t->n_columns; sorted[i] = i * t->n_columns;
typesafe_qsort_r(sorted, n_rows, table_data_compare, t); typesafe_qsort_r(sorted, n_rows, table_data_compare, t);
@ -2390,7 +2455,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
_cleanup_free_ char *mangled = NULL; _cleanup_free_ char *mangled = NULL;
const char *formatted; const char *formatted;
TableData *d; TableData *d;
@ -2398,7 +2463,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
assert_se(d = t->data[t->display_map ? t->display_map[j] : j]); assert_se(d = t->data[t->display_map ? t->display_map[j] : j]);
/* Field names must be strings, hence format whatever we got here as a string first */ /* Field names must be strings, hence format whatever we got here as a string first */
formatted = table_data_format(t, d, true); formatted = table_data_format(t, d, true, SIZE_MAX, NULL);
if (!formatted) { if (!formatted) {
r = -ENOMEM; r = -ENOMEM;
goto finish; goto finish;
@ -2422,7 +2487,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
goto finish; goto finish;
} }
for (i = 1; i < n_rows; i++) { for (size_t i = 1; i < n_rows; i++) {
TableData **row; TableData **row;
if (sorted) if (sorted)
@ -2430,7 +2495,7 @@ int table_to_json(Table *t, JsonVariant **ret) {
else else
row = t->data + i * t->n_columns; row = t->data + i * t->n_columns;
for (j = 0; j < display_columns; j++) { for (size_t j = 0; j < display_columns; j++) {
TableData *d; TableData *d;
size_t k; size_t k;

View File

@ -12,6 +12,7 @@ typedef enum TableDataType {
TABLE_EMPTY, TABLE_EMPTY,
TABLE_STRING, TABLE_STRING,
TABLE_STRV, TABLE_STRV,
TABLE_STRV_WRAPPED,
TABLE_PATH, TABLE_PATH,
TABLE_BOOLEAN, TABLE_BOOLEAN,
TABLE_TIMESTAMP, TABLE_TIMESTAMP,

View File

@ -12,6 +12,8 @@ static void test_issue_9549(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified")); assert_se(table = table_new("name", "type", "ro", "usage", "created", "modified"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(3), 100) >= 0);
assert_se(table_add_many(table, assert_se(table_add_many(table,
@ -36,6 +38,8 @@ static void test_multiline(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar")); assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -148,6 +152,8 @@ static void test_strv(void) {
_cleanup_(table_unrefp) Table *table = NULL; _cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar")); assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0); assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
@ -256,8 +262,111 @@ static void test_strv(void) {
formatted = mfree(formatted); formatted = mfree(formatted);
} }
int main(int argc, char *argv[]) { static void test_strv_wrapped(void) {
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *formatted = NULL;
log_info("/* %s */", __func__);
assert_se(table = table_new("foo", "bar"));
assert_se(table_set_align_percent(table, TABLE_HEADER_CELL(1), 100) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("three", "different", "lines"),
TABLE_STRV_WRAPPED, STRV_MAKE("two", "lines")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different lines two lines\n"));
formatted = mfree(formatted);
assert_se(table_add_many(table,
TABLE_STRING, "short",
TABLE_STRV_WRAPPED, STRV_MAKE("a", "pair")) >= 0);
assert_se(table_add_many(table,
TABLE_STRV_WRAPPED, STRV_MAKE("short2"),
TABLE_STRV_WRAPPED, STRV_MAKE("a", "eight", "line", "ćęłł",
"___5___", "___6___", "___7___", "___8___")) >= 0);
table_set_cell_height_max(table, 1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different… two lines\n"
"short a pair\n"
"short2 a eight line ćęłł…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 2);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___…\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, 3);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
table_set_cell_height_max(table, (size_t) -1);
assert_se(table_format(table, &formatted) >= 0);
fputs(formatted, stdout);
assert_se(streq(formatted,
"FOO BAR\n"
"three different two lines\n"
"lines \n"
"short a pair\n"
"short2 a eight line ćęłł\n"
" ___5___ ___6___\n"
" ___7___ ___8___\n"));
formatted = mfree(formatted);
}
int main(int argc, char *argv[]) {
_cleanup_(table_unrefp) Table *t = NULL; _cleanup_(table_unrefp) Table *t = NULL;
_cleanup_free_ char *formatted = NULL; _cleanup_free_ char *formatted = NULL;
@ -399,6 +508,7 @@ int main(int argc, char *argv[]) {
test_issue_9549(); test_issue_9549();
test_multiline(); test_multiline();
test_strv(); test_strv();
test_strv_wrapped();
return 0; return 0;
} }