mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #18863 from keszybz/cmdline-escaping
Escape command lines properly
This commit is contained in:
@@ -354,10 +354,40 @@ Fri … 552351 1000 1000 SIGSEGV present /usr/lib64/firefox/firefox 28.7M
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Show information about a process that dumped core,
|
||||
matching by its PID 6654</title>
|
||||
<title>Show information about a core dump matched by PID</title>
|
||||
|
||||
<programlisting>$ coredumpctl info 6654</programlisting>
|
||||
<programlisting>$ 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)
|
||||
</programlisting>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (<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.
|
||||
/* 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 <20>. 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user