177 lines
4.7 KiB
C
177 lines
4.7 KiB
C
#include "git-compat-util.h"
|
|
|
|
#include "strbuf.h"
|
|
#include "strvec.h"
|
|
#include "trace2.h"
|
|
|
|
/*
|
|
* We need more complex parsing in stat_parent_pid() and
|
|
* parse_proc_stat() below than a dumb fscanf(). That's because while
|
|
* the statcomm field is surrounded by parentheses, the process itself
|
|
* is free to insert any arbitrary byte sequence its its name. That
|
|
* can include newlines, spaces, closing parentheses etc.
|
|
*
|
|
* See do_task_stat() in fs/proc/array.c in linux.git, this is in
|
|
* contrast with the escaped version of the name found in
|
|
* /proc/%d/status.
|
|
*
|
|
* So instead of using fscanf() we'll read N bytes from it, look for
|
|
* the first "(", and then the last ")", anything in-between is our
|
|
* process name.
|
|
*
|
|
* How much N do we need? On Linux /proc/sys/kernel/pid_max is 2^15 by
|
|
* default, but it can be raised set to values of up to 2^22. So
|
|
* that's 7 digits for a PID. We have 2 PIDs in the first four fields
|
|
* we're interested in, so 2 * 7 = 14.
|
|
*
|
|
* We then have 3 spaces between those four values, and we'd like to
|
|
* get to the space between the 4th and the 5th (the "pgrp" field) to
|
|
* make sure we read the entire "ppid" field. So that brings us up to
|
|
* 14 + 3 + 1 = 18. Add the two parentheses around the "comm" value
|
|
* and it's 20. The "state" value itself is then one character (now at
|
|
* 21).
|
|
*
|
|
* Finally the maximum length of the "comm" name itself is 15
|
|
* characters, e.g. a setting of "123456789abcdefg" will be truncated
|
|
* to "123456789abcdef". See PR_SET_NAME in prctl(2). So all in all
|
|
* we'd need to read 21 + 15 = 36 bytes.
|
|
*
|
|
* Let's just read 2^6 (64) instead for good measure. If PID_MAX ever
|
|
* grows past 2^22 we'll be future-proof. We'll then anchor at the
|
|
* last ")" we find to locate the parent PID.
|
|
*/
|
|
#define STAT_PARENT_PID_READ_N 64
|
|
|
|
static int parse_proc_stat(struct strbuf *sb, struct strbuf *name,
|
|
int *statppid)
|
|
{
|
|
const char *comm_lhs = strchr(sb->buf, '(');
|
|
const char *comm_rhs = strrchr(sb->buf, ')');
|
|
const char *ppid_lhs, *ppid_rhs;
|
|
char *p;
|
|
pid_t ppid;
|
|
|
|
if (!comm_lhs || !comm_rhs)
|
|
goto bad_kernel;
|
|
|
|
/*
|
|
* We're at the ")", that's followed by " X ", where X is a
|
|
* single "state" character. So advance by 4 bytes.
|
|
*/
|
|
ppid_lhs = comm_rhs + 4;
|
|
|
|
/*
|
|
* Read until the space between the "ppid" and "pgrp" fields
|
|
* to make sure we're anchored after the untruncated "ppid"
|
|
* field..
|
|
*/
|
|
ppid_rhs = strchr(ppid_lhs, ' ');
|
|
if (!ppid_rhs)
|
|
goto bad_kernel;
|
|
|
|
ppid = strtol(ppid_lhs, &p, 10);
|
|
if (ppid_rhs == p) {
|
|
const char *comm = comm_lhs + 1;
|
|
size_t commlen = comm_rhs - comm;
|
|
|
|
strbuf_add(name, comm, commlen);
|
|
*statppid = ppid;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bad_kernel:
|
|
/*
|
|
* We were able to read our STAT_PARENT_PID_READ_N bytes from
|
|
* /proc/%d/stat, but the content is bad. Broken kernel?
|
|
* Should not happen, but handle it gracefully.
|
|
*/
|
|
return -1;
|
|
}
|
|
|
|
static int stat_parent_pid(pid_t pid, struct strbuf *name, int *statppid)
|
|
{
|
|
struct strbuf procfs_path = STRBUF_INIT;
|
|
struct strbuf sb = STRBUF_INIT;
|
|
FILE *fp;
|
|
int ret = -1;
|
|
|
|
/* try to use procfs if it's present. */
|
|
strbuf_addf(&procfs_path, "/proc/%d/stat", pid);
|
|
fp = fopen(procfs_path.buf, "r");
|
|
if (!fp)
|
|
goto cleanup;
|
|
|
|
/*
|
|
* We could be more strict here and assert that we read at
|
|
* least STAT_PARENT_PID_READ_N. My reading of procfs(5) is
|
|
* that on any modern kernel (at least since 2.6.0 released in
|
|
* 2003) even if all the mandatory numeric fields were zero'd
|
|
* out we'd get at least 100 bytes, but let's just check that
|
|
* we got anything at all and trust the parse_proc_stat()
|
|
* function to handle its "Bad Kernel?" error checking.
|
|
*/
|
|
if (!strbuf_fread(&sb, STAT_PARENT_PID_READ_N, fp))
|
|
goto cleanup;
|
|
if (parse_proc_stat(&sb, name, statppid) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (fp)
|
|
fclose(fp);
|
|
strbuf_release(&procfs_path);
|
|
strbuf_release(&sb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void push_ancestry_name(struct strvec *names, pid_t pid)
|
|
{
|
|
struct strbuf name = STRBUF_INIT;
|
|
int ppid;
|
|
|
|
if (stat_parent_pid(pid, &name, &ppid) < 0)
|
|
goto cleanup;
|
|
|
|
strvec_push(names, name.buf);
|
|
|
|
/*
|
|
* Both errors and reaching the end of the process chain are
|
|
* reported as fields of 0 by proc(5)
|
|
*/
|
|
if (ppid)
|
|
push_ancestry_name(names, ppid);
|
|
cleanup:
|
|
strbuf_release(&name);
|
|
|
|
return;
|
|
}
|
|
|
|
void trace2_collect_process_info(enum trace2_process_info_reason reason)
|
|
{
|
|
struct strvec names = STRVEC_INIT;
|
|
|
|
if (!trace2_is_enabled())
|
|
return;
|
|
|
|
switch (reason) {
|
|
case TRACE2_PROCESS_INFO_EXIT:
|
|
/*
|
|
* The Windows version of this calls its
|
|
* get_peak_memory_info() here. We may want to insert
|
|
* similar process-end statistics here in the future.
|
|
*/
|
|
break;
|
|
case TRACE2_PROCESS_INFO_STARTUP:
|
|
push_ancestry_name(&names, getppid());
|
|
|
|
if (names.nr)
|
|
trace2_cmd_ancestry(names.v);
|
|
strvec_clear(&names);
|
|
break;
|
|
}
|
|
|
|
return;
|
|
}
|