From 3ef4823f90ce7c92bdd65fb9b8723a29a80d2737 Mon Sep 17 00:00:00 2001 From: Craig Small Date: Mon, 1 Aug 2011 21:28:46 +1000 Subject: [PATCH] supgid/supgrp support, improved library interface Library changes readproc . added support for supplementary groups . eliminated 2 potential mem leak sources . shortcut used for multi-threaded str vectors & ptrs was obsoleted . freeing of proc_t related dynamic memory now rests with the library . standardized/normalized many c comments sysinfo . corrected note regarding glibc & cpuinfo library.map . made the visible freeproc accessable Program changes pmap . initialized buffer for new readproc i/f . eliminated now obsolete free() call ps . added width aware supgrp support . initialized buffers for new readproc i/f . eliminated now obsolete free() calls top . added supgrp support as variable width . eliminated now obsolete free() calls . expoilted library freeproc function . corrected -h|v args text & spacing . updated some c comments Documentation changes ps.1 . added supgid and supgrp top.1 . added supgid and supgrp . addition of above required renumbering many fields in section 3a. DESCRIPTIONS --- pmap.c | 2 +- proc/library.map | 2 +- proc/readproc.c | 294 ++++++++++++++++++++++++++++------------------- proc/readproc.h | 28 +++-- proc/sysinfo.c | 3 + ps/display.c | 30 +---- ps/output.c | 38 ++++-- ps/ps.1 | 12 ++ top.1 | 46 +++++--- top.c | 59 ++++------ top.h | 5 + 11 files changed, 304 insertions(+), 215 deletions(-) diff --git a/pmap.c b/pmap.c index 6b865fc5..24a4e427 100644 --- a/pmap.c +++ b/pmap.c @@ -358,11 +358,11 @@ int main(int argc, char *argv[]){ discover_shm_minor(); + memset(&p, '\0', sizeof(p)); pidlist[count] = 0; // old libproc interface is zero-terminated PT = openproc(PROC_FILLSTAT|PROC_FILLARG|PROC_PID, pidlist); while(readproc(PT, &p)){ ret |= one_proc(&p); - if(p.cmdline) free((void*)*p.cmdline); count--; } closeproc(PT); diff --git a/proc/library.map b/proc/library.map index d09c6e1d..fe31dacc 100644 --- a/proc/library.map +++ b/proc/library.map @@ -7,7 +7,7 @@ global: readproc; readtask; readproctab; readproctab2; look_up_our_self; escape_command; escape_str; escape_strlist; escaped_copy; read_cmdline; - openproc; closeproc; + openproc; closeproc; freeproc; tty_to_dev; dev_to_tty; open_psdb_message; open_psdb; lookup_wchan; display_version; procps_version; linux_version_code; Hertz; smp_num_cpus; have_privs; getbtime; diff --git a/proc/readproc.c b/proc/readproc.c index f71c514c..d30de742 100644 --- a/proc/readproc.c +++ b/proc/readproc.c @@ -38,6 +38,7 @@ extern void __cyg_profile_func_enter(void*,void*); #define LEAVE(x) #endif +#ifndef SIGNAL_STRING // convert hex string to unsigned long long static unsigned long long unhex(const char *restrict cp){ unsigned long long ull = 0; @@ -48,9 +49,21 @@ static unsigned long long unhex(const char *restrict cp){ } return ull; } +#endif static int task_dir_missing; +// free any additional dynamically acquired storage associated with a proc_t +// ( and if it's to be reused, refresh it otherwise destroy it ) +static inline void free_acquired (proc_t *p, int reuse) { + if (p->environ) free((void*)*p->environ); + if (p->cmdline) free((void*)*p->cmdline); + if (p->cgroup) free((void*)*p->cgroup); + if (p->supgid) free(p->supgid); + if (p->supgrp) free(p->supgrp); + memset(p, reuse ? '\0' : '\xff', sizeof(*p)); +} + /////////////////////////////////////////////////////////////////////////// typedef struct status_table_struct { @@ -71,12 +84,18 @@ typedef struct status_table_struct { #define NUL {"", 0, 0}, // Derived from: -// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c sml.gperf +// gperf -7 --language=ANSI-C --key-positions=1,3,4 -C -n -c // // Suggested method: // Grep this file for "case_", then strip those down to the name. -// (leave the colon and newline) So "Pid:\n" and "Threads:\n" -// would be lines in the file. (no quote, no escape, etc.) +// Eliminate duplicates (due to #ifs), the ' case_' prefix and +// any c comments. Leave the colon and newline so that "Pid:\n", +// "Threads:\n", etc. would be lines, but no quote, no escape, etc. +// +// After a pipe through gperf, insert the resulting 'asso_values' +// into our 'assoc' array. Then convert the gperf 'wordlist' array +// into our 'table' array by wrapping the string literals within +// the F macro and replacing empty strings with the NUL define. // // In the status_table_struct watch out for name size (grrr, expanding) // and the number of entries (we mask with 63 for now). The table @@ -169,17 +188,6 @@ static void status2proc(char *S, proc_t *restrict P, int is_proc){ ENTER(0x220); - P->vm_size = 0; - P->vm_lock = 0; - P->vm_rss = 0; - P->vm_data = 0; - P->vm_stack= 0; - P->vm_exe = 0; - P->vm_lib = 0; - P->vm_swap = 0; - P->nlwp = 0; - P->signal[0] = '\0'; // so we can detect it as missing for very old kernels - goto base; for(;;){ @@ -209,8 +217,8 @@ ENTER(0x220); goto *entry.addr; #endif - case_Name:{ - unsigned u = 0; + case_Name: + { unsigned u = 0; while(u < sizeof P->cmd - 1u){ int c = *S++; if(unlikely(c=='\n')) break; @@ -316,12 +324,25 @@ ENTER(0x220); case_VmSwap: // Linux 2.6.34 P->vm_swap = strtol(S,&S,10); continue; + case_Groups: + { int j = strchr(S, '\n') - S; // currently lines end space + \n + if (j) { + P->supgid = xmalloc(j+1); // +1 in case space disappears + memcpy(P->supgid, S, j); + if (unlikely(' ' != P->supgid[--j])) ++j; + P->supgid[j] = '\0'; // whack the space or the newline + for ( ; j; j--) + if (' ' == P->supgid[j]) + P->supgid[j] = ','; + } else + P->supgid = strdup("-"); + continue; + } case_CapBnd: case_CapEff: case_CapInh: case_CapPrm: case_FDSize: - case_Groups: case_SigQ: case_VmHWM: // 2005, peak VmRSS unless VmRSS is bigger case_VmPTE: @@ -332,20 +353,20 @@ ENTER(0x220); #if 0 // recent kernels supply per-tgid pending signals if(is_proc && *ShdPnd){ - memcpy(P->signal, ShdPnd, 16); - P->signal[16] = '\0'; + memcpy(P->signal, ShdPnd, 16); + P->signal[16] = '\0'; } #endif // recent kernels supply per-tgid pending signals #ifdef SIGNAL_STRING if(!is_proc || !P->signal[0]){ - memcpy(P->signal, P->_sigpnd, 16); - P->signal[16] = '\0'; + memcpy(P->signal, P->_sigpnd, 16); + P->signal[16] = '\0'; } #else - if(!is_proc || !have_process_pending){ - P->signal = P->_sigpnd; + if(!is_proc){ + P->signal = P->_sigpnd; } #endif @@ -354,18 +375,36 @@ ENTER(0x220); // Only 2.6.0 and above have "Threads" (nlwp) info. if(Threads){ - P->nlwp = Threads; - P->tgid = Tgid; // the POSIX PID value - P->tid = Pid; // the thread ID + P->nlwp = Threads; + P->tgid = Tgid; // the POSIX PID value + P->tid = Pid; // the thread ID }else{ - P->nlwp = 1; - P->tgid = Pid; - P->tid = Pid; + P->nlwp = 1; + P->tgid = Pid; + P->tid = Pid; } LEAVE(0x220); } +static void supgrps_from_supgids (proc_t *p) { + char *g, *s; + int t; + + if (!p->supgid || '-' == *p->supgid) { + p->supgrp = strdup("-"); + return; + } + s = p->supgid; + t = 0; + do { + if (',' == *s) ++s; + g = group_from_gid((uid_t)strtol(s, &s, 10)); + p->supgrp = realloc(p->supgrp, P_G_SZ+t+2); + t += snprintf(p->supgrp+t, P_G_SZ+2, "%s%s", t ? "," : "", g); + } while (*s); +} + /////////////////////////////////////////////////////////////////////// #ifdef OOMEM_ENABLE static void oomscore2proc(const char* S, proc_t *restrict P) @@ -521,7 +560,7 @@ static char** file2strvec(const char* directory, const char* what) { q = ret = (char**) (endbuf+align); /* ==> free(*ret) to dealloc */ *q++ = p = rbuf; /* point ptrs to the strings */ endbuf--; /* do not traverse final NUL */ - while (++p < endbuf) + while (++p < endbuf) if (!*p) /* NUL char implies that */ *q++ = p+1; /* next string -> next char */ @@ -652,35 +691,37 @@ int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid) { // The pid (tgid? tid?) is already in p, and a path to it in path, with some // room to spare. static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict const p) { - static struct stat sb; // stat() buffer - static char sbuf[1024]; // buffer for stat,statm + static struct stat sb; // stat() buffer + static char sbuf[1024]; // buffer for stat,statm,status char *restrict const path = PT->path; unsigned flags = PT->flags; - if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ - goto next_proc; + if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ + goto next_proc; if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) - goto next_proc; /* not one of the requested uids */ + goto next_proc; /* not one of the requested uids */ - p->euid = sb.st_uid; /* need a way to get real uid */ - p->egid = sb.st_gid; /* need a way to get real gid */ + p->euid = sb.st_uid; /* need a way to get real uid */ + p->egid = sb.st_gid; /* need a way to get real gid */ - if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */ - if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) - goto next_proc; /* error reading /proc/#/stat */ - stat2proc(sbuf, p); /* parse /proc/#/stat */ + if (flags & PROC_FILLSTAT) { // read /proc/#/stat + if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) + goto next_proc; + stat2proc(sbuf, p); } - if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */ - if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) - statm2proc(sbuf, p); /* ignore statm errors here */ - } /* statm fields just zero */ + if (flags & PROC_FILLMEM) { // read /proc/#/statm + if (likely(file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) + statm2proc(sbuf, p); + } - if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */ - if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){ - status2proc(sbuf, p, 1); - } + if (flags & PROC_FILLSTATUS) { // read /proc/#/status + if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){ + status2proc(sbuf, p, 1); + if (flags & PROC_FILLSUPGRP) + supgrps_from_supgids(p); + } } // if multithreaded, some values are crap @@ -688,17 +729,17 @@ static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict cons p->wchan = (KLONG)~0ull; } - /* some number->text resolving which is time consuming and kind of insane */ + /* some number->text resolving which is time consuming */ if (flags & PROC_FILLUSR){ - memcpy(p->euser, user_from_uid(p->euid), sizeof p->euser); + memcpy(p->euser, user_from_uid(p->euid), sizeof p->euser); if(flags & PROC_FILLSTATUS) { - memcpy(p->ruser, user_from_uid(p->ruid), sizeof p->ruser); - memcpy(p->suser, user_from_uid(p->suid), sizeof p->suser); - memcpy(p->fuser, user_from_uid(p->fuid), sizeof p->fuser); + memcpy(p->ruser, user_from_uid(p->ruid), sizeof p->ruser); + memcpy(p->suser, user_from_uid(p->suid), sizeof p->suser); + memcpy(p->fuser, user_from_uid(p->fuid), sizeof p->fuser); } } - /* some number->text resolving which is time consuming and kind of insane */ + /* some number->text resolving which is time consuming */ if (flags & PROC_FILLGRP){ memcpy(p->egroup, group_from_gid(p->egid), sizeof p->egroup); if(flags & PROC_FILLSTATUS) { @@ -708,12 +749,12 @@ static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict cons } } - if (unlikely(flags & PROC_FILLENV)) /* read /proc/#/environ */ + if (unlikely(flags & PROC_FILLENV)) // read /proc/#/environ p->environ = file2strvec(path, "environ"); else p->environ = NULL; - if (flags & (PROC_FILLCOM|PROC_FILLARG)) { /* read /proc/#/cmdline */ + if (flags & (PROC_FILLCOM|PROC_FILLARG)) { // read /proc/#/cmdline if (flags & PROC_EDITCMDLCVT) fill_cmdline_cvt(p); else @@ -721,7 +762,7 @@ static proc_t* simple_readproc(PROCTAB *restrict const PT, proc_t *restrict cons } else p->cmdline = NULL; - if ((flags & PROC_FILLCGROUP) /* read /proc/#/cgroup, if possible */ + if ((flags & PROC_FILLCGROUP) // read /proc/#/cgroup && linux_version_code >= LINUX_VERSION(2,6,24)) { if (flags & PROC_EDITCGRPCVT) fill_cgroup_cvt(p); @@ -750,55 +791,55 @@ next_proc: // t is the POSIX thread (task group member, generally not the leader) // path is a path to the task, with some room to spare. static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict const t, char *restrict const path) { - static struct stat sb; // stat() buffer - static char sbuf[1024]; // buffer for stat,statm + static struct stat sb; // stat() buffer + static char sbuf[1024]; // buffer for stat,statm,status unsigned flags = PT->flags; -//printf("hhh\n"); - if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ - goto next_task; + if (unlikely(stat(path, &sb) == -1)) /* no such dirent (anymore) */ + goto next_task; // if ((flags & PROC_UID) && !XinLN(uid_t, sb.st_uid, PT->uids, PT->nuid)) -// goto next_task; /* not one of the requested uids */ +// goto next_task; /* not one of the requested uids */ - t->euid = sb.st_uid; /* need a way to get real uid */ - t->egid = sb.st_gid; /* need a way to get real gid */ + t->euid = sb.st_uid; /* need a way to get real uid */ + t->egid = sb.st_gid; /* need a way to get real gid */ -//printf("iii\n"); - if (flags & PROC_FILLSTAT) { /* read, parse /proc/#/stat */ - if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) - goto next_task; /* error reading /proc/#/stat */ - stat2proc(sbuf, t); /* parse /proc/#/stat */ + if (flags & PROC_FILLSTAT) { // read /proc/#/task/#/stat + if (unlikely( file2str(path, "stat", sbuf, sizeof sbuf) == -1 )) + goto next_task; + stat2proc(sbuf, t); } - if (unlikely(flags & PROC_FILLMEM)) { /* read, parse /proc/#/statm */ -#if 0 - if (likely( file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) - statm2proc(sbuf, t); /* ignore statm errors here */ + if (flags & PROC_FILLMEM) { // read /proc/#/task/#statm +#if 1 + if (likely(file2str(path, "statm", sbuf, sizeof sbuf) != -1 )) + statm2proc(sbuf, t); #else - t->size = p->size; - t->resident = p->resident; - t->share = p->share; - t->trs = p->trs; - t->lrs = p->lrs; - t->drs = p->drs; - t->dt = p->dt; + t->size = p->size; + t->resident = p->resident; + t->share = p->share; + t->trs = p->trs; + t->lrs = p->lrs; + t->drs = p->drs; + t->dt = p->dt; #endif - } /* statm fields just zero */ + } - if (flags & PROC_FILLSTATUS) { /* read, parse /proc/#/status */ + if (flags & PROC_FILLSTATUS) { // read /proc/#/task/#/status if (likely( file2str(path, "status", sbuf, sizeof sbuf) != -1 )){ status2proc(sbuf, t, 0); + if (flags & PROC_FILLSUPGRP) + supgrps_from_supgids(t); } } /* some number->text resolving which is time consuming */ if (flags & PROC_FILLUSR){ - memcpy(t->euser, user_from_uid(t->euid), sizeof t->euser); + memcpy(t->euser, user_from_uid(t->euid), sizeof t->euser); if(flags & PROC_FILLSTATUS) { - memcpy(t->ruser, user_from_uid(t->ruid), sizeof t->ruser); - memcpy(t->suser, user_from_uid(t->suid), sizeof t->suser); - memcpy(t->fuser, user_from_uid(t->fuid), sizeof t->fuser); + memcpy(t->ruser, user_from_uid(t->ruid), sizeof t->ruser); + memcpy(t->suser, user_from_uid(t->suid), sizeof t->suser); + memcpy(t->fuser, user_from_uid(t->fuid), sizeof t->fuser); } } @@ -812,21 +853,42 @@ static proc_t* simple_readtask(PROCTAB *restrict const PT, const proc_t *restric } } -#if 0 - if ((flags & PROC_FILLCOM) || (flags & PROC_FILLARG)) /* read+parse /proc/#/cmdline */ - t->cmdline = file2strvec(path, "cmdline"); +#if 1 // begin active ------------------------ + if (unlikely(flags & PROC_FILLENV)) // read /proc/#/task/#/environ + t->environ = file2strvec(path, "environ"); else + t->environ = NULL; + + if (flags & (PROC_FILLCOM|PROC_FILLARG)) { // read /proc/#/task/#/cmdline + if (flags & PROC_EDITCMDLCVT) + fill_cmdline_cvt(t); + else + t->cmdline = file2strvec(path, "cmdline"); + } else t->cmdline = NULL; - if (unlikely(flags & PROC_FILLENV)) /* read+parse /proc/#/environ */ - t->environ = file2strvec(path, "environ"); + if ((flags & PROC_FILLCGROUP) // read /proc/#/task/#/cgroup + && linux_version_code >= LINUX_VERSION(2,6,24)) { + if (flags & PROC_EDITCGRPCVT) + fill_cgroup_cvt(t); else - t->environ = NULL; -#else + t->cgroup = file2strvec(path, "cgroup"); + } else + t->cgroup = NULL; +#else // end active -------------------------- t->cmdline = p->cmdline; // better not free these until done with all threads! t->environ = p->environ; + t->cgroup = p->cgroup; + t->supgid = p->supgid; + t->supgrp = p->supgrp; +#error we DO NOT BURDEN library users with the above insanity ANYMORE ! +#endif // end inactive ------------------------ + +#ifdef OOMEM_ENABLE + t->oom_score = p->oom_score; + t->oom_adj = p->oom_adj; #endif - t->cgroup = p->cgroup; + t->ppid = p->ppid; // ought to put the per-task ppid somewhere return t; @@ -916,7 +978,8 @@ proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p) { // } saved_p = p; - if(!p) p = xcalloc(p, sizeof *p); /* passed buf or alloced mem */ + if(!p) p = xcalloc(NULL, sizeof *p); + else free_acquired(p, 1); for(;;){ // fills in the path, plus p->tid and p->tgid @@ -944,20 +1007,25 @@ proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, pro proc_t *saved_t; saved_t = t; - if(!t) t = xcalloc(t, sizeof *t); /* passed buf or alloced mem */ + if(!t) t = xcalloc(NULL, sizeof *t); + else free_acquired(t, 1); // 1. got to fake a thread for old kernels - // 2. for single-threaded processes, this is faster (but must patch up stuff that differs!) - if(task_dir_missing || p->nlwp < 2){ + if(task_dir_missing) { if(PT->did_fake) goto out; PT->did_fake=1; memcpy(t,p,sizeof(proc_t)); // use the per-task pending, not per-tgid pending #ifdef SIGNAL_STRING - memcpy(&t->signal, &t->_sigpnd, sizeof t->signal); + memcpy(&t->signal, &t->_sigpnd, sizeof t->signal); #else - t->signal = t->_sigpnd; + t->signal = t->_sigpnd; #endif + t->environ = NULL; + t->cmdline = vectorize_this_str("n/a"); + t->cgroup = NULL; + t->supgid = NULL; + t->supgrp = NULL; return t; } @@ -1026,19 +1094,12 @@ void closeproc(PROCTAB* PT) { } } -// deallocate the space allocated by readproc if the passed rbuf was NULL +// deallocate space allocated by readproc void freeproc(proc_t* p) { - if (!p) /* in case p is NULL */ - return; - /* ptrs are after strings to avoid copying memory when building them. */ - /* so free is called on the address of the address of strvec[0]. */ - if (p->cmdline) - free((void*)*p->cmdline); - if (p->environ) - free((void*)*p->environ); - if (p->cgroup) - free((void*)*p->cgroup); - free(p); + if (p) { + free_acquired(p, 0); + free(p); + } } @@ -1115,6 +1176,7 @@ proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t * n_alloc = n_alloc*5/4+30; // grow by over 25% data = realloc(data,sizeof(proc_t)*n_alloc); //if(!data) return NULL; + memset(data+n_used, 0, sizeof(proc_t)*(n_alloc-n_used)); } if(n_proc_alloc == n_proc){ //proc_t **old = ptab; @@ -1133,8 +1195,8 @@ proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(proc_t * proc_t *old = data; n_alloc = n_alloc*5/4+30; // grow by over 25% data = realloc(data,sizeof(proc_t)*n_alloc); - // have to move tmp too - tmp = data+(tmp-old); + // have to move tmp too + tmp = data+(tmp-old); //if(!data) return NULL; } if(n_task_alloc == n_task){ diff --git a/proc/readproc.h b/proc/readproc.h index bf554be2..2d94afaa 100644 --- a/proc/readproc.h +++ b/proc/readproc.h @@ -113,7 +113,9 @@ typedef struct proc_t { char **environ, // (special) environment string vector (/proc/#/environ) **cmdline, // (special) command line string vector (/proc/#/cmdline) - **cgroup; // (special) cgroup string vector (/proc/#/cgroup) + **cgroup, // (special) cgroup string vector (/proc/#/cgroup) + *supgid, // status supplementary gids as comma delimited str + *supgrp; // supp grp names as comma delimited str, derived from supgid char // Be compatible: Digital allows 16 and NT allows 14 ??? euser[P_G_SZ], // stat(),status effective user name @@ -180,7 +182,7 @@ typedef struct PROCTAB { unsigned pathlen; // length of string in the above (w/o '\0') } PROCTAB; -// initialize a PROCTAB structure holding needed call-to-call persistent data +// Initialize a PROCTAB structure holding needed call-to-call persistent data extern PROCTAB* openproc(int flags, ... /* pid_t*|uid_t*|dev_t*|char* [, int n] */ ); typedef struct proc_data_t { @@ -198,13 +200,21 @@ extern proc_data_t *readproctab2(int(*want_proc)(proc_t *buf), int(*want_task)(p // table subset satisfying the constraints of flags and the optional PID list. // Free allocated memory with exit(). Access via tab[N]->member. The pointer // list is NULL terminated. - extern proc_t** readproctab(int flags, ... /* same as openproc */ ); -// clean-up open files, etc from the openproc() +// Clean-up open files, etc from the openproc() extern void closeproc(PROCTAB* PT); -// retrieve the next process matching the criteria set by the openproc() +// Retrieve the next process or task matching the criteria set by the openproc(). +// +// Note: When NULL is used as the readproc 'p' or readtask 't' parameter, +// the library will allocate the necessary proc_t storage. +// +// Alternately, you may provide your own reuseable buffer address +// in which case that buffer *MUST* be initialized to zero one time +// only before first use. Thereafter, the library will manage such +// a passed proc_t, freeing any additional acquired memory associated +// with the previous process or thread. extern proc_t* readproc(PROCTAB *restrict const PT, proc_t *restrict p); extern proc_t* readtask(PROCTAB *restrict const PT, const proc_t *restrict const p, proc_t *restrict t); @@ -213,11 +223,10 @@ extern int read_cmdline(char *restrict const dst, unsigned sz, unsigned pid); extern void look_up_our_self(proc_t *p); -// deallocate space allocated by readproc - +// Deallocate space allocated by readproc extern void freeproc(proc_t* p); -//fill out a proc_t for a single task +// Fill out a proc_t for a single task extern proc_t * get_proc_stats(pid_t pid, proc_t *p); // openproc/readproctab: @@ -244,7 +253,8 @@ extern proc_t * get_proc_stats(pid_t pid, proc_t *p); #define PROC_FILLWCHAN 0x0080 // look up WCHAN name #define PROC_FILLARG 0x0100 // alloc and fill in `cmdline' #define PROC_FILLCGROUP 0x0200 // alloc and fill in `cgroup` -#define PROC_FILLOOM 0x0400 // alloc and fill in oom_score, oom_adj +#define PROC_FILLSUPGRP 0x0400 // resolve supplementary group id -> group name +#define PROC_FILLOOM 0x0800 // alloc and fill in oom_score, oom_adj #define PROC_LOOSE_TASKS 0x2000 // threat threads as if they were processes diff --git a/proc/sysinfo.c b/proc/sysinfo.c index 40487aa9..c5f3298b 100644 --- a/proc/sysinfo.c +++ b/proc/sysinfo.c @@ -947,6 +947,9 @@ out: void cpuinfo (void) { // ought to count CPUs in /proc/stat instead of relying // on glibc, which foolishly tries to parse /proc/cpuinfo + // note: that may have been the case but now /proc/stat + // is the default source. parsing of /proc/cpuinfo + // only occurs if the open on /proc/stat fails // // SourceForge has an old Alpha running Linux 2.2.20 that // appears to have a non-SMP kernel on a 2-way SMP box. diff --git a/ps/display.c b/ps/display.c index 035a8840..acec0e00 100644 --- a/ps/display.c +++ b/ps/display.c @@ -327,61 +327,46 @@ static int want_this_proc_pcpu(proc_t *buf){ /***** just display */ static void simple_spew(void){ - proc_t buf; + static proc_t buf, buf2; // static avoids memset PROCTAB* ptp; + ptp = openproc(needs_for_format | needs_for_sort | needs_for_select | needs_for_threads); if(!ptp) { fprintf(stderr, "Error: can not access /proc.\n"); exit(1); } - memset(&buf, '#', sizeof(proc_t)); switch(thread_flags & (TF_show_proc|TF_loose_tasks|TF_show_task)){ case TF_show_proc: // normal non-thread output while(readproc(ptp,&buf)){ if(want_this_proc(&buf)){ show_one_proc(&buf, proc_format_list); } - if(buf.cmdline) free((void*)*buf.cmdline); // ought to reuse - if(buf.environ) free((void*)*buf.environ); // ought to reuse - if(buf.cgroup) free((void*)*buf.cgroup); } break; case TF_show_proc|TF_loose_tasks: // H option while(readproc(ptp,&buf)){ - proc_t buf2; // must still have the process allocated while(readtask(ptp,&buf,&buf2)){ if(!want_this_proc(&buf)) continue; show_one_proc(&buf2, task_format_list); } - if(buf.cmdline) free((void*)*buf.cmdline); // ought to reuse - if(buf.environ) free((void*)*buf.environ); // ought to reuse - if(buf.cgroup) free((void*)*buf.cgroup); } break; case TF_show_proc|TF_show_task: // m and -m options while(readproc(ptp,&buf)){ if(want_this_proc(&buf)){ - proc_t buf2; show_one_proc(&buf, proc_format_list); // must still have the process allocated while(readtask(ptp,&buf,&buf2)) show_one_proc(&buf2, task_format_list); } - if(buf.cmdline) free((void*)*buf.cmdline); // ought to reuse - if(buf.environ) free((void*)*buf.environ); // ought to reuse - if(buf.cgroup) free((void*)*buf.cgroup); } break; case TF_show_task: // -L and -T options while(readproc(ptp,&buf)){ if(want_this_proc(&buf)){ - proc_t buf2; // must still have the process allocated while(readtask(ptp,&buf,&buf2)) show_one_proc(&buf2, task_format_list); } - if(buf.cmdline) free((void*)*buf.cmdline); // ought to reuse - if(buf.environ) free((void*)*buf.environ); // ought to reuse - if(buf.cgroup) free((void*)*buf.cgroup); } break; } @@ -438,16 +423,10 @@ static void show_proc_array(PROCTAB *restrict ptp, int n){ while(n--){ if(thread_flags & TF_show_proc) show_one_proc(*p, proc_format_list); if(thread_flags & TF_show_task){ - proc_t buf2; + static proc_t buf2; // static avoids memset // must still have the process allocated while(readtask(ptp,*p,&buf2)) show_one_proc(&buf2, task_format_list); - // must not attempt to free cmdline and environ } - /* no point freeing any of this -- won't need more mem */ -// if((*p)->cmdline) free((void*)*(*p)->cmdline); -// if((*p)->environ) free((void*)*(*p)->environ); -// memset(*p, '%', sizeof(proc_t)); /* debug */ -// free(*p); p++; } } @@ -464,9 +443,6 @@ static void show_tree(const int self, const int n, const int level, const int ha forest_prefix[level] = '\0'; } show_one_proc(processes[self],format_list); /* first show self */ - /* no point freeing any of this -- won't need more mem */ -// if(processes[self]->cmdline) free((void*)*processes[self]->cmdline); -// if(processes[self]->environ) free((void*)*processes[self]->environ); for(;;){ /* look for children */ if(i >= n) return; /* no children */ if(processes[i]->ppid == processes[self]->XXXID) break; diff --git a/ps/output.c b/ps/output.c index 6063c903..46baa08a 100644 --- a/ps/output.c +++ b/ps/output.c @@ -38,7 +38,7 @@ * * Table 5 could go in a file with the output functions. */ - + #include #include #include @@ -360,7 +360,7 @@ static int pr_argcom(char *restrict const outbuf, const proc_t *restrict const p static int pr_cgroup(char *restrict const outbuf,const proc_t *restrict const pp) { int rightward = max_rightward; - + if(pp->cgroup) { escaped_copy(outbuf, *pp->cgroup, OUTBUF_SIZE, &rightward); return max_rightward-rightward; @@ -373,7 +373,7 @@ static int pr_cgroup(char *restrict const outbuf,const proc_t *restrict const pp static int pr_fname(char *restrict const outbuf, const proc_t *restrict const pp){ char *endp = outbuf; int rightward = max_rightward; - + if(forest_prefix){ int fh = forest_helper(outbuf); endp += fh; @@ -381,7 +381,7 @@ static int pr_fname(char *restrict const outbuf, const proc_t *restrict const pp } if (rightward>8) /* 8=default, but forest maybe feeds more */ rightward = 8; - + endp += escape_str(endp, pp->cmd, OUTBUF_SIZE, &rightward); //return endp - outbuf; return max_rightward-rightward; @@ -1022,10 +1022,10 @@ static int do_pr_name(char *restrict const outbuf, const char *restrict const na if(!user_is_number){ int rightward = OUTBUF_SIZE; /* max cells */ int len; /* real cells */ - + escape_str(outbuf, name, OUTBUF_SIZE, &rightward); len = OUTBUF_SIZE-rightward; - + if(len <= (int)max_rightward) return len; /* returns number of cells */ } @@ -1072,11 +1072,23 @@ static int pr_nlwp(char *restrict const outbuf, const proc_t *restrict const pp) static int pr_sess(char *restrict const outbuf, const proc_t *restrict const pp){ return snprintf(outbuf, COLWID, "%u", pp->session); } + +static int pr_supgid(char *restrict const outbuf, const proc_t *restrict const pp){ + int rightward = max_rightward; + escaped_copy(outbuf, pp->supgid ? pp->supgid : "n/a", OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + +static int pr_supgrp(char *restrict const outbuf, const proc_t *restrict const pp){ + int rightward = max_rightward; + escaped_copy(outbuf, pp->supgrp ? pp->supgrp : "n/a", OUTBUF_SIZE, &rightward); + return max_rightward-rightward; +} + static int pr_tpgid(char *restrict const outbuf, const proc_t *restrict const pp){ return snprintf(outbuf, COLWID, "%d", pp->tpgid); } - /* SGI uses "cpu" to print the processor ID with header "P" */ static int pr_sgi_p(char *restrict const outbuf, const proc_t *restrict const pp){ /* FIXME */ if(pp->state == 'R') return snprintf(outbuf, COLWID, "%d", pp->processor); @@ -1241,7 +1253,9 @@ static int pr_t_left2(char *restrict const outbuf, const proc_t *restrict const #define GRP PROC_FILLGRP /* gid_t -> group names */ #define WCH PROC_FILLWCHAN /* do WCHAN lookup */ +#define SGRP PROC_FILLSTATUS | PROC_FILLSUPGRP /* supgid -> supgrp (names) */ #define CGRP PROC_FILLCGROUP | PROC_EDITCGRPCVT /* read cgroup */ + /* TODO * pull out annoying BSD aliases into another table (to macro table?) * add sorting functions here (to unify names) @@ -1437,6 +1451,8 @@ static const format_struct format_array[] = { {"status", "STATUS", pr_nop, sr_nop, 6, 0, DEC, AN|RIGHT}, {"stime", "STIME", pr_stime, sr_stime, 5, 0, XXX, ET|RIGHT}, /* was 6 wide */ {"suid", "SUID", pr_suid, sr_suid, 5, 0, LNx, ET|RIGHT}, +{"supgid", "SUPGID", pr_supgid, sr_nop, 20, 0, LNX, PO|UNLIMITED}, +{"supgrp", "SUPGRP", pr_supgrp, sr_nop, 40,SGRP, LNX, PO|UNLIMITED}, {"suser", "SUSER", pr_suser, sr_suser, 8, USR, LNx, ET|USER}, {"svgid", "SVGID", pr_sgid, sr_sgid, 5, 0, XXX, ET|RIGHT}, {"svgroup", "SVGROUP", pr_sgroup, sr_sgroup, 8, GRP, LNX, ET|USER}, @@ -1797,14 +1813,14 @@ void show_one_proc(const proc_t *restrict const p, const format_node *restrict f } } max_leftward = fmt->width + actual - correct; /* TODO check this */ - + // fprintf(stderr, "cols: %d, max_rightward: %d, max_leftward: %d, actual: %d, correct: %d\n", // active_cols, max_rightward, max_leftward, actual, correct); - + /* prepare data and calculate leftpad */ if(likely(p) && likely(fmt->pr)) amount = (*fmt->pr)(outbuf,p); else amount = strlen(strcpy(outbuf, fmt->name)); /* AIX or headers */ - + switch((fmt->flags) & CF_JUST_MASK){ case 0: /* for AIX, assigned outside this file */ leftpad = 0; @@ -1869,7 +1885,7 @@ void show_one_proc(const proc_t *restrict const p, const format_node *restrict f /* real size -- don't forget in 'amount' is number of cells */ sz = strlen(outbuf); - + /* print data, set x position stuff */ if(unlikely(!fmt->next)){ /* Last column. Write padding + data + newline all together. */ diff --git a/ps/ps.1 b/ps/ps.1 index 6ce6e0f2..18c90c56 100644 --- a/ps/ps.1 +++ b/ps/ps.1 @@ -1321,6 +1321,18 @@ suid SUID T{ saved user\ ID. (alias\ \fBsvuid\fR). T} +supgid SUPGID T{ +group ids of supplementary groups, if any. +See +.BR getgroups (2). +T} + +supgrp SUPGRP T{ +group names of supplementary groups, if any. +See +.BR getgroups (2). +T} + suser SUSER T{ saved user name. This will be the textual user\ ID, if\ it can be obtained and the field width permits, diff --git a/top.1 b/top.1 index e88eec22..1e4f87eb 100644 --- a/top.1 +++ b/top.1 @@ -435,7 +435,7 @@ Many different hierarchies of cgroups can exist simultaneously on a system and each hierarchy is attached to one or more subsystems. A subsystem represents a single resource. -\*(NT The 'CGROUPS' field/column, unlike most columns, is not fixed-width. +\*(NT The 'CGROUPS' field, unlike most columns, is not fixed-width. When displayed, it plus any other variable width columns will be allocated all remaining screen width (up to the maximum \*(WX characters). @@ -461,7 +461,7 @@ fit in this field's current width. That width depends upon other fields selected, their order and the current screen width. -\*(NT The 'COMMAND' field/column, unlike most columns, is not fixed-width. +\*(NT The 'COMMAND' field, unlike most columns, is not fixed-width. When displayed, it plus any other variable width columns will be allocated all remaining screen width (up to the maximum \*(WX characters). @@ -621,15 +621,35 @@ login shell. The\fI saved\fR user ID. .TP 4 -27.\fB SUSER \*(Em Saved User Name \fR +27.\fB SUPGIDS \*(Em Supplementary Group IDs \fR +The IDs of any supplementary group(s) established at login or +inherited from a task's parent. +They are displayed in a comma delimited list. + +\*(NT The 'SUPGIDS' field, unlike most columns, is not fixed-width. +When displayed, it plus any other variable width columns will be allocated +all remaining screen width (up to the maximum \*(WX characters). + +.TP 4 +28.\fB SUPGRPS \*(Em Supplementary Group Names \fR +The names of any supplementary group(s) established at login or +inherited from a task's parent. +They are displayed in a comma delimited list. + +\*(NT The 'SUPGRPS' field, unlike most columns, is not fixed-width. +When displayed, it plus any other variable width columns will be allocated +all remaining screen width (up to the maximum \*(WX characters). + +.TP 4 +29.\fB SUSER \*(Em Saved User Name \fR The\fI saved\fR user name. .TP 4 -28.\fB SWAP \*(Em Swapped Size (kb) \fR +30.\fB SWAP \*(Em Swapped Size (kb) \fR The non-resident portion of a task's address space. .TP 4 -29.\fB TIME \*(Em \*(PU Time \fR +31.\fB TIME \*(Em \*(PU Time \fR Total \*(PU time the task has used since it started. When 'Cumulative mode' is \*O, each process is listed with the \*(Pu time that it and its dead children have used. @@ -637,19 +657,19 @@ You toggle 'Cumulative mode' with 'S', which is both a \*(CO and an \*(CI. \*(XC 'S' \*(CI for additional information regarding this mode. .TP 4 -30.\fB TIME+ \*(Em \*(PU Time, hundredths \fR +32.\fB TIME+ \*(Em \*(PU Time, hundredths \fR The same as 'TIME', but reflecting more granularity through hundredths of a second. .TP 4 -31.\fB TPGID \*(Em Tty Process Group Id \fR +33.\fB TPGID \*(Em Tty Process Group Id \fR The process group ID of the foreground process for the connected tty, or -1 if a process is not connected to a terminal. By convention, this value equals the process ID (\*(Xa PID) of the the process group leader (\*(Xa PGRP). .TP 4 -32.\fB TTY \*(Em Controlling Tty \fR +34.\fB TTY \*(Em Controlling Tty \fR The name of the controlling terminal. This is usually the device (serial port, pty, etc.) from which the process was started, and which it uses for input or output. @@ -657,21 +677,21 @@ However, a task need not be associated with a terminal, in which case you'll see '?' displayed. .TP 4 -33.\fB UID \*(Em User Id \fR +35.\fB UID \*(Em User Id \fR The\fI effective\fR user ID of the task's owner. .TP 4 -34.\fB USER \*(Em User Name \fR +36.\fB USER \*(Em User Name \fR The\fI effective\fR user name of the task's owner. .TP 4 -35.\fB VIRT \*(Em Virtual Memory Size (kb) \fR +37.\fB VIRT \*(Em Virtual Memory Size (kb) \fR The total amount of \*(MV used by the task. It includes all code, data and shared libraries plus pages that have been swapped out and pages that have been mapped but not used. .TP 4 -36.\fB WCHAN \*(Em Sleeping in Function \fR +38.\fB WCHAN \*(Em Sleeping in Function \fR Depending on the availability of the kernel link map ('System.map'), this field will show the name or the address of the kernel function in which the task is currently sleeping. @@ -682,7 +702,7 @@ By displaying this field, \*(We's own working set could be increased by over Should that occur, your only means of reducing that overhead will be to stop and restart \*(We. -\*(NT The 'WCHAN' field/column, unlike most columns, is not fixed-width. +\*(NT The 'WCHAN' field, unlike most columns, is not fixed-width. When displayed, it plus any other variable width columns will be allocated all remaining screen width (up to the maximum \*(WX characters). diff --git a/top.c b/top.c index 2a000280..9487ed7b 100644 --- a/top.c +++ b/top.c @@ -216,6 +216,8 @@ SCB_NUMx(PID, tid) SCB_NUMx(PPD, ppid) SCB_NUMx(PRI, priority) SCB_NUM1(RES, resident) // also serves MEM ! +SCB_STRX(SGD, supgid) +SCB_STRS(SGN, supgrp) SCB_NUM1(SHR, share) SCB_NUM1(SID, session) SCB_NUMx(STA, state) @@ -1164,6 +1166,7 @@ static inline int user_matched (WIN_t *q, const proc_t *p) { #define L_EUSER PROC_FILLUSR #define L_OUSER PROC_FILLSTATUS | PROC_FILLUSR #define L_EGROUP PROC_FILLSTATUS | PROC_FILLGRP +#define L_SUPGRP PROC_FILLSTATUS | PROC_FILLSUPGRP // make 'none' non-zero (used to be important to Frames_libflags) #define L_NONE PROC_SPARE_1 // from either 'stat' or 'status' (preferred), via bits not otherwise used @@ -1231,8 +1234,10 @@ static FLD_t Fieldstab[] = { #else { "Flags ", "%08lx ", -1, -1, SF(FLG), L_stat, "Task Flags " }, #endif - // next entry's like P_CMD/P_WCH, and '.head' must be same length -- they share varcolsz - { "CGROUPS ", NULL, -1, -1, SF(CGR), L_CGROUP, "Control Groups" } + // next 3 entries as P_CMD/P_WCH: '.head' must be same length -- they share varcolsz + { "CGROUPS ", NULL, -1, -1, SF(CGR), L_CGROUP, "Control Groups" }, + { "SUPGIDS ", NULL, -1, -1, SF(SGD), L_status, "Supp Groups IDs" }, + { "SUPGRPS ", NULL, -1, -1, SF(SGN), L_SUPGRP, "Supp Groups Names" } #ifdef OOMEM_ENABLE #define L_oom PROC_FILLOOM ,{ "Adj ", "%3d ", -1, -1, SF(OOA), L_oom, "oom_adjustment (2^X)" } @@ -1506,15 +1511,15 @@ static void calibrate_fields (void) { * ( xPRFX has pos 2 & 10 for 'extending' when at minimums ) * * The first 4 screen rows are reserved for explanatory text. - * Thus, with our current 36 fields, a maximum of 6 columns and + * Thus, with our current 38 fields, a maximum of 6 columns and * 1 space between columns, a tty will still remain useable under * these extremes: * rows cols displayed * ---- ---- ------------------ - * 10 66 xPRFX only - * 10 198 full xPRFX + xSUFX - * 22 22 xPRFX only - * 22 66 full xPRFX + xSUFX + * 11 66 xPRFX only (w/ room for +4) + * 11 198 full xPRFX + xSUFX (w/ room for +4) + * 23 22 xPRFX only + * 23 66 full xPRFX + xSUFX * ( if not, the user deserves our most cryptic messages ) */ static void display_fields (int focus, int extend) { @@ -1904,13 +1909,11 @@ static void prochlp (proc_t *this) { static proc_t **procs_refresh (proc_t **ppt) { #define PTRsz sizeof(proc_t *) #define ENTsz sizeof(proc_t) - static int threadshown = 0; // thread hack optimization static unsigned savmax = 0; // first time, Bypass: (i) proc_t *ptask = (proc_t *)-1; // first time, Force: (ii) unsigned curmax = 0; // every time (jeeze) PROCTAB* PT; proc_t *pthrd; // for thread hack - unsigned i; // ditto prochlp(NULL); // prep for a new frame if (NULL == (PT = openproc(Frames_libflags, Monpids))) @@ -1919,17 +1922,6 @@ static proc_t **procs_refresh (proc_t **ppt) { // i) Allocated Chunks: *Existing* table; refresh + reuse if (!Thread_mode) { while (curmax < savmax) { - if (ppt[curmax]->cmdline || ppt[curmax]->cgroup) { - if (threadshown) { // skip if never used (see note below) - for (i = curmax + 1; i < savmax; i++) { - if (ppt[i]->cmdline == ppt[curmax]->cmdline) ppt[i]->cmdline = NULL; - if (ppt[i]->cgroup == ppt[curmax]->cgroup) ppt[i]->cgroup = NULL; - } - } - if (ppt[curmax]->cmdline) free(*ppt[curmax]->cmdline); - if (ppt[curmax]->cgroup) free(*ppt[curmax]->cgroup); - ppt[curmax]->cmdline = ppt[curmax]->cgroup = NULL; - } if (!(ptask = readproc(PT, ppt[curmax]))) break; prochlp(ptask); // tally & complete this proc_t ++curmax; @@ -1939,23 +1931,11 @@ static proc_t **procs_refresh (proc_t **ppt) { while (curmax < savmax) { if (!(ptask = readproc(PT, NULL))) break; while (curmax < savmax) { - if (ppt[curmax]->cmdline || ppt[curmax]->cgroup) { - /* note: threads share some of the same storage, so we must look - through the rest of our table for duplicate ref's... */ - for (i = curmax + 1; i < savmax; i++) { - if (ppt[i]->cmdline == ppt[curmax]->cmdline) ppt[i]->cmdline = NULL; - if (ppt[i]->cgroup == ppt[curmax]->cgroup) ppt[i]->cgroup = NULL; - } /* ...but free only once ! */ - if (ppt[curmax]->cmdline) free(*ppt[curmax]->cmdline); - if (ppt[curmax]->cgroup) free(*ppt[curmax]->cgroup); - ppt[curmax]->cmdline = ppt[curmax]->cgroup = NULL; - } if (!(pthrd = readtask(PT, ptask, ppt[curmax]))) break; - threadshown = 1; prochlp(pthrd); // tally & complete this thread ++curmax; } - free(ptask); // readproc's proc_t not needed + freeproc(ptask); // readproc's proc_t not needed } } @@ -1977,11 +1957,10 @@ static proc_t **procs_refresh (proc_t **ppt) { for (;;) { ppt = alloc_r(ppt, (curmax + 1) * PTRsz); if (!(pthrd = readtask(PT, ptask, NULL))) break; - threadshown = 1; prochlp(pthrd); // tally & complete this thread ppt[curmax++] = pthrd; } - free(ptask); // readproc's proc_t not needed + freeproc(ptask); // readproc's proc_t not needed } } } @@ -2191,7 +2170,7 @@ static void parse_args (char **args) { . bunched args are actually handled properly and none are ignored . we tolerate NO whitespace and NO switches -- maybe too tolerant? */ static const char usage_str[] = - " -hv | -bcHiSs -d delay -n iters -u|U user | -p pid[,pid] -w [cols]"; + " -hv | -bcHiSs -d delay -n limit -u|U user | -p pid[,pid] -w [cols]"; static const char sel_error[] = "conflicting process selections (U/p/u)"; static const char numbs_str[] = "+,-.0123456789"; float tmp_delay = MAXFLOAT; @@ -2230,7 +2209,7 @@ static void parse_args (char **args) { break; case 'h': case 'v': case 'V': - fprintf(stdout, "\t%s\nusage:\t%s%s\n", procps_version, Myname, usage_str); + fprintf(stdout, "\t%s\nusage:\t%s%s", procps_version, Myname, usage_str); bye_bye(NULL); case 'i': TOGw(Curwin, Show_IDLEPS); @@ -3319,6 +3298,12 @@ static void task_show (const WIN_t *q, const proc_t *p) { case P_RES: makeCOL(scale_num(pages2K(p->resident), w, s)); break; + case P_SGD: + makeVAR(p->supgid ? p->supgid : "n/a"); + break; + case P_SGN: + makeVAR(p->supgrp ? p->supgrp : "n/a"); + break; case P_SHR: makeCOL(scale_num(pages2K(p->share), w, s)); break; diff --git a/top.h b/top.h index cba3dea6..34522922 100644 --- a/top.h +++ b/top.h @@ -129,6 +129,7 @@ enum pflag { P_MEM, P_VRT, P_SWP, P_RES, P_COD, P_DAT, P_SHR, P_FL1, P_FL2, P_DRT, P_STA, P_CMD, P_WCH, P_FLG, P_CGR, + P_SGD, P_SGN, #ifdef OOMEM_ENABLE P_OOA, P_OOM, #endif @@ -367,6 +368,10 @@ typedef struct WIN_t { if (!(*P)->v || !(*Q)->v) return SORT_eq; \ return Frame_srtflg * STRSORTCMP((*Q)->v[0], (*P)->v[0]); } \ return Frame_srtflg * STRSORTCMP((*Q)->s, (*P)->s); } +#define SCB_STRX(f,s) \ + int strverscmp(const char *s1, const char *s2); \ + static int SCB_NAME(f) (const proc_t **P, const proc_t **Q) { \ + return Frame_srtflg * strverscmp((*Q)->s, (*P)->s); } /* * The following two macros are used to 'inline' those portions of the -- 2.40.0