Saturday, August 28, 2010

Process list with /proc -r

You may already have encountered this small issue, you are on a system and want to list processes but /proc has no read permission so you cannot list entries. Does it stop us? No, obviously we can iterate all possible pids and check for /proc/<pid>/ directory.

In shell we can get a simple process list with:
$ for P in $(seq 1 32168); do
  [ -d "/proc/$P" ] || continue
  echo "$P: $(cat /proc/$P/cmdline 2>/dev/null |sed 's/\x00/ /g')"
done
1: init [2]
1423: -bash
[...]
But what if we want more information, just like ps from procps gives us?

Well, simply download procps and patch it to iterate over all PIDs instead of reading entries of /proc with opendir/readdir. By the way, the maximum PID is given by the kernel at the following location:
$ cat /proc/sys/kernel/pid_max
32768

So here is my small patch, just a few lines:
$ diff -ur procps-3.2.8{,.mine}
--- procps-3.2.8/proc/readproc.c       2006-06-16 08:18:13.000000000 +0000
+++ procps-3.2.8.mine/proc/readproc.c  2010-08-28 04:25:03.000000000 +0000
@@ -27,6 +27,10 @@
 #include <sys/types.h>
 #include <sys/stat.h>

+/* global variables to read /proc entries blindly if proc is not readable */
+int current_pid;
+int max_pid;
+
 // sometimes it's easier to do this manually, w/o gcc helping
 #ifdef PROF
 extern void __cyg_profile_func_enter(void*,void*);
@@ -700,15 +704,25 @@
 static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
   static struct direct *ent;           /* dirent handle */
   char *restrict const path = PT->path;
-  for (;;) {
-    ent = readdir(PT->procfs);
-    if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
-    if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
+  if (PT->procfs) {
+    for (;;) {
+      ent = readdir(PT->procfs);
+      if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
+      if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
+    }
+    p->tgid = strtoul(ent->d_name, NULL, 10);
+    p->tid = p->tgid;
+    memcpy(path, "/proc/", 6);
+    strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
+  }
+  else {
+    for (;;) {
+      if (unlikely(++current_pid > max_pid)) return 0;
+      snprintf(path, PROCPATHLEN, "/proc/%i", current_pid);
+      if (unlikely(!access(path,F_OK))) break;
+    }
+    p->tid = p->tgid = current_pid;
   }
-  p->tgid = strtoul(ent->d_name, NULL, 10);
-  p->tid = p->tgid;
-  memcpy(path, "/proc/", 6);
-  strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
   return 1;
 }

@@ -859,7 +873,17 @@
       PT->finder = listed_nextpid;
     }else{
       PT->procfs = opendir("/proc");
-      if(!PT->procfs) return NULL;
+      if(!PT->procfs) {
+        FILE *fp;
+        char maxpid[32];
+        if ((fp = fopen("/proc/sys/kernel/pid_max", "r")) > 0) {
+          fread(maxpid,sizeof(maxpid),1,fp);
+          max_pid = atoi(maxpid);
+          fclose(fp);
+        }
+        else max_pid = 32768; /* assume it would be the case */
+        current_pid = 0; /* start at 0, first iteration will increment */
+      }
       PT->finder = simple_nextpid;
     }
     PT->flags = flags;

For testing and portability we compile ps as a static binary:
$ make SHARED=0 CFLAGS=-static ps/ps
[...]
$ file ps/ps
ps/ps: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked,
for GNU/Linux 2.6.8, not stripped
$ ps/ps
  PID TTY          TIME CMD
 3479 pts/8    00:00:03 bash
19787 pts/8    00:00:00 ps

When /proc is not readable, this ps now works! And good to know, iterating over all pids does not make things much slower.

3 comments:

  1. >And good to know, iterating over all pids does not make things much slower.

    Why do you say that?

    ReplyDelete
  2. With /proc +r:
    $ time ps
    [...]
    real 0m0.008s

    With /proc -r:
    $ time ps
    real 0m0.088s

    Still acceptable :)

    ReplyDelete
  3. Oh okie, for a moment I underestimated ps(1)'s code :)

    Thanks for taking the time to reply :)

    ReplyDelete