]> granicus.if.org Git - procps-ng/commitdiff
library: implement task/thread support via the new api
authorJim Warner <james.warner@comcast.net>
Wed, 19 Aug 2015 05:00:00 +0000 (00:00 -0500)
committerCraig Small <csmall@enc.com.au>
Sun, 23 Aug 2015 11:05:06 +0000 (21:05 +1000)
This commit is the culmination of efforts to modernize
the library api. It should be treated as a first blush
attempt, especially since I have absolutely no library
design experience. But I did have a very strong desire
to lessen the new library's impact on the top program.

Under this new api, a 'stack' is the equivalent of the
old proc_t. It can be seen as a variable length record
whose contents & order is under complete user control.

That initial stack/record configuration is established
at procps_pids_new() time and will probably serve most
program needs. But, a dynamic & demanding program like
top will later change a stack via procps_pids_reset().

For programs like top & ps, procps_pids_reap() will be
the function that will retrieve all tasks and threads.

Any program that needs to filter / select only certain
processes or users have available other functions that
can be used: procps_pids_stacks_alloc, fill & dealloc.

This implementation attempts to maximize that existing
proven libprocps code base. As we gain more experience
such actual code can be migrated into the pids.c file.

Signed-off-by: Jim Warner <james.warner@comcast.net>
Makefile.am
proc/libprocps.sym
proc/pids.c [new file with mode: 0644]
proc/pids.h [new file with mode: 0644]

index 342a8e3c021e3cff0db2802cbbd27bee2ba99a19..fb98c077d04ddc622991675c40fcceabd5b3adcb 100644 (file)
@@ -163,6 +163,8 @@ proc_libprocps_la_SOURCES = \
        proc/procps-private.h \
        proc/meminfo.c \
        proc/meminfo.h \
+       proc/pids.c \
+       proc/pids.h \
        proc/procps.h \
        proc/pwcache.c \
        proc/pwcache.h \
@@ -189,6 +191,7 @@ proc_libprocps_la_include_HEADERS = \
        proc/devname.h \
        proc/diskstat.h \
        proc/escape.h \
+       proc/pids.h \
        proc/procps.h \
        proc/pwcache.h \
        proc/readproc.h \
index b4d19065a6d60130e0456b0d22275749dad59ee0..b2e1f0c7b2db4ae362e8b3e942574e38fc8ffe6e 100644 (file)
@@ -42,6 +42,15 @@ global:
        procps_meminfo_getstack;
        procps_meminfo_stack_fill;
        procps_meminfo_stack_alloc;
+       procps_pids_new;
+       procps_pids_reap;
+       procps_pids_ref;
+       procps_pids_reset;
+       procps_pids_stacks_alloc;
+       procps_pids_stacks_dealloc;
+       procps_pids_stacks_fill;
+       procps_pids_stacks_sort;
+       procps_pids_unref;
        procps_slabinfo_new;
        procps_slabinfo_read;
        procps_slabinfo_ref;
diff --git a/proc/pids.c b/proc/pids.c
new file mode 100644 (file)
index 0000000..e50f8f6
--- /dev/null
@@ -0,0 +1,1283 @@
+/*
+ * pids.c - task/thread/process related declarations for libproc
+ *
+ * Copyright (C) 1998-2005 Albert Cahalan
+ * Copyright (C) 2015 Craig Small <csmall@enc.com.au>
+ * Copyright (C) 2015 Jim Warner <james.warner@comcast.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+//efine _GNU_SOURCE             // for qsort_r
+#define OOMEM_ENABLE            // we will not disturb the api
+#define WITH_SYSTEMD            // with optional functionality
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <proc/pids.h>
+#include "procps-private.h"
+
+#include "readproc.h"                  // and two headers for bridged
+#include "wchan.h"                     // support (temporary include)
+
+//#define UNREF_RPTHASH                // report on hashing, at uref time
+
+#define FILL_ID_MAX  255               // upper limit for pid/uid fills
+
+enum pids_item PROCPS_PIDS_logical_end  = PROCPS_PIDS_noop + 1;
+enum pids_item PROCPS_PIDS_physical_end = PROCPS_PIDS_noop + 2;
+
+
+struct stacks_extent {                 // callers see a pids_stacks struct
+    struct pids_stack **stacks;
+    int ext_numitems;                  // includes 'physical_end' delimiter
+    int ext_numstacks;
+    struct stacks_extent *next;
+};
+
+struct procps_pidsinfo {
+    int refcount;
+    int maxitems;                      // includes 'physical_end' delimiter
+    int curitems;                      // includes 'logical_end' delimiter
+    enum pids_item *items;             // includes 'phy/log_end' delimiters
+    struct pids_stack **anchor;        // reapable stacks (consolidated extents)
+    int alloc_total;                   // number of above pointers allocated
+    int inuse_total;                   // number of above pointers occupied
+    struct stacks_extent *extents;     // anchor for all allocated extents
+    int history_yes;                   // need historical data
+    struct history_info *hist;         // pointer to historical support data
+    int dirty_stacks;                  // extents need dynamic storage clean
+    unsigned pgs2k_shift;              // to convert some proc vaules
+    unsigned flags;                    // the old library PROC_FILL flagss
+    PROCTAB *PT;                       // the old library essential interface
+    struct pids_counts counts;         // counts for 'procps_pids_stacks_fill'
+    struct pids_reap reap;             // counts + stacks for 'procps_pids_reap'
+};
+
+
+// ___ Results 'Set' Support ||||||||||||||||||||||||||||||||||||||||||||||||||
+
+        /* note: the vast majority of these 'set' functions have no need for
+                 the procps_pidsinfo structure, but it's being passed to all
+                 bacause of the CVT_set requirement & for future flexibility */
+
+#define setNAME(e) set_results_ ## e
+#define setDECL(e) static void setNAME(e) \
+    (struct procps_pidsinfo *I, struct pids_result *R, proc_t *P)
+
+        // value the addr member
+#define ADR_set(e,t,x) setDECL(e) { \
+    (void)I; R->result. t = (void *)P-> x; }
+        // convert pages to kib
+#define CVT_set(e,t,x) setDECL(e) { \
+    R->result. t = (unsigned long)(P-> x) << I -> pgs2k_shift; }
+        // strdup of a static char array
+#define DUP_set(e,x) setDECL(e) { \
+    (void)I; R->result.str = strdup(P-> x); }
+        // regular assignment copy
+#define REG_set(e,t,x) setDECL(e) { \
+    (void)I; R->result. t = P-> x; }
+        // take ownership of a regular char* string
+#define STR_set(e,x) setDECL(e) { \
+    (void)I; R->result.str = P-> x; P-> x = NULL; }
+        // take ownership of a vectorized single string
+#define VEC_set(e,x) setDECL(e) { \
+    (void)I; R->result.str = (const char *)*P-> x; P-> x = NULL; }
+
+ADR_set(ADDR_END_CODE,    addr,    end_code)
+ADR_set(ADDR_KSTK_EIP,    addr,    kstk_eip)
+ADR_set(ADDR_KSTK_ESP,    addr,    kstk_esp)
+ADR_set(ADDR_START_CODE,  addr,    start_code)
+ADR_set(ADDR_START_STACK, addr,    start_stack)
+REG_set(ALARM,            sl_int,  alarm)
+VEC_set(CGROUP,                    cgroup)
+STR_set(CMD,                       cmd)
+VEC_set(CMDLINE,                   cmdline)
+VEC_set(ENVIRON,                   environ)
+REG_set(EXIT_SIGNAL,      s_int,   exit_signal)
+REG_set(FLAGS,            ul_int,  flags)
+REG_set(FLT_MAJ,          ul_int,  maj_flt)
+REG_set(FLT_MAJ_C,        ul_int,  cmaj_flt)
+REG_set(FLT_MAJ_DELTA,    ul_int,  maj_delta)
+REG_set(FLT_MIN,          ul_int,  min_flt)
+REG_set(FLT_MIN_C,        ul_int,  cmin_flt)
+REG_set(FLT_MIN_DELTA,    ul_int,  min_delta)
+REG_set(ID_EGID,          u_int,   egid)
+REG_set(ID_EGROUP,        str,     egroup)
+REG_set(ID_EUID,          u_int,   euid)
+REG_set(ID_EUSER,         str,     euser)
+REG_set(ID_FGID,          u_int,   fgid)
+REG_set(ID_FGROUP,        str,     fgroup)
+REG_set(ID_FUID,          u_int,   fuid)
+REG_set(ID_FUSER,         str,     fuser)
+REG_set(ID_PGRP,          s_int,   pgrp)
+REG_set(ID_PID,           s_int,   tid)
+REG_set(ID_PPID,          s_int,   ppid)
+REG_set(ID_RGID,          u_int,   rgid)
+REG_set(ID_RGROUP,        str,     rgroup)
+REG_set(ID_RUID,          u_int,   ruid)
+REG_set(ID_RUSER,         str,     ruser)
+REG_set(ID_SESSION,       s_int,   session)
+REG_set(ID_SGID,          u_int,   sgid)
+REG_set(ID_SGROUP,        str,     sgroup)
+REG_set(ID_SUID,          u_int,   suid)
+REG_set(ID_SUSER,         str,     suser)
+REG_set(ID_TGID,          s_int,   tgid)
+REG_set(ID_TPGID,         s_int,   tpgid)
+REG_set(LXCNAME,          str,     lxcname)
+REG_set(MEM_CODE,         sl_int,  trs)
+CVT_set(MEM_CODE_KIB,     ul_int,  trs)
+REG_set(MEM_DATA,         sl_int,  drs)
+CVT_set(MEM_DATA_KIB,     ul_int,  drs)
+REG_set(MEM_DT,           sl_int,  dt)
+REG_set(MEM_LRS,          sl_int,  lrs)
+REG_set(MEM_RES,          sl_int,  resident)
+CVT_set(MEM_RES_KIB,      ul_int,  resident)
+REG_set(MEM_SHR,          sl_int,  share)
+CVT_set(MEM_SHR_KIB,      ul_int,  share)
+REG_set(MEM_VIRT,         sl_int,  size)
+CVT_set(MEM_VIRT_KIB,     ul_int,  size)
+REG_set(NICE,             sl_int,  nice)
+REG_set(NLWP,             s_int,   nlwp)
+REG_set(NS_IPC,           ul_int,  ns[0])
+REG_set(NS_MNT,           ul_int,  ns[1])
+REG_set(NS_NET,           ul_int,  ns[2])
+REG_set(NS_PID,           ul_int,  ns[3])
+REG_set(NS_USER,          ul_int,  ns[4])
+REG_set(NS_UTS,           ul_int,  ns[5])
+REG_set(OOM_ADJ,          s_int,   oom_adj)
+REG_set(OOM_SCORE,        s_int,   oom_score)
+REG_set(PRIORITY,         s_int,   priority)
+REG_set(PROCESSOR,        u_int,   processor)
+REG_set(RSS,              sl_int,  rss)
+REG_set(RSS_RLIM,         ul_int,  rss_rlim)
+REG_set(RTPRIO,           ul_int,  rtprio)
+REG_set(SCHED_CLASS,      ul_int,  sched)
+STR_set(SD_MACH,                   sd_mach)
+STR_set(SD_OUID,                   sd_ouid)
+STR_set(SD_SEAT,                   sd_seat)
+STR_set(SD_SESS,                   sd_sess)
+STR_set(SD_SLICE,                  sd_slice)
+STR_set(SD_UNIT,                   sd_unit)
+STR_set(SD_UUNIT,                  sd_uunit)
+DUP_set(SIGBLOCKED,                blocked)
+DUP_set(SIGCATCH,                  sigcatch)
+DUP_set(SIGIGNORE,                 sigignore)
+DUP_set(SIGNALS,                   signal)
+DUP_set(SIGPENDING,                _sigpnd)
+REG_set(STATE,            s_ch,    state)
+STR_set(SUPGIDS,                   supgid)
+STR_set(SUPGROUPS,                 supgrp)
+setDECL(TICS_ALL)     { (void)I; R->result.ull_int = P->utime + P->stime; }
+setDECL(TICS_ALL_C)   { (void)I; R->result.ull_int = P->utime + P->stime + P->cutime + P->cstime; }
+REG_set(TICS_DELTA,       u_int,   pcpu)
+REG_set(TICS_SYSTEM,      ull_int, stime)
+REG_set(TICS_SYSTEM_C,    ull_int, cstime)
+REG_set(TICS_USER,        ull_int, utime)
+REG_set(TICS_USER_C,      ull_int, cutime)
+REG_set(TIME_START,       ull_int, start_time)
+REG_set(TTY,              s_int,   tty)
+REG_set(VM_DATA,          ul_int,  vm_data)
+REG_set(VM_EXE,           ul_int,  vm_exe)
+REG_set(VM_LIB,           ul_int,  vm_lib)
+REG_set(VM_LOCK,          ul_int,  vm_lock)
+REG_set(VM_RSS,           ul_int,  vm_rss)
+REG_set(VM_SIZE,          ul_int,  vm_size)
+REG_set(VM_STACK,         ul_int,  vm_stack)
+REG_set(VM_SWAP,          ul_int,  vm_swap)
+setDECL(VM_USED)      { (void)I; R->result.ul_int = P->vm_swap + P->vm_rss; }
+REG_set(VSIZE_PGS,        ul_int,  vsize)
+ADR_set(WCHAN_ADDR,       addr,    wchan)
+setDECL(WCHAN_NAME)   { (void)I; R->result.str = strdup(lookup_wchan(P->tid)); }
+setDECL(noop)         { (void)I; (void)R; (void)P; return; }
+setDECL(logical_end)  { (void)I; (void)R; (void)P; return; }
+setDECL(physical_end) { (void)I; (void)R; (void)P; return; }
+
+#undef setDECL
+#undef ADR_set
+#undef CVT_set
+#undef DUP_set
+#undef REG_set
+#undef STR_set
+#undef VEC_set
+
+
+// ___ Sorting Support ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+struct sort_parms {
+    int offset;
+    enum pids_sort_order order;
+};
+
+#define srtNAME(e) sort_results_ ## e
+
+#define NUM_srt(T) static int srtNAME(T) ( \
+  const struct pids_stack **A, const struct pids_stack **B, struct sort_parms *P) { \
+    const struct pids_result *a = (*A)->head + P->offset; \
+    const struct pids_result *b = (*B)->head + P->offset; \
+    return P->order * (b->result. T - a->result. T); }
+
+#define REG_srt(T) static int srtNAME(T) ( \
+  const struct pids_stack **A, const struct pids_stack **B, struct sort_parms *P) { \
+    const struct pids_result *a = (*A)->head + P->offset; \
+    const struct pids_result *b = (*B)->head + P->offset; \
+    if ( a->result. T > b->result. T ) return P->order > 0 ? -1 :  1; \
+    if ( a->result. T < b->result. T ) return P->order > 0 ?  1 : -1; \
+    return 0; }
+
+NUM_srt(s_ch)
+NUM_srt(s_int)
+NUM_srt(sl_int)
+
+REG_srt(u_int)
+REG_srt(ul_int)
+REG_srt(ull_int)
+REG_srt(addr)
+
+static int srtNAME(str) (
+  const struct pids_stack **A, const struct pids_stack **B, struct sort_parms *P) {
+    const struct pids_result *a = (*A)->head + P->offset;
+    const struct pids_result *b = (*B)->head + P->offset;
+    return P->order * strcoll(b->result.str, a->result.str);
+}
+
+static int srtNAME(noop) (
+  const struct pids_stack **A, const struct pids_stack **B, enum pids_item *O) {
+    (void)A; (void)B; (void)O;
+    return 0;
+}
+
+#undef NUM_srt
+#undef REG_srt
+
+
+// ___ Controlling Table ||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+#define f_arg      PROC_FILLARG                                 // we don't use
+   // from either 'stat' or 'status' (preferred)
+#define f_either   PROC_SPARE_1
+#define f_env      PROC_FILLENV                                 // we don't use
+#define f_grp      PROC_FILLGRP
+#define f_lxc      PROC_FILL_LXC
+#define f_ns       PROC_FILLNS
+#define f_oom      PROC_FILLOOM
+#define f_stat     PROC_FILLSTAT
+#define f_statm    PROC_FILLMEM
+#define f_status   PROC_FILLSTATUS
+#define f_systemd  PROC_FILLSYSTEMD
+#define f_usr      PROC_FILLUSR
+   // remaining are compound flags, yielding single string
+#define x_cgroup   PROC_EDITCGRPCVT | PROC_FILLCGROUP           // just 1 str
+#define x_cmdline  PROC_EDITCMDLCVT | PROC_FILLARG              // just 1 str
+#define x_environ  PROC_EDITENVRCVT | PROC_FILLENV              // just 1 str
+#define x_ogroup   PROC_FILLSTATUS  | PROC_FILLGRP
+#define x_ouser    PROC_FILLSTATUS  | PROC_FILLUSR
+#define x_supgrp   PROC_FILLSTATUS  | PROC_FILLSUPGRP
+
+typedef void (*SET_t)(struct procps_pidsinfo *, struct pids_result *, proc_t *);
+typedef int  (*QSR_t)(const void *, const void *, void *);
+
+#define RS(e) (SET_t)setNAME(e)
+#define QS(t) (QSR_t)srtNAME(t)
+
+
+        /*
+         * Need it be said?
+         * This table must be kept in the exact same order as
+         * those 'enum pids_item' guys ! */
+static struct {
+    SET_t    function;            // the actual result setting routine
+    unsigned oldflags;            // PROC_FILLxxxx flags for this item
+    int      mustfree;            // free is needed for string storage
+    QSR_t    callback;            // sort cmp func for a specific type
+    int      makehist;            // a result requires history support
+} Item_table[] = {
+/*    function               oldflags    mustfree  callback      makehist
+      ---------------------  ----------  --------  ------------  -------- */
+    { RS(ADDR_END_CODE),     f_stat,     0,        QS(addr),     0       },
+    { RS(ADDR_KSTK_EIP),     f_stat,     0,        QS(addr),     0       },
+    { RS(ADDR_KSTK_ESP),     f_stat,     0,        QS(addr),     0       },
+    { RS(ADDR_START_CODE),   f_stat,     0,        QS(addr),     0       },
+    { RS(ADDR_START_STACK),  f_stat,     0,        QS(addr),     0       },
+    { RS(ALARM),             f_stat,     0,        QS(sl_int),   0       },
+    { RS(CGROUP),            x_cgroup,   -1,       QS(str),      0       },
+    { RS(CMD),               f_either,   -1,       QS(str),      0       },
+    { RS(CMDLINE),           x_cmdline,  -1,       QS(str),      0       },
+    { RS(ENVIRON),           x_environ,  -1,       QS(str),      0       },
+    { RS(EXIT_SIGNAL),       f_stat,     0,        QS(s_int),    0       },
+    { RS(FLAGS),             f_stat,     0,        QS(ul_int),   0       },
+    { RS(FLT_MAJ),           f_stat,     0,        QS(ul_int),   0       },
+    { RS(FLT_MAJ_C),         f_stat,     0,        QS(ul_int),   0       },
+    { RS(FLT_MAJ_DELTA),     f_stat,     0,        QS(ul_int),   -1      },
+    { RS(FLT_MIN),           f_stat,     0,        QS(ul_int),   0       },
+    { RS(FLT_MIN_C),         f_stat,     0,        QS(ul_int),   0       },
+    { RS(FLT_MIN_DELTA),     f_stat,     0,        QS(ul_int),   -1      },
+    { RS(ID_EGID),           0,          0,        QS(u_int),    0       },
+    { RS(ID_EGROUP),         f_grp,      0,        QS(str),      0       },
+    { RS(ID_EUID),           0,          0,        QS(u_int),    0       },
+    { RS(ID_EUSER),          f_usr,      0,        QS(str),      0       },
+    { RS(ID_FGID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_FGROUP),         x_ogroup,   0,        QS(str),      0       },
+    { RS(ID_FUID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_FUSER),          x_ouser,    0,        QS(str),      0       },
+    { RS(ID_PGRP),           f_stat,     0,        QS(s_int),    0       },
+    { RS(ID_PID),            0,          0,        QS(s_int),    0       },
+    { RS(ID_PPID),           f_either,   0,        QS(s_int),    0       },
+    { RS(ID_RGID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_RGROUP),         x_ogroup,   0,        QS(str),      0       },
+    { RS(ID_RUID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_RUSER),          x_ouser,    0,        QS(str),      0       },
+    { RS(ID_SESSION),        f_stat,     0,        QS(s_int),    0       },
+    { RS(ID_SGID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_SGROUP),         x_ogroup,   0,        QS(str),      0       },
+    { RS(ID_SUID),           f_status,   0,        QS(u_int),    0       },
+    { RS(ID_SUSER),          x_ouser,    0,        QS(str),      0       },
+    { RS(ID_TGID),           f_status,   0,        QS(s_int),    0       },
+    { RS(ID_TPGID),          f_stat,     0,        QS(s_int),    0       },
+    { RS(LXCNAME),           f_lxc,      0,        QS(str),      0       },
+    { RS(MEM_CODE),          f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_CODE_KIB),      f_statm,    0,        QS(ul_int),   0       },
+    { RS(MEM_DATA),          f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_DATA_KIB),      f_statm,    0,        QS(ul_int),   0       },
+    { RS(MEM_DT),            f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_LRS),           f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_RES),           f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_RES_KIB),       f_statm,    0,        QS(ul_int),   0       },
+    { RS(MEM_SHR),           f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_SHR_KIB),       f_statm,    0,        QS(ul_int),   0       },
+    { RS(MEM_VIRT),          f_statm,    0,        QS(sl_int),   0       },
+    { RS(MEM_VIRT_KIB),      f_statm,    0,        QS(ul_int),   0       },
+    { RS(NICE),              f_stat,     0,        QS(sl_int),   0       },
+    { RS(NLWP),              f_either,   0,        QS(s_int),    0       },
+    { RS(NS_IPC),            f_ns,       0,        QS(ul_int),   0       },
+    { RS(NS_MNT),            f_ns,       0,        QS(ul_int),   0       },
+    { RS(NS_NET),            f_ns,       0,        QS(ul_int),   0       },
+    { RS(NS_PID),            f_ns,       0,        QS(ul_int),   0       },
+    { RS(NS_USER),           f_ns,       0,        QS(ul_int),   0       },
+    { RS(NS_UTS),            f_ns,       0,        QS(ul_int),   0       },
+    { RS(OOM_ADJ),           f_oom,      0,        QS(s_int),    0       },
+    { RS(OOM_SCORE),         f_oom,      0,        QS(s_int),    0       },
+    { RS(PRIORITY),          f_stat,     0,        QS(s_int),    0       },
+    { RS(PROCESSOR),         f_stat,     0,        QS(u_int),    0       },
+    { RS(RSS),               f_stat,     0,        QS(sl_int),   0       },
+    { RS(RSS_RLIM),          f_stat,     0,        QS(ul_int),   0       },
+    { RS(RTPRIO),            f_stat,     0,        QS(ul_int),   0       },
+    { RS(SCHED_CLASS),       f_stat,     0,        QS(ul_int),   0       },
+    { RS(SD_MACH),           f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_OUID),           f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_SEAT),           f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_SESS),           f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_SLICE),          f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_UNIT),           f_systemd,  -1,       QS(str),      0       },
+    { RS(SD_UUNIT),          f_systemd,  -1,       QS(str),      0       },
+    { RS(SIGBLOCKED),        f_status,   -1,       QS(str),      0       },
+    { RS(SIGCATCH),          f_status,   -1,       QS(str),      0       },
+    { RS(SIGIGNORE),         f_status,   -1,       QS(str),      0       },
+    { RS(SIGNALS),           f_status,   -1,       QS(str),      0       },
+    { RS(SIGPENDING),        f_status,   -1,       QS(str),      0       },
+    { RS(STATE),             f_either,   0,        QS(s_ch),     0       },
+    { RS(SUPGIDS),           f_status,   -1,       QS(str),      0       },
+    { RS(SUPGROUPS),         x_supgrp,   -1,       QS(str),      0       },
+    { RS(TICS_ALL),          f_stat,     0,        QS(ull_int),  0       },
+    { RS(TICS_ALL_C),        f_stat,     0,        QS(ull_int),  0       },
+    { RS(TICS_DELTA),        f_stat,     0,        QS(u_int),    -1      },
+    { RS(TICS_SYSTEM),       f_stat,     0,        QS(ull_int),  0       },
+    { RS(TICS_SYSTEM_C),     f_stat,     0,        QS(ull_int),  0       },
+    { RS(TICS_USER),         f_stat,     0,        QS(ull_int),  0       },
+    { RS(TICS_USER_C),       f_stat,     0,        QS(ull_int),  0       },
+    { RS(TIME_START),        f_stat,     0,        QS(ull_int),  0       },
+    { RS(TTY),               f_stat,     0,        QS(s_int),    0       },
+    { RS(VM_DATA),           f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_EXE),            f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_LIB),            f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_LOCK),           f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_RSS),            f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_SIZE),           f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_STACK),          f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_SWAP),           f_status,   0,        QS(ul_int),   0       },
+    { RS(VM_USED),           f_status,   0,        QS(ul_int),   0       },
+    { RS(VSIZE_PGS),         f_stat,     0,        QS(ul_int),   0       },
+    { RS(WCHAN_ADDR),        f_stat,     0,        QS(addr),     0       },
+    { RS(WCHAN_NAME),        0,          -1,       QS(str),      0       },
+    { RS(noop),              0,          0,        QS(noop),     0       },
+    { RS(logical_end),       0,          0,        QS(noop),     0       },
+    { RS(physical_end),      0,          0,        QS(noop),     0       }
+};
+
+#undef RS
+#undef QS
+#undef srtNAME
+#undef setNAME
+
+#undef f_arg
+//#undef f_either                 // needed later
+#undef f_env
+#undef f_grp
+#undef f_lxc
+#undef f_ns
+#undef f_oom
+//#undef f_stat                   // needed later
+#undef f_statm
+//#undef f_status                 // needed later
+#undef f_systemd
+#undef f_usr
+#undef x_cgroup
+#undef x_cmdline
+#undef x_environ
+#undef x_ogroup
+#undef x_ouser
+#undef x_supgrp
+
+
+// ___ History Support Private Functions ||||||||||||||||||||||||||||||||||||||
+//   ( stolen from top when he wasn't looking ) -------------------------------
+
+#define HHASH_SIZE  1024
+#define _HASH_PID_(K) (K & (HHASH_SIZE - 1))
+
+#define Hr(x)  info->hist->x           // 'hist ref', minimize stolen impact
+
+typedef unsigned long long TIC_t;
+
+typedef struct HST_t {
+    TIC_t tics;                        // last frame's tics count
+    unsigned long maj, min;            // last frame's maj/min_flt counts
+    int pid;                           // record 'key'
+    int lnk;                           // next on hash chain
+} HST_t;
+
+
+struct history_info {
+    int    num_tasks;                  // used as index (tasks tallied)
+    int    HHist_siz;                  // max number of HST_t structs
+    HST_t *PHist_sav;                  // alternating 'old/new' HST_t anchors
+    HST_t *PHist_new;
+    int    HHash_one [HHASH_SIZE];     // the actual hash tables
+    int    HHash_two [HHASH_SIZE];     // (accessed via PHash_sav/PHash_new)
+    int    HHash_nul [HHASH_SIZE];     // an 'empty' hash table image
+    int   *PHash_sav;                  // alternating 'old/new' hash tables
+    int   *PHash_new;                  // (aka. the 'one/two' actual tables)
+};
+
+
+static void config_history (
+        struct procps_pidsinfo *info)
+{
+    int i;
+
+    for (i = 0; i < HHASH_SIZE; i++)   // make the 'empty' table image
+        Hr(HHash_nul[i]) = -1;
+    memcpy(Hr(HHash_one), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+    memcpy(Hr(HHash_two), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+    Hr(PHash_sav) = Hr(HHash_one);     // alternating 'old/new' hash tables
+    Hr(PHash_new) = Hr(HHash_two);
+} // end: config_history
+
+
+static inline HST_t *histget (
+        struct procps_pidsinfo *info,
+        int pid)
+{
+    int V = Hr(PHash_sav[_HASH_PID_(pid)]);
+
+    while (-1 < V) {
+        if (Hr(PHist_sav[V].pid) == pid)
+            return &Hr(PHist_sav[V]);
+        V = Hr(PHist_sav[V].lnk); }
+    return NULL;
+} // end: histget
+
+
+static inline void histput (
+        struct procps_pidsinfo *info,
+        unsigned this)
+{
+    int V = _HASH_PID_(Hr(PHist_new[this].pid));
+
+    Hr(PHist_new[this].lnk) = Hr(PHash_new[V]);
+    Hr(PHash_new[V] = this);
+} // end: histput
+
+#undef _HASH_PID_
+
+
+static int make_hist (
+        struct procps_pidsinfo *info,
+        proc_t *p)
+{
+ #define slot info->hist->num_tasks
+    TIC_t tics;
+    HST_t *h;
+
+    if (slot + 1 >= Hr(HHist_siz)) {
+        Hr(HHist_siz) = Hr(HHist_siz) * 5 / 4 + 100;
+        Hr(PHist_sav) = realloc(Hr(PHist_sav), sizeof(HST_t) * Hr(HHist_siz));
+        Hr(PHist_new) = realloc(Hr(PHist_new), sizeof(HST_t) * Hr(HHist_siz));
+        if (!Hr(PHist_sav || !Hr(PHist_new)))
+            return -ENOMEM;
+    }
+    Hr(PHist_new[slot].pid)  = p->tid;
+    Hr(PHist_new[slot].tics) = tics = (p->utime + p->stime);
+    Hr(PHist_new[slot].maj)  = p->maj_flt;
+    Hr(PHist_new[slot].min)  = p->min_flt;
+
+    histput(info, slot);
+
+    if ((h = histget(info, p->tid))) {
+        tics -= h->tics;
+        p->maj_delta = p->maj_flt - h->maj;
+        p->min_delta = p->min_flt - h->min;
+    }
+    p->pcpu = tics;
+
+    slot++;
+    return 0;
+ #undef slot
+} // end: make_hist
+
+
+static inline void toggle_history (
+        struct procps_pidsinfo *info)
+{
+    void *v;
+
+    v = Hr(PHist_sav);
+    Hr(PHist_sav) = Hr(PHist_new);
+    Hr(PHist_new) = v;
+
+    v = Hr(PHash_sav);
+    Hr(PHash_sav) = Hr(PHash_new);
+    Hr(PHash_new) = v;
+    memcpy(Hr(PHash_new), Hr(HHash_nul), sizeof(Hr(HHash_nul)));
+
+    info->hist->num_tasks = 0;
+} // end: toggle_history
+
+
+#ifdef UNREF_RPTHASH
+static void unref_rpthash (
+        struct procps_pidsinfo *info)
+{
+    int i, j, pop, total_occupied, maxdepth, maxdepth_sav, numdepth
+        , cross_foot, sz = HHASH_SIZE * (unsigned)sizeof(int);
+    int depths[HHASH_SIZE];
+
+    for (i = 0, total_occupied = 0, maxdepth = 0; i < HHASH_SIZE; i++) {
+        int V = Hr(PHash_new[i]);
+        j = 0;
+        if (-1 < V) {
+            ++total_occupied;
+            while (-1 < V) {
+                V = Hr(PHist_new[V].lnk);
+                if (-1 < V) j++;
+            }
+        }
+        depths[i] = j;
+        if (maxdepth < j) maxdepth = j;
+    }
+    maxdepth_sav = maxdepth;
+
+    fprintf(stderr,
+        "\n    Supplementary HASH report:"
+        "\n\tTwo Tables providing for %d entries each + 1 extra for 'empty' image"
+        "\n\t%dk (%d bytes) per table, %d total bytes (including 'empty' image)"
+        "\n\tResults from latest hash (PHash_new + PHist_new)..."
+        "\n"
+        "\n\tTotal hashed = %d"
+        "\n\tLevel-0 hash entries = %d (%d%% occupied)"
+        "\n\tMax Depth = %d"
+        "\n\n"
+        , HHASH_SIZE, sz / 1024, sz, sz * 3
+        , info->hist->num_tasks
+        , total_occupied, (total_occupied * 100) / HHASH_SIZE
+        , maxdepth + 1);
+
+    if (total_occupied) {
+        for (pop = total_occupied, cross_foot = 0; maxdepth; maxdepth--) {
+            for (i = 0, numdepth = 0; i < HHASH_SIZE; i++)
+                if (depths[i] == maxdepth) ++numdepth;
+            fprintf(stderr,
+                "\t %5d (%3d%%) hash table entries at depth %d\n"
+                , numdepth, (numdepth * 100) / total_occupied, maxdepth + 1);
+            pop -= numdepth;
+            cross_foot += numdepth;
+            if (0 == pop && cross_foot == total_occupied) break;
+        }
+        if (pop) {
+            fprintf(stderr, "\t %5d (%3d%%) unchained hash table entries\n"
+                , pop, (pop * 100) / total_occupied);
+            cross_foot += pop;
+        }
+        fprintf(stderr,
+            "\t -----\n"
+            "\t %5d total entries occupied\n", cross_foot);
+
+        if (maxdepth_sav) {
+            fprintf(stderr, "\n    PIDs at max depth: ");
+            for (i = 0; i < HHASH_SIZE; i++)
+                if (depths[i] == maxdepth_sav) {
+                    j = Hr(PHash_new[i]);
+                    fprintf(stderr, "\n\tpos %4d:  %05d", i, Hr(PHist_new[j].pid));
+                    while (-1 < j) {
+                        j = Hr(PHist_new[j].lnk);
+                        if (-1 < j) fprintf(stderr, ", %05d", Hr(PHist_new[j].pid));
+                    }
+                }
+            fprintf(stderr, "\n");
+        }
+    }
+} // end: unref_rpthash
+#endif // UNREF_RPTHASH
+
+#undef HHASH_SIZE
+
+
+// ___ Standard Private Functions |||||||||||||||||||||||||||||||||||||||||||||
+
+static inline void assign_results (
+        struct procps_pidsinfo *info,
+        struct pids_stack *stack,
+        proc_t *p)
+{
+    struct pids_result *this = stack->head;
+
+    for (;;) {
+        enum pids_item item = this->item;
+        if (item >= PROCPS_PIDS_logical_end)
+            break;
+        Item_table[item].function(info, this, p);
+        ++this;
+    }
+    return;
+} // end: assign_results
+
+
+static inline void cleanup_stack (
+        struct pids_result *p,
+        int depth)
+{
+    int i;
+
+    for (i = 0; i < depth; i++) {
+        if (p->item < PROCPS_PIDS_noop) {
+            if (Item_table[p->item].mustfree && p->result.str)
+                free((void*)p->result.str);
+            if (p->item < PROCPS_PIDS_noop)
+                p->result.ull_int = 0;
+        }
+        ++p;
+    }
+} // end: cleanup_stack
+
+
+static inline void cleanup_stacks_all (
+        struct procps_pidsinfo *info)
+{
+    struct stacks_extent *ext = info->extents;
+    int i;
+
+    while (ext) {
+        for (i = 0; ext->stacks[i]; i++)
+            cleanup_stack(ext->stacks[i]->head, info->maxitems);
+        ext = ext->next;
+    };
+    info->dirty_stacks = 0;
+} // end: cleanup_stacks_all
+
+
+static int free_extent (
+        struct procps_pidsinfo *info,
+        struct stacks_extent *ext)
+{
+    struct stacks_extent *p = info->extents;
+
+    if (ext) {
+        if (ext == p) {
+            info->extents = p->next;
+            free(ext);
+            return 0;
+        }
+        do {
+            if (ext == p->next) {
+                p->next = p->next->next;
+                free(ext);
+                return 0;
+            }
+            p = p->next;
+        } while (p);
+    }
+    return -1;
+} // end: free_extent
+
+
+static int items_check_failed (
+        int maxitems,
+        enum pids_item *items)
+{
+    int i;
+
+    for (i = 0; i < maxitems; i++) {
+        if (items[i] < 0)
+            return -1;
+        if (items[i] > PROCPS_PIDS_noop) {
+            return -1;
+        }
+    }
+    return 0;
+} // end: items_check_failed
+
+
+static inline void libflags_set (
+        struct procps_pidsinfo *info)
+{
+    int i;
+
+    info->flags = info->history_yes = 0;
+    for (i = 0; i < info->curitems; i++) {
+        info->flags |= Item_table[info->items[i]].oldflags;
+        info->history_yes |= Item_table[info->items[i]].makehist;
+    }
+    if (info->flags & f_either) {
+        if (!(info->flags & f_stat))
+            info->flags |= f_status;
+    }
+    return;
+} // end: libflags_set
+
+
+static inline void oldproc_close (
+        struct procps_pidsinfo *info)
+{
+    if (info->PT != NULL) {
+        closeproc(info->PT);
+        info->PT = NULL;
+    }
+    return;
+} // end: oldproc_close
+
+
+static inline int oldproc_open (
+        struct procps_pidsinfo *info,
+        int supp_flgs,
+        ...)
+{
+    va_list vl;
+    int *ids;
+
+    if (info->PT == NULL) {
+        va_start(vl, supp_flgs);
+        ids = va_arg(vl, int*);
+        va_end(vl);
+        if (NULL == (info->PT = openproc(info->flags | supp_flgs, ids)))
+            return 0;
+    }
+    return 1;
+} // end: oldproc_open
+
+
+static inline struct pids_result *stack_itemize (
+        struct pids_result *p,
+        int depth,
+        enum pids_item *items)
+{
+    struct pids_result *p_sav = p;
+    int i;
+
+    for (i = 0; i < depth; i++) {
+        p->item = items[i];
+        p->result.ull_int = 0;
+        ++p;
+    }
+    return p_sav;
+} // end: stack_itemize
+
+
+static inline int tally_proc (
+        struct procps_pidsinfo *info,
+        struct pids_counts *counts,
+        proc_t *p)
+{
+    switch (p->state) {
+        case 'R':
+            ++counts->running;
+            break;
+        case 'S':
+        case 'D':
+            ++counts->sleeping;
+            break;
+        case 'T':
+            ++counts->stopped;
+            break;
+        case 'Z':
+            ++counts->zombied;
+            break;
+        default:                // keep gcc happy
+            break;
+    }
+    ++counts->total;
+
+    if (info->history_yes)
+        return !make_hist(info, p);
+    return 1;
+} // end: tally_proc
+
+
+static void validate_stacks (
+        void *stacks,
+        const char *who)
+{
+#if 0
+    #include <stdio.h>
+    static int once = 0;
+    struct stacks_extent *ext = (struct stacks_extent *)stacks;
+    int i, t, x, n = 0;
+
+    fprintf(stderr, "  %s: called by '%s'\n", __func__, who);
+    fprintf(stderr, "  %s: ext_numitems = %d, ext_numstacks = %d, extents = %p, next = %p\n", __func__, ext->ext_numitems, ext->ext_numstacks, ext, ext->next);
+    fprintf(stderr, "  %s: stacks_extent results excluding the end-of-stack element ...\n", __func__);
+    for (x = 0; NULL != ext->stacks[x]; x++) {
+        struct pids_stack *h = ext->stacks[x];
+        struct pids_result *r = h->head;
+        fprintf(stderr, "  %s:   v[%03d] = %p, h = %p, fill_id #%-5u", __func__, x, h, r, (unsigned)h->fill_id);
+        for (i = 0; r->item < PROCPS_PIDS_logical_end; i++, r++)
+            ;
+        t = i + 1;
+        fprintf(stderr, " - found %d elements for stack %d\n", i, n);
+        ++n;
+    }
+    if (!once) {
+        fprintf(stderr, "  %s: found %d total stack(s), each %d bytes (including eos)\n", __func__, x, (int)(sizeof(struct pids_stack) + (sizeof(struct pids_result) * t)));
+        fprintf(stderr, "  %s: sizeof(struct pids_stack)    = %d\n", __func__, (int)sizeof(struct pids_stack));
+        fprintf(stderr, "  %s: sizeof(struct pids_result)   = %d\n", __func__, (int)sizeof(struct pids_result));
+        fprintf(stderr, "  %s: sizeof(struct stacks_extent) = %d\n", __func__, (int)sizeof(struct stacks_extent));
+        once = 1;
+    }
+    fputc('\n', stderr);
+    return;
+#endif
+} // end: validate_stacks
+
+
+// ___ Public Functions |||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+/*
+ * procps_pids_new():
+ *
+ * @info: location of returned new structure
+ *
+ * Returns: 0 on success <0 on failure
+ */
+PROCPS_EXPORT int procps_pids_new (
+        struct procps_pidsinfo **info,
+        int maxitems,
+        enum pids_item *items)
+{
+    struct procps_pidsinfo *p;
+    int pgsz;
+
+    if (info == NULL || *info != NULL)
+        return -EINVAL;
+    if (items_check_failed(maxitems, items))
+        return -EINVAL;
+
+    if (!(p = calloc(1, sizeof(struct procps_pidsinfo))))
+        return -ENOMEM;
+    // allow for our PROCPS_PIDS_physical_end
+    if (!(p->items = calloc((maxitems + 1), sizeof(enum pids_item)))) {
+        free(p);
+        return -ENOMEM;
+    }
+    if (!(p->hist = calloc((maxitems + 1), sizeof(struct history_info)))) {
+        free(p->items);
+        free(p);
+        return -ENOMEM;
+    }
+
+    memcpy(p->items, items, sizeof(enum pids_item) * maxitems);
+    p->items[maxitems] = PROCPS_PIDS_physical_end;
+    p->curitems = p->maxitems = maxitems + 1;
+    libflags_set(p);
+
+    pgsz = getpagesize();
+    while (pgsz > 1024) { pgsz >>= 1; p->pgs2k_shift++; }
+
+    config_history(p);
+
+    p->refcount = 1;
+    *info = p;
+    return 0;
+} // end: procps_pids_new
+
+
+/* procps_pids_reap():
+ *
+ * Harvest all the available tasks/threads and provide the result
+ * stacks along with a summary of the information gathered.
+ *
+ * Returns: pointer to a pids_reap struct on success, NULL on error.
+ */
+PROCPS_EXPORT struct pids_reap *procps_pids_reap (
+        struct procps_pidsinfo *info,
+        enum pids_reap_type which)
+{
+ #define amtGROW  256
+ #define n_alloc  info->alloc_total
+ #define n_inuse  info->inuse_total
+    static proc_t task;    // static for initial zeroes + later dynamic free(s)
+    proc_t*(*read_something)(PROCTAB*, proc_t*);
+    struct pids_stacks *ext;
+    int n_save = n_alloc;
+
+    if (!info->anchor) {
+        if (!(info->anchor = calloc(sizeof(void*), amtGROW)))
+            return NULL;
+        if (!(ext = procps_pids_stacks_alloc(info, amtGROW)))
+            return NULL;
+        memcpy(info->anchor, ext->stacks, sizeof(void*) * amtGROW);
+        if (!(info->reap.reaped.stacks = calloc(sizeof(void*), amtGROW)))
+            return NULL;
+        n_save = info->alloc_total = amtGROW;
+    }
+
+    if (info->dirty_stacks)
+        cleanup_stacks_all(info);
+
+    memset(&info->reap.counts, 0, sizeof(struct pids_counts));
+    read_something = which ? readeither : readproc;
+
+    if (!oldproc_open(info, 0))
+        return NULL;
+    toggle_history(info);
+
+    for (n_inuse = 0; ; n_inuse++) {
+        if (n_inuse == n_alloc) {
+            n_alloc += amtGROW;
+            if (!(info->anchor = realloc(info->anchor, sizeof(void*) * n_alloc)))
+                return NULL;
+           if (!(ext = procps_pids_stacks_alloc(info, amtGROW)))
+                return NULL;
+            memcpy(info->anchor + n_inuse, ext->stacks, sizeof(void*) * amtGROW);
+        }
+        if (NULL == read_something(info->PT, &task))
+            break;
+        if (!tally_proc(info, &info->reap.counts, &task))
+            return NULL;
+        assign_results(info, info->anchor[n_inuse], &task);
+    }
+
+    oldproc_close(info);
+    if (n_save != n_alloc
+    && !(info->reap.reaped.stacks = realloc(info->reap.reaped.stacks, sizeof(void*) * n_alloc)))
+        return NULL;
+    memcpy(info->reap.reaped.stacks, info->anchor, sizeof(void*) * n_alloc);
+    info->dirty_stacks = 1;
+    return &info->reap;
+ #undef n_alloc
+ #undef n_inuse
+ #undef amtGROW
+} // end: procps_pids_reap
+
+
+PROCPS_EXPORT int procps_pids_ref (
+        struct procps_pidsinfo *info)
+{
+    if (info == NULL)
+        return -EINVAL;
+
+    info->refcount++;
+    return info->refcount;
+} // end: procps_pids_ref
+
+
+PROCPS_EXPORT int procps_pids_reset (
+        struct procps_pidsinfo *info,
+        int newmaxitems,
+        enum pids_item *newitems)
+{
+    struct stacks_extent *ext;
+    int i;
+
+    if (info == NULL)
+        return -EINVAL;
+    /* disallow (for now?) absolute increases in stacks size
+       ( users must 'unref' and then 'new' to achieve that ) */
+    if (newmaxitems + 1 > info->maxitems)
+        return -EINVAL;
+    if (items_check_failed(newmaxitems, newitems))
+        return -EINVAL;
+
+    /* shame on this caller, they didn't change anything - but they might have
+       shortened their stacks. yet we cannot reposition their logical_end enum
+       lest we overlay some string result that would never be freed, let alone
+       all those strings that could follow it. so here's the deal, this caller
+       will just have to suffer the additional overhead of retrieving unneeded
+       results until they offer us a real, properly formatted 'reset' request! */
+    if (info->curitems == newmaxitems + 1
+    && !memcmp(info->items, newitems, sizeof(enum pids_item) * newmaxitems))
+        return 0;
+
+    if (info->dirty_stacks)
+        cleanup_stacks_all(info);
+
+    memcpy(info->items, newitems, sizeof(enum pids_item) * newmaxitems);
+    info->items[newmaxitems] = PROCPS_PIDS_logical_end;
+    // account for above PROCPS_PIDS_logical_end
+    info->curitems = newmaxitems + 1;
+
+    ext = info->extents;
+    while (ext) {
+        for (i = 0; ext->stacks[i]; i++)
+            stack_itemize(ext->stacks[i]->head, info->curitems, info->items);
+            validate_stacks(ext, __func__);
+        ext = ext->next;
+    };
+
+    libflags_set(info);
+    return 0;
+} // end: procps_pids_reset
+
+
+/*
+ * procps_pids_stacks_alloc():
+ *
+ * Allocate and initialize one or more stacks each of which is anchored in an
+ * associated pids_stack structure (which may include extra user space).
+ *
+ * All such stacks will will have their result structures properly primed with
+ * 'items', while the result itself will be zeroed.
+ *
+ * Returns an array of pointers representing the 'heads' of each new stack.
+ */
+PROCPS_EXPORT struct pids_stacks *procps_pids_stacks_alloc (
+        struct procps_pidsinfo *info,
+        int maxstacks)
+{
+    struct stacks_extent *p_blob;
+    struct pids_stack **p_vect;
+    struct pids_stack *p_head;
+    size_t vect_size, head_size, list_size, blob_size;
+    void *v_head, *v_list;
+    int i;
+
+    if (info == NULL || info->items == NULL)
+        return NULL;
+    if (maxstacks < 1)
+        return NULL;
+
+    vect_size  = sizeof(void *) * maxstacks;                   // address vectors themselves
+    vect_size += sizeof(void *);                               // plus NULL delimiter
+    head_size  = sizeof(struct pids_stack);                    // a head struct
+    list_size  = sizeof(struct pids_result) * info->maxitems;  // a results stack
+    blob_size  = sizeof(struct stacks_extent);                 // the extent anchor itself
+    blob_size += vect_size;                                    // all vectors + delim
+    blob_size += head_size * maxstacks;                        // all head structs
+    blob_size += list_size * maxstacks;                        // all results stacks
+
+    /* note: all memory is allocated in a single blob, facilitating a later free().
+       as a minimum, it's important that the result structures themselves always be
+       contiguous for any given stack (just as they are when defined statically). */
+    if (NULL == (p_blob = calloc(1, blob_size)))
+        return NULL;
+
+    p_blob->next = info->extents;
+    info->extents = p_blob;
+    p_blob->stacks = (void *)p_blob + sizeof(struct stacks_extent);
+    p_vect = p_blob->stacks;
+    v_head = (void *)p_vect + vect_size;
+    v_list = v_head + (head_size * maxstacks);
+
+    for (i = 0; i < maxstacks; i++) {
+        p_head = (struct pids_stack *)v_head;
+        p_head->head = stack_itemize((struct pids_result *)v_list, info->curitems, info->items);
+        p_blob->stacks[i] = p_head;
+        v_list += list_size;
+        v_head += head_size;
+    }
+    p_blob->ext_numitems = info->maxitems;
+    p_blob->ext_numstacks = maxstacks;
+    validate_stacks(p_blob, __func__);
+    return (struct pids_stacks *)p_blob;
+} // end: procps_pids_stacks_alloc
+
+
+PROCPS_EXPORT int procps_pids_stacks_dealloc (
+        struct procps_pidsinfo *info,
+        struct pids_stacks **these)
+{
+    struct stacks_extent *ext;
+
+    if (info == NULL || these == NULL)
+        return -EINVAL;
+    if ((*these)->stacks == NULL || (*these)->stacks[0] == NULL)
+        return -EINVAL;
+
+    ext = (struct stacks_extent *)(*these);
+    int rc = free_extent(info, ext);
+    *these = NULL;
+    return rc;
+} // end: procps_pids_stacks_dealloc
+
+
+PROCPS_EXPORT struct pids_counts *procps_pids_stacks_fill (
+        struct procps_pidsinfo *info,
+        struct pids_stacks *these,
+        int maxstacks,
+        enum pids_fill_type which)
+{
+    static proc_t task;    // static for initial zeroes + later dynamic free(s)
+    unsigned ids[FILL_ID_MAX + 1];
+    int i;
+
+    if (info == NULL || these == NULL)
+        return NULL;
+    if (these->stacks == NULL || these->stacks[0] == NULL)
+        return NULL;
+    if (which != PROCPS_FILL_PID && which != PROCPS_FILL_UID)
+        return NULL;
+    if (maxstacks < 1 || maxstacks > FILL_ID_MAX)
+        return NULL;
+
+    for (i = 0; i < maxstacks; i++) {
+        if (these->stacks[i] == NULL)
+            break;
+        ids[i] = these->stacks[i]->fill_id;
+    }
+    ids[i] = 0;
+
+    if (info->dirty_stacks)
+        cleanup_stacks_all(info);
+    memset(&info->counts, 0, sizeof(struct pids_counts));
+
+    if (!oldproc_open(info, which, ids, i))
+        return NULL;
+    toggle_history(info);
+
+    for (i = 0; i < maxstacks; i++) {
+        if (these->stacks[i] == NULL)
+            break;
+        if (!readproc(info->PT, &task))
+            break;
+        if (!tally_proc(info, &info->counts, &task)) {
+            oldproc_close(info);
+            return NULL;
+        }
+        assign_results(info, these->stacks[i], &task);
+    }
+
+    oldproc_close(info);
+    info->dirty_stacks = 1;
+    validate_stacks(these, __func__);
+    return &info->counts;
+} // end: procps_pids_stacks_fill
+
+
+/*
+ * procps_pids_stacks_sort():
+ *
+ * Sort stacks anchored in the passed pids_stack pointers array
+ * based on the designated sort enumerator and specified order.
+ *
+ * Returns those same addresses sorted.
+ *
+ * Note: all of the stacks must be homogeneous (of equal length and content).
+ */
+PROCPS_EXPORT struct pids_stack **procps_pids_stacks_sort (
+        struct procps_pidsinfo *info,
+        struct pids_stack **stacks,
+        int numstacked,
+        enum pids_item sort,
+        enum pids_sort_order order)
+{
+    struct sort_parms parms;
+    struct pids_result *p;
+    int offset;
+
+    if (info == NULL || stacks == NULL)
+        return NULL;
+    if (sort < 0  || sort > PROCPS_PIDS_noop)
+        return NULL;
+    if (order < -1  || order > +1)
+        return NULL;
+    if (numstacked < 2)
+        return stacks;
+
+    offset = 0;
+    p = stacks[0]->head;
+    for (;;) {
+        if (p->item == sort)
+            break;
+        ++offset;
+        if (offset >= info->curitems)
+            return NULL;
+        if (p->item > PROCPS_PIDS_noop)
+            return NULL;
+        ++p;
+    }
+
+    parms.offset = offset;
+    parms.order = order;
+
+    qsort_r(stacks, numstacked, sizeof(void *), (QSR_t)Item_table[p->item].callback, &parms);
+    return stacks;
+} // end: procps_pids_stacks_sort
+
+
+PROCPS_EXPORT int procps_pids_unref (
+        struct procps_pidsinfo **info)
+{
+    if (info == NULL || *info == NULL)
+        return -EINVAL;
+
+    (*info)->refcount--;
+    if ((*info)->refcount == 0) {
+#ifdef UNREF_RPTHASH
+        unref_rpthash(*info);
+#endif
+        if ((*info)->extents) {
+            cleanup_stacks_all(*info);
+            do {
+                struct stacks_extent *p = (*info)->extents;
+                (*info)->extents = (*info)->extents->next;
+                free(p);
+            } while ((*info)->extents);
+        }
+        if ((*info)->reap.reaped.stacks)
+            free((*info)->reap.reaped.stacks);
+        if ((*info)->anchor)
+            free((*info)->anchor);
+        if ((*info)->items)
+            free((*info)->items);
+        if ((*info)->hist) {
+            free((*info)->hist->PHist_sav);
+            free((*info)->hist->PHist_new);
+            free((*info)->hist);
+        }
+        free(*info);
+        *info = NULL;
+        return 0;
+    }
+    return (*info)->refcount;
+} // end: procps_pids_unref
diff --git a/proc/pids.h b/proc/pids.h
new file mode 100644 (file)
index 0000000..2d0bb3c
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * pids.h - task/thread/process related declarations for libproc
+ *
+ * Copyright (C) 1998-2005 Albert Cahalan
+ * Copyright (C) 2015 Craig Small <csmall@enc.com.au>
+ * Copyright (C) 2015 Jim Warner <james.warner@comcast.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _PROC_PIDS_H
+#define _PROC_PIDS_H
+
+__BEGIN_DECLS
+
+enum pids_item {
+    PROCPS_PIDS_ADDR_END_CODE,         // addr
+    PROCPS_PIDS_ADDR_KSTK_EIP,         // addr
+    PROCPS_PIDS_ADDR_KSTK_ESP,         // addr
+    PROCPS_PIDS_ADDR_START_CODE,       // addr
+    PROCPS_PIDS_ADDR_START_STACK,      // addr
+    PROCPS_PIDS_ALARM,                 // sl_int
+    PROCPS_PIDS_CGROUP,                // str
+    PROCPS_PIDS_CMD,                   // str
+    PROCPS_PIDS_CMDLINE,               // str
+    PROCPS_PIDS_ENVIRON,               // str
+    PROCPS_PIDS_EXIT_SIGNAL,           // s_int
+    PROCPS_PIDS_FLAGS,                 // ul_int
+    PROCPS_PIDS_FLT_MAJ,               // ul_int
+    PROCPS_PIDS_FLT_MAJ_C,             // ul_int
+    PROCPS_PIDS_FLT_MAJ_DELTA,         // ul_int
+    PROCPS_PIDS_FLT_MIN,               // ul_int
+    PROCPS_PIDS_FLT_MIN_C,             // ul_int
+    PROCPS_PIDS_FLT_MIN_DELTA,         // ul_int
+    PROCPS_PIDS_ID_EGID,               // u_int
+    PROCPS_PIDS_ID_EGROUP,             // str
+    PROCPS_PIDS_ID_EUID,               // u_int
+    PROCPS_PIDS_ID_EUSER,              // str
+    PROCPS_PIDS_ID_FGID,               // u_int
+    PROCPS_PIDS_ID_FGROUP,             // str
+    PROCPS_PIDS_ID_FUID,               // u_int
+    PROCPS_PIDS_ID_FUSER,              // str
+    PROCPS_PIDS_ID_PGRP,               // s_int
+    PROCPS_PIDS_ID_PID,                // s_int
+    PROCPS_PIDS_ID_PPID,               // s_int
+    PROCPS_PIDS_ID_RGID,               // u_int
+    PROCPS_PIDS_ID_RGROUP,             // str
+    PROCPS_PIDS_ID_RUID,               // u_int
+    PROCPS_PIDS_ID_RUSER,              // str
+    PROCPS_PIDS_ID_SESSION,            // s_int
+    PROCPS_PIDS_ID_SGID,               // u_int
+    PROCPS_PIDS_ID_SGROUP,             // str
+    PROCPS_PIDS_ID_SUID,               // u_int
+    PROCPS_PIDS_ID_SUSER,              // str
+    PROCPS_PIDS_ID_TGID,               // s_int
+    PROCPS_PIDS_ID_TPGID,              // s_int
+    PROCPS_PIDS_LXCNAME,               // str
+    PROCPS_PIDS_MEM_CODE,              // sl_int
+    PROCPS_PIDS_MEM_CODE_KIB,          // ul_int
+    PROCPS_PIDS_MEM_DATA,              // sl_int
+    PROCPS_PIDS_MEM_DATA_KIB,          // ul_int
+    PROCPS_PIDS_MEM_DT,                // sl_int
+    PROCPS_PIDS_MEM_LRS,               // sl_int
+    PROCPS_PIDS_MEM_RES,               // sl_int
+    PROCPS_PIDS_MEM_RES_KIB,           // ul_int
+    PROCPS_PIDS_MEM_SHR,               // sl_int
+    PROCPS_PIDS_MEM_SHR_KIB,           // ul_int
+    PROCPS_PIDS_MEM_VIRT,              // sl_int
+    PROCPS_PIDS_MEM_VIRT_KIB,          // ul_int
+    PROCPS_PIDS_NICE,                  // sl_int
+    PROCPS_PIDS_NLWP,                  // s_int
+    PROCPS_PIDS_NS_IPC,                // ul_int
+    PROCPS_PIDS_NS_MNT,                // ul_int
+    PROCPS_PIDS_NS_NET,                // ul_int
+    PROCPS_PIDS_NS_PID,                // ul_int
+    PROCPS_PIDS_NS_USER,               // ul_int
+    PROCPS_PIDS_NS_UTS,                // ul_int
+    PROCPS_PIDS_OOM_ADJ,               // s_int
+    PROCPS_PIDS_OOM_SCORE,             // s_int
+    PROCPS_PIDS_PRIORITY,              // s_int
+    PROCPS_PIDS_PROCESSOR,             // u_int
+    PROCPS_PIDS_RSS,                   // sl_int
+    PROCPS_PIDS_RSS_RLIM,              // ul_int
+    PROCPS_PIDS_RTPRIO,                // ul_int
+    PROCPS_PIDS_SCHED_CLASS,           // ul_int
+    PROCPS_PIDS_SD_MACH,               // str
+    PROCPS_PIDS_SD_OUID,               // str
+    PROCPS_PIDS_SD_SEAT,               // str
+    PROCPS_PIDS_SD_SESS,               // str
+    PROCPS_PIDS_SD_SLICE,              // str
+    PROCPS_PIDS_SD_UNIT,               // str
+    PROCPS_PIDS_SD_UUNIT,              // str
+    PROCPS_PIDS_SIGBLOCKED,            // str
+    PROCPS_PIDS_SIGCATCH,              // str
+    PROCPS_PIDS_SIGIGNORE,             // str
+    PROCPS_PIDS_SIGNALS,               // str
+    PROCPS_PIDS_SIGPENDING,            // str
+    PROCPS_PIDS_STATE,                 // s_ch
+    PROCPS_PIDS_SUPGIDS,               // str
+    PROCPS_PIDS_SUPGROUPS,             // str
+    PROCPS_PIDS_TICS_ALL,              // ull_int
+    PROCPS_PIDS_TICS_ALL_C,            // ull_int
+    PROCPS_PIDS_TICS_DELTA,            // u_int
+    PROCPS_PIDS_TICS_SYSTEM,           // ull_int
+    PROCPS_PIDS_TICS_SYSTEM_C,         // ull_int
+    PROCPS_PIDS_TICS_USER,             // ull_int
+    PROCPS_PIDS_TICS_USER_C,           // ull_int
+    PROCPS_PIDS_TIME_START,            // ull_int
+    PROCPS_PIDS_TTY,                   // s_int
+    PROCPS_PIDS_VM_DATA,               // ul_int
+    PROCPS_PIDS_VM_EXE,                // ul_int
+    PROCPS_PIDS_VM_LIB,                // ul_int
+    PROCPS_PIDS_VM_LOCK,               // ul_int
+    PROCPS_PIDS_VM_RSS,                // ul_int
+    PROCPS_PIDS_VM_SIZE,               // ul_int
+    PROCPS_PIDS_VM_STACK,              // ul_int
+    PROCPS_PIDS_VM_SWAP,               // ul_int
+    PROCPS_PIDS_VM_USED,               // ul_int
+    PROCPS_PIDS_VSIZE_PGS,             // ul_int
+    PROCPS_PIDS_WCHAN_ADDR,            // addr
+    PROCPS_PIDS_WCHAN_NAME,            // str
+    PROCPS_PIDS_noop                   // n/a
+};
+
+enum pids_fill_type {
+    PROCPS_FILL_PID  = 0x1000,
+    PROCPS_FILL_UID  = 0x4000
+};
+
+enum pids_reap_type {
+    PROCPS_REAP_TASKS_ONLY   = 0,
+    PROCPS_REAP_THREADS_TOO  = 1
+};
+
+enum pids_sort_order {
+    PROCPS_SORT_ASCEND   = -1,
+    PROCPS_SORT_DESCEND  = +1
+};
+
+
+struct procps_pidsinfo;
+
+struct pids_result {
+    enum pids_item item;
+    union {
+        char                 s_ch;
+        int                  s_int;
+        unsigned int         u_int;
+        long                 sl_int;
+        unsigned long        ul_int;
+        unsigned long long   ull_int;
+        void               * addr;
+        const char         * str;
+    } result;
+};
+
+struct pids_stack {
+    struct pids_result *head;
+    unsigned fill_id;
+};
+
+struct pids_stacks {
+    struct pids_stack **stacks;
+};
+
+struct pids_counts {
+    int total;
+    int running, sleeping, stopped, zombied;
+};
+
+struct pids_reap {
+    struct pids_stacks reaped;
+    struct pids_counts counts;
+};
+
+
+int procps_pids_new (
+    struct procps_pidsinfo **info,
+    int maxitems,
+    enum pids_item *items);
+
+struct pids_reap *procps_pids_reap (
+    struct procps_pidsinfo *info,
+    enum pids_reap_type which);
+
+int procps_pids_ref (
+    struct procps_pidsinfo *info);
+
+int procps_pids_reset (
+    struct procps_pidsinfo *info,
+    int newmaxitems,
+    enum pids_item *newitems);
+
+struct pids_stacks *procps_pids_stacks_alloc (
+    struct procps_pidsinfo *info,
+    int maxstacks);
+
+int procps_pids_stacks_dealloc (
+    struct procps_pidsinfo *info,
+    struct pids_stacks **these);
+
+struct pids_counts *procps_pids_stacks_fill (
+    struct procps_pidsinfo *info,
+    struct pids_stacks *these,
+    int maxstacks,
+    enum pids_fill_type which);
+
+struct pids_stack **procps_pids_stacks_sort (
+    struct procps_pidsinfo *info,
+    struct pids_stack **stacks,
+    int numstacked,
+    enum pids_item sort,
+    enum pids_sort_order order);
+
+int procps_pids_unref (
+    struct procps_pidsinfo **info);
+
+__END_DECLS
+
+#endif /* _PROC_PIDS_H */