Merge pull request #10701 from poettering/analyze-security
systemd-analyze: add a new "security" verb for analyzing unit sandboxing options
This commit is contained in:
@@ -25,6 +25,10 @@ Features:
|
||||
|
||||
* when we fork off generators and such, lower LIMIT_NOFILE soft limit to 1K
|
||||
|
||||
* rework seccomp/nnp logic that that even if User= is used in combination with
|
||||
a seccomp option we don't have to set NNP. For that, change uid first whil
|
||||
keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap.
|
||||
|
||||
* add a concept for automatically loading per-unit secrets off disk and
|
||||
inserting them into the kernel keyring. Maybe SecretsDirectory= similar to
|
||||
ConfigurationDirectory=.
|
||||
@@ -49,6 +53,9 @@ Features:
|
||||
|
||||
* set memory.oom.group in cgroupsv2 for all leaf cgroups (kernel v4.19+)
|
||||
|
||||
* add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and
|
||||
usefaultd() and make systemd-analyze check for it.
|
||||
|
||||
* drop umask() calls and suchlike from our generators, pid1 should set things up correctly anyway
|
||||
|
||||
* paranoia: whenever we process passwords, call mlock() on the memory
|
||||
@@ -290,9 +297,6 @@ Features:
|
||||
* beef up pam_systemd to take unit file settings such as cgroups properties as
|
||||
parameters
|
||||
|
||||
* a new "systemd-analyze security" tool outputting a checklist of security
|
||||
features a service does and does not implement
|
||||
|
||||
* maybe hook of xfs/ext4 quotactl() with services? i.e. automatically manage
|
||||
the quota of a the user indicated in User= via unit file settings, like the
|
||||
other resource management concepts. Would mix nicely with DynamicUser=1. Or
|
||||
|
||||
+6
-1
@@ -46,9 +46,14 @@ All tools:
|
||||
are understood, too (us, ms, s, min, h, d, w, month, y). If it is not set or set
|
||||
to 0, then the built-in default is used.
|
||||
|
||||
* `$SYSTEMD_MEMPOOL=0` — if set the internal memory caching logic employed by
|
||||
* `$SYSTEMD_MEMPOOL=0` — if set, the internal memory caching logic employed by
|
||||
hash tables is turned off, and libc malloc() is used for all allocations.
|
||||
|
||||
* `$SYSTEMD_EMOJI=0` — if set, tools such as "systemd-analyze security" will
|
||||
not output graphical smiley emojis, but ASCII alternatives instead. Note that
|
||||
this only controls use of Unicode emoji glyphs, and has no effect on other
|
||||
Unicode glyphs.
|
||||
|
||||
systemctl:
|
||||
|
||||
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID1's private D-Bus
|
||||
|
||||
@@ -112,6 +112,12 @@
|
||||
<arg choice="plain">timespan</arg>
|
||||
<arg choice="plain" rep="repeat"><replaceable>SPAN</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
<cmdsynopsis>
|
||||
<command>systemd-analyze</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="plain">security</arg>
|
||||
<arg choice="plain" rep="repeat"><replaceable>UNIT</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
@@ -263,6 +269,29 @@ NAutoVTs=8
|
||||
The time span should adhere to the same syntax documented in <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
|
||||
Values without associated magnitudes are parsed as seconds.</para>
|
||||
|
||||
<para><command>systemd-analyze security</command> analyzes the security and sandboxing settings of one or more
|
||||
specified service units. If at least one unit name is specified the security settings of the specified service
|
||||
units are inspected and a detailed analysis is shown. If no unit name is specified, all currently loaded,
|
||||
long-running service units are inspected and a terse table with results shown. The command checks for various
|
||||
security-related service settings, assigning each a numeric "exposure level" value, depending on how important a
|
||||
setting is. It then calculates an overall exposure level for the whole unit, which is an estimation in the range
|
||||
0.0…10.0 indicating how exposed a service is security-wise. High exposure levels indicate very little applied
|
||||
sandboxing. Low exposure levels indicate tight sandboxing and strongest security restrictions. Note that this only
|
||||
analyzes the per-service security features systemd itself implements. This means that any additional security
|
||||
mechanisms applied by the service code itself are not accounted for. The exposure level determined this way should
|
||||
not be misunderstood: a high exposure level neither means that there is no effective sandboxing applied by the
|
||||
service code itself, nor that the service is actually vulnerable to remote or local attacks. High exposure levels
|
||||
do indicate however that most likely the service might benefit from additional settings applied to them. Please
|
||||
note that many of the security and sandboxing settings individually can be circumvented — unless combined with
|
||||
others. For example, if a service retains the privilege to establish or undo mount points many of the sandboxing
|
||||
options can be undone by the service code itself. Due to that is essential that each service uses the most
|
||||
comprehensive and strict sandboxing and security settings possible. The tool will take into account some of these
|
||||
combinations and relationships between the settings, but not all. Also note that the security and sandboxing
|
||||
settings analyzed here only apply to the operations executed by the service code itself. If a service has access to
|
||||
an IPC system (such as D-Bus) it might request operations from other services that are not subject to the same
|
||||
restrictions. Any comprehensive security and sandboxing analysis is hence incomplete if the IPC access policy is
|
||||
not validated too.</para>
|
||||
|
||||
<para>If no command is passed, <command>systemd-analyze
|
||||
time</command> is implied.</para>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "sd-bus.h"
|
||||
|
||||
typedef enum AnalyzeSecurityFlags {
|
||||
ANALYZE_SECURITY_SHORT = 1 << 0,
|
||||
ANALYZE_SECURITY_ONLY_LOADED = 1 << 1,
|
||||
ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2,
|
||||
} AnalyzeSecurityFlags;
|
||||
|
||||
int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags);
|
||||
+26
-9
@@ -12,14 +12,15 @@
|
||||
#include "sd-bus.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "analyze-security.h"
|
||||
#include "analyze-verify.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-unit-util.h"
|
||||
#include "bus-util.h"
|
||||
#include "calendarspec.h"
|
||||
#include "def.h"
|
||||
#include "conf-files.h"
|
||||
#include "copy.h"
|
||||
#include "def.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "glob-util.h"
|
||||
@@ -169,7 +170,7 @@ static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *in
|
||||
't', val);
|
||||
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, -r));
|
||||
return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -192,7 +193,7 @@ static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char
|
||||
&error,
|
||||
strv);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
|
||||
return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -355,7 +356,7 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
|
||||
&error, &reply,
|
||||
NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
|
||||
return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
|
||||
if (r < 0)
|
||||
@@ -978,7 +979,7 @@ static int list_dependencies(sd_bus *bus, const char *name) {
|
||||
&reply,
|
||||
"s");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, -r));
|
||||
return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_read(reply, "s", &id);
|
||||
if (r < 0)
|
||||
@@ -1238,7 +1239,7 @@ static int dot(int argc, char *argv[], void *userdata) {
|
||||
&reply,
|
||||
"");
|
||||
if (r < 0)
|
||||
log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
|
||||
log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
|
||||
if (r < 0)
|
||||
@@ -1340,15 +1341,16 @@ static int dump(int argc, char *argv[], void *userdata) {
|
||||
}
|
||||
|
||||
static int cat_config(int argc, char *argv[], void *userdata) {
|
||||
char **arg;
|
||||
char **arg, **list;
|
||||
int r;
|
||||
|
||||
(void) pager_open(arg_pager_flags);
|
||||
|
||||
STRV_FOREACH(arg, argv + 1) {
|
||||
list = strv_skip(argv, 1);
|
||||
STRV_FOREACH(arg, list) {
|
||||
const char *t = NULL;
|
||||
|
||||
if (arg != argv + 1)
|
||||
if (arg != list)
|
||||
print_separator();
|
||||
|
||||
if (path_is_absolute(*arg)) {
|
||||
@@ -1783,6 +1785,19 @@ static int do_verify(int argc, char *argv[], void *userdata) {
|
||||
return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
|
||||
}
|
||||
|
||||
static int do_security(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
|
||||
r = acquire_bus(&bus, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create bus connection: %m");
|
||||
|
||||
(void) pager_open(arg_pager_flags);
|
||||
|
||||
return analyze_security(bus, strv_skip(argv, 1), 0);
|
||||
}
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
int r;
|
||||
@@ -1827,6 +1842,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" calendar SPEC... Validate repetitive calendar time events\n"
|
||||
" service-watchdogs [BOOL] Get/set service watchdog state\n"
|
||||
" timespan SPAN... Validate a time span\n"
|
||||
" security [UNIT...] Analyze security of unit\n"
|
||||
"\nSee the %s for details.\n"
|
||||
, program_invocation_short_name
|
||||
, link
|
||||
@@ -2014,6 +2030,7 @@ static int run(int argc, char *argv[]) {
|
||||
{ "calendar", 2, VERB_ANY, 0, test_calendar },
|
||||
{ "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
|
||||
{ "timespan", 2, VERB_ANY, 0, dump_timespan },
|
||||
{ "security", VERB_ANY, VERB_ANY, 0, do_security },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,4 +4,6 @@ systemd_analyze_sources = files('''
|
||||
analyze.c
|
||||
analyze-verify.c
|
||||
analyze-verify.h
|
||||
analyze-security.c
|
||||
analyze-security.h
|
||||
'''.split())
|
||||
|
||||
+62
-27
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "def.h"
|
||||
#include "dirent-util.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "locale-util.h"
|
||||
@@ -347,6 +348,24 @@ bool keymap_is_valid(const char *name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool emoji_enabled(void) {
|
||||
static int cached_emoji_enabled = -1;
|
||||
|
||||
if (cached_emoji_enabled < 0) {
|
||||
int val;
|
||||
|
||||
val = getenv_bool("SYSTEMD_EMOJI");
|
||||
if (val < 0)
|
||||
cached_emoji_enabled =
|
||||
is_locale_utf8() &&
|
||||
!STRPTR_IN_SET(getenv("TERM"), "dumb", "linux");
|
||||
else
|
||||
cached_emoji_enabled = val;
|
||||
}
|
||||
|
||||
return cached_emoji_enabled;
|
||||
}
|
||||
|
||||
const char *special_glyph(SpecialGlyph code) {
|
||||
|
||||
/* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be
|
||||
@@ -359,40 +378,56 @@ const char *special_glyph(SpecialGlyph code) {
|
||||
static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
|
||||
/* ASCII fallback */
|
||||
[false] = {
|
||||
[TREE_VERTICAL] = "| ",
|
||||
[TREE_BRANCH] = "|-",
|
||||
[TREE_RIGHT] = "`-",
|
||||
[TREE_SPACE] = " ",
|
||||
[TRIANGULAR_BULLET] = ">",
|
||||
[BLACK_CIRCLE] = "*",
|
||||
[BULLET] = "*",
|
||||
[ARROW] = "->",
|
||||
[MDASH] = "-",
|
||||
[ELLIPSIS] = "...",
|
||||
[MU] = "u",
|
||||
[CHECK_MARK] = "+",
|
||||
[CROSS_MARK] = "-",
|
||||
[TREE_VERTICAL] = "| ",
|
||||
[TREE_BRANCH] = "|-",
|
||||
[TREE_RIGHT] = "`-",
|
||||
[TREE_SPACE] = " ",
|
||||
[TRIANGULAR_BULLET] = ">",
|
||||
[BLACK_CIRCLE] = "*",
|
||||
[BULLET] = "*",
|
||||
[ARROW] = "->",
|
||||
[MDASH] = "-",
|
||||
[ELLIPSIS] = "...",
|
||||
[MU] = "u",
|
||||
[CHECK_MARK] = "+",
|
||||
[CROSS_MARK] = "-",
|
||||
[ECSTATIC_SMILEY] = ":-]",
|
||||
[HAPPY_SMILEY] = ":-}",
|
||||
[SLIGHTLY_HAPPY_SMILEY] = ":-)",
|
||||
[NEUTRAL_SMILEY] = ":-|",
|
||||
[SLIGHTLY_UNHAPPY_SMILEY] = ":-(",
|
||||
[UNHAPPY_SMILEY] = ":-{️",
|
||||
[DEPRESSED_SMILEY] = ":-[",
|
||||
},
|
||||
|
||||
/* UTF-8 */
|
||||
[true] = {
|
||||
[TREE_VERTICAL] = "\342\224\202 ", /* │ */
|
||||
[TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
|
||||
[TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
|
||||
[TREE_SPACE] = " ", /* */
|
||||
[TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
|
||||
[BLACK_CIRCLE] = "\342\227\217", /* ● */
|
||||
[BULLET] = "\342\200\242", /* • */
|
||||
[ARROW] = "\342\206\222", /* → */
|
||||
[MDASH] = "\342\200\223", /* – */
|
||||
[ELLIPSIS] = "\342\200\246", /* … */
|
||||
[MU] = "\316\274", /* μ */
|
||||
[CHECK_MARK] = "\342\234\223", /* ✓ */
|
||||
[CROSS_MARK] = "\342\234\227", /* ✗ */
|
||||
[TREE_VERTICAL] = "\342\224\202 ", /* │ */
|
||||
[TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
|
||||
[TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
|
||||
[TREE_SPACE] = " ", /* */
|
||||
[TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
|
||||
[BLACK_CIRCLE] = "\342\227\217", /* ● */
|
||||
[BULLET] = "\342\200\242", /* • */
|
||||
[ARROW] = "\342\206\222", /* → */
|
||||
[MDASH] = "\342\200\223", /* – */
|
||||
[ELLIPSIS] = "\342\200\246", /* … */
|
||||
[MU] = "\316\274", /* μ */
|
||||
[CHECK_MARK] = "\342\234\223", /* ✓ */
|
||||
[CROSS_MARK] = "\342\234\227", /* ✗ */
|
||||
[ECSTATIC_SMILEY] = "\360\237\230\207", /* 😇 */
|
||||
[HAPPY_SMILEY] = "\360\237\230\200", /* 😀 */
|
||||
[SLIGHTLY_HAPPY_SMILEY] = "\360\237\231\202", /* 🙂 */
|
||||
[NEUTRAL_SMILEY] = "\360\237\230\220", /* 😐 */
|
||||
[SLIGHTLY_UNHAPPY_SMILEY] = "\360\237\231\201", /* 🙁 */
|
||||
[UNHAPPY_SMILEY] = "\360\237\230\250", /* 😨️️ */
|
||||
[DEPRESSED_SMILEY] = "\360\237\244\242", /* 🤢 */
|
||||
},
|
||||
};
|
||||
|
||||
return draw_table[is_locale_utf8()][code];
|
||||
assert(code < _SPECIAL_GLYPH_MAX);
|
||||
|
||||
return draw_table[code >= _SPECIAL_GLYPH_FIRST_SMILEY ? emoji_enabled() : is_locale_utf8()][code];
|
||||
}
|
||||
|
||||
void locale_variables_free(char*l[_VARIABLE_LC_MAX]) {
|
||||
|
||||
@@ -52,6 +52,14 @@ typedef enum {
|
||||
MU,
|
||||
CHECK_MARK,
|
||||
CROSS_MARK,
|
||||
_SPECIAL_GLYPH_FIRST_SMILEY,
|
||||
ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_SMILEY,
|
||||
HAPPY_SMILEY,
|
||||
SLIGHTLY_HAPPY_SMILEY,
|
||||
NEUTRAL_SMILEY,
|
||||
SLIGHTLY_UNHAPPY_SMILEY,
|
||||
UNHAPPY_SMILEY,
|
||||
DEPRESSED_SMILEY,
|
||||
_SPECIAL_GLYPH_MAX
|
||||
} SpecialGlyph;
|
||||
|
||||
|
||||
+5
-4
@@ -246,11 +246,12 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
|
||||
* computation should be possible in the given type. Therefore, we use
|
||||
* [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
|
||||
* quotient and the remainder, so both should be equally fast. */
|
||||
#define DIV_ROUND_UP(_x, _y) \
|
||||
#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y))
|
||||
#define __DIV_ROUND_UP(xq, x, yq, y) \
|
||||
({ \
|
||||
const typeof(_x) __x = (_x); \
|
||||
const typeof(_y) __y = (_y); \
|
||||
(__x / __y + !!(__x % __y)); \
|
||||
const typeof(x) UNIQ_T(X, xq) = (x); \
|
||||
const typeof(y) UNIQ_T(Y, yq) = (y); \
|
||||
(UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
|
||||
})
|
||||
|
||||
#ifdef __COVERITY__
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#include "string-util.h"
|
||||
|
||||
int parse_boolean(const char *v) {
|
||||
assert(v);
|
||||
if (!v)
|
||||
return -EINVAL;
|
||||
|
||||
if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
|
||||
return 1;
|
||||
|
||||
+229
-14
@@ -9,6 +9,7 @@
|
||||
#include "gunicode.h"
|
||||
#include "pager.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "time-util.h"
|
||||
@@ -58,6 +59,7 @@ typedef struct TableData {
|
||||
unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
|
||||
|
||||
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 */
|
||||
|
||||
union {
|
||||
@@ -68,6 +70,8 @@ typedef struct TableData {
|
||||
uint64_t size;
|
||||
char string[0];
|
||||
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() */
|
||||
/* … add more here as we start supporting more cell data types … */
|
||||
};
|
||||
} TableData;
|
||||
@@ -103,6 +107,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) {
|
||||
@@ -175,6 +181,8 @@ static TableData *table_data_free(TableData *d) {
|
||||
assert(d);
|
||||
|
||||
free(d->formatted);
|
||||
free(d->url);
|
||||
|
||||
return mfree(d);
|
||||
}
|
||||
|
||||
@@ -193,6 +201,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,11 +224,15 @@ static size_t table_data_size(TableDataType type, const void *data) {
|
||||
return sizeof(usec_t);
|
||||
|
||||
case TABLE_SIZE:
|
||||
case TABLE_UINT64:
|
||||
return sizeof(uint64_t);
|
||||
|
||||
case TABLE_UINT32:
|
||||
return sizeof(uint32_t);
|
||||
|
||||
case TABLE_PERCENT:
|
||||
return sizeof(int);
|
||||
|
||||
default:
|
||||
assert_not_reached("Uh? Unexpected cell type");
|
||||
}
|
||||
@@ -262,7 +275,7 @@ static bool table_data_matches(
|
||||
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(
|
||||
@@ -376,6 +389,7 @@ int table_dup_cell(Table *t, TableCell *cell) {
|
||||
}
|
||||
|
||||
static int table_dedup_cell(Table *t, TableCell *cell) {
|
||||
_cleanup_free_ char *curl = NULL;
|
||||
TableData *nd, *od;
|
||||
size_t i;
|
||||
|
||||
@@ -394,10 +408,26 @@ static int table_dedup_cell(Table *t, TableCell *cell) {
|
||||
|
||||
assert(od->n_ref > 1);
|
||||
|
||||
nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
|
||||
if (od->url) {
|
||||
curl = strdup(od->url);
|
||||
if (!curl)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
nd = table_data_new(
|
||||
od->type,
|
||||
od->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);
|
||||
|
||||
table_data_unref(od);
|
||||
t->data[i] = nd;
|
||||
|
||||
@@ -524,6 +554,66 @@ int table_set_color(Table *t, TableCell *cell, const char *color) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int table_set_url(Table *t, TableCell *cell, const char *url) {
|
||||
_cleanup_free_ char *copy = NULL;
|
||||
int r;
|
||||
|
||||
assert(t);
|
||||
assert(cell);
|
||||
|
||||
if (url) {
|
||||
copy = strdup(url);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = table_dedup_cell(t, cell);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return free_and_replace(table_get_data(t, cell)->url, copy);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
@@ -542,6 +632,8 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
|
||||
uint64_t size;
|
||||
usec_t usec;
|
||||
uint32_t uint32;
|
||||
uint64_t uint64;
|
||||
int percent;
|
||||
bool b;
|
||||
} buffer;
|
||||
|
||||
@@ -576,6 +668,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
|
||||
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_DATA_TYPE_MAX:
|
||||
/* Used as end marker */
|
||||
va_end(ap);
|
||||
@@ -699,6 +801,12 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
|
||||
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);
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
@@ -732,7 +840,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 */
|
||||
@@ -809,6 +917,30 @@ 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;
|
||||
}
|
||||
|
||||
default:
|
||||
assert_not_reached("Unexpected type?");
|
||||
}
|
||||
@@ -838,11 +970,13 @@ static int table_data_requested_width(TableData *d, size_t *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
|
||||
size_t w = 0, space, lspace, old_length;
|
||||
static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
|
||||
size_t w = 0, space, lspace, old_length, clickable_length;
|
||||
_cleanup_free_ char *clickable = NULL;
|
||||
const char *p;
|
||||
char *ret;
|
||||
size_t i;
|
||||
int r;
|
||||
|
||||
/* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
|
||||
|
||||
@@ -851,6 +985,15 @@ static char *align_string_mem(const char *str, size_t new_length, unsigned perce
|
||||
|
||||
old_length = strlen(str);
|
||||
|
||||
if (url) {
|
||||
r = terminal_urlify(url, str, &clickable);
|
||||
if (r < 0)
|
||||
return NULL;
|
||||
|
||||
clickable_length = strlen(clickable);
|
||||
} else
|
||||
clickable_length = old_length;
|
||||
|
||||
/* Determine current width on screen */
|
||||
p = str;
|
||||
while (p < str + old_length) {
|
||||
@@ -867,23 +1010,23 @@ static char *align_string_mem(const char *str, size_t new_length, unsigned perce
|
||||
|
||||
/* Already wider than the target, if so, don't do anything */
|
||||
if (w >= new_length)
|
||||
return strndup(str, old_length);
|
||||
return clickable ? TAKE_PTR(clickable) : strdup(str);
|
||||
|
||||
/* How much spaces shall we add? An how much on the left side? */
|
||||
space = new_length - w;
|
||||
lspace = space * percent / 100U;
|
||||
|
||||
ret = new(char, space + old_length + 1);
|
||||
ret = new(char, space + clickable_length + 1);
|
||||
if (!ret)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < lspace; i++)
|
||||
ret[i] = ' ';
|
||||
memcpy(ret + lspace, str, old_length);
|
||||
for (i = lspace + old_length; i < space + old_length; i++)
|
||||
memcpy(ret + lspace, clickable ?: str, clickable_length);
|
||||
for (i = lspace + clickable_length; i < space + clickable_length; i++)
|
||||
ret[i] = ' ';
|
||||
|
||||
ret[space + old_length] = 0;
|
||||
ret[space + clickable_length] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1136,23 +1279,41 @@ int table_print(Table *t, FILE *f) {
|
||||
} else if (l < width[j]) {
|
||||
/* Field is shorter than allocated space. Let's align with spaces */
|
||||
|
||||
buffer = align_string_mem(field, width[j], d->align_percent);
|
||||
buffer = align_string_mem(field, d->url, width[j], d->align_percent);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
field = buffer;
|
||||
}
|
||||
|
||||
if (l >= width[j] && d->url) {
|
||||
_cleanup_free_ char *clickable = NULL;
|
||||
|
||||
r = terminal_urlify(d->url, field, &clickable);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(buffer, clickable);
|
||||
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)
|
||||
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)
|
||||
fputs(ansi_normal(), f);
|
||||
if (colors_enabled() && (d->color || row == t->data))
|
||||
fputs(ANSI_NORMAL, f);
|
||||
}
|
||||
|
||||
fputc('\n', f);
|
||||
@@ -1199,3 +1360,57 @@ 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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ typedef enum TableDataType {
|
||||
TABLE_TIMESPAN,
|
||||
TABLE_SIZE,
|
||||
TABLE_UINT32,
|
||||
TABLE_UINT64,
|
||||
TABLE_PERCENT,
|
||||
_TABLE_DATA_TYPE_MAX,
|
||||
_TABLE_DATA_TYPE_INVALID = -1,
|
||||
} TableDataType;
|
||||
@@ -42,6 +44,9 @@ int table_set_weight(Table *t, TableCell *cell, unsigned weight);
|
||||
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
|
||||
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
|
||||
int table_set_color(Table *t, TableCell *cell, const char *color);
|
||||
int table_set_url(Table *t, TableCell *cell, const char *color);
|
||||
|
||||
int table_update(Table *t, TableCell *cell, TableDataType type, const void *data);
|
||||
|
||||
int table_add_many_internal(Table *t, TableDataType first_type, ...);
|
||||
#define table_add_many(t, ...) table_add_many_internal(t, __VA_ARGS__, _TABLE_DATA_TYPE_MAX)
|
||||
@@ -50,6 +55,7 @@ void table_set_header(Table *table, bool b);
|
||||
void table_set_width(Table *t, size_t width);
|
||||
int table_set_display(Table *t, size_t first_column, ...);
|
||||
int table_set_sort(Table *t, size_t first_column, ...);
|
||||
int table_set_reverse(Table *t, size_t column, bool b);
|
||||
|
||||
int table_print(Table *t, FILE *f);
|
||||
int table_format(Table *t, char **ret);
|
||||
@@ -60,3 +66,8 @@ static inline TableCell* TABLE_HEADER_CELL(size_t i) {
|
||||
|
||||
size_t table_get_rows(Table *t);
|
||||
size_t table_get_columns(Table *t);
|
||||
|
||||
TableCell *table_get_cell(Table *t, size_t row, size_t column);
|
||||
|
||||
const void *table_get(Table *t, TableCell *cell);
|
||||
const void *table_get_at(Table *t, size_t row, size_t column);
|
||||
|
||||
+33
-19
@@ -60,10 +60,38 @@ int terminal_urlify(const char *url, const char *text, char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_urlify_path(const char *path, const char *text, char **ret) {
|
||||
int file_url_from_path(const char *path, char **ret) {
|
||||
_cleanup_free_ char *absolute = NULL;
|
||||
struct utsname u;
|
||||
const char *url;
|
||||
char *url = NULL;
|
||||
int r;
|
||||
|
||||
if (uname(&u) < 0)
|
||||
return -errno;
|
||||
|
||||
if (!path_is_absolute(path)) {
|
||||
r = path_make_absolute_cwd(path, &absolute);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
path = absolute;
|
||||
}
|
||||
|
||||
/* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
|
||||
* hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
|
||||
* in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
|
||||
* careful with validating the strings either. */
|
||||
|
||||
url = strjoin("file://", u.nodename, path);
|
||||
if (!url)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = url;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int terminal_urlify_path(const char *path, const char *text, char **ret) {
|
||||
_cleanup_free_ char *url = NULL;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
@@ -88,23 +116,9 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (uname(&u) < 0)
|
||||
return -errno;
|
||||
|
||||
if (!path_is_absolute(path)) {
|
||||
r = path_make_absolute_cwd(path, &absolute);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
path = absolute;
|
||||
}
|
||||
|
||||
/* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
|
||||
* hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
|
||||
* in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
|
||||
* careful with validating the strings either. */
|
||||
|
||||
url = strjoina("file://", u.nodename, path);
|
||||
r = file_url_from_path(path, &url);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return terminal_urlify(url, text, ret);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
void print_separator(void);
|
||||
|
||||
int file_url_from_path(const char *path, char **ret);
|
||||
|
||||
int terminal_urlify(const char *url, const char *text, char **ret);
|
||||
int terminal_urlify_path(const char *path, const char *text, char **ret);
|
||||
int terminal_urlify_man(const char *page, const char *section, char **ret);
|
||||
|
||||
@@ -371,8 +371,6 @@ const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = {
|
||||
.value =
|
||||
"lookup_dcookie\0"
|
||||
"perf_event_open\0"
|
||||
"process_vm_readv\0"
|
||||
"process_vm_writev\0"
|
||||
"ptrace\0"
|
||||
"rtas\0"
|
||||
#ifdef __NR_s390_runtime_instr
|
||||
|
||||
@@ -65,7 +65,7 @@ static void test_keymaps(void) {
|
||||
|
||||
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
|
||||
static void dump_special_glyphs(void) {
|
||||
assert_cc(CROSS_MARK + 1 == _SPECIAL_GLYPH_MAX);
|
||||
assert_cc(DEPRESSED_SMILEY + 1 == _SPECIAL_GLYPH_MAX);
|
||||
|
||||
log_info("/* %s */", __func__);
|
||||
|
||||
@@ -84,6 +84,13 @@ static void dump_special_glyphs(void) {
|
||||
dump_glyph(MU);
|
||||
dump_glyph(CHECK_MARK);
|
||||
dump_glyph(CROSS_MARK);
|
||||
dump_glyph(ECSTATIC_SMILEY);
|
||||
dump_glyph(HAPPY_SMILEY);
|
||||
dump_glyph(SLIGHTLY_HAPPY_SMILEY);
|
||||
dump_glyph(NEUTRAL_SMILEY);
|
||||
dump_glyph(SLIGHTLY_UNHAPPY_SMILEY);
|
||||
dump_glyph(UNHAPPY_SMILEY);
|
||||
dump_glyph(DEPRESSED_SMILEY);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
Reference in New Issue
Block a user