about summary refs log tree commit diff
path: root/src/libps
diff options
context:
space:
mode:
Diffstat (limited to 'src/libps')
-rw-r--r--src/libps/deps-lib/s6ps7
-rw-r--r--src/libps/s6ps_grcache.c67
-rw-r--r--src/libps/s6ps_otree.c100
-rw-r--r--src/libps/s6ps_pfield.c561
-rw-r--r--src/libps/s6ps_pwcache.c67
-rw-r--r--src/libps/s6ps_statparse.c186
-rw-r--r--src/libps/s6ps_ttycache.c138
-rw-r--r--src/libps/s6ps_wchan.c96
8 files changed, 1222 insertions, 0 deletions
diff --git a/src/libps/deps-lib/s6ps b/src/libps/deps-lib/s6ps
new file mode 100644
index 0000000..0f078a6
--- /dev/null
+++ b/src/libps/deps-lib/s6ps
@@ -0,0 +1,7 @@
+s6ps_grcache.o
+s6ps_otree.o
+s6ps_pfield.o
+s6ps_pwcache.o
+s6ps_statparse.o
+s6ps_ttycache.o
+s6ps_wchan.o
diff --git a/src/libps/s6ps_grcache.c b/src/libps/s6ps_grcache.c
new file mode 100644
index 0000000..189f5ae
--- /dev/null
+++ b/src/libps/s6ps_grcache.c
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#include <stdint.h>
+
+#include <grp.h>
+#include <errno.h>
+#include <skalibs/types.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+
+#include "s6-ps.h"
+
+static avltree grcache_tree = AVLTREE_ZERO ;
+static genalloc grcache_index = GENALLOC_ZERO ;
+
+int s6ps_grcache_init (void)
+{
+  avltree_init(&grcache_tree, 5, 3, 8, &left_dtok, &uint32_cmp, &grcache_index) ;
+  return 1 ;
+}
+
+void s6ps_grcache_finish (void)
+{
+  avltree_free(&grcache_tree) ;
+  genalloc_free(dius_t, &grcache_index) ;
+}
+
+int s6ps_grcache_lookup (stralloc *sa, gid_t gid)
+{
+  int wasnull = !satmp.s ;
+  dius_t d = { .left = (uint32_t)gid, .right = satmp.len } ;
+  uint32_t i ;
+  if (!avltree_search(&grcache_tree, &d.left, &i))
+  {
+    struct group *gr ;
+    unsigned int n = genalloc_len(dius_t, &grcache_index) ;
+    errno = 0 ;
+    gr = getgrgid(gid) ;
+    if (!gr)
+    {
+      if (errno) return 0 ;
+      if (!stralloc_readyplus(&satmp, UINT_FMT + 2)) return 0 ;
+      stralloc_catb(&satmp, "(", 1) ;
+      satmp.len += uint_fmt(satmp.s + satmp.len, gid) ;
+      stralloc_catb(&satmp, ")", 2) ;
+    }
+    else if (!stralloc_cats(&satmp, gr->gr_name) || !stralloc_0(&satmp)) return 0 ;
+    if (!genalloc_append(dius_t, &grcache_index, &d)) goto err ;
+    if (!avltree_insert(&grcache_tree, n))
+    {
+      genalloc_setlen(dius_t, &grcache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(dius_t, &grcache_index)[i].right) ;
+ err:
+  {
+    int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/libps/s6ps_otree.c b/src/libps/s6ps_otree.c
new file mode 100644
index 0000000..6ba8589
--- /dev/null
+++ b/src/libps/s6ps_otree.c
@@ -0,0 +1,100 @@
+/* ISC license. */
+
+#include <errno.h>
+
+#include <skalibs/avltreen.h>
+
+#include "s6-ps.h"
+
+typedef struct ptreeiter_s ptreeiter_t, *ptreeiter_t_ref ;
+struct ptreeiter_s
+{
+  unsigned int *childlist ;
+  unsigned int const *childindex ;
+  unsigned int const *ppindex ;
+  unsigned int *cpos ;
+} ;
+
+typedef struct pstuff_s pstuff_t, *pstuff_t_ref ;
+struct pstuff_s
+{
+  unsigned int *orderedlist ;
+  pscan_t *p ;
+  unsigned int const *childlist ;
+  unsigned int const *childindex ;
+  unsigned int const *nchild ;
+} ;
+
+static int fillchildlist (unsigned int i, unsigned int h, void *x)
+{
+  ptreeiter_t *pt = x ;
+  unsigned int j = pt->ppindex[i] ;
+  pt->childlist[pt->childindex[j] + pt->cpos[j]++] = i ;
+  (void)h ;
+  return 1 ;
+}
+
+static void fillo_tree_rec (pstuff_t *blah, unsigned int root, signed int h)
+{
+  static unsigned int j = 0 ;
+  unsigned int i = !blah->p[root].pid ;
+  if (blah->p[root].pid == 1) h = -1 ;
+  blah->p[root].height = (h > 0) ? h : 0 ;
+  blah->orderedlist[j++] = root ;
+  for (; i < blah->nchild[root] ; i++)
+    fillo_tree_rec(blah, blah->childlist[blah->childindex[root] + i], h+1) ;
+}
+
+ /*
+    Fills up orderedlist with the right indices to print a process tree.
+    O(n log n) time, O(n) space, all in the stack.
+ */
+
+void s6ps_otree (pscan_t *p, unsigned int n, avltreen *pidtree, unsigned int *orderedlist)
+{
+  unsigned int childlist[n] ;
+  unsigned int childindex[n] ;
+  unsigned int nchild[n] ;
+  unsigned int i = 0 ;
+  for (; i < n ; i++) nchild[i] = 0 ;
+
+ /* Compute the ppid tree */
+  for (i = 0 ; i < n ; i++)
+  {
+    uint32_t k ;
+    if (!avltreen_search(pidtree, &p[i].ppid, &k)) k = n-1 ;
+    orderedlist[i] = k ; /* using orderedlist as ppindex */
+    nchild[k]++ ;
+  }
+  {
+    unsigned int j = 0 ;
+    for (i = 0 ; i < n ; i++)
+    {
+      childindex[i] = j ;
+      j += nchild[i] ;
+    }
+  }
+
+ /* Fill the childlist by increasing pids so it is sorted */
+  {
+    unsigned int cpos[n] ;
+    ptreeiter_t blah = { .childlist = childlist, .childindex = childindex, .ppindex = orderedlist, .cpos = cpos } ;
+    for (i = 0 ; i < n ; i++) cpos[i] = 0 ;
+    avltreen_iter_nocancel(pidtree, avltreen_totalsize(pidtree), &fillchildlist, &blah) ;
+  }
+
+ /* If we have init, make it the last in the orphan list */
+  if (n > 1 && p[childlist[childindex[n-1]+1]].pid == 1)
+  {
+    unsigned int pos1 = childlist[childindex[n-1] + 1] ;
+    for (i = 2 ; i < nchild[n-1] ; i++)
+      childlist[childindex[n-1]+i-1] = childlist[childindex[n-1]+i] ;
+    childlist[childindex[n-1]+nchild[n-1]-1] = pos1 ;
+  } 
+
+ /* Finally, fill orderedlist by walking the childindex tree. */
+  {
+    pstuff_t blah = { .orderedlist = orderedlist, .p = p, .childlist = childlist, .childindex = childindex, .nchild = nchild } ;
+    fillo_tree_rec(&blah, n-1, -1) ;
+  }
+}
diff --git a/src/libps/s6ps_pfield.c b/src/libps/s6ps_pfield.c
new file mode 100644
index 0000000..9e53e50
--- /dev/null
+++ b/src/libps/s6ps_pfield.c
@@ -0,0 +1,561 @@
+/* ISC license. */
+
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/sysinfo.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/types.h>
+#include <skalibs/strerr.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbtime.h>
+#include <skalibs/stralloc.h>
+
+#include "s6-ps.h"
+
+static char const *const fieldheaders[PFIELD_PHAIL] =
+{
+  "PID",
+  "COMM",
+  "STAT",
+  "PPID",
+  "PGRP",
+  "SESSION",
+  "TTY",
+  "TPGID",
+  "UTIME",
+  "STIME",
+  "CUTIME",
+  "CSTIME",
+  "PRIO",
+  "NICE",
+  "THREADS",
+  "START",
+  "VSZ",
+  "RSS",
+  "RSSLIM",
+  "CPU",
+  "RTPRIO",
+  "RTPOLICY",
+  "USER",
+  "GROUP",
+  "%MEM",
+  "WCHAN",
+  "COMMAND",
+  "ENVIRONMENT",
+  "%CPU",
+  "TTIME",
+  "CTTIME",
+  "TSTART",
+  "C%CPU"
+} ;
+
+char const *const *s6ps_fieldheaders = fieldheaders ;
+
+static char const *const opttable[PFIELD_PHAIL] =
+{
+  "pid",
+  "comm",
+  "s",
+  "ppid",
+  "pgrp",
+  "sess",
+  "tty",
+  "tpgid",
+  "utime",
+  "stime",
+  "cutime",
+  "cstime",
+  "prio",
+  "nice",
+  "thcount",
+  "start",
+  "vsize",
+  "rss",
+  "rsslimit",
+  "psr",
+  "rtprio",
+  "policy",
+  "user",
+  "group",
+  "pmem",
+  "wchan",
+  "args",
+  "env",
+  "pcpu",
+  "ttime",
+  "cttime",
+  "tstart",
+  "cpcpu"
+} ;
+
+char const *const *s6ps_opttable = opttable ;
+
+static tain boottime = TAIN_EPOCH ;
+
+static int fmt_64 (pscan_t *p, size_t *pos, size_t *len, uint64_t u)
+{                                                          
+  if (!stralloc_readyplus(&p->data, UINT64_FMT)) return 0 ;
+  *pos = p->data.len ;
+  *len = uint64_fmt(p->data.s + *pos, u) ;
+  p->data.len += *len ;
+  return 1 ;
+}                                                          
+
+static int fmt_i (pscan_t *p, size_t *pos, size_t *len, int d)
+{
+  if (!stralloc_readyplus(&p->data, UINT32_FMT+1)) return 0 ;
+  *pos = p->data.len ;
+  *len = int_fmt(p->data.s + *pos, d) ;
+  p->data.len += *len ;
+  return 1 ;
+}
+
+static int fmt_pid (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->pid) ;
+}
+
+static int fmt_comm (pscan_t *p, size_t *pos, size_t *len)
+{
+  *pos = p->statlen ;
+  *len = p->commlen ;
+  return 1 ;
+}
+
+static int fmt_s (pscan_t *p, size_t *pos, size_t *len)
+{
+  if (!stralloc_readyplus(&p->data, 4)) return 0 ;
+  *pos = p->data.len ;
+  p->data.s[p->data.len++] = p->data.s[p->state] ;
+  if (p->pid == p->session) p->data.s[p->data.len++] = 's' ;
+  if (p->threads > 1) p->data.s[p->data.len++] = 'l' ;
+  if ((p->tpgid > 0) && ((unsigned int)p->tpgid == p->pgrp))
+    p->data.s[p->data.len++] = '+' ;
+  if (p->nice) p->data.s[p->data.len++] = (p->nice < 0) ? '<' : 'N' ;
+  
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_ppid (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->ppid) ;
+}
+
+static int fmt_pgrp (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->pgrp) ;
+}
+
+static int fmt_session (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->session) ;
+}
+
+static int fmt_ttynr(pscan_t *p, size_t *pos, size_t *len)
+{
+  if (p->ttynr)
+  {
+    size_t tmppos = p->data.len ;
+    if (!s6ps_ttycache_lookup(&p->data, p->ttynr)) return 0 ;
+    *pos = tmppos ;
+    *len = p->data.len - tmppos ;
+  }
+  else
+  {
+    if (!stralloc_catb(&p->data, "-", 1)) return 0 ;
+    *pos = p->data.len - 1 ;
+    *len = 1 ;
+  }
+  return 1 ;
+}
+
+static int fmt_tpgid (pscan_t *p, size_t *pos, size_t *len)
+{
+  return p->tpgid < 0 ? fmt_i(p, pos, len, -1) : fmt_64(p, pos, len, p->tpgid) ;
+}
+
+static unsigned int gethz (void)
+{
+  static unsigned int hz = 0 ;
+  if (!hz)
+  {            
+    long jiffies = sysconf(_SC_CLK_TCK) ;
+    if (jiffies < 1)
+    {             
+      char fmt[ULONG_FMT + 1] ;
+      fmt[long_fmt(fmt, jiffies)] = 0 ;
+      strerr_warnw3x("invalid _SC_CLK_TCK value (", fmt, "), using 100") ;
+      hz = 100 ;
+    }         
+    else hz = (unsigned int)jiffies ;
+  }
+  return hz ;
+}                
+
+int s6ps_compute_boottime (pscan_t *p, unsigned int mypos)
+{
+  if (!mypos--)
+  {
+    strerr_warnwu1x("compute boot time - using epoch") ;
+    return 0 ;
+  }
+  else
+  {
+    unsigned int hz = gethz() ;
+    tain offset = { .sec = { .x = p[mypos].start / hz }, .nano = (p[mypos].start % hz) * (1000000000 / hz) } ;
+    tain_sub(&boottime, &STAMP, &offset) ;
+    return 1 ;
+  }
+}
+
+static int fmt_jiffies (pscan_t *p, size_t *pos, size_t *len, uint64_t j)
+{
+  unsigned int hz = gethz() ;
+  uint32_t hrs, mins, secs, hfrac ;
+  if (!stralloc_readyplus(&p->data, UINT64_FMT + 13)) return 0 ;
+  hfrac = (j % hz) * 100 / hz ;
+  *pos = p->data.len ;
+  j /= hz ;
+  secs = j % 60 ; j /= 60 ;
+  mins = j % 60 ; j /= 60 ;
+  hrs = j % 24 ; j /= 24 ;
+  if (j)
+  {
+    p->data.len += uint64_fmt(p->data.s + p->data.len, j) ;
+    p->data.s[p->data.len++] = 'd' ;
+  }
+  if (j || hrs)
+  {
+    uint320_fmt(p->data.s + p->data.len, hrs, 2) ;
+    p->data.len += 2 ;
+    p->data.s[p->data.len++] = 'h' ;
+  }
+  if (j || hrs || mins)
+  {
+    uint320_fmt(p->data.s + p->data.len, mins, 2) ;
+    p->data.len += 2 ;
+    p->data.s[p->data.len++] = 'm' ;
+  }
+  uint320_fmt(p->data.s + p->data.len, secs, 2) ;
+  p->data.len += 2 ;
+  if (!j && !hrs && !mins)
+  {
+    p->data.s[p->data.len++] = '.' ;
+    uint320_fmt(p->data.s + p->data.len, hfrac, 2) ;
+    p->data.len += 2 ;
+  }
+  p->data.s[p->data.len++] = 's' ;
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_utime (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime) ;
+}
+
+static int fmt_stime (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_jiffies(p, pos, len, p->stime) ;
+}
+
+static int fmt_cutime (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->cutime) ;
+}
+
+static int fmt_cstime (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_jiffies(p, pos, len, p->stime + p->cstime) ;
+}
+
+static int fmt_prio (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_i(p, pos, len, p->prio) ;
+}
+
+static int fmt_nice (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_i(p, pos, len, p->nice) ;
+}
+
+static int fmt_threads (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->threads) ;
+}
+
+static int fmt_timedate (pscan_t *p, size_t *pos, size_t *len, struct tm const *tm)
+{
+  static struct tm nowtm = { .tm_year = 0 } ;
+  size_t tmplen ;
+  char *tmpstrf = "%F" ;
+  if (!nowtm.tm_year && !localtm_from_tai(&nowtm, tain_secp(&STAMP), 1)) return 0 ;
+  if (!stralloc_readyplus(&p->data, 20)) return 0 ;
+  if (tm->tm_year == nowtm.tm_year && tm->tm_yday == nowtm.tm_yday)
+    tmpstrf = "%T" ;
+  else if (tm->tm_year == nowtm.tm_year || (tm->tm_year+1 == nowtm.tm_year && (nowtm.tm_mon + 12 - tm->tm_mon) % 12 < 9))
+    tmpstrf = "%b%d %R" ;
+  tmplen = strftime(p->data.s + p->data.len, 20, tmpstrf, tm) ;
+  if (!tmplen) return 0 ;
+  *len = tmplen ;
+  *pos = p->data.len ;
+  p->data.len += tmplen ;
+  return 1 ;
+}
+
+static int fmt_start (pscan_t *p, size_t *pos, size_t *len)
+{
+  struct tm starttm ;
+  unsigned int hz = gethz() ;
+  tain blah = { .sec = { .x = p->start / hz }, .nano = (p->start % hz) * (1000000000 / hz) } ;
+  tain_add(&blah, &boottime, &blah) ;
+  if (!localtm_from_tai(&starttm, tain_secp(&blah), 1)) return 0 ;
+  return fmt_timedate(p, pos, len, &starttm) ;
+}
+
+static unsigned int getpgsz (void)
+{
+  static unsigned int pgsz = 0 ;
+  if (!pgsz)
+  {
+    long sz = sysconf(_SC_PAGESIZE) ;
+    if (sz < 1)
+    {
+      char fmt[ULONG_FMT + 1] ;
+      fmt[long_fmt(fmt, sz)] = 0 ;
+      strerr_warnw3x("invalid _SC_PAGESIZE value (", fmt, "), using 4096") ;
+      pgsz = 4096 ;
+    }
+    else pgsz = sz ;
+  }
+  return pgsz ;
+}
+
+static int fmt_vsize (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->vsize / 1024) ;
+}
+
+static int fmt_rss (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->rss * (getpgsz() / 1024)) ;
+}
+
+static int fmt_rsslim (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->rsslim / 1024) ;
+}
+
+static int fmt_cpuno (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->cpuno) ;
+}
+
+static int fmt_rtprio (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_64(p, pos, len, p->rtprio) ;
+}
+
+static int fmt_policy (pscan_t *p, size_t *pos, size_t *len)
+{
+  static char const *const policies[8] = { "NORMAL", "FIFO", "RR", "BATCH", "ISO", "IDLE", "UNKNOWN", "UNKNOWN" } ;
+  size_t tmppos = p->data.len ;
+  if (!stralloc_cats(&p->data, policies[p->policy & 7])) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static int fmt_user (pscan_t *p, size_t *pos, size_t *len)
+{
+  size_t tmppos = p->data.len ;
+  if (!s6ps_pwcache_lookup(&p->data, p->uid)) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static int fmt_group (pscan_t *p, size_t *pos, size_t *len)
+{
+  size_t tmppos = p->data.len ;
+  if (!s6ps_grcache_lookup(&p->data, p->gid)) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static struct sysinfo si = { .totalram = 0, .loads = { 0, 0, 0 } } ;
+
+static uint64_t gettotalmem (void)
+{
+  uint64_t totalmem = 0 ;
+  if (!si.totalram && (sysinfo(&si) < 0)) return 0 ;
+  totalmem = si.totalram ;
+  totalmem *= si.mem_unit ;
+  return totalmem ;
+}
+
+static int percent (stralloc *sa, unsigned int n, size_t *pos, size_t *len)
+{
+  if (!stralloc_readyplus(sa, UINT64_FMT+1)) return 0 ;
+  *pos = sa->len ;
+  sa->len += uint_fmt(sa->s + sa->len, n / 100) ;
+  sa->s[sa->len++] = '.' ;
+  uint0_fmt(sa->s + sa->len, n % 100, 2) ;
+  sa->len += 2 ;
+  *len = sa->len - *pos ;
+  return 1 ;
+}
+
+static int fmt_pmem (pscan_t *p, size_t *pos, size_t *len)
+{
+  uint64 l = gettotalmem() ;
+  return l ? percent(&p->data, p->rss * getpgsz() * 10000 / l, pos, len) : 0 ;
+}
+
+static int fmt_wchan (pscan_t *p, size_t *pos, size_t *len)
+{
+  size_t tmppos = p->data.len ;
+  if (!s6ps_wchan_lookup(&p->data, p->wchan)) return 0 ;
+  *len = p->data.len - tmppos ;
+  *pos = tmppos ;
+  return 1 ;
+}
+
+static int fmt_args (pscan_t *p, size_t *pos, size_t *len)
+{
+  if (!stralloc_readyplus(&p->data, (p->height << 2) + (p->cmdlen ? p->cmdlen : p->commlen + (p->data.s[p->state] == 'Z' ? 11 : 3))))
+    return 0 ;
+  *pos = p->data.len ;
+  if (p->height)
+  {
+    unsigned int i = 0 ;
+    for (; i < 4 * (unsigned int)p->height - 3 ; i++)
+      p->data.s[p->data.len + i] = ' ' ;
+    memcpy(p->data.s + p->data.len + 4 * p->height - 3, "\\_ ", 3) ;
+    p->data.len += p->height << 2 ;
+  }
+  if (p->cmdlen)
+  {
+    char const *r = p->data.s + p->statlen + p->commlen ;
+    char *w = p->data.s + p->data.len ;
+    size_t i = p->cmdlen ;
+    while (i--)
+    {
+      char c = *r++ ;
+      *w++ = c ? c : ' ' ;
+    }
+    p->data.len += p->cmdlen ;
+  }
+  else if (p->data.s[p->state] == 'Z')
+  {
+    stralloc_catb(&p->data, p->data.s + uint32_fmt(0, p->pid) + 2, p->commlen) ;
+    stralloc_catb(&p->data, " <defunct>", 10) ;
+  }
+  else
+    stralloc_catb(&p->data, p->data.s + uint32_fmt(0, p->pid) + 1, p->commlen+2) ;
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_env (pscan_t *p, size_t *pos, size_t *len)
+{
+  size_t i = 0 ;
+  if (!p->envlen)
+  {
+    if (!stralloc_catb(&p->data, "*", 1)) return 0 ;
+    *pos = p->data.len - 1 ;
+    *len = 1 ;
+    return 1 ;
+  }
+  *pos = p->statlen + p->commlen + p->cmdlen ;
+  *len = p->envlen ;
+  for (; i < *len ; i++)
+    if (!p->data.s[*pos + i]) p->data.s[*pos + i] = ' ' ;
+  return 1 ;
+}
+
+static uint64_t gettotalj (uint64_t j)
+{
+  tain totaltime ;
+  unsigned int hz = gethz() ;
+  tain_sub(&totaltime, &STAMP, &boottime) ;
+  j = totaltime.sec.x * hz + totaltime.nano / (1000000000 / hz) - j ;
+  if (!j) j = 1 ;
+  return j ;
+}
+
+static int fmt_pcpu (pscan_t *p, size_t *pos, size_t *len)
+{
+  return percent(&p->data, 10000 * (p->utime + p->stime) / gettotalj(p->start), pos, len) ;
+}
+
+static int fmt_ttime (pscan_t *p, size_t *pos, size_t *len) 
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->stime) ;
+}
+
+static int fmt_cttime (pscan_t *p, size_t *pos, size_t *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->stime + p->cutime + p->cstime) ;
+}
+
+static int fmt_tstart (pscan_t *p, size_t *pos, size_t *len)
+{
+  unsigned int hz = gethz() ;
+  tain blah = { .sec = { .x = p->start / hz }, .nano = (p->start % hz) * (1000000000 / hz) } ;
+  if (!stralloc_readyplus(&p->data, TIMESTAMP)) return 0 ;
+  tain_add(&blah, &boottime, &blah) ;
+  *pos = p->data.len ;
+  *len = timestamp_fmt(p->data.s + p->data.len, &blah) ;
+  p->data.len += *len ;
+  return 1 ;
+}
+
+static int fmt_cpcpu (pscan_t *p, size_t *pos, size_t *len)
+{
+  return percent(&p->data, 10000 * (p->utime + p->stime + p->cutime + p->cstime) / gettotalj(p->start), pos, len) ;
+}
+
+static pfieldfmt_func_ref pfieldfmt_table[PFIELD_PHAIL] =
+{
+  &fmt_pid,
+  &fmt_comm,
+  &fmt_s,
+  &fmt_ppid,
+  &fmt_pgrp,
+  &fmt_session,
+  &fmt_ttynr,
+  &fmt_tpgid,
+  &fmt_utime,
+  &fmt_stime,
+  &fmt_cutime,
+  &fmt_cstime,
+  &fmt_prio,
+  &fmt_nice,
+  &fmt_threads,
+  &fmt_start,
+  &fmt_vsize,
+  &fmt_rss,
+  &fmt_rsslim,
+  &fmt_cpuno,
+  &fmt_rtprio,
+  &fmt_policy,
+  &fmt_user,
+  &fmt_group,
+  &fmt_pmem,
+  &fmt_wchan,
+  &fmt_args,
+  &fmt_env,
+  &fmt_pcpu,
+  &fmt_ttime,
+  &fmt_cttime,
+  &fmt_tstart,
+  &fmt_cpcpu
+} ;
+
+pfieldfmt_func_ref *s6ps_pfield_fmt = pfieldfmt_table ;
diff --git a/src/libps/s6ps_pwcache.c b/src/libps/s6ps_pwcache.c
new file mode 100644
index 0000000..ce4caa8
--- /dev/null
+++ b/src/libps/s6ps_pwcache.c
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <pwd.h>
+#include <errno.h>
+
+#include <skalibs/types.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+#include "s6-ps.h"
+
+static avltree pwcache_tree = AVLTREE_ZERO ;
+static genalloc pwcache_index = GENALLOC_ZERO ;
+
+int s6ps_pwcache_init (void)
+{
+  avltree_init(&pwcache_tree, 5, 3, 8, &left_dtok, &uint32_cmp, &pwcache_index) ;
+  return 1 ;
+}
+
+void s6ps_pwcache_finish (void)
+{
+  avltree_free(&pwcache_tree) ;
+  genalloc_free(dius_t, &pwcache_index) ;
+}
+
+int s6ps_pwcache_lookup (stralloc *sa, uid_t uid)
+{
+  int wasnull = !satmp.s ;
+  dius_t d = { .left = (uint32_t)uid, .right = satmp.len } ;
+  uint32_t i ;
+  if (!avltree_search(&pwcache_tree, &d.left, &i))
+  {
+    struct passwd *pw ;
+    unsigned int n = genalloc_len(dius_t, &pwcache_index) ;
+    errno = 0 ;
+    pw = getpwuid(uid) ;
+    if (!pw)
+    {
+      if (errno) return 0 ;
+      if (!stralloc_readyplus(&satmp, UINT_FMT + 2)) return 0 ;
+      stralloc_catb(&satmp, "(", 1) ;
+      satmp.len += uint_fmt(satmp.s + satmp.len, uid) ;
+      stralloc_catb(&satmp, ")", 2) ;
+    }
+    else if (!stralloc_cats(&satmp, pw->pw_name) || !stralloc_0(&satmp)) return 0 ;
+    if (!genalloc_append(dius_t, &pwcache_index, &d)) goto err ;
+    if (!avltree_insert(&pwcache_tree, n))
+    {
+      genalloc_setlen(dius_t, &pwcache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(dius_t, &pwcache_index)[i].right) ;
+ err:
+  {
+    int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/libps/s6ps_statparse.c b/src/libps/s6ps_statparse.c
new file mode 100644
index 0000000..ea46d70
--- /dev/null
+++ b/src/libps/s6ps_statparse.c
@@ -0,0 +1,186 @@
+/* ISC license. */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/types.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/tai.h>
+
+#include "s6-ps.h"
+
+
+ /*
+    going to great lengths to avoid scanf(), but all this code
+    is still smaller than scanf (no floating point parsing etc.)
+ */
+
+#define STATVARS 49
+
+typedef size_t pscan_func (char const *, void *) ;
+typedef pscan_func *pscan_func_ref ;
+
+static size_t f64 (char const *s, void *u64)
+{
+  uint64_t *u = u64 ;
+  return uint64_scan(s, u) ;
+}
+
+#define DEFUNU(name, type) \
+static size_t name (char const *s, void *p) \
+{ \
+  uint64_t u ; \
+  size_t len = uint64_scan(s, &u) ; \
+  *(type *)p = u ; \
+  return len ; \
+} \
+
+#define DEFUNS(name, type) \
+static size_t name (char const *s, void *p) \
+{ \
+  int64_t d ; \
+  size_t len = int64_scan(s, &d) ; \
+  *(type *)p = d ; \
+  return len ; \
+} \
+
+DEFUNS(fint, int)
+DEFUNS(fpid, pid_t)
+DEFUNU(fdev, dev_t)
+
+static pscan_func_ref scanfuncs[STATVARS] =
+{
+  &fpid, /* ppid */
+  &fpid, /* pgrp */
+  &fpid, /* session */
+  &fdev, /* tty_nr */
+  &fpid, /* tpgid */
+  &f64, /* flags */
+  &f64, /* minflt */
+  &f64, /* cminflt */
+  &f64, /* majflt */
+  &f64, /* cmajflt */
+  &f64, /* utime */
+  &f64, /* stime */
+  &f64, /* cutime */
+  &f64, /* cstime */
+  &fint, /* priority */
+  &fint, /* nice */
+  &f64, /* num_threads */
+  &f64, /* itrealvalue */
+  &f64, /* starttime */
+  &f64, /* vsize */
+  &f64, /* rss */
+  &f64, /* rsslim */
+  &f64, /* startcode */
+  &f64, /* endcode */
+  &f64, /* startstack */
+  &f64, /* kstkesp */
+  &f64, /* kstkeip */
+  &f64, /* signal */
+  &f64, /* blocked */
+  &f64, /* sigignore */
+  &f64, /* sigcatch */
+  &f64, /* wchan */
+  &f64, /* nswap */
+  &f64, /* cnswap */
+  &fint, /* exit_signal */
+  &f64, /* processor */
+  &f64, /* rt_priority */
+  &f64, /* policy */
+  &f64, /* delayacct_blkio_ticks */
+  &f64, /* guest_time */
+  &f64, /* cguest_time */
+  &f64, /* start_data */
+  &f64, /* end_data */
+  &f64, /* start_brk */
+  &f64, /* arg_start */
+  &f64, /* arg_end */
+  &f64, /* env_start */
+  &f64, /* env_end */
+  &fint /* exit_code */
+} ;
+
+int s6ps_statparse (pscan_t *p)
+{
+  uint64_t dummy64 ;
+  int dummyint ;
+  size_t pos = 0 ;
+  void *scanresults[STATVARS] =
+  {
+    &p->ppid,
+    &p->pgrp,
+    &p->session,
+    &p->ttynr,
+    &p->tpgid,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &p->utime,
+    &p->stime,
+    &p->cutime,
+    &p->cstime,
+    &p->prio,
+    &p->nice,
+    &p->threads,
+    &dummy64,
+    &p->start,
+    &p->vsize,
+    &p->rss,
+    &p->rsslim,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &p->wchan,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &p->cpuno,
+    &p->rtprio,
+    &p->policy,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummyint
+  } ;
+  unsigned int i = 0 ;
+
+  if (!p->statlen) return 0 ;
+  pos = uint64_scan(p->data.s, &dummy64) ;
+  if (!pos) return 0 ;
+  if (dummy64 != p->pid) return 0 ;
+  if (pos + 5 + p->commlen > p->statlen) return 0 ;
+  if (p->data.s[pos++] != ' ') return 0 ;
+  if (p->data.s[pos++] != '(') return 0 ;
+  pos += p->commlen ;
+  if (p->data.s[pos++] != ')') return 0 ;
+  if (p->data.s[pos++] != ' ') return 0 ;
+  p->state = pos++ ;
+  for (; i < STATVARS ; i++)
+  {
+    size_t w ;
+    if (pos + 1 > p->statlen) return 0 ;
+    if (p->data.s[pos++] != ' ') return 0 ;
+    w = (*scanfuncs[i])(p->data.s + pos, scanresults[i]) ;
+    if (!w) return 0 ;
+    pos += w ;
+  }
+  return 1 ;
+}
diff --git a/src/libps/s6ps_ttycache.c b/src/libps/s6ps_ttycache.c
new file mode 100644
index 0000000..27282e7
--- /dev/null
+++ b/src/libps/s6ps_ttycache.c
@@ -0,0 +1,138 @@
+/* ISC license. */
+
+#include <string.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <errno.h>
+
+#include <skalibs/types.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+
+#include "s6-ps.h"
+
+static avltree ttycache_tree = AVLTREE_ZERO ;
+static genalloc ttycache_index = GENALLOC_ZERO ;
+
+int s6ps_ttycache_init (void)
+{
+  avltree_init(&ttycache_tree, 5, 3, 8, &left_dtok, &uint32_cmp, &ttycache_index) ;
+  return 1 ;
+}
+
+void s6ps_ttycache_finish (void)
+{
+  avltree_free(&ttycache_tree) ;
+  genalloc_free(dius_t, &ttycache_index) ;
+}
+
+static int check (char const *s, dev_t ttynr)
+{
+  struct stat st ;
+  if (stat(s, &st) < 0) return 0 ;
+  return S_ISCHR(st.st_mode) && (st.st_rdev == ttynr) ;
+}
+
+
+ /* No blind scanning of all /dev or /sys/devices, kthx */
+
+static int ttyguess (stralloc *sa, dev_t ttynr)
+{
+  unsigned int maj = major(ttynr), min = minor(ttynr) ;
+
+ /* Try /dev/tty? and /dev/pts/? */
+  if (maj == 4 && min < 64)
+  {
+    char tmp[11] = "/dev/tty" ;
+    tmp[uint_fmt(tmp+8, min)] = 0 ;
+    if (check(tmp, ttynr)) return stralloc_cats(sa, tmp+5) && stralloc_0(sa) ;
+  }
+  else if (maj >= 136 && maj < 144)
+  {
+    unsigned int n = ((maj - 136) << 20) | min ;
+    char tmp[9 + UINT_FMT] = "/dev/pts/" ;
+    tmp[9 + uint_fmt(tmp+9, n)] = 0 ;
+    if (check(tmp, ttynr)) return stralloc_cats(sa, tmp+5) && stralloc_0(sa) ;
+  }
+
+ /* Use /sys/dev/char/maj:min if it exists */
+  {
+    int fd ;
+    size_t pos = 14 ;
+    char path[23 + 2 * UINT_FMT] = "/sys/dev/char/" ;
+    pos += uint_fmt(path + pos, maj) ;
+    path[pos++] = ':' ;
+    pos += uint_fmt(path + pos, min) ;
+    memcpy(path + pos, "/uevent", 8) ;
+    fd = open_read(path) ;
+    if (fd >= 0)
+    {
+      char buf[4097] ;
+      buffer b = BUFFER_INIT(&fd_readv, fd, buf, 4097) ;
+      size_t start = satmp.len ;
+      int r ;
+      for (;;)
+      {
+        satmp.len = start ;
+        r = skagetln(&b, &satmp, '\n') ;
+        if (r <= 0) break ;
+        if ((satmp.len - start) > 8 && !memcmp(satmp.s + start, "DEVNAME=", 8)) break ;
+      }
+      fd_close(fd) ;
+      if (r > 0)
+      {
+        satmp.s[satmp.len - 1] = 0 ;
+        satmp.len = start ;
+        memcpy(satmp.s + start + 3, "/dev/", 5) ;
+        if (check(satmp.s + start + 3, ttynr))
+          return stralloc_cats(sa, satmp.s + start + 8) && stralloc_0(sa) ;
+      }
+    }
+  }
+
+ /* Fallback: print explicit maj:min */
+  {
+    size_t pos = 1 ;
+    char tmp[3 + 2 * UINT_FMT] = "(" ;
+    pos += uint_fmt(tmp + pos, maj) ;
+    tmp[pos++] = ':' ;
+    pos += uint_fmt(tmp + pos, min) ;
+    tmp[pos++] = ')' ;
+    tmp[pos++] = 0 ;
+    return stralloc_catb(sa, tmp, pos) ;
+  }
+}
+
+int s6ps_ttycache_lookup (stralloc *sa, dev_t ttynr)
+{
+  int wasnull = !satmp.s ;
+  dius_t d = { .left = (uint32_t)ttynr, .right = satmp.len } ;
+  uint32_t i ;
+  if (!avltree_search(&ttycache_tree, &d.left, &i))
+  {
+    unsigned int n = genalloc_len(dius_t, &ttycache_index) ;
+    if (!ttyguess(&satmp, ttynr)) return 0 ;
+    if (!genalloc_append(dius_t, &ttycache_index, &d)) goto err ;
+    if (!avltree_insert(&ttycache_tree, n))
+    {
+      genalloc_setlen(dius_t, &ttycache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(dius_t, &ttycache_index)[i].right) ;
+ err:
+  {
+    int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/libps/s6ps_wchan.c b/src/libps/s6ps_wchan.c
new file mode 100644
index 0000000..9261693
--- /dev/null
+++ b/src/libps/s6ps_wchan.c
@@ -0,0 +1,96 @@
+/* ISC license. */
+
+#include <string.h>
+#include <sys/utsname.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+
+#include "s6-ps.h"
+
+static stralloc sysmap = STRALLOC_ZERO ;
+static genalloc ind = GENALLOC_ZERO ;
+
+int s6ps_wchan_init (char const *file)
+{
+  if (file)
+  {
+    if (!openslurpclose(&sysmap, file)) return 0 ;
+  }
+  else
+  {
+    char *files[3] = { "/proc/kallsyms", 0, "/boot/System.map" } ;
+    struct utsname uts ;
+    size_t n ;
+    if (uname(&uts) < 0) return 0 ;
+    n = strlen(uts.release) ;
+    {
+      char buf[18 + n] ;
+      unsigned int i = 0 ;
+      memcpy(buf, "/boot/System.map", 16) ;
+      buf[16] = '-' ;
+      memcpy(buf + 17, uts.release, n + 1) ;
+      files[1] = buf ;
+      for (; i < 3 ; i++)
+        if (openslurpclose(&sysmap, files[i])) break ;
+      if (i >= 3) return 0 ;
+    }
+  }
+  {
+    size_t i = 0 ;
+    if (!genalloc_append(size_t, &ind, &i)) goto err2 ;
+    for (i = 1 ; i <= sysmap.len ; i++)
+      if (sysmap.s[i-1] == '\n')
+        if (!genalloc_append(size_t, &ind, &i)) goto err ;
+  }
+  return 1 ;
+ err:
+  genalloc_free(size_t, &ind) ;
+ err2:
+  stralloc_free(&sysmap) ;
+  return 0 ;
+}
+
+void s6ps_wchan_finish (void)
+{
+  genalloc_free(size_t, &ind) ;
+  stralloc_free(&sysmap) ;
+}
+
+static inline size_t lookup (uint64_t addr, size_t *i)
+{
+  size_t low = 0, mid, high = genalloc_len(size_t, &ind), len ;
+  for (;;)
+  {
+    uint64_t cur ;
+    mid = (low + high) >> 1 ;
+    len = uint64_xscan(sysmap.s + genalloc_s(size_t, &ind)[mid], &cur) ;
+    if (!len) return 0 ;
+    if (cur == addr) break ;
+    if (mid == low) return 0 ;
+    if (addr < cur) high = mid ; else low = mid ;
+  }
+  *i = mid ;
+  return len ;
+}
+
+int s6ps_wchan_lookup (stralloc *sa, uint64_t addr)
+{
+  if (addr == (sizeof(void *) == 8 ? 0xffffffffffffffffULL : 0xffffffffUL))
+    return stralloc_catb(sa, "*", 1) ;
+  if (!addr) return stralloc_catb(sa, "-", 1) ;
+  if (sysmap.len)
+  {
+    size_t i, pos, len = lookup(addr, &i) ;
+    if (!len) return stralloc_catb(sa, "?", 1) ;
+    pos = genalloc_s(size_t, &ind)[i] + len + 3 ;
+    return stralloc_catb(sa, sysmap.s + pos, genalloc_s(size_t, &ind)[i+1] - 1 - pos) ;
+  }
+  if (!stralloc_readyplus(sa, UINT64_FMT + 3)) return 0 ;
+  stralloc_catb(sa, "(0x", 3) ;
+  sa->len += uint64_fmt(sa->s + sa->len, addr) ;
+  stralloc_catb(sa, ")", 1) ;
+  return 1 ;
+}