procstat: construct shortened process cmdline during exec
MODIFY
bin/ps/ps
bin/ps/ps
#!/bin/ksh
-echo "euid pid ppid pgrp session state elfpath"
+echo "euid pid ppid pgrp session state command"
cat /dev/procstat

MODIFY
bin/utest/Makefile
bin/utest/Makefile
@@ -13,6 +13,7 @@ SOURCES = \
misbehave.c \
mmap.c \
pgrp.c \
+ procstat.c \
pty.c \
sbrk.c \
signal.c \

MODIFY
bin/utest/main.c
bin/utest/main.c
@@ -122,6 +122,9 @@ int main(int argc, char **argv) {
CHECKRUN_TEST(tty_canon);
CHECKRUN_TEST(tty_echo);
CHECKRUN_TEST(tty_signals);
+
+ CHECKRUN_TEST(procstat);
+
printf("No user test \"%s\" available.\n", test_name);
return 1;
}

ADD
bin/utest/procstat.c
+#include <stdio.h>
+#include <assert.h>
+
+int test_procstat(void) {
+ int euid, pid, ppid, pgrp, session, got;
+ char state;
+#define PROC_COMM_MAX 128
+ char command[PROC_COMM_MAX];
+
+ FILE *pstat = fopen("/dev/procstat", "r");
+ assert(pstat != NULL);
+
+ for (;;) {
+ if ((got = fscanf(pstat, "%d\t%d\t%d\t%d\t%d\t%c\t", &euid, &pid, &ppid,
+ &pgrp, &session, &state)) == -1)
+ break;
+
+ if (got < 6)
+ return 1;
+
+ if ((fgets(command, PROC_COMM_MAX, pstat)) == NULL)
+ return 1;
+ }
+ return 0;
+}

MODIFY
bin/utest/utest.h
bin/utest/utest.h
@@ -117,4 +117,6 @@ int test_tty_canon(void);
int test_tty_echo(void);
int test_tty_signals(void);
+int test_procstat(void);
+
#endif /* __UTEST_H__ */

MODIFY
include/sys/proc.h
include/sys/proc.h
@@ -101,6 +101,7 @@ struct proc {
pid_t p_pid; /* (!) Process ID */
cred_t p_cred; /* (@, *) Process credentials */
char *p_elfpath; /* (!) path of loaded elf file */
+ char *p_args; /* (!) prefix of process arguments */
TAILQ_ENTRY(proc) p_pglist; /* (g + a) link on pg_members list */
pgrp_t *p_pgrp; /* (@ + a) process group */
volatile proc_state_t p_state; /* (@) process state */

MODIFY
include/sys/syslimits.h
include/sys/syslimits.h
#define PATH_MAX 1024 /* max bytes in pathname */
#define PIPE_BUF 512 /* max bytes for atomic pipe writes */
#define IOV_MAX 1024 /* max elements in i/o vector */
+#define PARGS_MAX 100 /* max length of stored process arguments */
#define BC_BASE_MAX 99 /* max ibase/obase values in bc(1) */
#define BC_DIM_MAX 2048 /* max array elements in bc(1) */

MODIFY
sys/kern/dev_procstat.c
sys/kern/dev_procstat.c
* During read calls to /dev/procstat this info can be read.
*
* Example:
- * euid pid ppid pgrp session state elfpath
- * 0 1 0 1 1 R /bin/ksh
+ * euid pid ppid pgrp session state command
+ * 0 1 0 1 1 R ls -l
*/
+/* length of displayed command of process (includes terminating null byte) */
+#define PROC_COMM_MAX 128
+
/* maximum size of output string of proc info
- * name + path + state (1 character)+ spaces (10 characters) +
+ * command + state (1 character)+ spaces (10 characters) +
* + 5 * max length of uint32 (10 characters)*/
-#define MAX_P_STRING (PATH_MAX + 5 * 10 + 1 + 10)
+#define MAX_P_STRING (PROC_COMM_MAX + 1 + 10 + 5 * 10)
/* maximum amount of processes that procstat can handle */
#define MAX_PROC 40
/* we want to have at most 10 instances of procstat */
#define MAX_PROCSTAT 10
+/* string used when process has no p_elfpath or p_args */
+#define NONAME "-"
+
static char proc_state[4] = {
[PS_NORMAL] = 'R',
[PS_STOPPED] = 'S',
@@ -44,7 +50,7 @@ typedef struct ps_entry {
pgid_t pgrp;
sid_t sid;
proc_state_t proc_state;
- char *elfpath;
+ char *command;
} ps_entry_t;
typedef struct ps_buf {
@@ -76,6 +82,39 @@ static vnodeops_t dev_procstat_vnodeops = {
.v_close = dev_procstat_close,
};
+static char *get_command(proc_t *p) {
+ char *buf = kmalloc(M_TEMP, PROC_COMM_MAX, M_ZERO);
+ ssize_t left = PROC_COMM_MAX, used = 0;
+ char *command;
+
+ /* copy program's name from elfpath */
+ if (p->p_elfpath) {
+ command = strrchr(p->p_elfpath, '/');
+ if (command == NULL)
+ command = p->p_elfpath;
+ else
+ /* omit slash */
+ command += 1;
+ } else {
+ command = NONAME;
+ }
+
+ ssize_t wanted = strlcpy(buf, command, left);
+ used += min(wanted, left - 1);
+ left = PROC_COMM_MAX - used;
+
+ /* if there is enough space append space after program name */
+ if (left > 1) {
+ buf[used++] = ' ';
+ left--;
+ }
+
+ char *args = p->p_args ? p->p_args : NONAME;
+ strlcpy(buf + used, args, left);
+
+ return buf;
+}
+
static void ps_entry_fill(ps_entry_t *pe, proc_t *p) {
SCOPED_MTX_LOCK(&p->p_lock);
pe->uid = p->p_cred.cr_euid;
@@ -84,7 +123,7 @@ static void ps_entry_fill(ps_entry_t *pe, proc_t *p) {
pe->pgrp = p->p_pgrp->pg_id;
pe->sid = p->p_pgrp->pg_session->s_sid;
pe->proc_state = p->p_state;
- pe->elfpath = kstrndup(M_TEMP, p->p_elfpath, PATH_MAX);
+ pe->command = get_command(p);
}
/* buf must be at least MAX_P_STRING long
@@ -93,7 +132,7 @@ static void ps_entry_fill(ps_entry_t *pe, proc_t *p) {
static int ps_entry_tostring(char *buf, ps_entry_t *pe) {
int r = snprintf(buf, MAX_P_STRING, "%d\t%d\t%d\t%d\t%d\t%c\t%s\n", pe->uid,
pe->pid, pe->ppid, pe->pgrp, pe->sid,
- proc_state[pe->proc_state], pe->elfpath);
+ proc_state[pe->proc_state], pe->command);
return min(r, MAX_P_STRING);
}
@@ -182,7 +221,7 @@ static int dev_procstat_read(file_t *f, uio_t *uio) {
static int dev_procstat_close(vnode_t *v, file_t *fp) {
ps_buf_t *ps = fp->f_data;
for (int i = 0; i < ps->nproc; ++i) {
- kfree(M_TEMP, ps->ps[i].elfpath);
+ kfree(M_TEMP, ps->ps[i].command);
}
kfree(M_TEMP, ps);

MODIFY
sys/kern/exec.c
sys/kern/exec.c
@@ -323,6 +323,28 @@ static bool check_setid(vnode_t *vn, uid_t *uid, gid_t *gid) {
return (*uid != (uid_t)-1) || (*gid != (gid_t)-1);
}
+static char *pargs_create(exec_args_t *args) {
+ char *pargs = kmalloc(M_STR, PARGS_MAX, M_ZERO);
+ size_t used = 0;
+ size_t left = PARGS_MAX;
+
+ for (size_t i = 1; used + 1 < PARGS_MAX && i < args->argc; i++) {
+ size_t wanted = strlcpy(pargs + used, args->argv[i], left);
+
+ /* calculate how much we have copied (without terminating null byte) */
+ used += min(wanted, left - 1);
+ left = PARGS_MAX - used;
+
+ /* add space between args if there is space and it is not last argument*/
+ if (left > 1 && i + 1 < args->argc) {
+ pargs[used++] = ' ';
+ left--;
+ }
+ }
+
+ return pargs;
+}
+
/* XXX We assume process may only have a single thread. But if there were more
* than one thread in the process that called exec, all other threads must be
* forcefully terminated. */
@@ -383,6 +405,9 @@ static int _do_execve(exec_args_t *args) {
if ((error = exec_args_copyout(args, &stack_top)))
goto fail;
+ kfree(M_STR, p->p_args);
+ p->p_args = pargs_create(args);
+
fdtab_onexec(p->p_fdtable);
/* Set up user context. */

MODIFY
sys/kern/proc.c
sys/kern/proc.c
@@ -83,6 +83,7 @@ proc_t proc0 = {
.p_pgrp = &pgrp0,
.p_state = PS_NORMAL,
.p_children = TAILQ_HEAD_INITIALIZER(proc0.p_children),
+ .p_args = NULL,
};
void init_proc0(void) {
@@ -452,8 +453,10 @@ proc_t *proc_create(thread_t *td, proc_t *parent) {
p->p_thread = td;
p->p_parent = parent;
- if (parent && parent->p_elfpath)
+ if (parent->p_elfpath)
p->p_elfpath = kstrndup(M_STR, parent->p_elfpath, PATH_MAX);
+ if (parent->p_args)
+ p->p_args = kstrndup(M_STR, parent->p_args, PARGS_MAX);
TAILQ_INIT(CHILDREN(p));
kitimer_init(p);
@@ -528,6 +531,7 @@ static void proc_reap(proc_t *p) {
TAILQ_REMOVE(CHILDREN(p->p_parent), p, p_child);
TAILQ_REMOVE(&zombie_list, p, p_zombie);
kfree(M_STR, p->p_elfpath);
+ kfree(M_TEMP, p->p_args);
TAILQ_REMOVE(PROC_HASH_CHAIN(p->p_pid), p, p_hash);
pool_free(P_PROC, p);
}

MODIFY
sys/tests/utest.c
sys/tests/utest.c
@@ -179,3 +179,5 @@ UTEST_ADD_SIMPLE(pty_simple);
UTEST_ADD_SIMPLE(tty_canon);
UTEST_ADD_SIMPLE(tty_echo);
UTEST_ADD_SIMPLE(tty_signals);
+
+UTEST_ADD_SIMPLE(procstat);

procstat: construct shortened process cmdline during exec (#1043)