From 77599f06e121943b09ca730168a6cbe7f202c782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 May 2019 09:01:44 +0200 Subject: [PATCH 01/12] cgtop,test: use consistent capitalization for CGroup --- src/cgtop/cgtop.c | 2 +- test/TEST-13-NSPAWN-SMOKE/test.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index c548be07ca1..fff6b505cc3 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -930,7 +930,7 @@ static int run(int argc, char *argv[]) { r = show_cgroup_get_path_and_warn(arg_machine, arg_root, &root); if (r < 0) return log_error_errno(r, "Failed to get root control group path: %m"); - log_debug("Cgroup path: %s", root); + log_debug("CGroup path: %s", root); a = hashmap_new(&group_hash_ops); b = hashmap_new(&group_hash_ops); diff --git a/test/TEST-13-NSPAWN-SMOKE/test.sh b/test/TEST-13-NSPAWN-SMOKE/test.sh index bd85b92caa5..8252c4b2aaf 100755 --- a/test/TEST-13-NSPAWN-SMOKE/test.sh +++ b/test/TEST-13-NSPAWN-SMOKE/test.sh @@ -98,7 +98,7 @@ function run { return 0 fi if [[ "$2" = "yes" && "$is_cgns_supported" = "no" ]]; then - printf "Cgroup namespaces are not supported. Skipping.\n" >&2 + printf "CGroup namespaces are not supported. Skipping.\n" >&2 return 0 fi From 21c491e106feb7d4caa7f4b9503b7e9de6e8af23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 11 May 2019 09:51:33 +0200 Subject: [PATCH 02/12] Introduce sc_arg_max() helper Just a cast and an assert. --- src/basic/env-util.c | 4 ++-- src/basic/env-util.h | 7 +++++++ src/basic/process-util.c | 8 +++----- src/journal/journald-context.c | 9 +++------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index fd449dcce07..896eec58356 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -72,7 +72,7 @@ bool env_value_is_valid(const char *e) { * either. Discounting the shortest possible variable name of * length 1, the equal sign and trailing NUL this hence leaves * ARG_MAX-3 as longest possible variable value. */ - if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 3) + if (strlen(e) > sc_arg_max() - 3) return false; return true; @@ -95,7 +95,7 @@ bool env_assignment_is_valid(const char *e) { * be > ARG_MAX, hence the individual variable assignments * cannot be either, but let's leave room for one trailing NUL * byte. */ - if (strlen(e) > (size_t) sysconf(_SC_ARG_MAX) - 1) + if (strlen(e) > sc_arg_max() - 1) return false; return true; diff --git a/src/basic/env-util.h b/src/basic/env-util.h index d54f99658bd..92802ed7744 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -4,10 +4,17 @@ #include #include #include +#include #include "macro.h" #include "string.h" +static inline size_t sc_arg_max(void) { + long l = sysconf(_SC_ARG_MAX); + assert(l > 0); + return (size_t) l; +} + bool env_name_is_valid(const char *e); bool env_value_is_valid(const char *e); bool env_assignment_is_valid(const char *e); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 052bce6645f..21af88f5f88 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "architecture.h" #include "escape.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -126,12 +127,9 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * if (r < 0) return r; - if (max_length == 0) { + if (max_length == 0) /* This is supposed to be a safety guard against runaway command lines. */ - long l = sysconf(_SC_ARG_MAX); - assert(l > 0); - max_length = l; - } + max_length = sc_arg_max(); if (max_length == 1) { diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index e6aeb03b779..e41e4fc5a72 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "audit-util.h" #include "cgroup-util.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -76,18 +77,14 @@ static size_t cache_max(void) { if (r < 0) { log_warning_errno(r, "Cannot query /proc/meminfo for MemTotal: %m"); cached = CACHE_MAX_FALLBACK; - } else { + } else /* Cache entries are usually a few kB, but the process cmdline is controlled by the * user and can be up to _SC_ARG_MAX, usually 2MB. Let's say that approximately up to * 1/8th of memory may be used by the cache. * * In the common case, this formula gives 64 cache entries for each GB of RAM. */ - long l = sysconf(_SC_ARG_MAX); - assert(l > 0); - - cached = CLAMP(mem_total / 8 / (uint64_t) l, CACHE_MAX_MIN, CACHE_MAX_MAX); - } + cached = CLAMP(mem_total / 8 / sc_arg_max(), CACHE_MAX_MIN, CACHE_MAX_MAX); } return cached; From 9743846e2342e35ab66671e69753811b638a0161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 May 2019 00:04:39 +0200 Subject: [PATCH 03/12] test-utf8: add function headers and rename tests after functions they test --- src/test/test-utf8.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index d1e48da2a66..343cbf4ced3 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -7,6 +7,8 @@ #include "util.h" static void test_utf8_is_printable(void) { + log_info("/* %s */", __func__); + assert_se(utf8_is_printable("ascii is valid\tunicode", 22)); assert_se(utf8_is_printable("\342\204\242", 3)); assert_se(!utf8_is_printable("\341\204", 2)); @@ -14,18 +16,24 @@ static void test_utf8_is_printable(void) { } static void test_utf8_is_valid(void) { + log_info("/* %s */", __func__); + assert_se(utf8_is_valid("ascii is valid unicode")); assert_se(utf8_is_valid("\342\204\242")); assert_se(!utf8_is_valid("\341\204")); } static void test_ascii_is_valid(void) { + log_info("/* %s */", __func__); + assert_se( ascii_is_valid("alsdjf\t\vbarr\nba z")); assert_se(!ascii_is_valid("\342\204\242")); assert_se(!ascii_is_valid("\341\204")); } static void test_ascii_is_valid_n(void) { + log_info("/* %s */", __func__); + assert_se( ascii_is_valid_n("alsdjf\t\vbarr\nba z", 17)); assert_se( ascii_is_valid_n("alsdjf\t\vbarr\nba z", 16)); assert_se(!ascii_is_valid_n("alsdjf\t\vbarr\nba z", 18)); @@ -36,6 +44,8 @@ static void test_ascii_is_valid_n(void) { } static void test_utf8_encoded_valid_unichar(void) { + log_info("/* %s */", __func__); + assert_se(utf8_encoded_valid_unichar("\342\204\242", 1) == -EINVAL); /* truncated */ assert_se(utf8_encoded_valid_unichar("\342\204\242", 2) == -EINVAL); /* truncated */ assert_se(utf8_encoded_valid_unichar("\342\204\242", 3) == 3); @@ -53,9 +63,11 @@ static void test_utf8_encoded_valid_unichar(void) { assert_se(utf8_encoded_valid_unichar("\341\204\341\204", 5) == -EINVAL); } -static void test_utf8_escaping(void) { +static void test_utf8_escape_invalid(void) { _cleanup_free_ char *p1, *p2, *p3; + log_info("/* %s */", __func__); + p1 = utf8_escape_invalid("goo goo goo"); puts(p1); assert_se(utf8_is_valid(p1)); @@ -69,9 +81,11 @@ static void test_utf8_escaping(void) { assert_se(utf8_is_valid(p3)); } -static void test_utf8_escaping_printable(void) { +static void test_utf8_escape_non_printable(void) { _cleanup_free_ char *p1, *p2, *p3, *p4, *p5, *p6; + log_info("/* %s */", __func__); + p1 = utf8_escape_non_printable("goo goo goo"); puts(p1); assert_se(utf8_is_valid(p1)); @@ -103,6 +117,8 @@ static void test_utf16_to_utf8(void) { _cleanup_free_ char16_t *b = NULL; _cleanup_free_ char *a = NULL; + log_info("/* %s */", __func__); + /* Convert UTF-16 to UTF-8, filtering embedded bad chars */ a = utf16_to_utf8(utf16, sizeof(utf16)); assert_se(a); @@ -120,6 +136,8 @@ static void test_utf16_to_utf8(void) { } static void test_utf8_n_codepoints(void) { + log_info("/* %s */", __func__); + assert_se(utf8_n_codepoints("abc") == 3); assert_se(utf8_n_codepoints("zażółcić gęślą jaźń") == 19); assert_se(utf8_n_codepoints("串") == 1); @@ -129,6 +147,8 @@ static void test_utf8_n_codepoints(void) { } static void test_utf8_console_width(void) { + log_info("/* %s */", __func__); + assert_se(utf8_console_width("abc") == 3); assert_se(utf8_console_width("zażółcić gęślą jaźń") == 19); assert_se(utf8_console_width("串") == 2); @@ -140,6 +160,8 @@ static void test_utf8_console_width(void) { static void test_utf8_to_utf16(void) { const char *p; + log_info("/* %s */", __func__); + FOREACH_STRING(p, "abc", "zażółcić gęślą jaźń", @@ -165,8 +187,8 @@ int main(int argc, char *argv[]) { test_ascii_is_valid(); test_ascii_is_valid_n(); test_utf8_encoded_valid_unichar(); - test_utf8_escaping(); - test_utf8_escaping_printable(); + test_utf8_escape_invalid(); + test_utf8_escape_non_printable(); test_utf16_to_utf8(); test_utf8_n_codepoints(); test_utf8_console_width(); From 31c294dc4194f60fb5f8ae517e7f5d3caed69566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 14 May 2019 00:05:32 +0200 Subject: [PATCH 04/12] shared/cgroup-show: fix off-by-one in column counting We'd get one column too many, and the trailing ellipsis would end up in the next row. --- src/shared/cgroup-show.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index 91a243944c7..cac7922ec0e 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -53,8 +53,8 @@ static void show_pid_array( if (flags & OUTPUT_FULL_WIDTH) n_columns = 0; else { - if (n_columns > pid_width+2) - n_columns -= pid_width+2; + if (n_columns > pid_width + 3) /* something like "├─1114784 " */ + n_columns -= pid_width + 3; else n_columns = 20; } From da88f542d9315a341a500897e5e1bf8809d1aeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 May 2019 10:57:07 +0200 Subject: [PATCH 05/12] util-lib: add truncation based on printable width to utf8_escape_non_printable() --- src/basic/utf8.c | 72 ++++++++++++++++++++++++++++++++++++-------- src/basic/utf8.h | 5 ++- src/test/test-utf8.c | 32 ++++++++++++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 090c69d1400..e12876962d6 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -196,42 +196,88 @@ char *utf8_escape_invalid(const char *str) { return p; } -char *utf8_escape_non_printable(const char *str) { - char *p, *s; +static int utf8_char_console_width(const char *str) { + char32_t c; + int r; + + r = utf8_encoded_to_unichar(str, &c); + if (r < 0) + return r; + + /* TODO: we should detect combining characters */ + + return unichar_iswide(c) ? 2 : 1; +} + +char *utf8_escape_non_printable_full(const char *str, size_t console_width) { + char *p, *s, *prev_s; + size_t n = 0; /* estimated print width */ assert(str); - p = s = malloc(strlen(str) * 4 + 1); + if (console_width == 0) + return strdup(""); + + p = s = prev_s = malloc(strlen(str) * 4 + 1); if (!p) return NULL; - while (*str) { + for (;;) { int len; + char *saved_s = s; + + if (!*str) /* done! */ + goto finish; len = utf8_encoded_valid_unichar(str, (size_t) -1); if (len > 0) { if (utf8_is_printable(str, len)) { + int w; + + w = utf8_char_console_width(str); + assert(w >= 0); + if (n + w > console_width) + goto truncation; + s = mempcpy(s, str, len); str += len; + n += w; + } else { - while (len > 0) { + for (; len > 0; len--) { + if (n + 4 > console_width) + goto truncation; + *(s++) = '\\'; *(s++) = 'x'; *(s++) = hexchar((int) *str >> 4); *(s++) = hexchar((int) *str); str += 1; - len--; + n += 4; } } } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + if (n + 1 > console_width) + goto truncation; + + s = mempcpy(s, UTF8_REPLACEMENT_CHARACTER, strlen(UTF8_REPLACEMENT_CHARACTER)); str += 1; + n += 1; } + + prev_s = saved_s; } - *s = '\0'; + truncation: + /* Try to go back one if we don't have enough space for the ellipsis */ + if (n + 1 >= console_width) + s = prev_s; + s = mempcpy(s, "…", strlen("…")); + + finish: + *s = '\0'; return p; } @@ -519,15 +565,15 @@ size_t utf8_console_width(const char *str) { /* Returns the approximate width a string will take on screen when printed on a character cell * terminal/console. */ - while (*str != 0) { - char32_t c; + while (*str) { + int w; - if (utf8_encoded_to_unichar(str, &c) < 0) + w = utf8_char_console_width(str); + if (w < 0) return (size_t) -1; + n += w; str = utf8_next_char(str); - - n += unichar_iswide(c) ? 2 : 1; } return n; diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 6df70921dbd..62e99b72802 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -22,7 +22,10 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pu #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable(const char *str); +char *utf8_escape_non_printable_full(const char *str, size_t console_width); +static inline char *utf8_escape_non_printable(const char *str) { + return utf8_escape_non_printable_full(str, (size_t) -1); +} size_t utf8_encode_unichar(char *out_utf8, char32_t g); size_t utf16_encode_unichar(char16_t *out, char32_t c); diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index 343cbf4ced3..b5c4e3dc343 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -111,6 +111,37 @@ static void test_utf8_escape_non_printable(void) { assert_se(utf8_is_valid(p6)); } +static void test_utf8_escape_non_printable_full(void) { + log_info("/* %s */", __func__); + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("goo goo goo", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("\001 \019\20\a", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("\xef\xbf\x30\x13", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } +} + static void test_utf16_to_utf8(void) { const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) }; static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 }; @@ -189,6 +220,7 @@ int main(int argc, char *argv[]) { test_utf8_encoded_valid_unichar(); test_utf8_escape_invalid(); test_utf8_escape_non_printable(); + test_utf8_escape_non_printable_full(); test_utf16_to_utf8(); test_utf8_n_codepoints(); test_utf8_console_width(); From bc28751ed2372065fd6f6f7ba6137edf21dee88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 May 2019 11:20:26 +0200 Subject: [PATCH 06/12] Rework cmdline printing to use unicode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The functions to retrieve and print process cmdlines were based on the assumption that they contain printable ASCII, and everything else should be filtered out. That assumption doesn't hold in today's world, where people are free to use unicode everywhere. This replaces the custom cmdline reading code with a more generic approach using utf8_escape_non_printable_full(). For kernel threads, truncation is done on the parenthesized name, so we'll get "[worker]", "[worker…]", …, "[w…]", "[…", "…" as we reduce the number of available columns. This implementation is most likely slower for very long cmdlines, but I don't think this is very important. The common case is to have short commandlines, and should print those properly. Absurdly long cmdlines are the exception, which needs to be handled correctly and safely, but speed is not too important. Fixes #12532. v2: - use size_t for the number of columns. This change propagates into various other functions that call get_process_cmdline(), increasing the size of the patch, but the changes are rather trivial. --- src/basic/proc-cmdline.c | 2 +- src/basic/process-util.c | 164 ++++++++++----------------------- src/basic/process-util.h | 2 +- src/basic/string-util.h | 13 +++ src/core/dbus-unit.c | 2 +- src/coredump/coredump.c | 4 +- src/journal/journald-context.c | 2 +- src/shared/cgroup-show.c | 14 +-- src/shared/cgroup-show.h | 6 +- src/test/test-process-util.c | 79 ++++++++-------- 10 files changed, 115 insertions(+), 173 deletions(-) diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 16700013641..7dca9e60b61 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -34,7 +34,7 @@ int proc_cmdline(char **ret) { } if (detect_container() > 0) - return get_process_cmdline(1, 0, false, ret); + return get_process_cmdline(1, SIZE_MAX, false, ret); else return read_one_line_file("/proc/cmdline", ret); } diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 21af88f5f88..4c05f16db52 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -44,6 +44,7 @@ #include "string-util.h" #include "terminal-util.h" #include "user-util.h" +#include "utf8.h" static int get_process_state(pid_t pid) { const char *p; @@ -100,22 +101,24 @@ int get_process_comm(pid_t pid, char **ret) { return 0; } -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { +int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char **line) { _cleanup_fclose_ FILE *f = NULL; - bool space = false; - char *k; - _cleanup_free_ char *ans = NULL; + _cleanup_free_ char *t = NULL, *ans = NULL; const char *p; - int c, r; + int r; + size_t k; + + /* This is supposed to be a safety guard against runaway command lines. */ + size_t max_length = sc_arg_max(); assert(line); assert(pid >= 0); - /* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing - * multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most - * (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If - * comm_fallback is true and the process has no command line set (the case for kernel threads), or has a - * command line that resolves to the empty string will return the "comm" name of the process instead. + /* Retrieves a process' command line. Replaces non-utf8 bytes by replacement character (�). If + * max_columns is != -1 will return a string of the specified console width at most, abbreviated with + * an ellipsis. If comm_fallback is true and the process has no command line set (the case for kernel + * threads), or has a command line that resolves to the empty string will return the "comm" name of + * the process instead. This will use at most _SC_ARG_MAX bytes of input data. * * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and * comm_fallback is false). Returns 0 and sets *line otherwise. */ @@ -127,127 +130,54 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * if (r < 0) return r; - if (max_length == 0) - /* This is supposed to be a safety guard against runaway command lines. */ - max_length = sc_arg_max(); + /* We assume that each four-byte character uses one or two columns. If we ever check for combining + * characters, this assumption will need to be adjusted. */ + if ((size_t) 4 * max_columns + 1 < max_columns) + max_length = MIN(max_length, (size_t) 4 * max_columns + 1); - if (max_length == 1) { + t = new(char, max_length); + if (!t) + return -ENOMEM; - /* If there's only room for one byte, return the empty string */ - ans = new0(char, 1); - if (!ans) - return -ENOMEM; - - *line = TAKE_PTR(ans); - return 0; + k = fread(t, 1, max_length, f); + if (k > 0) { + /* Arguments are separated by NULs. Let's replace those with spaces. */ + for (size_t i = 0; i < k - 1; i++) + if (t[i] == '\0') + t[i] = ' '; + t[k] = '\0'; /* Normally, t[k] is already NUL, so this is just a guard in case of short read */ } else { - bool dotdotdot = false; - size_t left; - - ans = new(char, max_length); - if (!ans) - return -ENOMEM; - - k = ans; - left = max_length; - while ((c = getc(f)) != EOF) { - - if (isprint(c)) { - - if (space) { - if (left <= 2) { - dotdotdot = true; - break; - } - - *(k++) = ' '; - left--; - space = false; - } - - if (left <= 1) { - dotdotdot = true; - break; - } - - *(k++) = (char) c; - left--; - } else if (k > ans) - space = true; - } - - if (dotdotdot) { - if (max_length <= 4) { - k = ans; - left = max_length; - } else { - k = ans + max_length - 4; - left = 4; - - /* Eat up final spaces */ - while (k > ans && isspace(k[-1])) { - k--; - left++; - } - } - - strncpy(k, "...", left-1); - k[left-1] = 0; - } else - *k = 0; - } - - /* Kernel threads have no argv[] */ - if (isempty(ans)) { - _cleanup_free_ char *t = NULL; - int h; - - ans = mfree(ans); + /* We only treat getting nothing as an error. We *could* also get an error after reading some + * data, but we ignore that case, as such an error is rather unlikely and we prefer to get + * some data rather than none. */ + if (ferror(f)) + return -errno; if (!comm_fallback) return -ENOENT; - h = get_process_comm(pid, &t); - if (h < 0) - return h; + /* Kernel threads have no argv[] */ + _cleanup_free_ char *t2 = NULL; - size_t l = strlen(t); + r = get_process_comm(pid, &t2); + if (r < 0) + return r; - if (l + 3 <= max_length) { - ans = strjoin("[", t, "]"); - if (!ans) - return -ENOMEM; - - } else if (max_length <= 6) { - ans = new(char, max_length); - if (!ans) - return -ENOMEM; - - memcpy(ans, "[...]", max_length-1); - ans[max_length-1] = 0; - } else { - t[max_length - 6] = 0; - - /* Chop off final spaces */ - delete_trailing_chars(t, WHITESPACE); - - ans = strjoin("[", t, "...]"); - if (!ans) - return -ENOMEM; - } - - *line = TAKE_PTR(ans); - return 0; + mfree(t); + t = strjoin("[", t2, "]"); + if (!t) + return -ENOMEM; } - k = realloc(ans, strlen(ans) + 1); - if (!k) + delete_trailing_chars(t, WHITESPACE); + + ans = utf8_escape_non_printable_full(t, max_columns); + if (!ans) return -ENOMEM; - ans = NULL; - *line = k; - + (void) str_realloc(&ans); + *line = TAKE_PTR(ans); return 0; } diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 83ba93d0d74..dec584606b6 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -32,7 +32,7 @@ }) int get_process_comm(pid_t pid, char **name); -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line); +int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char **line); int get_process_exe(pid_t pid, char **name); int get_process_uid(pid_t pid, uid_t *uid); int get_process_gid(pid_t pid, gid_t *gid); diff --git a/src/basic/string-util.h b/src/basic/string-util.h index a630856236a..47b17c9d3e7 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -257,3 +257,16 @@ static inline void *memory_startswith_no_case(const void *p, size_t sz, const ch return (uint8_t*) p + n; } + +static inline char* str_realloc(char **p) { + /* Reallocate *p to actual size */ + + if (!*p) + return NULL; + + char *t = realloc(*p, strlen(*p) + 1); + if (!t) + return NULL; + + return (*p = t); +} diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 16b4a6b1332..1e996a2f1b0 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -901,7 +901,7 @@ static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set * p = buf; } - (void) get_process_cmdline(pid, 0, true, &cmdline); + (void) get_process_cmdline(pid, SIZE_MAX, true, &cmdline); return sd_bus_message_append(reply, "(sus)", diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 9ab704e2076..c0bd03762f1 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -661,7 +661,7 @@ static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) { if (r < 0) return r; - r = get_process_cmdline(container_pid, 0, false, cmdline); + r = get_process_cmdline(container_pid, SIZE_MAX, false, cmdline); if (r < 0) return r; @@ -1154,7 +1154,7 @@ static int gather_pid_metadata( if (sd_pid_get_slice(pid, &t) >= 0) set_iovec_field_free(iovec, n_iovec, "COREDUMP_SLICE=", t); - if (get_process_cmdline(pid, 0, false, &t) >= 0) + if (get_process_cmdline(pid, SIZE_MAX, false, &t) >= 0) set_iovec_field_free(iovec, n_iovec, "COREDUMP_CMDLINE=", t); if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index e41e4fc5a72..684ae9aaae0 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -230,7 +230,7 @@ static void client_context_read_basic(ClientContext *c) { if (get_process_exe(c->pid, &t) >= 0) free_and_replace(c->exe, t); - if (get_process_cmdline(c->pid, 0, false, &t) >= 0) + if (get_process_cmdline(c->pid, SIZE_MAX, false, &t) >= 0) free_and_replace(c->cmdline, t); if (get_process_capeff(c->pid, &t) >= 0) diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index cac7922ec0e..2bd0e09caee 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -29,7 +29,7 @@ static void show_pid_array( pid_t pids[], unsigned n_pids, const char *prefix, - unsigned n_columns, + size_t n_columns, bool extra, bool more, OutputFlags flags) { @@ -51,7 +51,7 @@ static void show_pid_array( pid_width = DECIMAL_STR_WIDTH(pids[j]); if (flags & OUTPUT_FULL_WIDTH) - n_columns = 0; + n_columns = SIZE_MAX; else { if (n_columns > pid_width + 3) /* something like "├─1114784 " */ n_columns -= pid_width + 3; @@ -75,7 +75,7 @@ static void show_pid_array( static int show_cgroup_one_by_path( const char *path, const char *prefix, - unsigned n_columns, + size_t n_columns, bool more, OutputFlags flags) { @@ -119,7 +119,7 @@ static int show_cgroup_one_by_path( int show_cgroup_by_path( const char *path, const char *prefix, - unsigned n_columns, + size_t n_columns, OutputFlags flags) { _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; @@ -199,7 +199,7 @@ int show_cgroup_by_path( int show_cgroup(const char *controller, const char *path, const char *prefix, - unsigned n_columns, + size_t n_columns, OutputFlags flags) { _cleanup_free_ char *p = NULL; int r; @@ -217,7 +217,7 @@ static int show_extra_pids( const char *controller, const char *path, const char *prefix, - unsigned n_columns, + size_t n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) { @@ -262,7 +262,7 @@ int show_cgroup_and_extra( const char *controller, const char *path, const char *prefix, - unsigned n_columns, + size_t n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { diff --git a/src/shared/cgroup-show.h b/src/shared/cgroup-show.h index 3593e9dcf43..dfb26f82910 100644 --- a/src/shared/cgroup-show.h +++ b/src/shared/cgroup-show.h @@ -9,10 +9,10 @@ #include "logs-show.h" #include "output-mode.h" -int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, OutputFlags flags); -int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, OutputFlags flags); +int show_cgroup_by_path(const char *path, const char *prefix, size_t n_columns, OutputFlags flags); +int show_cgroup(const char *controller, const char *path, const char *prefix, size_t n_columns, OutputFlags flags); -int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, size_t n_columns, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); int show_cgroup_get_unit_path_and_warn( sd_bus *bus, diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 89f6618e2e8..aaa8041ec35 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -237,117 +237,116 @@ static void test_get_process_cmdline_harder(void) { assert_se(prctl(PR_SET_NAME, "testa") >= 0); - assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) == -ENOENT); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + log_info("'%s'", line); assert_se(streq(line, "")); line = mfree(line); + assert_se(get_process_cmdline(getpid_cached(), 1, true, &line) >= 0); + assert_se(streq(line, "…")); + line = mfree(line); + assert_se(get_process_cmdline(getpid_cached(), 2, true, &line) >= 0); - assert_se(streq(line, "[")); + assert_se(streq(line, "[…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 3, true, &line) >= 0); - assert_se(streq(line, "[.")); + assert_se(streq(line, "[t…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 4, true, &line) >= 0); - assert_se(streq(line, "[..")); + assert_se(streq(line, "[te…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 5, true, &line) >= 0); - assert_se(streq(line, "[...")); + assert_se(streq(line, "[tes…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 6, true, &line) >= 0); - assert_se(streq(line, "[...]")); + assert_se(streq(line, "[test…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 7, true, &line) >= 0); - assert_se(streq(line, "[t...]")); + assert_se(streq(line, "[testa]")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 8, true, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(write(fd, "\0\0\0\0\0\0\0\0\0", 10) == 10); + assert_se(write(fd, "foo\0bar", 8) == 8); - assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) == -ENOENT); - - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); - assert_se(streq(line, "[testa]")); - line = mfree(line); - - assert_se(write(fd, "foo\0bar\0\0\0\0\0", 10) == 10); - - assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) >= 0); + log_info("'%s'", line); assert_se(streq(line, "foo bar")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); assert_se(streq(line, "foo bar")); line = mfree(line); assert_se(write(fd, "quux", 4) == 4); - assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) >= 0); + log_info("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 1, true, &line) >= 0); - assert_se(streq(line, "")); + assert_se(streq(line, "…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 2, true, &line) >= 0); - assert_se(streq(line, ".")); + assert_se(streq(line, "f…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 3, true, &line) >= 0); - assert_se(streq(line, "..")); + assert_se(streq(line, "fo…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 4, true, &line) >= 0); - assert_se(streq(line, "...")); + assert_se(streq(line, "foo…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 5, true, &line) >= 0); - assert_se(streq(line, "f...")); + assert_se(streq(line, "foo …")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 6, true, &line) >= 0); - assert_se(streq(line, "fo...")); + assert_se(streq(line, "foo b…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 7, true, &line) >= 0); - assert_se(streq(line, "foo...")); + assert_se(streq(line, "foo ba…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 8, true, &line) >= 0); - assert_se(streq(line, "foo...")); + assert_se(streq(line, "foo bar…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 9, true, &line) >= 0); - assert_se(streq(line, "foo b...")); + assert_se(streq(line, "foo bar …")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 10, true, &line) >= 0); - assert_se(streq(line, "foo ba...")); + assert_se(streq(line, "foo bar q…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 11, true, &line) >= 0); - assert_se(streq(line, "foo bar...")); + assert_se(streq(line, "foo bar qu…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 12, true, &line) >= 0); - assert_se(streq(line, "foo bar...")); + assert_se(streq(line, "foo bar quux")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 13, true, &line) >= 0); @@ -365,22 +364,22 @@ static void test_get_process_cmdline_harder(void) { assert_se(ftruncate(fd, 0) >= 0); assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0); - assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) == -ENOENT); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); assert_se(streq(line, "[aaaa bbbb cccc]")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 10, true, &line) >= 0); - assert_se(streq(line, "[aaaa...]")); + assert_se(streq(line, "[aaaa bbb…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 11, true, &line) >= 0); - assert_se(streq(line, "[aaaa...]")); + assert_se(streq(line, "[aaaa bbbb…")); line = mfree(line); assert_se(get_process_cmdline(getpid_cached(), 12, true, &line) >= 0); - assert_se(streq(line, "[aaaa b...]")); + assert_se(streq(line, "[aaaa bbbb …")); line = mfree(line); safe_close(fd); @@ -409,7 +408,7 @@ static void test_rename_process_now(const char *p, int ret) { log_info("comm = <%s>", comm); assert_se(strneq(comm, p, TASK_COMM_LEN-1)); - r = get_process_cmdline(0, 0, false, &cmdline); + r = get_process_cmdline(0, SIZE_MAX, false, &cmdline); assert_se(r >= 0); /* we cannot expect cmdline to be renamed properly without privileges */ if (geteuid() == 0) { From 0e85cbcfc35d3a0f18f593e7ffbd6b98e778f401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 May 2019 11:55:59 +0200 Subject: [PATCH 07/12] util-lib: do not truncate kernel comm names It turns out that the kernel allows comm names higher than our expected limit of 16. $ wc -c /proc/*/comm|sort -g|tail -n3 35 /proc/1292317/comm 35 /proc/1293610/comm 36 /proc/1287112/comm $ cat /proc/1287112/comm kworker/u9:3-kcryptd/253:0 --- src/basic/process-util.c | 11 ++++++++--- src/test/test-process-util.c | 14 ++++++++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 4c05f16db52..8db7f462d75 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -46,6 +46,11 @@ #include "user-util.h" #include "utf8.h" +/* The kernel limits userspace processes to TASK_COMM_LEN (16 bytes), but allows higher values for its own + * workers, e.g. "kworker/u9:3-kcryptd/253:0". Let's pick a fixed smallish limit that will work for the kernel. + */ +#define COMM_MAX_LEN 128 + static int get_process_state(pid_t pid) { const char *p; char state; @@ -82,7 +87,7 @@ int get_process_comm(pid_t pid, char **ret) { assert(ret); assert(pid >= 0); - escaped = new(char, TASK_COMM_LEN); + escaped = new(char, COMM_MAX_LEN); if (!escaped) return -ENOMEM; @@ -95,7 +100,7 @@ int get_process_comm(pid_t pid, char **ret) { return r; /* Escape unprintable characters, just in case, but don't grow the string beyond the underlying size */ - cellescape(escaped, TASK_COMM_LEN, comm); + cellescape(escaped, COMM_MAX_LEN, comm); *ret = TAKE_PTR(escaped); return 0; @@ -209,7 +214,7 @@ int rename_process(const char name[]) { * can use PR_SET_NAME, which sets the thread name for the calling thread. */ if (prctl(PR_SET_NAME, name) < 0) log_debug_errno(errno, "PR_SET_NAME failed: %m"); - if (l >= TASK_COMM_LEN) /* Linux process names can be 15 chars at max */ + if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */ truncated = true; /* Second step, change glibc's ID of the process name. */ diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index aaa8041ec35..107d0b5d224 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -107,12 +107,12 @@ static void test_get_process_comm_escape(void) { test_get_process_comm_escape_one("foo", "foo"); test_get_process_comm_escape_one("012345678901234", "012345678901234"); test_get_process_comm_escape_one("0123456789012345", "012345678901234"); - test_get_process_comm_escape_one("äöüß", "\\303\\244\\303…"); - test_get_process_comm_escape_one("xäöüß", "x\\303\\244…"); - test_get_process_comm_escape_one("xxäöüß", "xx\\303\\244…"); - test_get_process_comm_escape_one("xxxäöüß", "xxx\\303\\244…"); - test_get_process_comm_escape_one("xxxxäöüß", "xxxx\\303\\244…"); - test_get_process_comm_escape_one("xxxxxäöüß", "xxxxx\\303…"); + test_get_process_comm_escape_one("äöüß", "\\303\\244\\303\\266\\303\\274\\303\\237"); + test_get_process_comm_escape_one("xäöüß", "x\\303\\244\\303\\266\\303\\274\\303\\237"); + test_get_process_comm_escape_one("xxäöüß", "xx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_get_process_comm_escape_one("xxxäöüß", "xxx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_get_process_comm_escape_one("xxxxäöüß", "xxxx\\303\\244\\303\\266\\303\\274\\303\\237"); + test_get_process_comm_escape_one("xxxxxäöüß", "xxxxx\\303\\244\\303\\266\\303\\274\\303\\237"); assert_se(prctl(PR_SET_NAME, saved) >= 0); } @@ -407,6 +407,8 @@ static void test_rename_process_now(const char *p, int ret) { assert_se(get_process_comm(0, &comm) >= 0); log_info("comm = <%s>", comm); assert_se(strneq(comm, p, TASK_COMM_LEN-1)); + /* We expect comm to be at most 16 bytes (TASK_COMM_LEN). The kernel may raise this limit in the + * future. We'd only check the initial part, at least until we recompile, but this will still pass. */ r = get_process_cmdline(0, SIZE_MAX, false, &cmdline); assert_se(r >= 0); From 390f0875e45d573c495330ed5ca41099c84b9bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 15 May 2019 11:56:16 +0200 Subject: [PATCH 08/12] TODO: update --- TODO | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TODO b/TODO index d87a70a0b53..e8f59d0932f 100644 --- a/TODO +++ b/TODO @@ -4,6 +4,11 @@ Bugfixes: manager or system manager can be always set. It would be better to reject them when parsing config. +* busctl --user call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager GetUnitProcesses "s" run-rbff1b85427b34ba3adf864281aeda8e7.service +Failed to set address: No such file or directory + + → improve error message + External: * Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros. From 70d558199cf2b783f894a2769df44884a5ba8719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 May 2019 13:12:37 +0200 Subject: [PATCH 09/12] basic/escape: add truncation to xescape too This does for ASCII and non-unicode encodings what utf8_escape_non_printable_full() does for utf8-based encodings. --- src/basic/escape.c | 64 +++++++++++++++++++++++++++++++++--------- src/basic/escape.h | 5 +++- src/test/test-escape.c | 44 +++++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/basic/escape.c b/src/basic/escape.c index 5f715156fbf..77e86a58f31 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -368,33 +368,71 @@ int cunescape(const char *s, UnescapeFlags flags, char **ret) { return cunescape_length(s, strlen(s), flags, ret); } -char *xescape(const char *s, const char *bad) { - char *r, *t; +char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) { + char *ans, *t, *prev, *prev2; const char *f; - /* Escapes all chars in bad, in addition to \ and all special - * chars, in \xFF style escaping. May be reversed with - * cunescape(). */ + /* Escapes all chars in bad, in addition to \ and all special chars, in \xFF style escaping. May be + * reversed with cunescape(). If eight_bits is true, characters >= 127 are let through unchanged. + * This corresponds to non-ASCII printable characters in pre-unicode encodings. + * + * If console_width is reached, output is truncated and "..." is appended. */ - r = new(char, strlen(s) * 4 + 1); - if (!r) + if (console_width == 0) + return strdup(""); + + ans = new(char, MIN(strlen(s), console_width) * 4 + 1); + if (!ans) return NULL; - for (f = s, t = r; *f; f++) { + memset(ans, '_', MIN(strlen(s), console_width) * 4); + ans[MIN(strlen(s), console_width) * 4] = 0; + + for (f = s, t = prev = prev2 = ans; ; f++) { + char *tmp_t = t; + + if (!*f) { + *t = 0; + return ans; + } + + if ((unsigned char) *f < ' ' || (!eight_bits && (unsigned char) *f >= 127) || + *f == '\\' || strchr(bad, *f)) { + if ((size_t) (t - ans) + 4 > console_width) + break; - if ((*f < ' ') || (*f >= 127) || - (*f == '\\') || strchr(bad, *f)) { *(t++) = '\\'; *(t++) = 'x'; *(t++) = hexchar(*f >> 4); *(t++) = hexchar(*f); - } else + } else { + if ((size_t) (t - ans) + 1 > console_width) + break; + *(t++) = *f; + } + + /* We might need to go back two cycles to fit three dots, so remember two positions */ + prev2 = prev; + prev = tmp_t; } - *t = 0; + /* We can just write where we want, since chars are one-byte */ + size_t c = MIN(console_width, 3u); /* If the console is too narrow, write fewer dots */ + size_t off; + if (console_width - c >= (size_t) (t - ans)) + off = (size_t) (t - ans); + else if (console_width - c >= (size_t) (prev - ans)) + off = (size_t) (prev - ans); + else if (console_width - c >= (size_t) (prev2 - ans)) + off = (size_t) (prev2 - ans); + else + off = console_width - c; + assert(off <= (size_t) (t - ans)); - return r; + memcpy(ans + off, "...", c); + ans[off + c] = '\0'; + return ans; } char *octescape(const char *s, size_t len) { diff --git a/src/basic/escape.h b/src/basic/escape.h index 515620993d0..45e23d0651b 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -46,7 +46,10 @@ int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **r int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); -char *xescape(const char *s, const char *bad); +char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits); +static inline char *xescape(const char *s, const char *bad) { + return xescape_full(s, bad, SIZE_MAX, false); +} char *octescape(const char *s, size_t len); char *shell_escape(const char *s, const char *bad); diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 4ee4aa974d7..add17f9547d 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -6,10 +6,45 @@ #include "tests.h" static void test_cescape(void) { - _cleanup_free_ char *escaped; + _cleanup_free_ char *t; - assert_se(escaped = cescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313")); - assert_se(streq(escaped, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\a\\003\\177\\234\\313")); + assert_se(t = cescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313")); + assert_se(streq(t, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\a\\003\\177\\234\\313")); +} + +static void test_xescape(void) { + _cleanup_free_ char *t; + + assert_se(t = xescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "")); + assert_se(streq(t, "abc\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb")); +} + +static void test_xescape_full(bool eight_bits) { + const char* escaped = !eight_bits ? + "a\\x62c\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb" : + "a\\x62c\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\177\234\313"; + const unsigned full_fit = !eight_bits ? 55 : 46; + + for (unsigned i = 0; i < 60; i++) { + _cleanup_free_ char *t; + + assert_se(t = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, eight_bits)); + + log_info("%02d: %s", i, t); + + if (i >= full_fit) + assert_se(streq(t, escaped)); + else if (i >= 3) { + /* We need up to four columns, so up to three three columns may be wasted */ + assert_se(strlen(t) == i || strlen(t) == i - 1 || strlen(t) == i - 2 || strlen(t) == i - 3); + assert_se(strneq(t, escaped, i - 3) || strneq(t, escaped, i - 4) || + strneq(t, escaped, i - 5) || strneq(t, escaped, i - 6)); + assert_se(endswith(t, "...")); + } else { + assert_se(strlen(t) == i); + assert_se(strneq(t, "...", i)); + } + } } static void test_cunescape(void) { @@ -123,6 +158,9 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); test_cescape(); + test_xescape(); + test_xescape_full(false); + test_xescape_full(true); test_cunescape(); test_shell_escape(); test_shell_maybe_quote(); From 09c1dceef180630ecd6cf40e5c1fb653029b615c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 May 2019 12:14:52 +0200 Subject: [PATCH 10/12] basic/process-util: convert bool arg to flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation for the next commit… --- src/basic/proc-cmdline.c | 2 +- src/basic/process-util.c | 11 ++--- src/basic/process-util.h | 6 ++- src/core/dbus-unit.c | 2 +- src/coredump/coredump.c | 4 +- src/journal/journald-context.c | 2 +- src/shared/cgroup-show.c | 2 +- src/test/test-process-util.c | 78 +++++++++++++++++----------------- 8 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 7dca9e60b61..b82ca4b21b2 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -34,7 +34,7 @@ int proc_cmdline(char **ret) { } if (detect_container() > 0) - return get_process_cmdline(1, SIZE_MAX, false, ret); + return get_process_cmdline(1, SIZE_MAX, 0, ret); else return read_one_line_file("/proc/cmdline", ret); } diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 8db7f462d75..3e94c9a4aa7 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -106,7 +106,7 @@ int get_process_comm(pid_t pid, char **ret) { return 0; } -int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char **line) { +int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *t = NULL, *ans = NULL; const char *p; @@ -121,9 +121,10 @@ int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char /* Retrieves a process' command line. Replaces non-utf8 bytes by replacement character (�). If * max_columns is != -1 will return a string of the specified console width at most, abbreviated with - * an ellipsis. If comm_fallback is true and the process has no command line set (the case for kernel - * threads), or has a command line that resolves to the empty string will return the "comm" name of - * the process instead. This will use at most _SC_ARG_MAX bytes of input data. + * an ellipsis. If PROCESS_CMDLINE_COMM_FALLBACK is specified in flags and the process has no command + * line set (the case for kernel threads), or has a command line that resolves to the empty string + * will return the "comm" name of the process instead. This will use at most _SC_ARG_MAX bytes of + * input data. * * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and * comm_fallback is false). Returns 0 and sets *line otherwise. */ @@ -159,7 +160,7 @@ int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char if (ferror(f)) return -errno; - if (!comm_fallback) + if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK)) return -ENOENT; /* Kernel threads have no argv[] */ diff --git a/src/basic/process-util.h b/src/basic/process-util.h index dec584606b6..b68729cf78b 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -31,8 +31,12 @@ _r_; \ }) +typedef enum ProcessCmdlineFlags { + PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0, +} ProcessCmdlineFlags; + int get_process_comm(pid_t pid, char **name); -int get_process_cmdline(pid_t pid, size_t max_columns, bool comm_fallback, char **line); +int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line); int get_process_exe(pid_t pid, char **name); int get_process_uid(pid_t pid, uid_t *uid); int get_process_gid(pid_t pid, gid_t *gid); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 1e996a2f1b0..a60362fff65 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -901,7 +901,7 @@ static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set * p = buf; } - (void) get_process_cmdline(pid, SIZE_MAX, true, &cmdline); + (void) get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &cmdline); return sd_bus_message_append(reply, "(sus)", diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index c0bd03762f1..2cceb49f4b1 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -661,7 +661,7 @@ static int get_process_container_parent_cmdline(pid_t pid, char** cmdline) { if (r < 0) return r; - r = get_process_cmdline(container_pid, SIZE_MAX, false, cmdline); + r = get_process_cmdline(container_pid, SIZE_MAX, 0, cmdline); if (r < 0) return r; @@ -1154,7 +1154,7 @@ static int gather_pid_metadata( if (sd_pid_get_slice(pid, &t) >= 0) set_iovec_field_free(iovec, n_iovec, "COREDUMP_SLICE=", t); - if (get_process_cmdline(pid, SIZE_MAX, false, &t) >= 0) + if (get_process_cmdline(pid, SIZE_MAX, 0, &t) >= 0) set_iovec_field_free(iovec, n_iovec, "COREDUMP_CMDLINE=", t); if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index 684ae9aaae0..46edf24cd65 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -230,7 +230,7 @@ static void client_context_read_basic(ClientContext *c) { if (get_process_exe(c->pid, &t) >= 0) free_and_replace(c->exe, t); - if (get_process_cmdline(c->pid, SIZE_MAX, false, &t) >= 0) + if (get_process_cmdline(c->pid, SIZE_MAX, 0, &t) >= 0) free_and_replace(c->cmdline, t); if (get_process_capeff(c->pid, &t) >= 0) diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index 2bd0e09caee..464a3e5c035 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -61,7 +61,7 @@ static void show_pid_array( for (i = 0; i < n_pids; i++) { _cleanup_free_ char *t = NULL; - (void) get_process_cmdline(pids[i], n_columns, true, &t); + (void) get_process_cmdline(pids[i], n_columns, PROCESS_CMDLINE_COMM_FALLBACK, &t); if (extra) printf("%s%s ", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 107d0b5d224..9b644c09bbf 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -48,14 +48,14 @@ static void test_get_process_comm(pid_t pid) { } else log_warning("%s not exist.", path); - assert_se(get_process_cmdline(pid, 0, true, &c) >= 0); + assert_se(get_process_cmdline(pid, 0, PROCESS_CMDLINE_COMM_FALLBACK, &c) >= 0); log_info("PID"PID_FMT" cmdline: '%s'", pid, c); - assert_se(get_process_cmdline(pid, 8, false, &d) >= 0); + assert_se(get_process_cmdline(pid, 8, 0, &d) >= 0); log_info("PID"PID_FMT" cmdline truncated to 8: '%s'", pid, d); free(d); - assert_se(get_process_cmdline(pid, 1, false, &d) >= 0); + assert_se(get_process_cmdline(pid, 1, 0, &d) >= 0); log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d); assert_se(get_process_ppid(pid, &e) >= 0); @@ -237,148 +237,148 @@ static void test_get_process_cmdline_harder(void) { assert_se(prctl(PR_SET_NAME, "testa") >= 0); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) == -ENOENT); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 0, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 0, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_info("'%s'", line); assert_se(streq(line, "")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 2, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 3, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[t…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 4, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[te…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 5, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[tes…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 6, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[test…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 7, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 8, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); assert_se(write(fd, "foo\0bar", 8) == 8); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) >= 0); log_info("'%s'", line); assert_se(streq(line, "foo bar")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar")); line = mfree(line); assert_se(write(fd, "quux", 4) == 4); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) >= 0); log_info("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 2, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "f…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 3, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "fo…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 4, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 5, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo …")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 6, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo b…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 7, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo ba…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 8, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 9, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 9, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar …")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 10, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar q…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 11, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar qu…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 12, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 13, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 13, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 14, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 14, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1000, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 1000, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "foo bar quux")); line = mfree(line); assert_se(ftruncate(fd, 0) >= 0); assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, false, &line) == -ENOENT); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[aaaa bbbb cccc]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 10, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[aaaa bbb…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 11, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[aaaa bbbb…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 12, true, &line) >= 0); + assert_se(get_process_cmdline(getpid_cached(), 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[aaaa bbbb …")); line = mfree(line); @@ -410,7 +410,7 @@ static void test_rename_process_now(const char *p, int ret) { /* We expect comm to be at most 16 bytes (TASK_COMM_LEN). The kernel may raise this limit in the * future. We'd only check the initial part, at least until we recompile, but this will still pass. */ - r = get_process_cmdline(0, SIZE_MAX, false, &cmdline); + r = get_process_cmdline(0, SIZE_MAX, 0, &cmdline); assert_se(r >= 0); /* we cannot expect cmdline to be renamed properly without privileges */ if (geteuid() == 0) { From e3b4efd28f36a11a15531aa1a56ecadfd6f7253d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 May 2019 17:44:57 +0200 Subject: [PATCH 11/12] Add 8bit-version of get_process_cmdline() and use in cgroup-show.c This restores show_pid_array() output in legacy locales on the console. Only one call to get_process_cmdline() is changed, all others retain utf8-only mode. This affects systemd-cgls, systemctl status, etc, when working locally. Calls to get_process_cmdline() that cross a process boundary always use utf8. It's the callers responsibility to convert this to some encoding that they use. This means that we always pass utf8 over the bus. --- src/basic/escape.c | 7 +++++++ src/basic/escape.h | 1 + src/basic/process-util.c | 5 ++++- src/basic/process-util.h | 1 + src/shared/cgroup-show.c | 4 +++- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/basic/escape.c b/src/basic/escape.c index 77e86a58f31..33a6f204f55 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -435,6 +435,13 @@ char *xescape_full(const char *s, const char *bad, size_t console_width, bool ei return ans; } +char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit) { + if (eight_bit) + return xescape_full(str, "", console_width, true); + else + return utf8_escape_non_printable_full(str, console_width); +} + char *octescape(const char *s, size_t len) { char *r, *t; const char *f; diff --git a/src/basic/escape.h b/src/basic/escape.h index 45e23d0651b..b26054c5df8 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -51,6 +51,7 @@ static inline char *xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, false); } char *octescape(const char *s, size_t len); +char *escape_non_printable_full(const char *str, size_t console_width, bool eight_bit); char *shell_escape(const char *s, const char *bad); char* shell_maybe_quote(const char *s, EscapeStyle style); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 3e94c9a4aa7..b50537908cc 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -30,6 +30,7 @@ #include "fileio.h" #include "fs-util.h" #include "ioprio.h" +#include "locale-util.h" #include "log.h" #include "macro.h" #include "memory-util.h" @@ -178,7 +179,9 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags delete_trailing_chars(t, WHITESPACE); - ans = utf8_escape_non_printable_full(t, max_columns); + bool eight_bit = (flags & PROCESS_CMDLINE_USE_LOCALE) && !is_locale_utf8(); + + ans = escape_non_printable_full(t, max_columns, eight_bit); if (!ans) return -ENOMEM; diff --git a/src/basic/process-util.h b/src/basic/process-util.h index b68729cf78b..2e3bd725054 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -33,6 +33,7 @@ typedef enum ProcessCmdlineFlags { PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0, + PROCESS_CMDLINE_USE_LOCALE = 1 << 1, } ProcessCmdlineFlags; int get_process_comm(pid_t pid, char **name); diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index 464a3e5c035..e6fdcfa277d 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -61,7 +61,9 @@ static void show_pid_array( for (i = 0; i < n_pids; i++) { _cleanup_free_ char *t = NULL; - (void) get_process_cmdline(pids[i], n_columns, PROCESS_CMDLINE_COMM_FALLBACK, &t); + (void) get_process_cmdline(pids[i], n_columns, + PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_USE_LOCALE, + &t); if (extra) printf("%s%s ", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); From 4c6d51390c66ccbf647c2943657e8bb693312f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 22 May 2019 10:20:52 +0200 Subject: [PATCH 12/12] basic/utf8: reduce memory usage --- src/basic/utf8.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/basic/utf8.c b/src/basic/utf8.c index e12876962d6..afc24700dd7 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -32,6 +32,7 @@ #include "gunicode.h" #include "hexdecoct.h" #include "macro.h" +#include "string-util.h" #include "utf8.h" bool unichar_is_valid(char32_t ch) { @@ -192,7 +193,7 @@ char *utf8_escape_invalid(const char *str) { } *s = '\0'; - + (void) str_realloc(&p); return p; } @@ -278,6 +279,7 @@ char *utf8_escape_non_printable_full(const char *str, size_t console_width) { finish: *s = '\0'; + (void) str_realloc(&p); return p; }