1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 10:25:37 +03:00

Merge pull request #12590 from keszybz/unicode-cmdlines

Use unicode for cmdline printing
This commit is contained in:
Lennart Poettering 2019-05-24 10:41:30 +02:00 committed by GitHub
commit 05332e243c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 430 additions and 271 deletions

5
TODO
View File

@ -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.

View File

@ -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;

View File

@ -4,10 +4,17 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#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);

View File

@ -368,33 +368,78 @@ 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 *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) {

View File

@ -46,8 +46,12 @@ 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 *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);

View File

@ -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, 0, ret);
else
return read_one_line_file("/proc/cmdline", ret);
}

View File

@ -25,10 +25,12 @@
#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"
#include "ioprio.h"
#include "locale-util.h"
#include "log.h"
#include "macro.h"
#include "memory-util.h"
@ -43,6 +45,12 @@
#include "string-util.h"
#include "terminal-util.h"
#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;
@ -80,7 +88,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;
@ -93,28 +101,31 @@ 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;
}
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, ProcessCmdlineFlags flags, 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 (<28>). If
* max_columns is != -1 will return a string of the specified console width at most, abbreviated with
* 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. */
@ -126,130 +137,56 @@ 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. */
long l = sysconf(_SC_ARG_MAX);
assert(l > 0);
max_length = l;
}
/* 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) {
/* 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;
} 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);
if (!comm_fallback)
return -ENOENT;
h = get_process_comm(pid, &t);
if (h < 0)
return h;
size_t l = strlen(t);
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;
}
k = realloc(ans, strlen(ans) + 1);
if (!k)
t = new(char, max_length);
if (!t)
return -ENOMEM;
ans = NULL;
*line = k;
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 {
/* 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 (!(flags & PROCESS_CMDLINE_COMM_FALLBACK))
return -ENOENT;
/* Kernel threads have no argv[] */
_cleanup_free_ char *t2 = NULL;
r = get_process_comm(pid, &t2);
if (r < 0)
return r;
mfree(t);
t = strjoin("[", t2, "]");
if (!t)
return -ENOMEM;
}
delete_trailing_chars(t, WHITESPACE);
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;
(void) str_realloc(&ans);
*line = TAKE_PTR(ans);
return 0;
}
@ -281,7 +218,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. */

View File

@ -31,8 +31,13 @@
_r_; \
})
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);
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, 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);

View File

@ -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);
}

View File

@ -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,46 +193,93 @@ char *utf8_escape_invalid(const char *str) {
}
*s = '\0';
(void) str_realloc(&p);
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';
(void) str_realloc(&p);
return p;
}
@ -519,15 +567,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;

View File

@ -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);

View File

@ -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);

View File

@ -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, PROCESS_CMDLINE_COMM_FALLBACK, &cmdline);
return sd_bus_message_append(reply,
"(sus)",

View File

@ -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, 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, 0, 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)

View File

@ -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;
@ -233,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, 0, &t) >= 0)
free_and_replace(c->cmdline, t);
if (get_process_capeff(c->pid, &t) >= 0)

View File

@ -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,17 +51,19 @@ 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+2)
n_columns -= pid_width+2;
if (n_columns > pid_width + 3) /* something like "├─1114784 " */
n_columns -= pid_width + 3;
else
n_columns = 20;
}
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 | PROCESS_CMDLINE_USE_LOCALE,
&t);
if (extra)
printf("%s%s ", prefix, special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET));
@ -75,7 +77,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 +121,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 +201,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 +219,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 +264,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) {

View File

@ -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,

View File

@ -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();

View File

@ -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);
@ -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);
}
@ -237,150 +237,149 @@ 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, 0, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid_cached(), 0, 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(), 1, 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(), 2, true, &line) >= 0);
assert_se(streq(line, "["));
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(), 3, true, &line) >= 0);
assert_se(streq(line, "[."));
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(), 4, true, &line) >= 0);
assert_se(streq(line, "[.."));
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(), 5, true, &line) >= 0);
assert_se(streq(line, "[..."));
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(), 6, true, &line) >= 0);
assert_se(streq(line, "[...]"));
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(), 7, true, &line) >= 0);
assert_se(streq(line, "[t...]"));
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(), 8, 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(write(fd, "\0\0\0\0\0\0\0\0\0", 10) == 10);
assert_se(get_process_cmdline(getpid_cached(), 0, false, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid_cached(), 0, 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\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) >= 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(), 0, 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(), 0, 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(), 0, 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(streq(line, ""));
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(streq(line, "."));
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(streq(line, ".."));
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(streq(line, "..."));
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(streq(line, "f..."));
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(streq(line, "fo..."));
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(streq(line, "foo..."));
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(streq(line, "foo..."));
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(streq(line, "foo b..."));
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(streq(line, "foo ba..."));
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(streq(line, "foo bar..."));
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(streq(line, "foo bar..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid_cached(), 13, 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(), 14, 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(), 1000, 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, 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(), 0, false, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid_cached(), 0, 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(streq(line, "[aaaa...]"));
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(streq(line, "[aaaa...]"));
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(streq(line, "[aaaa b...]"));
assert_se(get_process_cmdline(getpid_cached(), 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0);
assert_se(streq(line, "[aaaa bbbb …"));
line = mfree(line);
safe_close(fd);
@ -408,8 +407,10 @@ 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, 0, 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) {

View File

@ -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));
@ -97,12 +111,45 @@ static void test_utf8_escaping_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 };
_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 +167,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 +178,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 +191,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 +218,9 @@ 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_utf8_escape_non_printable_full();
test_utf16_to_utf8();
test_utf8_n_codepoints();
test_utf8_console_width();

View File

@ -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