Subject: ps /proc-based lookup enhancement
To: None <tech-userlevel@netbsd.org>
From: Jaromir Dolecek <dolecek@ics.muni.cz>
List: tech-userlevel
Date: 10/07/1999 16:36:05
Hi,
I finally got to clean up some old patches I had. Basically, they
alter the /proc-based lookup so that more information is exstracted
from the procfs, namely process start time, whether it's session
leader and program's arguments (from /proc/<pid>/cmdline).  It also
falls back to procfs when kvm_openfiles() (called on the very
beginning) fails.

If nobody would complain, I'll commit the changes shortly.

Jaromir

XXXXX
Index: extern.h
===================================================================
RCS file: /cvsroot/basesrc/bin/ps/extern.h,v
retrieving revision 1.13
diff -u -p -r1.13 extern.h
--- extern.h	1999/03/26 22:36:02	1.13
+++ extern.h	1999/10/07 14:22:09
@@ -68,7 +68,8 @@ void	 pmem __P((KINFO *, VARENT *));
 void	 pnice __P((KINFO *, VARENT *));
 void	 pri __P((KINFO *, VARENT *));
 void	 printheader __P((void));
-struct kinfo_proc *	 procfs_getprocs __P((int, int, int*));
+KINFO	*procfs_getprocs __P((int, int, int*));
+char	**procfs_getargv __P((const struct kinfo_proc *, int));
 void	 pvar __P((KINFO *, VARENT *));
 void	 rssize __P((KINFO *, VARENT *));
 void	 runame __P((KINFO *, VARENT *));
Index: print.c
===================================================================
RCS file: /cvsroot/basesrc/bin/ps/print.c,v
retrieving revision 1.43
diff -u -p -r1.43 print.c
--- print.c	1999/07/23 08:56:14	1.43
+++ print.c	1999/10/07 14:22:10
@@ -68,7 +68,7 @@ __RCSID("$NetBSD: print.c,v 1.43 1999/07
 #include "ps.h"
 
 extern kvm_t *kd;
-extern int needenv, needcomm, commandonly;
+extern int needenv, needcomm, commandonly, dontuseprocfs;
 
 static char *cmdpart __P((char *));
 static void  printval __P((char *, VAR *));
@@ -144,6 +144,7 @@ command(ki, ve)
 {
 	VAR *v;
 	int left;
+	static int use_procfs=0;
 	char **argv, **p, *name;
 
 	v = ve->var;
@@ -156,7 +157,7 @@ command(ki, ve)
 			left = v->width;
 	} else
 		left = -1;
-	if (needenv) {
+	if (needenv && kd) {
 		argv = kvm_getenvv(kd, ki->ki_p, termwidth);
 		if ((p = argv) != NULL) {
 			while (*p) {
@@ -169,7 +170,13 @@ command(ki, ve)
 	if (needcomm) {
 		name = KI_PROC(ki)->p_comm;
 		if (!commandonly) {
-			argv = kvm_getargv(kd, ki->ki_p, termwidth);
+			argv = NULL;
+			if (kd && !use_procfs)
+				argv = kvm_getargv(kd, ki->ki_p, termwidth);
+			if (argv == NULL && !dontuseprocfs) {
+				argv = procfs_getargv(ki->ki_p, termwidth);
+				use_procfs = 1;
+			}
 			if ((p = argv) != NULL) {
 				while (*p) {
 					fmt_puts(*p, &left);
@@ -182,6 +189,10 @@ command(ki, ve)
 				fmt_puts(name, &left);
 				fmt_putc(')', &left);
 			}
+			if (use_procfs) {
+				free(argv[0]);
+				free(argv);
+			}
 		} else {
 			fmt_puts(name, &left);
 		}
@@ -548,7 +559,7 @@ getpcpu(k)
 	static int failure;
 
 	if (!nlistread)
-		failure = donlist();
+		failure = (kd) ? donlist() : 1;
 	if (failure)
 		return (0.0);
 
@@ -587,7 +598,7 @@ getpmem(k)
 	int szptudot;
 
 	if (!nlistread)
-		failure = donlist();
+		failure = (kd) ? donlist() : 1;
 	if (failure)
 		return (0.0);
 
Index: procfs_ops.c
===================================================================
RCS file: /cvsroot/basesrc/bin/ps/procfs_ops.c,v
retrieving revision 1.5
diff -u -p -r1.5 procfs_ops.c
--- procfs_ops.c	1999/05/09 19:23:38	1.5
+++ procfs_ops.c	1999/10/07 14:22:10
@@ -42,6 +42,7 @@
 #include <sys/sysctl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/resource.h> /* for rusage */
 
 #include <fcntl.h>
 #include <dirent.h>
@@ -52,6 +53,8 @@
 #include <err.h>
 #include <kvm.h>
 
+#include "ps.h"
+
 /* Assume that no process status file will ever be larger than this. */
 #define STATUS_SIZE	8192
 
@@ -70,8 +73,8 @@
 }
 
 static int verify_procfs_fd __P((int, const char *));
-static int parsekinfo __P((const char *, struct kinfo_proc *));
-struct kinfo_proc *procfs_getprocs __P((int, int, int *));
+static int parsekinfo __P((const char *, KINFO *));
+KINFO *procfs_getprocs __P((int, int, int *));
 
 static int
 verify_procfs_fd(fd, path)
@@ -97,15 +100,16 @@ verify_procfs_fd(fd, path)
 }
 
 static int
-parsekinfo(path, kp)
+parsekinfo(path, ki)
 	const char *path;
-	struct kinfo_proc *kp;
+	KINFO *ki;
 {
 	char    fullpath[MAXPATHLEN];
-	int     dirfd, fd, nbytes, devmajor, devminor;
+	int     fd, nbytes, devmajor, devminor;
 	struct timeval usertime, systime, starttime;
 	char    buff[STATUS_SIZE];
 	char    flagstr[256];
+	struct kinfo_proc *kp = ki->ki_p;
 
 	/*
 	 * Verify that /proc/<pid> is a procfs file (and that no one has
@@ -131,7 +135,6 @@ parsekinfo(path, kp)
 		 * Don't print warning, as the process may have died since our
 		 * scan of the directory entries.
 		 */
-		close(fd);
 		return -1;	/* Process may no longer exist. */
 	}
 
@@ -174,6 +177,14 @@ parsekinfo(path, kp)
 	kp->kp_proc.p_rtime.tv_sec = usertime.tv_sec + systime.tv_sec;
 	kp->kp_proc.p_rtime.tv_usec = usertime.tv_usec + systime.tv_usec;
 
+	/* if starttime.[u]sec is != -1, it's in-memory process */
+	if (starttime.tv_sec != -1 && starttime.tv_usec != -1) {
+		kp->kp_proc.p_flag |= P_INMEM;
+		ki->ki_u.u_valid = 1;
+		ki->ki_u.u_start.tv_sec = starttime.tv_sec;
+		ki->ki_u.u_start.tv_usec = starttime.tv_usec;
+	}
+
 	/*
 	 * CPU time isn't shown unless the ki_u.u_valid flag is set.
 	 * Unfortunately, we don't have access to that here.
@@ -183,10 +194,14 @@ parsekinfo(path, kp)
 	if (strstr(flagstr, "ctty"))
 		kp->kp_proc.p_flag |= P_CONTROLT;
 
+	/* Set the flag for whether or not this process is session leader */
+	if (strstr(flagstr, "sldr"))
+		kp->kp_eproc.e_flag |= EPROC_SLEADER;
+
 	return 0;
 }
 
-struct kinfo_proc *
+KINFO *
 procfs_getprocs(op, arg, cnt)
 	int     op, arg;
 	int    *cnt;
@@ -195,6 +210,7 @@ procfs_getprocs(op, arg, cnt)
 	int     procdirfd, nbytes, knum = 0, maxknum = 0;
 	char   *direntbuff;
 	struct kinfo_proc *kp;
+	KINFO	*ki;
 	int     mib[4];
 	size_t  len;
 	struct statfs procfsstat;
@@ -250,8 +266,8 @@ procfs_getprocs(op, arg, cnt)
 		err(1, "sysctl to fetch maxproc");
 	maxknum *= 2;		/* Double it, to be really paranoid.  */
 
-	kp = (struct kinfo_proc *) malloc(sizeof(struct kinfo_proc) * maxknum);
-	memset(kp, 0, sizeof(struct kinfo_proc) * maxknum);
+	kp = (struct kinfo_proc *) calloc(sizeof(struct kinfo_proc)*maxknum, 1);
+	ki = (KINFO *) calloc(sizeof(KINFO)*maxknum, 1);
 
 	/* Read in a batch of entries at a time.  */
 	while ((knum < maxknum) &&
@@ -268,7 +284,8 @@ procfs_getprocs(op, arg, cnt)
 				continue;
 			if (strcmp(dp->d_name, "curproc") == 0)
 				continue;
-			if (parsekinfo(dp->d_name, &kp[knum]) != 0)
+			ki[knum].ki_p = &kp[knum];
+			if (parsekinfo(dp->d_name, &ki[knum]) != 0)
 				continue;
 			/*
 			 * Now check some of the flags.  If the newest entry
@@ -323,5 +340,80 @@ procfs_getprocs(op, arg, cnt)
 
 	*cnt = knum;
 	close(procdirfd);
-	return kp;
+	/* free unused memory */
+	if (knum < maxknum) {
+		kp = realloc(kp, sizeof(*kp) * knum);
+		ki = realloc(ki, sizeof(*ki) * knum);
+		for(; knum >= 0; knum--)
+			ki[knum].ki_p = &kp[knum];
+	}
+	return ki;
+}
+
+/*
+ * return process arguments, possibly ones used when exec()ing
+ * the process; return the array as two element array, first is
+ * argv[0], second element is all the other args separated by spaces
+ */
+char **
+procfs_getargv(kp, nchr)
+	const struct kinfo_proc *kp;
+	int nchr;
+{
+	char    fullpath[MAXPATHLEN], *buf, *name, *args, **argv;
+	int fd, num;
+	ssize_t len;
+	size_t idx;
+
+	/* Open /proc/<pid>/cmdline, and parse it into the argv array */
+	snprintf(fullpath, MAXPATHLEN, "/proc/%d/cmdline", kp->kp_proc.p_pid);
+	fd = open(fullpath, O_RDONLY, 0);
+	if (fd == -1 || verify_procfs_fd(fd, fullpath)) {
+		/*
+		 * Don't print warning, as the process may have died since our
+		 * scan of the directory entries.
+		 */
+		return NULL;	/* Process may no longer exist. */
+	}
+
+	buf = (char *)malloc(nchr+1);
+	len = read(fd, buf, nchr);
+	close(fd);
+	if (len == -1) {
+		warnx("procfs_getargv");
+		return NULL;
+	}
+	
+	num = 1;
+	args = NULL;
+	name = buf;
+	/* substitute any \0's with space */
+	for(idx=0; idx < len; idx++) {
+		if (buf[idx] == '\0') {
+			if (!args)
+				args = &buf[idx+1];
+			else
+				buf[idx] = ' ';
+			num++;
+		}
+	}
+	buf[len] = '\0'; /* end the string */
+
+	/* if the name is the same as the p_comm, just enclosed
+	 * in parentheses, remove the parentheses */
+	if (num == 1 && name[0] == '(' && name[len-1] == ')'
+		&& strncmp(name+1, kp->kp_proc.p_comm, len-2) == 0)
+	{
+		len -= 2;
+		strncpy(name, name+1, len);
+		name[len] = '\0';
+	}
+	
+	argv = (char **) malloc(3*sizeof(char *));
+	argv[0] = name;
+	argv[1] = args;
+	argv[2] = NULL;
+
+	return argv;
 }
+
Index: ps.c
===================================================================
RCS file: /cvsroot/basesrc/bin/ps/ps.c,v
retrieving revision 1.28
diff -u -p -r1.28 ps.c
--- ps.c	1999/03/27 21:38:08	1.28
+++ ps.c	1999/10/07 14:22:10
@@ -305,8 +305,14 @@ main(argc, argv)
 		(void)setegid(egid);
 
 	kd = kvm_openfiles(nlistf, memf, swapf, O_RDONLY, errbuf);
-	if (kd == 0)
-		errx(1, "%s", errbuf);
+	if (kd == 0) {
+		if (dontuseprocfs)
+			errx(1, "%s", errbuf);
+		else {
+			warnx("kvm_openfiles: %s", errbuf);
+			fprintf(stderr, "ps: falling back to /proc-based lookup\n");
+		}
+	}
 
 	if (nlistf == NULL && memf == NULL && swapf == NULL)
 		(void)setgid(getgid());
@@ -322,8 +328,16 @@ main(argc, argv)
 	/*
 	 * select procs
 	 */
-	if ((kp = kvm_getprocs(kd, what, flag, &nentries)) == 0)
+	if (kd && (kp = kvm_getprocs(kd, what, flag, &nentries)) != 0)
 	{
+		if ((kinfo = malloc(nentries * sizeof(*kinfo))) == NULL)
+			err(1, "%s", "");
+		for (i = nentries; --i >= 0; ++kp) {
+			kinfo[i].ki_p = kp;
+			if (needuser)
+				saveuser(&kinfo[i]);
+		}
+	} else {
 		/*  If/when the /proc-based code is ripped out
 		 *  again, make sure all references to the -K
 		 *  option are also pulled (getopt(), usage(),
@@ -339,14 +353,15 @@ main(argc, argv)
 		 *  mounted) to grab as much information as we can.  
 		 *  The guts of emulating kvm_getprocs() is in
 		 *  the file procfs_ops.c.  */
-		warnx("%s.", kvm_geterr(kd));
+		if (kd)
+			warnx("%s.", kvm_geterr(kd));
 		if (dontuseprocfs) {
 			exit(1);
 		}
 		/*  procfs_getprocs supports all but the
 		 *  KERN_PROC_RUID flag.  */
-		kp=procfs_getprocs(what, flag, &nentries);
-		if (kp == 0) {
+		kinfo=procfs_getprocs(what, flag, &nentries);
+		if (kinfo == 0) {
 		  errx(1, "fallback /proc-based lookup also failed.  %s",
 				  "Giving up...");
 		}
@@ -354,13 +369,7 @@ main(argc, argv)
 		    "Warning:  /proc does not provide ",
 		    "valid data for all fields.\n");
 	}
-	if ((kinfo = malloc(nentries * sizeof(*kinfo))) == NULL)
-		err(1, "%s", "");
-	for (i = nentries; --i >= 0; ++kp) {
-		kinfo[i].ki_p = kp;
-		if (needuser)
-			saveuser(&kinfo[i]);
-	}
+
 	/*
 	 * print header
 	 */
@@ -425,7 +434,7 @@ saveuser(ki)
 	struct usave *usp;
 
 	usp = &ki->ki_u;
-	if (kvm_read(kd, (u_long)&KI_PROC(ki)->p_addr->u_stats,
+	if (kd && kvm_read(kd, (u_long)&KI_PROC(ki)->p_addr->u_stats,
 	    (char *)&pstats, sizeof(pstats)) == sizeof(pstats)) {
 		/*
 		 * The u-area might be swapped out, and we can't get
XXXXX

-- 
Jaromir Dolecek <jdolecek@NetBSD.org>      http://www.ics.muni.cz/~dolecek/
"The only way how to get rid temptation is to yield to it." -- Oscar Wilde