diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml index 2b602b71fa..6ceed41b05 100644 --- a/man/coredumpctl.xml +++ b/man/coredumpctl.xml @@ -354,10 +354,40 @@ Fri … 552351 1000 1000 SIGSEGV present /usr/lib64/firefox/firefox 28.7M - Show information about a process that dumped core, - matching by its PID 6654 + Show information about a core dump matched by PID - $ coredumpctl info 6654 + $ coredumpctl info 6654 + PID: 6654 (bash) + UID: 1000 (user) + GID: 1000 (user) + Signal: 11 (SEGV) + Timestamp: Mon 2021-01-01 00:00:01 CET (20s ago) + Command Line: bash -c $'kill -SEGV $$' + Executable: /usr/bin/bash + Control Group: /user.slice/user-1000.slice/… + Unit: user@1000.service + User Unit: vte-spawn-….scope + Slice: user-1000.slice + Owner UID: 1000 (user) + Boot ID: … + Machine ID: … + Hostname: … + Storage: /var/lib/systemd/coredump/core.bash.1000.….zst (present) + Disk Size: 51.7K + Message: Process 130414 (bash) of user 1000 dumped core. + + Stack trace of thread 130414: + #0 0x00007f398142358b kill (libc.so.6 + 0x3d58b) + #1 0x0000558c2c7fda09 kill_builtin (bash + 0xb1a09) + #2 0x0000558c2c79dc59 execute_builtin.lto_priv.0 (bash + 0x51c59) + #3 0x0000558c2c79709c execute_simple_command (bash + 0x4b09c) + #4 0x0000558c2c798408 execute_command_internal (bash + 0x4c408) + #5 0x0000558c2c7f6bdc parse_and_execute (bash + 0xaabdc) + #6 0x0000558c2c85415c run_one_command.isra.0 (bash + 0x10815c) + #7 0x0000558c2c77d040 main (bash + 0x31040) + #8 0x00007f398140db75 __libc_start_main (libc.so.6 + 0x27b75) + #9 0x0000558c2c77dd1e _start (bash + 0x31d1e) + diff --git a/src/basic/escape.c b/src/basic/escape.c index af785ecfa4..2a3a0e31a1 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -360,15 +360,16 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi return t - r; } -char* xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) { +char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags) { 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(). If eight_bits is true, characters >= 127 are let through unchanged. - * This corresponds to non-ASCII printable characters in pre-unicode encodings. + * reversed with cunescape(). If XESCAPE_8_BIT is specified, 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. */ + * If console_width is reached, or XESCAPE_FORCE_ELLIPSIS is set, output is truncated and "..." is + * appended. */ if (console_width == 0) return strdup(""); @@ -380,17 +381,23 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, bool ei memset(ans, '_', MIN(strlen(s), console_width) * 4); ans[MIN(strlen(s), console_width) * 4] = 0; + bool force_ellipsis = FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS); + for (f = s, t = prev = prev2 = ans; ; f++) { char *tmp_t = t; if (!*f) { + if (force_ellipsis) + break; + *t = 0; return ans; } - if ((unsigned char) *f < ' ' || (!eight_bits && (unsigned char) *f >= 127) || + if ((unsigned char) *f < ' ' || + (!FLAGS_SET(flags, XESCAPE_8_BIT) && (unsigned char) *f >= 127) || *f == '\\' || strchr(bad, *f)) { - if ((size_t) (t - ans) + 4 > console_width) + if ((size_t) (t - ans) + 4 + 3 * force_ellipsis > console_width) break; *(t++) = '\\'; @@ -398,7 +405,7 @@ char* xescape_full(const char *s, const char *bad, size_t console_width, bool ei *(t++) = hexchar(*f >> 4); *(t++) = hexchar(*f); } else { - if ((size_t) (t - ans) + 1 > console_width) + if ((size_t) (t - ans) + 1 + 3 * force_ellipsis > console_width) break; *(t++) = *f; @@ -427,11 +434,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); +char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags) { + if (FLAGS_SET(flags, XESCAPE_8_BIT)) + return xescape_full(str, "", console_width, flags); else - return utf8_escape_non_printable_full(str, console_width); + return utf8_escape_non_printable_full(str, + console_width, + FLAGS_SET(flags, XESCAPE_FORCE_ELLIPSIS)); } char* octescape(const char *s, size_t len) { @@ -462,88 +471,74 @@ char* octescape(const char *s, size_t len) { } -static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad, bool escape_tab_nl) { +static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { assert(bad); - for (; *s; s++) { - if (escape_tab_nl && IN_SET(*s, '\n', '\t')) { - *(t++) = '\\'; - *(t++) = *s == '\n' ? 'n' : 't'; - continue; + for (; *s; s++) + if (char_is_cc(*s)) + t += cescape_char(*s, t); + else { + if (*s == '\\' || strchr(bad, *s)) + *(t++) = '\\'; + *(t++) = *s; } - if (*s == '\\' || strchr(bad, *s)) - *(t++) = '\\'; - - *(t++) = *s; - } - return t; } char* shell_escape(const char *s, const char *bad) { - char *r, *t; + char *buf, *t; - r = new(char, strlen(s)*2+1); - if (!r) + buf = new(char, strlen(s)*4+1); + if (!buf) return NULL; - t = strcpy_backslash_escaped(r, s, bad, false); + t = strcpy_backslash_escaped(buf, s, bad); *t = 0; - return r; + return buf; } -char* shell_maybe_quote(const char *s, EscapeStyle style) { +char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) { const char *p; - char *r, *t; + char *buf, *t; assert(s); - /* Encloses a string in quotes if necessary to make it OK as a shell - * string. Note that we treat benign UTF-8 characters as needing - * escaping too, but that should be OK. */ + /* Encloses a string in quotes if necessary to make it OK as a shell string. */ + + if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s)) + return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */ for (p = s; *p; p++) - if (*p <= ' ' || - *p >= 127 || - strchr(SHELL_NEED_QUOTES, *p)) + if (char_is_cc(*p) || + strchr(WHITESPACE SHELL_NEED_QUOTES, *p)) break; if (!*p) return strdup(s); - r = new(char, (style == ESCAPE_POSIX) + 1 + strlen(s)*2 + 1 + 1); - if (!r) + buf = new(char, FLAGS_SET(flags, SHELL_ESCAPE_POSIX) + 1 + strlen(s)*4 + 1 + 1); + if (!buf) return NULL; - t = r; - switch (style) { - case ESCAPE_BACKSLASH: - case ESCAPE_BACKSLASH_ONELINE: - *(t++) = '"'; - break; - case ESCAPE_POSIX: + t = buf; + if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) { *(t++) = '$'; *(t++) = '\''; - break; - default: - assert_not_reached("Bad EscapeStyle"); - } + } else + *(t++) = '"'; t = mempcpy(t, s, p - s); - if (IN_SET(style, ESCAPE_BACKSLASH, ESCAPE_BACKSLASH_ONELINE)) - t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE, - style == ESCAPE_BACKSLASH_ONELINE); - else - t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE_POSIX, true); + t = strcpy_backslash_escaped(t, p, + FLAGS_SET(flags, SHELL_ESCAPE_POSIX) ? SHELL_NEED_ESCAPE_POSIX : SHELL_NEED_ESCAPE); - if (IN_SET(style, ESCAPE_BACKSLASH, ESCAPE_BACKSLASH_ONELINE)) - *(t++) = '"'; - else + if (FLAGS_SET(flags, SHELL_ESCAPE_POSIX)) *(t++) = '\''; + else + *(t++) = '"'; *t = 0; - return r; + return str_realloc(buf); } diff --git a/src/basic/escape.h b/src/basic/escape.h index 691b6d802c..907b572bd4 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -33,15 +33,13 @@ typedef enum UnescapeFlags { UNESCAPE_ACCEPT_NUL = 1 << 1, } UnescapeFlags; -typedef enum EscapeStyle { - ESCAPE_BACKSLASH = 1, /* Add shell quotes ("") so the shell will consider this a single - argument, possibly multiline. Tabs and newlines are not escaped. */ - ESCAPE_BACKSLASH_ONELINE = 2, /* Similar to ESCAPE_BACKSLASH, but always produces a single-line - string instead. Shell escape sequences are produced for tabs and - newlines. */ - ESCAPE_POSIX = 3, /* Similar to ESCAPE_BACKSLASH_ONELINE, but uses POSIX shell escape - * syntax (a string enclosed in $'') instead of plain quotes. */ -} EscapeStyle; +typedef enum ShellEscapeFlags { + /* The default is to add shell quotes ("") so the shell will consider this a single argument. + * Tabs and newlines are escaped. */ + + SHELL_ESCAPE_POSIX = 1 << 1, /* Use POSIX shell escape syntax (a string enclosed in $'') instead of plain quotes. */ + SHELL_ESCAPE_EMPTY = 1 << 2, /* Format empty arguments as "". */ +} ShellEscapeFlags; char* cescape(const char *s); char* cescape_length(const char *s, size_t n); @@ -56,12 +54,17 @@ static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) { } int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul); -char* xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits); +typedef enum XEscapeFlags { + XESCAPE_8_BIT = 1 << 0, + XESCAPE_FORCE_ELLIPSIS = 1 << 1, +} XEscapeFlags; + +char* xescape_full(const char *s, const char *bad, size_t console_width, XEscapeFlags flags); static inline char* xescape(const char *s, const char *bad) { - return xescape_full(s, bad, SIZE_MAX, false); + return xescape_full(s, bad, SIZE_MAX, 0); } char* octescape(const char *s, size_t len); -char* escape_non_printable_full(const char *str, size_t console_width, bool eight_bit); +char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); char* shell_escape(const char *s, const char *bad); -char* shell_maybe_quote(const char *s, EscapeStyle style); +char* shell_maybe_quote(const char *s, ShellEscapeFlags flags); diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 16cd2408f0..93d8547b32 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -364,32 +364,40 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { return 1; } -int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { +int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size) { _cleanup_free_ char *buf = NULL; _cleanup_close_ int fd = -1; - struct stat st; size_t n, size; int n_retries; + bool truncated = false; assert(ret_contents); - /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work - * with two sorts of virtual files. One sort uses "seq_file", and the results of - * the first read are buffered for the second read. The other sort uses "raw" - * reads which always go direct to the device. In the latter case, the content of - * the virtual file must be retrieved with a single read otherwise a second read - * might get the new value instead of finding EOF immediately. That's the reason - * why the usage of fread(3) is prohibited in this case as it always performs a - * second call to read(2) looking for EOF. See issue 13585. */ + /* Virtual filesystems such as sysfs or procfs use kernfs, and kernfs can work with two sorts of + * virtual files. One sort uses "seq_file", and the results of the first read are buffered for the + * second read. The other sort uses "raw" reads which always go direct to the device. In the latter + * case, the content of the virtual file must be retrieved with a single read otherwise a second read + * might get the new value instead of finding EOF immediately. That's the reason why the usage of + * fread(3) is prohibited in this case as it always performs a second call to read(2) looking for + * EOF. See issue #13585. + * + * max_size specifies a limit on the bytes read. If max_size is SIZE_MAX, the full file is read. If + * the the full file is too large to read, an error is returned. For other values of max_size, + * *partial contents* may be returned. (Though the read is still done using one syscall.) + * Returns 0 on partial success, 1 if untruncated contents were read. */ fd = open(filename, O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; + assert(max_size <= READ_FULL_BYTES_MAX || max_size == SIZE_MAX); + /* Limit the number of attempts to read the number of bytes returned by fstat(). */ n_retries = 3; for (;;) { + struct stat st; + if (fstat(fd, &st) < 0) return -errno; @@ -399,13 +407,16 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re /* Be prepared for files from /proc which generally report a file size of 0. */ assert_cc(READ_FULL_BYTES_MAX < SSIZE_MAX); if (st.st_size > 0) { - if (st.st_size > READ_FULL_BYTES_MAX) + if (st.st_size > SSIZE_MAX) /* Avoid overflow with 32-bit size_t and 64-bit off_t. */ + return -EFBIG; + + size = MIN((size_t) st.st_size, max_size); + if (size > READ_FULL_BYTES_MAX) return -EFBIG; - size = st.st_size; n_retries--; } else { - size = READ_FULL_BYTES_MAX; + size = MIN(READ_FULL_BYTES_MAX, max_size); n_retries = 0; } @@ -413,7 +424,7 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re if (!buf) return -ENOMEM; /* Use a bigger allocation if we got it anyway, but not more than the limit. */ - size = MIN(malloc_usable_size(buf) - 1, READ_FULL_BYTES_MAX); + size = MIN3(malloc_usable_size(buf) - 1, max_size, READ_FULL_BYTES_MAX); for (;;) { ssize_t k; @@ -440,8 +451,15 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re * processing, let's try again either with a bigger guessed size or the new * file size. */ - if (n_retries <= 0) - return st.st_size > 0 ? -EIO : -EFBIG; + if (n_retries <= 0) { + if (max_size == SIZE_MAX) + return st.st_size > 0 ? -EIO : -EFBIG; + + /* Accept a short read, but truncate it appropropriately. */ + n = MIN(n, max_size); + truncated = true; + break; + } if (lseek(fd, 0, SEEK_SET) < 0) return -errno; @@ -470,7 +488,7 @@ int read_full_virtual_file(const char *filename, char **ret_contents, size_t *re buf[n] = 0; *ret_contents = TAKE_PTR(buf); - return 0; + return !truncated; } int read_full_stream_full( diff --git a/src/basic/fileio.h b/src/basic/fileio.h index d772b0a50d..c28b17fef5 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -65,7 +65,12 @@ int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_ static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) { return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); } -int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size); + +int read_virtual_file(const char *filename, size_t max_size, char **ret_contents, size_t *ret_size); +static inline int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size) { + return read_virtual_file(filename, SIZE_MAX, ret_contents, ret_size); +} + int read_full_stream_full(FILE *f, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, char **ret_contents, size_t *ret_size); static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_size) { return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 7d4301eadb..1b8e663efe 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -123,64 +123,136 @@ int get_process_comm(pid_t pid, char **ret) { return 0; } -int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) { - _cleanup_free_ char *t = NULL, *ans = NULL; +static int get_process_cmdline_nulstr( + pid_t pid, + size_t max_size, + ProcessCmdlineFlags flags, + char **ret, + size_t *ret_size) { + const char *p; + char *t; size_t k; int r; - assert(line); - assert(pid >= 0); - - /* 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 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. + /* Retrieves a process' command line as a "sized nulstr", i.e. possibly without the last NUL, but + * with a specified size. * - * 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. */ + * 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 an error, 0 if output was read but is truncated, 1 otherwise. + */ p = procfs_file_alloca(pid, "cmdline"); - r = read_full_virtual_file(p, &t, &k); + r = read_virtual_file(p, max_size, &t, &k); /* Let's assume that each input byte results in >= 1 + * columns of output. We ignore zero-width codepoints. */ if (r == -ENOENT) return -ESRCH; if (r < 0) return r; - 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] = ' '; - } else { + if (k == 0) { + t = mfree(t); + if (!(flags & PROCESS_CMDLINE_COMM_FALLBACK)) return -ENOENT; /* Kernel threads have no argv[] */ - _cleanup_free_ char *t2 = NULL; + _cleanup_free_ char *comm = NULL; - r = get_process_comm(pid, &t2); + r = get_process_comm(pid, &comm); if (r < 0) return r; - free(t); - t = strjoin("[", t2, "]"); + t = strjoin("[", comm, "]"); if (!t) return -ENOMEM; + + k = strlen(t); + r = k <= max_size; + if (r == 0) /* truncation */ + t[max_size] = '\0'; } - delete_trailing_chars(t, WHITESPACE); + *ret = t; + *ret_size = k; + return r; +} - bool eight_bit = (flags & PROCESS_CMDLINE_USE_LOCALE) && !is_locale_utf8(); +int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **line) { + _cleanup_free_ char *t = NULL; + size_t k; + char *ans; - ans = escape_non_printable_full(t, max_columns, eight_bit); - if (!ans) - return -ENOMEM; + assert(line); + assert(pid >= 0); - (void) str_realloc(&ans); - *line = TAKE_PTR(ans); + /* Retrieve adn format a commandline. See above for discussion of retrieval options. + * + * There are two main formatting modes: + * + * - when PROCESS_CMDLINE_QUOTE is specified, output is quoted in C/Python style. If no shell special + * characters are present, this output can be copy-pasted into the terminal to execute. UTF-8 + * output is assumed. + * + * - otherwise, a compact non-roundtrippable form is returned. Non-UTF8 bytes are replaced by �. The + * returned string is of the specified console width at most, abbreviated with an ellipsis. + * + * Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and + * PROCESS_CMDLINE_COMM_FALLBACK is not specified). Returns 0 and sets *line otherwise. */ + + int full = get_process_cmdline_nulstr(pid, max_columns, flags, &t, &k); + if (full < 0) + return full; + + if (flags & (PROCESS_CMDLINE_QUOTE | PROCESS_CMDLINE_QUOTE_POSIX)) { + ShellEscapeFlags shflags = SHELL_ESCAPE_EMPTY | + FLAGS_SET(flags, PROCESS_CMDLINE_QUOTE_POSIX) * SHELL_ESCAPE_POSIX; + + assert(!(flags & PROCESS_CMDLINE_USE_LOCALE)); + + _cleanup_strv_free_ char **args = NULL; + + args = strv_parse_nulstr(t, k); + if (!args) + return -ENOMEM; + + for (size_t i = 0; args[i]; i++) { + char *e; + + e = shell_maybe_quote(args[i], shflags); + if (!e) + return -ENOMEM; + + free_and_replace(args[i], e); + } + + ans = strv_join(args, " "); + if (!ans) + return -ENOMEM; + + } else { + /* 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] = ' '; + + 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 * XESCAPE_8_BIT | !full * XESCAPE_FORCE_ELLIPSIS); + if (!ans) + return -ENOMEM; + + ans = str_realloc(ans); + } + + *line = ans; return 0; } diff --git a/src/basic/process-util.h b/src/basic/process-util.h index ddce7bd272..8ce6d60f39 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -35,6 +35,8 @@ typedef enum ProcessCmdlineFlags { PROCESS_CMDLINE_COMM_FALLBACK = 1 << 0, PROCESS_CMDLINE_USE_LOCALE = 1 << 1, + PROCESS_CMDLINE_QUOTE = 1 << 2, + PROCESS_CMDLINE_QUOTE_POSIX = 1 << 3, } ProcessCmdlineFlags; int get_process_comm(pid_t pid, char **name); diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 60c877ce71..c1f368dbd8 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -150,7 +150,7 @@ char *delete_chars(char *s, const char *bad) { } char *delete_trailing_chars(char *s, const char *bad) { - char *p, *c = s; + char *c = s; /* Drops all specified bad characters, at the end of the string */ @@ -160,7 +160,7 @@ char *delete_trailing_chars(char *s, const char *bad) { if (!bad) bad = WHITESPACE; - for (p = s; *p; p++) + for (char *p = s; *p; p++) if (!strchr(bad, *p)) c = p + 1; @@ -193,34 +193,28 @@ char ascii_toupper(char x) { } char *ascii_strlower(char *t) { - char *p; - assert(t); - for (p = t; *p; p++) + for (char *p = t; *p; p++) *p = ascii_tolower(*p); return t; } char *ascii_strupper(char *t) { - char *p; - assert(t); - for (p = t; *p; p++) + for (char *p = t; *p; p++) *p = ascii_toupper(*p); return t; } char *ascii_strlower_n(char *t, size_t n) { - size_t i; - if (n <= 0) return t; - for (i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) t[i] = ascii_tolower(t[i]); return t; @@ -252,10 +246,8 @@ int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { } bool chars_intersect(const char *a, const char *b) { - const char *p; - /* Returns true if any of the chars in a are in b. */ - for (p = a; *p; p++) + for (const char *p = a; *p; p++) if (strchr(b, *p)) return true; @@ -263,8 +255,6 @@ bool chars_intersect(const char *a, const char *b) { } bool string_has_cc(const char *p, const char *ok) { - const char *t; - assert(p); /* @@ -273,14 +263,11 @@ bool string_has_cc(const char *p, const char *ok) { * considered OK. */ - for (t = p; *t; t++) { + for (const char *t = p; *t; t++) { if (ok && strchr(ok, *t)) continue; - if (*t > 0 && *t < ' ') - return true; - - if (*t == 127) + if (char_is_cc(*t)) return true; } @@ -468,7 +455,7 @@ char *cellescape(char *buf, size_t len, const char *s) { * very end. */ - size_t i = 0, last_char_width[4] = {}, k = 0, j; + size_t i = 0, last_char_width[4] = {}, k = 0; assert(len > 0); /* at least a terminating NUL */ @@ -497,7 +484,7 @@ char *cellescape(char *buf, size_t len, const char *s) { /* Ellipsation is necessary. This means we might need to truncate the string again to make space for 4 * characters ideally, but the buffer is shorter than that in the first place take what we can get */ - for (j = 0; j < ELEMENTSOF(last_char_width); j++) { + for (size_t j = 0; j < ELEMENTSOF(last_char_width); j++) { if (i + 4 <= len) /* nice, we reached our space goal */ break; @@ -984,14 +971,12 @@ int free_and_strndup(char **p, const char *s, size_t l) { } bool string_is_safe(const char *p) { - const char *t; - if (!p) return false; /* Checks if the specified string contains no quotes or control characters */ - for (t = p; *t; t++) { + for (const char *t = p; *t; t++) { if (*t > 0 && *t < ' ') /* no control characters */ return false; diff --git a/src/basic/string-util.h b/src/basic/string-util.h index 6364a0a55f..116dbd4583 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -129,6 +129,9 @@ static inline bool _pure_ in_charset(const char *s, const char* charset) { return s[strspn(s, charset)] == '\0'; } +static inline bool char_is_cc(char p) { + return (p >= 0 && p < ' ') || p == 127; +} bool string_has_cc(const char *p, const char *ok) _pure_; char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); @@ -216,17 +219,13 @@ 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 */ +static inline char* str_realloc(char *p) { + /* Reallocate *p to actual size. Ignore failure, and return the original string on error. */ - if (!*p) + if (!p) return NULL; - char *t = realloc(*p, strlen(*p) + 1); - if (!t) - return NULL; - - return (*p = t); + return realloc(p, strlen(p) + 1) ?: p; } char* string_erase(char *x); diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 46c3a463b9..63fc9f71d1 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -196,8 +196,7 @@ char *utf8_escape_invalid(const char *str) { } *s = '\0'; - (void) str_realloc(&p); - return p; + return str_realloc(p); } static int utf8_char_console_width(const char *str) { @@ -213,7 +212,7 @@ static int utf8_char_console_width(const char *str) { return unichar_iswide(c) ? 2 : 1; } -char *utf8_escape_non_printable_full(const char *str, size_t console_width) { +char *utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis) { char *p, *s, *prev_s; size_t n = 0; /* estimated print width */ @@ -230,8 +229,12 @@ char *utf8_escape_non_printable_full(const char *str, size_t console_width) { int len; char *saved_s = s; - if (!*str) /* done! */ - goto finish; + if (!*str) { /* done! */ + if (force_ellipsis) + goto truncation; + else + goto finish; + } len = utf8_encoded_valid_unichar(str, SIZE_MAX); if (len > 0) { @@ -275,15 +278,14 @@ char *utf8_escape_non_printable_full(const char *str, size_t console_width) { truncation: /* Try to go back one if we don't have enough space for the ellipsis */ - if (n + 1 >= console_width) + if (n + 1 > console_width) s = prev_s; s = mempcpy(s, "…", strlen("…")); finish: *s = '\0'; - (void) str_realloc(&p); - return p; + return str_realloc(p); } char *ascii_is_valid(const char *str) { diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 219ca89184..b0e969f655 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -25,9 +25,9 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool allow_newlin #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_full(const char *str, size_t console_width); +char *utf8_escape_non_printable_full(const char *str, size_t console_width, bool force_ellipsis); static inline char *utf8_escape_non_printable(const char *str) { - return utf8_escape_non_printable_full(str, SIZE_MAX); + return utf8_escape_non_printable_full(str, SIZE_MAX, false); } size_t utf8_encode_unichar(char *out_utf8, char32_t g); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 2af7761c0f..a83648d968 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -1229,7 +1229,9 @@ static int append_process(sd_bus_message *reply, const char *p, pid_t pid, Set * p = buf; } - (void) get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &cmdline); + (void) get_process_cmdline(pid, SIZE_MAX, + PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_QUOTE, + &cmdline); return sd_bus_message_append(reply, "(sus)", diff --git a/src/core/job.c b/src/core/job.c index cfc1209615..57829d185a 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -848,7 +848,7 @@ static void job_print_done_status_message(Unit *u, JobType t, JobResult result) if (t == JOB_START && result == JOB_FAILED) { _cleanup_free_ char *quoted = NULL; - quoted = shell_maybe_quote(u->id, ESCAPE_BACKSLASH); + quoted = shell_maybe_quote(u->id, 0); manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); } } diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 62467d4cf9..b75a7c39ce 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -665,7 +665,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, 0, cmdline); + r = get_process_cmdline(container_pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, cmdline); if (r < 0) return r; @@ -1145,7 +1145,7 @@ static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) { if (sd_pid_get_slice(pid, &t) >= 0) (void) iovw_put_string_field_free(iovw, "COREDUMP_SLICE=", t); - if (get_process_cmdline(pid, SIZE_MAX, 0, &t) >= 0) + if (get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &t) >= 0) (void) iovw_put_string_field_free(iovw, "COREDUMP_CMDLINE=", t); if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) diff --git a/src/environment-d-generator/environment-d-generator.c b/src/environment-d-generator/environment-d-generator.c index 852e29f11d..1171fdc290 100644 --- a/src/environment-d-generator/environment-d-generator.c +++ b/src/environment-d-generator/environment-d-generator.c @@ -70,7 +70,7 @@ static int load_and_print(void) { t = strchr(*i, '='); assert(t); - q = shell_maybe_quote(t + 1, ESCAPE_BACKSLASH); + q = shell_maybe_quote(t + 1, 0); if (!q) return log_oom(); diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index add0a5480d..694056d558 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -225,7 +225,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, 0, &t) >= 0) + if (get_process_cmdline(c->pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &t) >= 0) free_and_replace(c->cmdline, t); if (get_process_capeff(c->pid, &t) >= 0) diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index e4427dbced..cbd5fb087e 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -250,7 +250,7 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &str)) > 0) { _cleanup_free_ char *e = NULL; - e = shell_maybe_quote(str, ESCAPE_BACKSLASH_ONELINE); + e = shell_maybe_quote(str, 0); if (!e) return -ENOMEM; diff --git a/src/shared/bus-wait-for-jobs.c b/src/shared/bus-wait-for-jobs.c index 8458fe8684..e4a3ab9a95 100644 --- a/src/shared/bus-wait-for-jobs.c +++ b/src/shared/bus-wait-for-jobs.c @@ -181,7 +181,7 @@ static void log_job_error_with_service_result(const char* service, const char *r assert(service); - service_shell_quoted = shell_maybe_quote(service, ESCAPE_BACKSLASH); + service_shell_quoted = shell_maybe_quote(service, 0); if (!strv_isempty((char**) extra_args)) { _cleanup_free_ char *t = NULL; diff --git a/src/systemctl/systemctl-set-environment.c b/src/systemctl/systemctl-set-environment.c index bfd87c0cdd..a19e031dd3 100644 --- a/src/systemctl/systemctl-set-environment.c +++ b/src/systemctl/systemctl-set-environment.c @@ -17,7 +17,7 @@ static int print_variable(const char *s) { return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Invalid environment block"); - esc = shell_maybe_quote(sep + 1, ESCAPE_POSIX); + esc = shell_maybe_quote(sep + 1, SHELL_ESCAPE_POSIX); if (!esc) return log_oom(); diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 3e410ca299..991b135a33 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -24,13 +24,14 @@ static void test_xescape_full(bool 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; + XEscapeFlags flags = eight_bits * XESCAPE_8_BIT; for (unsigned i = 0; i < 60; i++) { - _cleanup_free_ char *t; + _cleanup_free_ char *t, *q; - assert_se(t = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, eight_bits)); + assert_se(t = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, flags)); - log_info("%02d: %s", i, t); + log_info("%02d: <%s>", i, t); if (i >= full_fit) assert_se(streq(t, escaped)); @@ -44,6 +45,15 @@ static void test_xescape_full(bool eight_bits) { assert_se(strlen(t) == i); assert_se(strneq(t, "...", i)); } + + assert_se(q = xescape_full("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "b", i, + flags | XESCAPE_FORCE_ELLIPSIS)); + + log_info("%02d: <%s>", i, q); + if (i > 0) + assert_se(endswith(q, ".")); + assert(strlen(q) <= i); + assert(strlen(q) + 3 >= strlen(t)); } } @@ -118,6 +128,7 @@ static void test_shell_escape_one(const char *s, const char *bad, const char *ex _cleanup_free_ char *r; assert_se(r = shell_escape(s, bad)); + log_debug("%s → %s (expected %s)", s, r, expected); assert_se(streq_ptr(r, expected)); } @@ -127,58 +138,58 @@ static void test_shell_escape(void) { test_shell_escape_one("foobar", "", "foobar"); test_shell_escape_one("foobar", "o", "f\\o\\obar"); test_shell_escape_one("foo:bar,baz", ",:", "foo\\:bar\\,baz"); + test_shell_escape_one("foo\nbar\nbaz", ",:", "foo\\nbar\\nbaz"); } -static void test_shell_maybe_quote_one(const char *s, - EscapeStyle style, - const char *expected) { +static void test_shell_maybe_quote_one(const char *s, ShellEscapeFlags flags, const char *expected) { _cleanup_free_ char *ret = NULL; - assert_se(ret = shell_maybe_quote(s, style)); + assert_se(ret = shell_maybe_quote(s, flags)); log_debug("[%s] → [%s] (%s)", s, ret, expected); assert_se(streq(ret, expected)); } static void test_shell_maybe_quote(void) { - test_shell_maybe_quote_one("", ESCAPE_BACKSLASH, ""); - test_shell_maybe_quote_one("", ESCAPE_BACKSLASH_ONELINE, ""); - test_shell_maybe_quote_one("", ESCAPE_POSIX, ""); - test_shell_maybe_quote_one("\\", ESCAPE_BACKSLASH, "\"\\\\\""); - test_shell_maybe_quote_one("\\", ESCAPE_BACKSLASH_ONELINE, "\"\\\\\""); - test_shell_maybe_quote_one("\\", ESCAPE_POSIX, "$'\\\\'"); - test_shell_maybe_quote_one("\"", ESCAPE_BACKSLASH, "\"\\\"\""); - test_shell_maybe_quote_one("\"", ESCAPE_BACKSLASH_ONELINE, "\"\\\"\""); - test_shell_maybe_quote_one("\"", ESCAPE_POSIX, "$'\"'"); - test_shell_maybe_quote_one("foobar", ESCAPE_BACKSLASH, "foobar"); - test_shell_maybe_quote_one("foobar", ESCAPE_BACKSLASH_ONELINE, "foobar"); - test_shell_maybe_quote_one("foobar", ESCAPE_POSIX, "foobar"); - test_shell_maybe_quote_one("foo bar", ESCAPE_BACKSLASH, "\"foo bar\""); - test_shell_maybe_quote_one("foo bar", ESCAPE_BACKSLASH_ONELINE, "\"foo bar\""); - test_shell_maybe_quote_one("foo bar", ESCAPE_POSIX, "$'foo bar'"); - test_shell_maybe_quote_one("foo\tbar", ESCAPE_BACKSLASH, "\"foo\tbar\""); - test_shell_maybe_quote_one("foo\tbar", ESCAPE_BACKSLASH_ONELINE, "\"foo\\tbar\""); - test_shell_maybe_quote_one("foo\tbar", ESCAPE_POSIX, "$'foo\\tbar'"); - test_shell_maybe_quote_one("foo\nbar", ESCAPE_BACKSLASH, "\"foo\nbar\""); - test_shell_maybe_quote_one("foo\nbar", ESCAPE_BACKSLASH_ONELINE, "\"foo\\nbar\""); - test_shell_maybe_quote_one("foo\nbar", ESCAPE_POSIX, "$'foo\\nbar'"); - test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_BACKSLASH, "\"foo \\\"bar\\\" waldo\""); - test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_BACKSLASH_ONELINE, "\"foo \\\"bar\\\" waldo\""); - test_shell_maybe_quote_one("foo \"bar\" waldo", ESCAPE_POSIX, "$'foo \"bar\" waldo'"); - test_shell_maybe_quote_one("foo$bar", ESCAPE_BACKSLASH, "\"foo\\$bar\""); - test_shell_maybe_quote_one("foo$bar", ESCAPE_BACKSLASH_ONELINE, "\"foo\\$bar\""); - test_shell_maybe_quote_one("foo$bar", ESCAPE_POSIX, "$'foo$bar'"); + test_shell_maybe_quote_one("", 0, ""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_EMPTY, "\"\""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_POSIX, ""); + test_shell_maybe_quote_one("", SHELL_ESCAPE_POSIX | SHELL_ESCAPE_EMPTY, "\"\""); + test_shell_maybe_quote_one("\\", 0, "\"\\\\\""); + test_shell_maybe_quote_one("\\", SHELL_ESCAPE_POSIX, "$'\\\\'"); + test_shell_maybe_quote_one("\"", 0, "\"\\\"\""); + test_shell_maybe_quote_one("\"", SHELL_ESCAPE_POSIX, "$'\"'"); + test_shell_maybe_quote_one("foobar", 0, "foobar"); + test_shell_maybe_quote_one("foobar", SHELL_ESCAPE_POSIX, "foobar"); + test_shell_maybe_quote_one("foo bar", 0, "\"foo bar\""); + test_shell_maybe_quote_one("foo bar", SHELL_ESCAPE_POSIX, "$'foo bar'"); + test_shell_maybe_quote_one("foo\tbar", 0, "\"foo\\tbar\""); + test_shell_maybe_quote_one("foo\tbar", SHELL_ESCAPE_POSIX, "$'foo\\tbar'"); + test_shell_maybe_quote_one("foo\nbar", 0, "\"foo\\nbar\""); + test_shell_maybe_quote_one("foo\nbar", SHELL_ESCAPE_POSIX, "$'foo\\nbar'"); + test_shell_maybe_quote_one("foo \"bar\" waldo", 0, "\"foo \\\"bar\\\" waldo\""); + test_shell_maybe_quote_one("foo \"bar\" waldo", SHELL_ESCAPE_POSIX, "$'foo \"bar\" waldo'"); + test_shell_maybe_quote_one("foo$bar", 0, "\"foo\\$bar\""); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_EMPTY, "\"foo\\$bar\""); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_POSIX, "$'foo$bar'"); + test_shell_maybe_quote_one("foo$bar", SHELL_ESCAPE_POSIX | SHELL_ESCAPE_EMPTY, "$'foo$bar'"); - /* Note that current users disallow control characters, so this "test" - * is here merely to establish current behaviour. If control characters - * were allowed, they should be quoted, i.e. \001 should become \\001. */ - test_shell_maybe_quote_one("a\nb\001", ESCAPE_BACKSLASH, "\"a\nb\001\""); - test_shell_maybe_quote_one("a\nb\001", ESCAPE_BACKSLASH_ONELINE, "\"a\\nb\001\""); - test_shell_maybe_quote_one("a\nb\001", ESCAPE_POSIX, "$'a\\nb\001'"); + /* Exclamation mark is special in the interactive shell, but we don't treat it so. */ + test_shell_maybe_quote_one("foo!bar", 0, "\"foo!bar\""); + test_shell_maybe_quote_one("foo!bar", SHELL_ESCAPE_POSIX, "$'foo!bar'"); - test_shell_maybe_quote_one("foo!bar", ESCAPE_BACKSLASH, "\"foo!bar\""); - test_shell_maybe_quote_one("foo!bar", ESCAPE_BACKSLASH_ONELINE, "\"foo!bar\""); - test_shell_maybe_quote_one("foo!bar", ESCAPE_POSIX, "$'foo!bar'"); + /* Control characters and unicode */ + test_shell_maybe_quote_one("a\nb\001", 0, "\"a\\nb\\001\""); + test_shell_maybe_quote_one("a\nb\001", SHELL_ESCAPE_POSIX, "$'a\\nb\\001'"); + + test_shell_maybe_quote_one("głąb", 0, "głąb"); + test_shell_maybe_quote_one("głąb", SHELL_ESCAPE_POSIX, "głąb"); + + test_shell_maybe_quote_one("głąb\002\003", 0, "\"głąb\\002\\003\""); + test_shell_maybe_quote_one("głąb\002\003", SHELL_ESCAPE_POSIX, "$'głąb\\002\\003'"); + + test_shell_maybe_quote_one("głąb\002\003rząd", 0, "\"głąb\\002\\003rząd\""); + test_shell_maybe_quote_one("głąb\002\003rząd", SHELL_ESCAPE_POSIX, "$'głąb\\002\\003rząd'"); } int main(int argc, char *argv[]) { diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index 4d2895c847..51ae665279 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -322,6 +322,8 @@ static void test_executable_is_script(void) { char *command; int r; + log_info("/* %s */", __func__); + assert_se(fmkostemp_safe(t, "w", &f) == 0); fputs("#! /bin/script -a -b \ngoo goo", f); fflush(f); @@ -347,6 +349,8 @@ static void test_status_field(void) { unsigned long long total = 0, buffers = 0; int r; + log_info("/* %s */", __func__); + assert_se(get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t) == 0); puts(t); assert_se(streq(t, "1")); @@ -378,11 +382,11 @@ static void test_status_field(void) { } static void test_capeff(void) { - int pid, p; + log_info("/* %s */", __func__); - for (pid = 0; pid < 2; pid++) { + for (int pid = 0; pid < 2; pid++) { _cleanup_free_ char *capeff = NULL; - int r; + int r, p; r = get_process_capeff(0, &capeff); log_info("capeff: '%s' (r=%d)", capeff, r); @@ -403,6 +407,8 @@ static void test_write_string_stream(void) { int fd; char buf[64]; + log_info("/* %s */", __func__); + fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -437,6 +443,8 @@ static void test_write_string_file(void) { char buf[64] = {}; _cleanup_close_ int fd; + log_info("/* %s */", __func__); + fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -451,6 +459,8 @@ static void test_write_string_file_no_create(void) { _cleanup_close_ int fd; char buf[64] = {}; + log_info("/* %s */", __func__); + fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -465,6 +475,8 @@ static void test_write_string_file_verify(void) { _cleanup_free_ char *buf = NULL, *buf2 = NULL; int r; + log_info("/* %s */", __func__); + r = read_one_line_file("/proc/version", &buf); if (ERRNO_IS_PRIVILEGE(r)) return; @@ -491,6 +503,8 @@ static void test_load_env_file_pairs(void) { _cleanup_strv_free_ char **l = NULL; char **k, **v; + log_info("/* %s */", __func__); + fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -538,6 +552,8 @@ static void test_search_and_fopen(void) { const char *e; int r; + log_info("/* %s */", __func__); + fd = mkostemp_safe(name); assert_se(fd >= 0); fd = safe_close(fd); @@ -579,6 +595,8 @@ static void test_search_and_fopen_nulstr(void) { "/tmp/foo/bar\0" "/tmp\0"; + log_info("/* %s */", __func__); + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-search_and_fopen.XXXXXX"; _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; @@ -620,12 +638,15 @@ static void test_writing_tmpfile(void) { _cleanup_free_ char *contents = NULL; size_t size; _cleanup_close_ int fd = -1; - struct iovec iov[3]; int r; - iov[0] = IOVEC_MAKE_STRING("abc\n"); - iov[1] = IOVEC_MAKE_STRING(ALPHANUMERICAL "\n"); - iov[2] = IOVEC_MAKE_STRING(""); + log_info("/* %s */", __func__); + + struct iovec iov[] = { + IOVEC_MAKE_STRING("abc\n"), + IOVEC_MAKE_STRING(ALPHANUMERICAL "\n"), + IOVEC_MAKE_STRING(""), + }; fd = mkostemp_safe(name); printf("tmpfile: %s", name); @@ -642,6 +663,8 @@ static void test_writing_tmpfile(void) { static void test_tempfn(void) { char *ret = NULL, *p; + log_info("/* %s */", __func__); + assert_se(tempfn_xxxxxx("/foo/bar/waldo", NULL, &ret) >= 0); assert_se(streq_ptr(ret, "/foo/bar/.#waldoXXXXXX")); free(ret); @@ -684,8 +707,7 @@ static void test_fgetc(void) { _cleanup_fclose_ FILE *f = NULL; char c; - f = fmemopen_unlocked((void*) chars, sizeof(chars), "re"); - assert_se(f); + assert_se(f = fmemopen_unlocked((void*) chars, sizeof(chars), "re")); for (size_t i = 0; i < sizeof(chars); i++) { assert_se(safe_fgetc(f, &c) == 1); @@ -778,9 +800,9 @@ static void test_read_line_one_file(FILE *f) { static void test_read_line(void) { _cleanup_fclose_ FILE *f = NULL; - f = fmemopen_unlocked((void*) buffer, sizeof(buffer), "re"); - assert_se(f); + log_info("/* %s */", __func__); + assert_se(f = fmemopen_unlocked((void*) buffer, sizeof(buffer), "re")); test_read_line_one_file(f); } @@ -789,6 +811,8 @@ static void test_read_line2(void) { int fd; _cleanup_fclose_ FILE *f = NULL; + log_info("/* %s */", __func__); + fd = mkostemp_safe(name); assert_se(fd >= 0); assert_se((size_t) write(fd, buffer, sizeof(buffer)) == sizeof(buffer)); @@ -804,6 +828,8 @@ static void test_read_line3(void) { _cleanup_free_ char *line = NULL; int r; + log_info("/* %s */", __func__); + f = fopen("/proc/uptime", "re"); if (!f && IN_SET(errno, ENOENT, EPERM)) return; @@ -836,10 +862,9 @@ static void test_read_line4(void) { { 6, "foo\n\r\0" }, }; - size_t i; int r; - for (i = 0; i < ELEMENTSOF(eof_endings); i++) { + for (size_t i = 0; i < ELEMENTSOF(eof_endings); i++) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *s = NULL; @@ -864,6 +889,8 @@ static void test_read_nul_string(void) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *s = NULL; + log_info("/* %s */", __func__); + assert_se(f = fmemopen_unlocked((void*) test, sizeof(test)-1, "r")); assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 13 && streq_ptr(s, "string nr. 1")); @@ -953,6 +980,8 @@ static void test_read_full_file_offset_size(void) { size_t rbuf_size; uint8_t buf[4711]; + log_info("/* %s */", __func__); + random_bytes(buf, sizeof(buf)); assert_se(tempfn_random_child(NULL, NULL, &fn) >= 0); @@ -990,10 +1019,12 @@ static void test_read_full_file_offset_size(void) { rbuf = mfree(rbuf); } -static void test_read_full_virtual_file(void) { +static void test_read_virtual_file(size_t max_size) { const char *filename; int r; + log_info("/* %s (max_size=%zu) */", __func__, max_size); + FOREACH_STRING(filename, "/proc/1/cmdline", "/etc/nsswitch.conf", @@ -1002,8 +1033,8 @@ static void test_read_full_virtual_file(void) { _cleanup_free_ char *buf = NULL; size_t size = 0; - r = read_full_virtual_file(filename, &buf, &size); - log_info_errno(r, "read_full_virtual_file(\"%s\"): %m (%zu bytes)", filename, size); + r = read_virtual_file(filename, max_size, &buf, &size); + log_info_errno(r, "read_virtual_file(\"%s\", %zu): %m (%zu bytes)", filename, max_size, size); assert_se(r == 0 || ERRNO_IS_PRIVILEGE(r) || r == -ENOENT); } } @@ -1035,7 +1066,9 @@ int main(int argc, char *argv[]) { test_read_nul_string(); test_read_full_file_socket(); test_read_full_file_offset_size(); - test_read_full_virtual_file(); + test_read_virtual_file(20); + test_read_virtual_file(4096); + test_read_virtual_file(SIZE_MAX); return 0; } diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 5ca654e193..ca21eadaba 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -15,6 +15,8 @@ #include "alloc-util.h" #include "architecture.h" #include "errno-util.h" +#include "errno-list.h" +#include "dirent-util.h" #include "fd-util.h" #include "log.h" #include "macro.h" @@ -43,6 +45,8 @@ static void test_get_process_comm(pid_t pid) { dev_t h; int r; + log_info("/* %s */", __func__); + xsprintf(path, "/proc/"PID_FMT"/comm", pid); if (stat(path, &st) == 0) { @@ -88,15 +92,61 @@ static void test_get_process_comm(pid_t pid) { log_info("PID"PID_FMT" $PATH: '%s'", pid, strna(i)); } +static void test_get_process_cmdline_one(pid_t pid) { + _cleanup_free_ char *c = NULL, *d = NULL, *e = NULL, *f = NULL, *g = NULL, *h = NULL; + int r; + + r = get_process_cmdline(pid, SIZE_MAX, 0, &c); + log_info("PID "PID_FMT": %s", pid, r >= 0 ? c : errno_to_name(r)); + + r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &d); + log_info(" %s", r >= 0 ? d : errno_to_name(r)); + + r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &e); + log_info(" %s", r >= 0 ? e : errno_to_name(r)); + + r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE | PROCESS_CMDLINE_COMM_FALLBACK, &f); + log_info(" %s", r >= 0 ? f : errno_to_name(r)); + + r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &g); + log_info(" %s", r >= 0 ? g : errno_to_name(r)); + + r = get_process_cmdline(pid, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX | PROCESS_CMDLINE_COMM_FALLBACK, &h); + log_info(" %s", r >= 0 ? h : errno_to_name(r)); +} + +static void test_get_process_cmdline(void) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + log_info("/* %s */", __func__); + + assert_se(d = opendir("/proc")); + + FOREACH_DIRENT(de, d, return) { + pid_t pid; + + dirent_ensure_type(d, de); + + if (de->d_type != DT_DIR) + continue; + + if (parse_pid(de->d_name, &pid) < 0) + continue; + + test_get_process_cmdline_one(pid); + } +} + static void test_get_process_comm_escape_one(const char *input, const char *output) { _cleanup_free_ char *n = NULL; - log_info("input: <%s> — output: <%s>", input, output); + log_debug("input: <%s> — output: <%s>", input, output); assert_se(prctl(PR_SET_NAME, input) >= 0); assert_se(get_process_comm(0, &n) >= 0); - log_info("got: <%s>", n); + log_debug("got: <%s>", n); assert_se(streq_ptr(n, output)); } @@ -104,6 +154,8 @@ static void test_get_process_comm_escape_one(const char *input, const char *outp static void test_get_process_comm_escape(void) { _cleanup_free_ char *saved = NULL; + log_info("/* %s */", __func__); + assert_se(get_process_comm(0, &saved) >= 0); test_get_process_comm_escape_one("", ""); @@ -140,6 +192,8 @@ static void test_pid_is_unwaited(void) { static void test_pid_is_alive(void) { pid_t pid; + log_info("/* %s */", __func__); + pid = fork(); assert_se(pid >= 0); if (pid == 0) { @@ -155,6 +209,7 @@ static void test_pid_is_alive(void) { } static void test_personality(void) { + log_info("/* %s */", __func__); assert_se(personality_to_string(PER_LINUX)); assert_se(!personality_to_string(PERSONALITY_INVALID)); @@ -183,6 +238,8 @@ static void test_get_process_cmdline_harder(void) { _cleanup_free_ char *line = NULL; pid_t pid; + log_info("/* %s */", __func__); + if (geteuid() != 0) { log_info("Skipping %s: not root", __func__); return; @@ -245,151 +302,219 @@ 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, 0, &line) == -ENOENT); + assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 0, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - log_info("'%s'", line); + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("'%s'", line); + assert_se(streq(line, "\"[testa]\"")); /* quoting is enabled here */ + line = mfree(line); + + assert_se(get_process_cmdline(0, 0, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[t…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[te…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[tes…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[test…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); assert_se(streq(line, "[testa]")); line = mfree(line); + /* Test with multiple arguments that don't require quoting */ + assert_se(write(fd, "foo\0bar", 8) == 8); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, 0, &line) >= 0); - log_info("'%s'", line); + assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 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, 0, &line) >= 0); - log_info("'%s'", line); + assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "f…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "fo…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo …")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo b…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo ba…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 9, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 9, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar …")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar q…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar qu…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 13, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 13, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "foo bar quux")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 14, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 14, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); 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(get_process_cmdline(0, 1000, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); 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, 0, &line) == -ENOENT); + assert_se(get_process_cmdline(0, SIZE_MAX, 0, &line) == -ENOENT); - assert_se(get_process_cmdline(getpid_cached(), SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "[aaaa bbbb cccc]")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "[aaaa bbb…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "[aaaa bbbb…")); line = mfree(line); - assert_se(get_process_cmdline(getpid_cached(), 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + assert_se(get_process_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); + log_debug("'%s'", line); assert_se(streq(line, "[aaaa bbbb …")); line = mfree(line); + /* Test with multiple arguments that do require quoting */ + +#define CMDLINE1 "foo\0'bar'\0\"bar$\"\0x y z\0!``\0" +#define EXPECT1 "foo \"'bar'\" \"\\\"bar\\$\\\"\" \"x y z\" \"!\\`\\`\" \"\"" +#define EXPECT1p "foo $'\\'bar\\'' $'\"bar$\"' $'x y z' $'!``' \"\"" + assert_se(lseek(fd, SEEK_SET, 0) == 0); + assert_se(write(fd, CMDLINE1, sizeof CMDLINE1) == sizeof CMDLINE1); + assert_se(ftruncate(fd, sizeof CMDLINE1) == 0); + + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT1); + assert_se(streq(line, EXPECT1)); + line = mfree(line); + + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT1p); + assert_se(streq(line, EXPECT1p)); + line = mfree(line); + +#define CMDLINE2 "foo\0\1\2\3\0\0" +#define EXPECT2 "foo \"\\001\\002\\003\" \"\" \"\"" +#define EXPECT2p "foo $'\\001\\002\\003' \"\" \"\"" + assert_se(lseek(fd, SEEK_SET, 0) == 0); + assert_se(write(fd, CMDLINE2, sizeof CMDLINE2) == sizeof CMDLINE2); + assert_se(ftruncate(fd, sizeof CMDLINE2) == 0); + + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT2); + assert_se(streq(line, EXPECT2)); + line = mfree(line); + + assert_se(get_process_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); + log_debug("got: ==%s==", line); + log_debug("exp: ==%s==", EXPECT2p); + assert_se(streq(line, EXPECT2p)); + line = mfree(line); + safe_close(fd); _exit(EXIT_SUCCESS); } @@ -398,11 +523,15 @@ static void test_rename_process_now(const char *p, int ret) { _cleanup_free_ char *comm = NULL, *cmdline = NULL; int r; + log_info("/* %s */", __func__); + r = rename_process(p); assert_se(r == ret || (ret == 0 && r >= 0) || (ret > 0 && r > 0)); + log_debug_errno(r, "rename_process(%s): %m", p); + if (r < 0) return; @@ -413,7 +542,7 @@ static void test_rename_process_now(const char *p, int ret) { #endif assert_se(get_process_comm(0, &comm) >= 0); - log_info("comm = <%s>", comm); + log_debug("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. */ @@ -425,9 +554,12 @@ static void test_rename_process_now(const char *p, int ret) { if (r == 0 && detect_container() > 0) log_info("cmdline = <%s> (not verified, Running in unprivileged container?)", cmdline); else { - log_info("cmdline = <%s>", cmdline); - assert_se(strneq(p, cmdline, STRLEN("test-process-util"))); - assert_se(startswith(p, cmdline)); + log_info("cmdline = <%s> (expected <%.*s>)", cmdline, (int) strlen("test-process-util"), p); + + bool skip = cmdline[0] == '"'; /* A shortcut to check if the string is quoted */ + + assert_se(strneq(cmdline + skip, p, strlen("test-process-util"))); + assert_se(startswith(cmdline + skip, p)); } } else log_info("cmdline = <%s> (not verified)", cmdline); @@ -437,6 +569,8 @@ static void test_rename_process_one(const char *p, int ret) { siginfo_t si; pid_t pid; + log_info("/* %s */", __func__); + pid = fork(); assert_se(pid >= 0); @@ -491,6 +625,8 @@ static void test_getpid_cached(void) { siginfo_t si; pid_t a, b, c, d, e, f, child; + log_info("/* %s */", __func__); + a = raw_getpid(); b = getpid_cached(); c = getpid(); @@ -521,25 +657,28 @@ static void test_getpid_cached(void) { assert_se(si.si_code == CLD_EXITED); } -#define MEASURE_ITERATIONS (10000000LLU) - static void test_getpid_measure(void) { - unsigned long long i; usec_t t, q; + unsigned long long iterations = slow_tests_enabled() ? 1000000 : 1000; + + log_info("/* %s (%llu iterations) */", __func__, iterations); + t = now(CLOCK_MONOTONIC); - for (i = 0; i < MEASURE_ITERATIONS; i++) + for (unsigned long long i = 0; i < iterations; i++) (void) getpid(); q = now(CLOCK_MONOTONIC) - t; - log_info(" glibc getpid(): %lf µs each\n", (double) q / MEASURE_ITERATIONS); + log_info(" glibc getpid(): %lf µs each\n", (double) q / iterations); + + iterations *= 50; /* _cached() is about 50 times faster, so we need more iterations */ t = now(CLOCK_MONOTONIC); - for (i = 0; i < MEASURE_ITERATIONS; i++) + for (unsigned long long i = 0; i < iterations; i++) (void) getpid_cached(); q = now(CLOCK_MONOTONIC) - t; - log_info("getpid_cached(): %lf µs each\n", (double) q / MEASURE_ITERATIONS); + log_info("getpid_cached(): %lf µs each\n", (double) q / iterations); } static void test_safe_fork(void) { @@ -547,6 +686,8 @@ static void test_safe_fork(void) { pid_t pid; int r; + log_info("/* %s */", __func__); + BLOCK_SIGNALS(SIGCHLD); r = safe_fork("(test-child)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_NULL_STDIO|FORK_REOPEN_LOG, &pid); @@ -595,6 +736,8 @@ static void test_ioprio_class_from_to_string_one(const char *val, int expected) } static void test_ioprio_class_from_to_string(void) { + log_info("/* %s */", __func__); + test_ioprio_class_from_to_string_one("none", IOPRIO_CLASS_NONE); test_ioprio_class_from_to_string_one("realtime", IOPRIO_CLASS_RT); test_ioprio_class_from_to_string_one("best-effort", IOPRIO_CLASS_BE); @@ -610,7 +753,10 @@ static void test_ioprio_class_from_to_string(void) { static void test_setpriority_closest(void) { int r; - r = safe_fork("(test-setprio)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); + log_info("/* %s */", __func__); + + r = safe_fork("(test-setprio)", + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL); assert_se(r >= 0); if (r == 0) { @@ -694,7 +840,8 @@ static void test_setpriority_closest(void) { } int main(int argc, char *argv[]) { - test_setup_logging(LOG_DEBUG); + log_show_color(true); + test_setup_logging(LOG_INFO); save_argc_argv(argc, argv); @@ -709,6 +856,7 @@ int main(int argc, char *argv[]) { } test_get_process_comm_escape(); + test_get_process_cmdline(); test_pid_is_unwaited(); test_pid_is_alive(); test_personality(); diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index 042b94634b..4ba9ca8439 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "string-util.h" #include "strv.h" +#include "tests.h" #include "utf8.h" #include "util.h" @@ -91,15 +92,15 @@ static void test_utf8_escape_invalid(void) { log_info("/* %s */", __func__); p1 = utf8_escape_invalid("goo goo goo"); - puts(p1); + log_debug("\"%s\"", p1); assert_se(utf8_is_valid(p1)); p2 = utf8_escape_invalid("\341\204\341\204"); - puts(p2); + log_debug("\"%s\"", p2); assert_se(utf8_is_valid(p2)); p3 = utf8_escape_invalid("\341\204"); - puts(p3); + log_debug("\"%s\"", p3); assert_se(utf8_is_valid(p3)); } @@ -109,59 +110,56 @@ static void test_utf8_escape_non_printable(void) { log_info("/* %s */", __func__); p1 = utf8_escape_non_printable("goo goo goo"); - puts(p1); + log_debug("\"%s\"", p1); assert_se(utf8_is_valid(p1)); p2 = utf8_escape_non_printable("\341\204\341\204"); - puts(p2); + log_debug("\"%s\"", p2); assert_se(utf8_is_valid(p2)); p3 = utf8_escape_non_printable("\341\204"); - puts(p3); + log_debug("\"%s\"", p3); assert_se(utf8_is_valid(p3)); p4 = utf8_escape_non_printable("ąę\n가너도루\n1234\n\341\204\341\204\n\001 \019\20\a"); - puts(p4); + log_debug("\"%s\"", p4); assert_se(utf8_is_valid(p4)); p5 = utf8_escape_non_printable("\001 \019\20\a"); - puts(p5); + log_debug("\"%s\"", p5); assert_se(utf8_is_valid(p5)); p6 = utf8_escape_non_printable("\xef\xbf\x30\x13"); - puts(p6); + log_debug("\"%s\"", p6); 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; + const char *s; + FOREACH_STRING(s, + "goo goo goo", /* ASCII */ + "\001 \019\20\a", /* control characters */ + "\xef\xbf\x30\x13") /* misplaced continuation bytes followed by a digit and cc */ + for (size_t cw = 0; cw < 22; cw++) { + _cleanup_free_ char *p, *q; + size_t ew; - 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); - } + p = utf8_escape_non_printable_full(s, cw, false); + ew = utf8_console_width(p); + log_debug("%02zu \"%s\" (%zu wasted)", cw, p, cw - ew); + assert_se(utf8_is_valid(p)); + assert_se(ew <= cw); - 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); - } + q = utf8_escape_non_printable_full(s, cw, true); + ew = utf8_console_width(q); + log_debug(" \"%s\" (%zu wasted)", q, cw - ew); + assert_se(utf8_is_valid(q)); + assert_se(ew <= cw); + if (cw > 0) + assert_se(endswith(q, "…")); + } } static void test_utf16_to_utf8(void) { @@ -235,6 +233,9 @@ static void test_utf8_to_utf16(void) { } int main(int argc, char *argv[]) { + log_show_color(true); + test_setup_logging(LOG_INFO); + test_utf8_n_is_valid(); test_utf8_is_valid(); test_utf8_is_printable();