util-lib: rework get_process_cmdline() (#3529)

This reworks get_process_cmdline() quite substantially, fixing the following:

- Fixes:
  a4e3bf4d7a (r66837630)

- The passed max_length is also applied to the "comm" name, if comm_fallback is
  set.

- The right thing happens if max_length == 1 is specified

- when the cmdline "foobar" is abbreviated to 6 characters the result is not
  "foobar" instead of "foo...".

- trailing whitespace are removed before the ... suffix is appended. The 7
  character abbreviation of "foo barz" is hence "foo..." instead of "foo ...".

- leading whitespace are suppressed from the cmdline

- a comprehensive test case is added
This commit is contained in:
Lennart Poettering 2016-06-14 23:52:29 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent 8dd57192bc
commit 69281c49eb
2 changed files with 270 additions and 12 deletions

View File

@ -110,6 +110,15 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
assert(line);
assert(pid >= 0);
/* Retrieves a process' command line. Replaces unprintable characters while doing so by whitespace (coalescing
* multiple sequential ones into one). If max_length is != 0 will return a string of the specified size at most
* (the trailing NUL byte does count towards the length here!), abbreviated with a "..." ellipsis. If
* comm_fallback is true and the process has no command line set (the case for kernel threads), or has a
* command line that resolves to the empty string will return the "comm" name of the process instead.
*
* Returns -ESRCH if the process doesn't exist, and -ENOENT if the process has no command line (and
* comm_fallback is false). */
p = procfs_file_alloca(pid, "cmdline");
f = fopen(p, "re");
@ -119,12 +128,22 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
return -errno;
}
if (max_length == 0) {
if (max_length == 1) {
/* If there's only room for one byte, return the empty string */
r = new0(char, 1);
if (!r)
return -ENOMEM;
*line = r;
return 0;
} else if (max_length == 0) {
size_t len = 0, allocated = 0;
while ((c = getc(f)) != EOF) {
if (!GREEDY_REALLOC(r, allocated, len+2)) {
if (!GREEDY_REALLOC(r, allocated, len+3)) {
free(r);
return -ENOMEM;
}
@ -136,7 +155,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
}
r[len++] = c;
} else
} else if (len > 0)
space = true;
}
@ -144,6 +163,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
r[len] = 0;
} else {
bool dotdotdot = false;
size_t left;
r = new(char, max_length);
@ -155,28 +175,46 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
while ((c = getc(f)) != EOF) {
if (isprint(c)) {
if (space) {
if (left <= 4)
if (left <= 2) {
dotdotdot = true;
break;
}
*(k++) = ' ';
left--;
space = false;
}
if (left <= 4)
if (left <= 1) {
dotdotdot = true;
break;
}
*(k++) = (char) c;
left--;
} else
} else if (k > r)
space = true;
}
if (left <= 4) {
size_t n = MIN(left-1, 3U);
memcpy(k, "...", n);
k[n] = 0;
if (dotdotdot) {
if (max_length <= 4) {
k = r;
left = max_length;
} else {
k = r + max_length - 4;
left = 4;
/* Eat up final spaces */
while (k > r && isspace(k[-1])) {
k--;
left++;
}
}
strncpy(k, "...", left-1);
k[left] = 0;
} else
*k = 0;
}
@ -195,7 +233,37 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char *
if (h < 0)
return h;
r = strjoin("[", t, "]", NULL);
if (max_length == 0)
r = strjoin("[", t, "]", NULL);
else {
size_t l;
l = strlen(t);
if (l + 3 <= max_length)
r = strjoin("[", t, "]", NULL);
else if (max_length <= 6) {
r = new(char, max_length);
if (!r)
return -ENOMEM;
memcpy(r, "[...]", max_length-1);
r[max_length-1] = 0;
} else {
char *e;
t[max_length - 6] = 0;
/* Chop off final spaces */
e = strchr(t, 0);
while (e > t && isspace(e[-1]))
e--;
*e = 0;
r = strjoin("[", t, "...]", NULL);
}
}
if (!r)
return -ENOMEM;
}

View File

@ -18,7 +18,10 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <sched.h>
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@ -26,6 +29,7 @@
#include "alloc-util.h"
#include "architecture.h"
#include "fd-util.h"
#include "log.h"
#include "macro.h"
#include "parse-util.h"
@ -59,7 +63,11 @@ static void test_get_process_comm(pid_t pid) {
log_info("PID"PID_FMT" cmdline: '%s'", pid, c);
assert_se(get_process_cmdline(pid, 8, false, &d) >= 0);
log_info("PID"PID_FMT" cmdline truncated: '%s'", pid, d);
log_info("PID"PID_FMT" cmdline truncated to 8: '%s'", pid, d);
free(d);
assert_se(get_process_cmdline(pid, 1, false, &d) >= 0);
log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d);
assert_se(get_process_ppid(pid, &e) >= 0);
log_info("PID"PID_FMT" PPID: "PID_FMT, pid, e);
@ -147,6 +155,187 @@ static void test_personality(void) {
#endif
}
static void test_get_process_cmdline_harder(void) {
char path[] = "/tmp/test-cmdlineXXXXXX";
_cleanup_close_ int fd = -1;
_cleanup_free_ char *line = NULL;
pid_t pid;
if (geteuid() != 0)
return;
pid = fork();
if (pid > 0) {
siginfo_t si;
(void) wait_for_terminate(pid, &si);
assert_se(si.si_code == CLD_EXITED);
assert_se(si.si_status == 0);
return;
}
assert_se(pid == 0);
assert_se(unshare(CLONE_NEWNS) >= 0);
fd = mkostemp(path, O_CLOEXEC);
assert_se(fd >= 0);
assert_se(mount(path, "/proc/self/cmdline", "bind", MS_BIND, NULL) >= 0);
assert_se(unlink(path) >= 0);
assert_se(prctl(PR_SET_NAME, "testa") >= 0);
assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
assert_se(streq(line, "[testa]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 1, true, &line) >= 0);
assert_se(streq(line, ""));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 2, true, &line) >= 0);
assert_se(streq(line, "["));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 3, true, &line) >= 0);
assert_se(streq(line, "[."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 4, true, &line) >= 0);
assert_se(streq(line, "[.."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 5, true, &line) >= 0);
assert_se(streq(line, "[..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 6, true, &line) >= 0);
assert_se(streq(line, "[...]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 7, true, &line) >= 0);
assert_se(streq(line, "[t...]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 8, true, &line) >= 0);
assert_se(streq(line, "[testa]"));
line = mfree(line);
assert_se(write(fd, "\0\0\0\0\0\0\0\0\0", 10) == 10);
assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
assert_se(streq(line, "[testa]"));
line = mfree(line);
assert_se(write(fd, "foo\0bar\0\0\0\0\0", 10) == 10);
assert_se(get_process_cmdline(getpid(), 0, false, &line) >= 0);
assert_se(streq(line, "foo bar"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
assert_se(streq(line, "foo bar"));
line = mfree(line);
assert_se(write(fd, "quux", 4) == 4);
assert_se(get_process_cmdline(getpid(), 0, false, &line) >= 0);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 1, true, &line) >= 0);
assert_se(streq(line, ""));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 2, true, &line) >= 0);
assert_se(streq(line, "."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 3, true, &line) >= 0);
assert_se(streq(line, ".."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 4, true, &line) >= 0);
assert_se(streq(line, "..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 5, true, &line) >= 0);
assert_se(streq(line, "f..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 6, true, &line) >= 0);
assert_se(streq(line, "fo..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 7, true, &line) >= 0);
assert_se(streq(line, "foo..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 8, true, &line) >= 0);
assert_se(streq(line, "foo..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 9, true, &line) >= 0);
assert_se(streq(line, "foo b..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 10, true, &line) >= 0);
assert_se(streq(line, "foo ba..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 11, true, &line) >= 0);
assert_se(streq(line, "foo bar..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 12, true, &line) >= 0);
assert_se(streq(line, "foo bar..."));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 13, true, &line) >= 0);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 14, true, &line) >= 0);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 1000, true, &line) >= 0);
assert_se(streq(line, "foo bar quux"));
line = mfree(line);
assert_se(ftruncate(fd, 0) >= 0);
assert_se(prctl(PR_SET_NAME, "aaaa bbbb cccc") >= 0);
assert_se(get_process_cmdline(getpid(), 0, false, &line) == -ENOENT);
assert_se(get_process_cmdline(getpid(), 0, true, &line) >= 0);
assert_se(streq(line, "[aaaa bbbb cccc]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 10, true, &line) >= 0);
assert_se(streq(line, "[aaaa...]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 11, true, &line) >= 0);
assert_se(streq(line, "[aaaa...]"));
line = mfree(line);
assert_se(get_process_cmdline(getpid(), 12, true, &line) >= 0);
assert_se(streq(line, "[aaaa b...]"));
line = mfree(line);
safe_close(fd);
_exit(0);
}
int main(int argc, char *argv[]) {
log_parse_environment();
log_open();
@ -164,6 +353,7 @@ int main(int argc, char *argv[]) {
test_pid_is_unwaited();
test_pid_is_alive();
test_personality();
test_get_process_cmdline_harder();
return 0;
}