mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-02-22 17:57:49 +03:00
systemd-analyze: add new option to generate JSON output of security analysis table
The new option --json= works with the 'security' verb and takes in one of three format flags.
These are off which is the default, pretty and short which use JSON format flags for output.
When set to true, it generates a JSON formatted output of the security analysis table. The
format is a JSON array with objects containing the following fields: set which indicates if
the id has been set or not, name which is what is used to refer to the id, json_field
which is the equivalent JSON formatted id name only used for JSON outputs, description which
is an outline of the id state, and exposure which is an unsigned integer in the range 0.0..10.0,
where a higher value corresponds to a higher security threat. The JSON version of the table is
printed on the standard output file.
Example Run:
The unit file testfile.service was created to test the --json= option
maanya-goenka@debian:~/systemd (json-security)$ cat <<EOF >testfile.service
> [Service]
> ExecStart = echo hello
> PrivateNetwork = yes
> PrivateMounts = yes
> PrivateDevices = yes
> EOF
Both the JSON output and the security analysis table below have been truncated to increase readability.
1. Testing for when --json=off
maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=off --root= --offline=true
testfile.service --no-pager
/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.
NAME DESCRIPTION EXPOSURE
✓ PrivateNetwork= Service has no access to the host's network
✗ User=/DynamicUser= Service runs as root user 0.4
✗ CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP) Service may change UID/GID identities/capabilities 0.3
✗ CapabilityBoundingSet=~CAP_NET_ADMIN Service has administrator privileges 0.3
→ Overall exposure level for testfile.service: 8.3 EXPOSED 🙁
2. Testing for when --json=pretty
maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=pretty --root= --offline=true
testfile.service
/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.
[
{
"set" : true,
"name" : "PrivateNetwork=",
"json-field" : "PrivateNetwork",
"description" : "Service has no access to the host's network",
"exposure" : null
},
{
"set" : false,
"name" : "User=/DynamicUser=",
"json-field" : "UserOrDynamicUser",
"decsription" : "Service runs as root user",
"exposure" : "0.4"
},
{
"set" : false,
"name" : "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
"json_field" : "CapabilityBoundingSet_CAP_SET_UID_GID_PCAP",
"description" : "Service may change UID/GID identities/capabilities",
"exposure" : "0.3"
},
{
"set" : false,
"name" : "CapabilityBoundingSet=~CAP_NET_ADMIN",
"json_field" : "CapabilityBoundingSet_CAP_NET_ADMIN",
"description" : "Service has administrator privileges",
"exposure" : "0.3"
},
...
]
3. Testing for when --json=short
maanya-goenka@debian:~/systemd (json-security)$ sudo build/systemd-analyze security --json=short --root= --offline=true
testfile.service
/usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's
process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'.
Support for KillMode=none is deprecated and will eventually be removed.
/usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating
/var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly.
/usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your
unit file, and consider removing the setting altogether.
/home/maanya-goenka/systemd/foo.service:2: Unknown key name 'foo' in section 'Unit', ignoring.
[{"set":true,"name":"PrivateNetwork=", "json_field":"PrivateNetwork", "description":"Service has no access to the host's network","exposure":null}, ...]
This commit is contained in:
parent
c1e6f21556
commit
4b4a8ef741
@ -1109,6 +1109,22 @@ Service b@0.service not loaded, b.socket cannot be started.
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--json=<replaceable>MODE</replaceable></option></term>
|
||||
|
||||
<listitem><para>With the <command>security</command> command, generate a JSON formatted
|
||||
output of the security analysis table. The format is a JSON array with objects
|
||||
containing the following fields: <varname>set</varname> which indicates if the setting has
|
||||
been enabled or not, <varname>name</varname> which is what is used to refer to the setting,
|
||||
<varname>json_field</varname> which is the JSON compatible identifier of the setting,
|
||||
<varname>description</varname> which is an outline of the setting state, and
|
||||
<varname>exposure</varname> which is a number in the range 0.0…10.0, where a higher value
|
||||
corresponds to a higher security threat. The JSON version of the table is printed to standard
|
||||
output. The <replaceable>MODE</replaceable> passed to the option can be one of three:
|
||||
<option>off</option> which is the default, <option>pretty</option> and <option>short</option>
|
||||
which respectively output a prettified or shorted JSON version of the security table.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--iterations=<replaceable>NUMBER</replaceable></option></term>
|
||||
|
||||
|
@ -145,7 +145,7 @@ _systemd_analyze() {
|
||||
|
||||
elif __contains_word "$verb" ${VERBS[SECURITY]}; then
|
||||
if [[ $cur = -* ]]; then
|
||||
comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy'
|
||||
comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy --json=off --json=pretty --json=short'
|
||||
else
|
||||
if __contains_word "--user" ${COMP_WORDS[*]}; then
|
||||
mode=--user
|
||||
|
@ -93,6 +93,7 @@ _arguments \
|
||||
'--offline=[Perform a security review of the specified unit file(s)]:BOOL' \
|
||||
'--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
|
||||
'--security-policy=[Allow user to use customized requirements to compare unit file(s) against]: PATH' \
|
||||
'--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \
|
||||
'--no-pager[Do not pipe output into a pager]' \
|
||||
'--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \
|
||||
'--order[When generating graph for dot, show only order]' \
|
||||
|
@ -1709,7 +1709,9 @@ static int assess(const SecurityInfo *info,
|
||||
Table *overview_table,
|
||||
AnalyzeSecurityFlags flags,
|
||||
unsigned threshold,
|
||||
JsonVariant *policy) {
|
||||
JsonVariant *policy,
|
||||
PagerFlags pager_flags,
|
||||
JsonFormatFlags json_format_flags) {
|
||||
|
||||
static const struct {
|
||||
uint64_t exposure;
|
||||
@ -1732,15 +1734,19 @@ static int assess(const SecurityInfo *info,
|
||||
int r;
|
||||
|
||||
if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
|
||||
details_table = table_new(" ", "name", "description", "weight", "badness", "range", "exposure");
|
||||
details_table = table_new(" ", "name", "json_field", "description", "weight", "badness", "range", "exposure");
|
||||
if (!details_table)
|
||||
return log_oom();
|
||||
|
||||
r = table_set_json_field_name(details_table, 0, "set");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
|
||||
|
||||
(void) table_set_sort(details_table, (size_t) 3, (size_t) 1);
|
||||
(void) table_set_reverse(details_table, 3, true);
|
||||
|
||||
if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
|
||||
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 6);
|
||||
(void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
|
||||
}
|
||||
|
||||
for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
|
||||
@ -1774,23 +1780,23 @@ static int assess(const SecurityInfo *info,
|
||||
}
|
||||
|
||||
if (details_table) {
|
||||
const char *checkmark, *description, *color = NULL;
|
||||
const char *id = a->id;
|
||||
const char *description, *color = NULL;
|
||||
int checkmark;
|
||||
|
||||
if (badness == UINT64_MAX) {
|
||||
checkmark = " ";
|
||||
checkmark = -1;
|
||||
description = access_description_na(a, policy);
|
||||
color = NULL;
|
||||
} else if (badness == a->range) {
|
||||
checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
|
||||
checkmark = 0;
|
||||
description = access_description_bad(a, policy);
|
||||
color = ansi_highlight_red();
|
||||
} else if (badness == 0) {
|
||||
checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK);
|
||||
checkmark = 1;
|
||||
description = access_description_good(a, policy);
|
||||
color = ansi_highlight_green();
|
||||
} else {
|
||||
checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
|
||||
checkmark = 0;
|
||||
description = NULL;
|
||||
color = ansi_highlight_red();
|
||||
}
|
||||
@ -1798,16 +1804,24 @@ static int assess(const SecurityInfo *info,
|
||||
if (d)
|
||||
description = d;
|
||||
|
||||
if (json_variant_by_key(policy, a->json_field) != NULL)
|
||||
id = a->json_field;
|
||||
|
||||
if (checkmark < 0) {
|
||||
r = table_add_many(details_table, TABLE_EMPTY);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
} else {
|
||||
r = table_add_many(details_table,
|
||||
TABLE_STRING, checkmark,
|
||||
TABLE_BOOLEAN_CHECKMARK, checkmark > 0,
|
||||
TABLE_SET_MINIMUM_WIDTH, 1,
|
||||
TABLE_SET_MAXIMUM_WIDTH, 1,
|
||||
TABLE_SET_ELLIPSIZE_PERCENT, 0,
|
||||
TABLE_SET_COLOR, color,
|
||||
TABLE_STRING, id, TABLE_SET_URL, a->url,
|
||||
TABLE_SET_COLOR, color);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
r = table_add_many(details_table,
|
||||
TABLE_STRING, a->id, TABLE_SET_URL, a->url,
|
||||
TABLE_STRING, a->json_field,
|
||||
TABLE_STRING, description,
|
||||
TABLE_UINT64, weight, TABLE_SET_ALIGN_PERCENT, 100,
|
||||
TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100,
|
||||
@ -1829,14 +1843,14 @@ static int assess(const SecurityInfo *info,
|
||||
TableCell *cell;
|
||||
uint64_t x;
|
||||
|
||||
assert_se(weight = table_get_at(details_table, row, 3));
|
||||
assert_se(badness = table_get_at(details_table, row, 4));
|
||||
assert_se(range = table_get_at(details_table, row, 5));
|
||||
assert_se(weight = table_get_at(details_table, row, 4));
|
||||
assert_se(badness = table_get_at(details_table, row, 5));
|
||||
assert_se(range = table_get_at(details_table, row, 6));
|
||||
|
||||
if (*badness == UINT64_MAX || *badness == 0)
|
||||
continue;
|
||||
|
||||
assert_se(cell = table_get_cell(details_table, row, 6));
|
||||
assert_se(cell = table_get_cell(details_table, row, 7));
|
||||
|
||||
x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
|
||||
xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
|
||||
@ -1846,7 +1860,13 @@ static int assess(const SecurityInfo *info,
|
||||
return log_error_errno(r, "Failed to update cell in table: %m");
|
||||
}
|
||||
|
||||
r = table_print(details_table, stdout);
|
||||
if (json_format_flags & JSON_FORMAT_OFF) {
|
||||
r = table_hide_column_from_display(details_table, (size_t) 2);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set columns to display: %m");
|
||||
}
|
||||
|
||||
r = table_print_with_pager(details_table, json_format_flags, pager_flags, /* show_header= */true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to output table: %m");
|
||||
}
|
||||
@ -1859,7 +1879,7 @@ static int assess(const SecurityInfo *info,
|
||||
|
||||
assert(i < ELEMENTSOF(badness_table));
|
||||
|
||||
if (details_table) {
|
||||
if (details_table && (json_format_flags & JSON_FORMAT_OFF)) {
|
||||
_cleanup_free_ char *clickable = NULL;
|
||||
const char *name;
|
||||
|
||||
@ -2386,7 +2406,9 @@ static int analyze_security_one(sd_bus *bus,
|
||||
Table *overview_table,
|
||||
AnalyzeSecurityFlags flags,
|
||||
unsigned threshold,
|
||||
JsonVariant *policy) {
|
||||
JsonVariant *policy,
|
||||
PagerFlags pager_flags,
|
||||
JsonFormatFlags json_format_flags) {
|
||||
|
||||
_cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
|
||||
if (!info)
|
||||
@ -2403,7 +2425,7 @@ static int analyze_security_one(sd_bus *bus,
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = assess(info, overview_table, flags, threshold, policy);
|
||||
r = assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -2589,7 +2611,12 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int offline_security_check(Unit *u, unsigned threshold, JsonVariant *policy) {
|
||||
static int offline_security_check(Unit *u,
|
||||
unsigned threshold,
|
||||
JsonVariant *policy,
|
||||
PagerFlags pager_flags,
|
||||
JsonFormatFlags json_format_flags) {
|
||||
|
||||
_cleanup_(table_unrefp) Table *overview_table = NULL;
|
||||
AnalyzeSecurityFlags flags = 0;
|
||||
_cleanup_(security_info_freep) SecurityInfo *info = NULL;
|
||||
@ -2604,7 +2631,7 @@ static int offline_security_check(Unit *u, unsigned threshold, JsonVariant *poli
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return assess(info, overview_table, flags, threshold, policy);
|
||||
return assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
|
||||
}
|
||||
|
||||
static int offline_security_checks(char **filenames,
|
||||
@ -2613,7 +2640,9 @@ static int offline_security_checks(char **filenames,
|
||||
bool check_man,
|
||||
bool run_generators,
|
||||
unsigned threshold,
|
||||
const char *root) {
|
||||
const char *root,
|
||||
PagerFlags pager_flags,
|
||||
JsonFormatFlags json_format_flags) {
|
||||
|
||||
const ManagerTestRunFlags flags =
|
||||
MANAGER_TEST_RUN_MINIMAL |
|
||||
@ -2673,7 +2702,7 @@ static int offline_security_checks(char **filenames,
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
k = offline_security_check(units[i], threshold, policy);
|
||||
k = offline_security_check(units[i], threshold, policy, pager_flags, json_format_flags);
|
||||
if (k < 0 && r == 0)
|
||||
r = k;
|
||||
}
|
||||
@ -2690,6 +2719,8 @@ int analyze_security(sd_bus *bus,
|
||||
bool offline,
|
||||
unsigned threshold,
|
||||
const char *root,
|
||||
JsonFormatFlags json_format_flags,
|
||||
PagerFlags pager_flags,
|
||||
AnalyzeSecurityFlags flags) {
|
||||
|
||||
_cleanup_(table_unrefp) Table *overview_table = NULL;
|
||||
@ -2698,7 +2729,7 @@ int analyze_security(sd_bus *bus,
|
||||
assert(bus);
|
||||
|
||||
if (offline)
|
||||
return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root);
|
||||
return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root, pager_flags, json_format_flags);
|
||||
|
||||
if (strv_length(units) != 1) {
|
||||
overview_table = table_new("unit", "exposure", "predicate", "happy");
|
||||
@ -2758,7 +2789,7 @@ int analyze_security(sd_bus *bus,
|
||||
flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
|
||||
|
||||
STRV_FOREACH(i, list) {
|
||||
r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy);
|
||||
r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
|
||||
if (r < 0 && ret >= 0)
|
||||
ret = r;
|
||||
}
|
||||
@ -2793,7 +2824,7 @@ int analyze_security(sd_bus *bus,
|
||||
} else
|
||||
name = mangled;
|
||||
|
||||
r = analyze_security_one(bus, name, overview_table, flags, threshold, policy);
|
||||
r = analyze_security_one(bus, name, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
|
||||
if (r < 0 && ret >= 0)
|
||||
ret = r;
|
||||
}
|
||||
@ -2805,10 +2836,9 @@ int analyze_security(sd_bus *bus,
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
r = table_print(overview_table, stdout);
|
||||
r = table_print_with_pager(overview_table, json_format_flags, pager_flags, /* show_header= */true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to output table: %m");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "json.h"
|
||||
#include "pager.h"
|
||||
#include "unit-file.h"
|
||||
|
||||
typedef enum AnalyzeSecurityFlags {
|
||||
@ -23,4 +24,6 @@ int analyze_security(sd_bus *bus,
|
||||
bool offline,
|
||||
unsigned threshold,
|
||||
const char *root,
|
||||
JsonFormatFlags json_format_flags,
|
||||
PagerFlags pager_flags,
|
||||
AnalyzeSecurityFlags flags);
|
||||
|
@ -97,6 +97,7 @@ static unsigned arg_threshold = 100;
|
||||
static unsigned arg_iterations = 1;
|
||||
static usec_t arg_base_time = USEC_INFINITY;
|
||||
static char *arg_unit = NULL;
|
||||
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
|
||||
@ -2196,6 +2197,8 @@ static int do_security(int argc, char *argv[], void *userdata) {
|
||||
arg_offline,
|
||||
arg_threshold,
|
||||
arg_root,
|
||||
arg_json_format_flags,
|
||||
arg_pager_flags,
|
||||
/*flags=*/ 0);
|
||||
}
|
||||
|
||||
@ -2250,6 +2253,8 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" --version Show package version\n"
|
||||
" --security-policy=PATH Use custom JSON security policy instead\n"
|
||||
" of built-in one\n"
|
||||
" --json=pretty|short|off Generate JSON output of the security\n"
|
||||
" analysis table\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --system Operate on system systemd instance\n"
|
||||
" --user Operate on user systemd instance\n"
|
||||
@ -2303,6 +2308,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_OFFLINE,
|
||||
ARG_THRESHOLD,
|
||||
ARG_SECURITY_POLICY,
|
||||
ARG_JSON,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -2330,6 +2336,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "iterations", required_argument, NULL, ARG_ITERATIONS },
|
||||
{ "base-time", required_argument, NULL, ARG_BASE_TIME },
|
||||
{ "unit", required_argument, NULL, 'U' },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{}
|
||||
};
|
||||
|
||||
@ -2454,6 +2461,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_ITERATIONS:
|
||||
r = safe_atou(optarg, &arg_iterations);
|
||||
if (r < 0)
|
||||
@ -2489,6 +2502,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option --offline= is only supported for security right now.");
|
||||
|
||||
if (arg_json_format_flags != JSON_FORMAT_OFF && !streq_ptr(argv[optind], "security"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option --json= is only supported for security right now.");
|
||||
|
||||
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option --threshold= is only supported for security right now.");
|
||||
|
@ -267,6 +267,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
|
||||
case TABLE_STRV_WRAPPED:
|
||||
return sizeof(char **);
|
||||
|
||||
case TABLE_BOOLEAN_CHECKMARK:
|
||||
case TABLE_BOOLEAN:
|
||||
return sizeof(bool);
|
||||
|
||||
@ -848,6 +849,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
|
||||
data = va_arg(ap, char * const *);
|
||||
break;
|
||||
|
||||
case TABLE_BOOLEAN_CHECKMARK:
|
||||
case TABLE_BOOLEAN:
|
||||
buffer.b = va_arg(ap, int);
|
||||
data = &buffer.b;
|
||||
@ -1443,6 +1445,9 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
|
||||
case TABLE_BOOLEAN:
|
||||
return yes_no(d->boolean);
|
||||
|
||||
case TABLE_BOOLEAN_CHECKMARK:
|
||||
return special_glyph(d->boolean ? SPECIAL_GLYPH_CHECK_MARK : SPECIAL_GLYPH_CROSS_MARK);
|
||||
|
||||
case TABLE_TIMESTAMP:
|
||||
case TABLE_TIMESTAMP_UTC:
|
||||
case TABLE_TIMESTAMP_RELATIVE: {
|
||||
@ -2488,6 +2493,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
|
||||
case TABLE_STRV_WRAPPED:
|
||||
return json_variant_new_array_strv(ret, d->strv);
|
||||
|
||||
case TABLE_BOOLEAN_CHECKMARK:
|
||||
case TABLE_BOOLEAN:
|
||||
return json_variant_new_boolean(ret, d->boolean);
|
||||
|
||||
|
@ -16,6 +16,7 @@ typedef enum TableDataType {
|
||||
TABLE_STRV_WRAPPED,
|
||||
TABLE_PATH,
|
||||
TABLE_BOOLEAN,
|
||||
TABLE_BOOLEAN_CHECKMARK,
|
||||
TABLE_TIMESTAMP,
|
||||
TABLE_TIMESTAMP_UTC,
|
||||
TABLE_TIMESTAMP_RELATIVE,
|
||||
|
Loading…
x
Reference in New Issue
Block a user