]> granicus.if.org Git - procps-ng/blob - src/top/top.c
top: avoid '%Cpu' distortion resulting from keystrokes
[procps-ng] / src / top / top.c
1 /* top.c - Source file:         show Linux processes */
2 /*
3  * Copyright © 2002-2023 Jim Warner <james.warner@comcast.net
4  *
5  * This file may be used subject to the terms and conditions of the
6  * GNU Library General Public License Version 2, or any later version
7  * at your option, as published by the Free Software Foundation.
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU Library General Public License for more details.
12  */
13 /* For contributions to this program, the author wishes to thank:
14  *    Craig Small, <csmall@dropbear.xyz>
15  *    Albert D. Cahalan, <albert@users.sf.net>
16  *    Sami Kerola, <kerolasa@iki.fi>
17  */
18
19 #include <ctype.h>
20 #include <curses.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <float.h>
24 #include <getopt.h>
25 #include <limits.h>
26 #include <pwd.h>
27 #include <pthread.h>
28 #include <semaphore.h>
29 #include <signal.h>
30 #include <stdarg.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <term.h>            // foul sob, defines all sorts of stuff...
35 #undef    raw
36 #undef    tab
37 #undef    TTY
38 #include <termios.h>
39 #include <time.h>
40 #include <unistd.h>
41 #include <wchar.h>
42
43 #include <sys/ioctl.h>
44 #include <sys/resource.h>
45 #include <sys/select.h>      // also available via <sys/types.h>
46 #include <sys/stat.h>
47 #include <sys/time.h>
48 #include <sys/types.h>       // also available via <stdlib.h>
49
50 #include "fileutils.h"
51 #include "signals.h"
52 #include "nls.h"
53
54 #include "meminfo.h"
55 #include "misc.h"
56 #include "pids.h"
57 #include "stat.h"
58
59 #include "top.h"
60 #include "top_nls.h"
61
62 /*######  Miscellaneous global stuff  ####################################*/
63
64         /* The original and new terminal definitions
65            (only set when not in 'Batch' mode) */
66 static struct termios Tty_original,    // our inherited terminal definition
67 #ifdef TERMIOS_ONLY
68                       Tty_tweaked,     // for interactive 'line' input
69 #endif
70                       Tty_raw;         // for unsolicited input
71 static int Ttychanged = 0;
72
73         /* Last established cursor state/shape */
74 static const char *Cursor_state = "";
75
76         /* Program name used in error messages and local 'rc' file name */
77 static char *Myname;
78
79         /* Our constant sigset, so we need initialize it but once */
80 static sigset_t Sigwinch_set;
81
82         /* The 'local' config file support */
83 static char  Rc_name [OURPATHSZ];
84 static RCF_t Rc = DEF_RCFILE;
85 static int   Rc_questions;
86 static int   Rc_compatibilty;
87
88         /* SMP, Irix/Solaris mode, Linux 2.5.xx support (and beyond) */
89 static long        Hertz;
90 static int         Cpu_cnt;
91 static float       Cpu_pmax;
92 static const char *Cpu_States_fmts;
93
94         /* Specific process id monitoring support */
95 static int Monpids [MONPIDMAX+1] = { 0 };
96 static int Monpidsidx = 0;
97
98         /* Current screen dimensions.
99            note: the number of processes displayed is tracked on a per window
100                  basis (see the WIN_t).  Max_lines is the total number of
101                  screen rows after deducting summary information overhead. */
102         /* Current terminal screen size. */
103 static int   Screen_cols, Screen_rows, Max_lines;
104
105         /* These 'SCREEN_ROWS', 'BOT_ and 'Bot_' guys are used
106            in managing the special separate bottom 'window' ... */
107 #define      SCREEN_ROWS ( Screen_rows - Bot_rsvd )
108 #define      BOT_PRESENT ( Bot_what != 0 )
109 #define      BOT_ITEMMAX  10           // Bot_item array's max size
110 #define      BOT_MSGSMAX  10           // total entries for Msg_tab
111 #define      BOT_UNFOCUS  -1           // tab focus not established
112         // negative 'item' values won't be seen by build_headers() ...
113 #define      BOT_DELIMIT  -1           // fencepost with item array
114 #define      BOT_ITEM_NS  -2           // data for namespaces req'd
115 #define      BOT_MSG_LOG  -3           // show the most recent msgs
116         // next 4 are used when toggling window contents
117 #define      BOT_SEP_CMA  ','
118 #define      BOT_SEP_SLS  '/'
119 #define      BOT_SEP_SMI  ';'
120 #define      BOT_SEP_SPC  ' '
121         // 1 for horizontal separator
122 #define      BOT_RSVD  1
123 #define      BOT_KEEP  Bot_show_func = NULL
124 #define      BOT_TOSS  do { Bot_show_func = NULL; Bot_item[0] = BOT_DELIMIT; \
125                 Bot_task = Bot_rsvd = Bot_what = 0; \
126                 Bot_indx = BOT_UNFOCUS; \
127                 } while(0)
128 static int   Bot_task,
129              Bot_what,
130              Bot_rsvd,
131              Bot_indx = BOT_UNFOCUS,
132              Bot_item[BOT_ITEMMAX] = { BOT_DELIMIT };
133 static char  Bot_sep,
134             *Bot_head,
135              Bot_buf[BOTBUFSIZ];       // the 'environ' can be huge
136 typedef int(*BOT_f)(const void *, const void *);
137 static BOT_f Bot_focus_func;
138 static void(*Bot_show_func)(void);
139
140         /* This is really the number of lines needed to display the summary
141            information (0 - nn), but is used as the relative row where we
142            stick the cursor between frames. */
143 static int Msg_row;
144
145         /* Global/Non-windows mode stuff that is NOT persistent */
146 static int Batch = 0,           // batch mode, collect no input, dumb output
147            Loops = -1,          // number of iterations, -1 loops forever
148            Secure_mode = 0,     // set if some functionality restricted
149            Width_mode = 0,      // set w/ 'w' - potential output override
150            Thread_mode = 0;     // set w/ 'H' - show threads vs. tasks
151
152         /* Unchangeable cap's stuff built just once (if at all) and
153            thus NOT saved in a WIN_t's RCW_t.  To accommodate 'Batch'
154            mode, they begin life as empty strings so the overlying
155            logic need not change ! */
156 static char  Cap_clr_eol    [CAPBUFSIZ] = "",    // global and/or static vars
157              Cap_nl_clreos  [CAPBUFSIZ] = "",    // are initialized to zeros!
158              Cap_clr_scr    [CAPBUFSIZ] = "",    // the assignments used here
159              Cap_curs_norm  [CAPBUFSIZ] = "",    // cost nothing but DO serve
160              Cap_curs_huge  [CAPBUFSIZ] = "",    // to remind people of those
161              Cap_curs_hide  [CAPBUFSIZ] = "",    // batch requirements!
162              Cap_clr_eos    [CAPBUFSIZ] = "",
163              Cap_home       [CAPBUFSIZ] = "",
164              Cap_norm       [CAPBUFSIZ] = "",
165              Cap_reverse    [CAPBUFSIZ] = "",
166              Caps_off       [CAPBUFSIZ] = "",
167              Caps_endline   [SMLBUFSIZ] = "";
168 #ifndef RMAN_IGNORED
169 static char  Cap_rmam       [CAPBUFSIZ] = "",
170              Cap_smam       [CAPBUFSIZ] = "";
171         /* set to 1 if writing to the last column would be troublesome
172            (we don't distinguish the lowermost row from the other rows) */
173 static int   Cap_avoid_eol = 0;
174 #endif
175 static int   Cap_can_goto = 0;
176
177         /* Some optimization stuff, to reduce output demands...
178            The Pseudo_ guys are managed by adj_geometry and frame_make.  They
179            are exploited in a macro and represent 90% of our optimization.
180            The Stdout_buf is transparent to our code and regardless of whose
181            buffer is used, stdout is flushed at frame end or if interactive. */
182 static char  *Pseudo_screen;
183 static int    Pseudo_row = PROC_XTRA;
184 static size_t Pseudo_size;
185 #ifndef OFF_STDIOLBF
186         // less than stdout's normal buffer but with luck mostly '\n' anyway
187 static char  Stdout_buf[2048];
188 #endif
189
190         /* Our four WIN_t's, and which of those is considered the 'current'
191            window (ie. which window is associated with any summ info displayed
192            and to which window commands are directed) */
193 static WIN_t  Winstk [GROUPSMAX];
194 static WIN_t *Curwin;
195
196         /* Frame oriented stuff that can't remain local to any 1 function
197            and/or that would be too cumbersome managed as parms,
198            and/or that are simply more efficiently handled as globals
199            [ 'Frames_...' (plural) stuff persists beyond 1 frame ]
200            [ or are used in response to async signals received ! ] */
201 static volatile int Frames_signal;     // time to rebuild all column headers
202 static float        Frame_etscale;     // so we can '*' vs. '/' WHEN 'pcpu'
203
204         /* Support for automatically sized fixed-width column expansions.
205          * (hopefully, the macros help clarify/document our new 'feature') */
206 static int Autox_array [EU_MAXPFLGS],
207            Autox_found;
208 #define AUTOX_NO      EU_MAXPFLGS
209 #define AUTOX_COL(f)  if (EU_MAXPFLGS > f && f >= 0) Autox_array[f] = Autox_found = 1
210 #define AUTOX_MODE   (0 > Rc.fixed_widest)
211
212         /* Support for scale_mem and scale_num (to avoid duplication. */
213 #ifdef CASEUP_SUFIX                                                // nls_maybe
214    static char Scaled_sfxtab[] =  { 'K', 'M', 'G', 'T', 'P', 'E', 0 };
215 #else                                                              // nls_maybe
216    static char Scaled_sfxtab[] =  { 'k', 'm', 'g', 't', 'p', 'e', 0 };
217 #endif
218
219         /* Support for NUMA Node display plus node expansion and targeting */
220 #ifndef OFF_STDERROR
221 static int Stderr_save = -1;
222 #endif
223 static int Numa_node_tot;
224 static int Numa_node_sel = -1;
225
226         /* Support for Graphing of the View_STATES ('t') and View_MEMORY ('m')
227            commands -- which are now both 4-way toggles */
228 #define GRAPH_length_max  100  // the actual bars or blocks
229 #define GRAPH_length_min   10  // the actual bars or blocks
230 #define GRAPH_prefix_std   25  // '.......: 100.0/100.0 100['
231 #define GRAPH_prefix_abv   12  // '.......:100['
232 #define GRAPH_suffix        2  // '] ' (bracket + trailing space)
233         // first 3 more static (adj_geometry), last 3 volatile (sum_tics/do_memory)
234 struct graph_parms {
235    float adjust;               // bars/blocks scaling factor
236    int   length;               // scaled length (<= GRAPH_length_max)
237    int   style;                // rc.graph_cpus or rc.graph_mems
238    long  total, part1, part2;  // elements to be graphed
239 };
240 static struct graph_parms *Graph_cpus, *Graph_mems;
241 static const char Graph_blks[] = "                                                                                                    ";
242 static const char Graph_bars[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
243
244         /* Support for 'Other Filters' in the configuration file */
245 static const char Osel_delim_1_txt[] = "begin: saved other filter data -------------------\n";
246 static const char Osel_delim_2_txt[] = "end  : saved other filter data -------------------\n";
247 static const char Osel_window_fmts[] = "window #%d, osel_tot=%d\n";
248 #define OSEL_FILTER   "filter="
249 static const char Osel_filterO_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%s\n";
250 static const char Osel_filterI_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%*s\n";
251
252         /* Support for adjoining display (if terminal is wide enough) */
253 #ifdef TOG4_SEP_OFF
254 static char Adjoin_sp[] =  "  ";
255 #define ADJOIN_space  (sizeof(Adjoin_sp) - 1)    // 1 for null
256 #else
257 #ifdef TOG4_SEP_STD
258 static char Adjoin_sp[] =  "~1 ~6 ";
259 #else
260 static char Adjoin_sp[] =  " ~6 ~1";
261 #endif
262 #define ADJOIN_space  (sizeof(Adjoin_sp) - 5)    // 1 for null + 4 unprintable
263 #endif
264 #define ADJOIN_limit  8
265
266         /* Support for the new library API -- acquired (if necessary)
267            at program startup and referenced throughout our lifetime. */
268         /*
269          *  --- <proc/pids.h> -------------------------------------------------- */
270 static struct pids_info *Pids_ctx;
271 static int Pids_itms_tot;                   // same as MAXTBL(Fieldstab)
272 static enum pids_item *Pids_itms;           // allocated as MAXTBL(Fieldstab)
273 static struct pids_fetch *Pids_reap;        // for reap or select
274 #define PIDSmaxt Pids_reap->counts->total   // just a little less wordy
275         // pid stack results extractor macro, where e=our EU enum, t=type, s=stack
276         // ( we'll exploit that <proc/pids.h> provided macro as much as possible )
277         // ( but many functions use their own unique tailored version for access )
278 #define PID_VAL(e,t,s) PIDS_VAL(e, t, s, Pids_ctx)
279         /*
280          *  --- <proc/stat.h> -------------------------------------------------- */
281 static struct stat_info *Stat_ctx;
282 static struct stat_reaped *Stat_reap;
283 static enum stat_item Stat_items[] = {
284    STAT_TIC_ID,             STAT_TIC_NUMA_NODE,
285    STAT_TIC_DELTA_USER,     STAT_TIC_DELTA_SYSTEM,
286    STAT_TIC_DELTA_NICE,     STAT_TIC_DELTA_IDLE,
287    STAT_TIC_DELTA_IOWAIT,   STAT_TIC_DELTA_IRQ,
288    STAT_TIC_DELTA_SOFTIRQ,  STAT_TIC_DELTA_STOLEN,
289    STAT_TIC_SUM_DELTA_USER, STAT_TIC_SUM_DELTA_SYSTEM,
290 #ifdef CORE_TYPE_NO
291    STAT_TIC_SUM_DELTA_TOTAL };
292 #else
293    STAT_TIC_SUM_DELTA_TOTAL, STAT_TIC_TYPE_CORE };
294 #endif
295 enum Rel_statitems {
296    stat_ID, stat_NU,
297    stat_US, stat_SY,
298    stat_NI, stat_IL,
299    stat_IO, stat_IR,
300    stat_SI, stat_ST,
301    stat_SUM_USR, stat_SUM_SYS,
302 #ifdef CORE_TYPE_NO
303    stat_SUM_TOT };
304 #else
305    stat_SUM_TOT, stat_COR_TYP };
306 #endif
307         // cpu/node stack results extractor macros, where e=rel enum, x=index
308 #define CPU_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->cpus->stacks[x], Stat_ctx)
309 #define NOD_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->numa->stacks[x], Stat_ctx)
310 #define TIC_VAL(e,s) STAT_VAL(e, sl_int, s, Stat_ctx)
311 #define E_CORE  1            // values for stat_COR_TYP itself
312 #define P_CORE  2            // ( 0 = unsure/unknown )
313 #define P_CORES_ONLY  2      // values of rc.core_types toggle, for filtering
314 #define E_CORES_ONLY  3      // ( 0 = Cpu shown, 1 = both CpP & CpE shown )
315         /*
316          * --- <proc/meminfo.h> ----------------------------------------------- */
317 static struct meminfo_info *Mem_ctx;
318 static struct meminfo_stack *Mem_stack;
319 static enum meminfo_item Mem_items[] = {
320    MEMINFO_MEM_FREE,       MEMINFO_MEM_USED,    MEMINFO_MEM_TOTAL,
321    MEMINFO_MEM_CACHED_ALL, MEMINFO_MEM_BUFFERS, MEMINFO_MEM_AVAILABLE,
322    MEMINFO_SWAP_TOTAL,     MEMINFO_SWAP_FREE,   MEMINFO_SWAP_USED };
323 enum Rel_memitems {
324    mem_FRE, mem_USE, mem_TOT,
325    mem_QUE, mem_BUF, mem_AVL,
326    swp_TOT, swp_FRE, swp_USE };
327         // mem stack results extractor macro, where e=rel enum
328 #define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx)
329
330         /* Support for concurrent library updates via
331            multithreaded background processes */
332 #ifdef THREADED_CPU
333 static pthread_t Thread_id_cpus;
334 static sem_t Semaphore_cpus_beg, Semaphore_cpus_end;
335 #endif
336 #ifdef THREADED_MEM
337 static pthread_t Thread_id_memory;
338 static sem_t Semaphore_memory_beg, Semaphore_memory_end;
339 #endif
340 #ifdef THREADED_TSK
341 static pthread_t Thread_id_tasks;
342 static sem_t Semaphore_tasks_beg, Semaphore_tasks_end;
343 #endif
344 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
345 static pthread_t Thread_id_main;
346 #endif
347
348         /* Support for a namespace with proc mounted subset=pid,
349            ( we'll limit our display to task information only ). */
350 static int Restrict_some = 0;
351 \f
352 /*######  Tiny useful routine(s)  ########################################*/
353
354         /*
355          * This routine simply formats whatever the caller wants and
356          * returns a pointer to the resulting 'const char' string... */
357 static const char *fmtmk (const char *fmts, ...) __attribute__((format(printf,1,2)));
358 static const char *fmtmk (const char *fmts, ...) {
359    static char buf[BIGBUFSIZ];          // with help stuff, our buffer
360    va_list va;                          // requirements now exceed 1k
361
362    va_start(va, fmts);
363    vsnprintf(buf, sizeof(buf), fmts, va);
364    va_end(va);
365    return (const char *)buf;
366 } // end: fmtmk
367
368
369         /*
370          * Interger based fieldscur version of 'strlen' */
371 static inline int mlen (const int *mem) {
372    int i;
373
374    for (i = 0; mem[i]; i++)
375       ;
376    return i;
377 } // end: mlen
378
379
380         /*
381          * Interger based fieldscur version of 'strchr' */
382 static inline int *msch (const int *mem, int obj, int max) {
383    int i;
384
385    for (i = 0; i < max; i++)
386       if (*(mem + i) == obj) return (int *)mem + i;
387    return NULL;
388 } // end: msch
389
390
391         /*
392          * This guy is just our way of avoiding the overhead of the standard
393          * strcat function (should the caller choose to participate) */
394 static inline char *scat (char *dst, const char *src) {
395    while (*dst) dst++;
396    while ((*(dst++) = *(src++)));
397    return --dst;
398 } // end: scat
399
400
401         /*
402          * This guy just facilitates Batch and protects against dumb ttys
403          * -- we'd 'inline' him but he's only called twice per frame,
404          * yet used in many other locations. */
405 static const char *tg2 (int x, int y) {
406    // it's entirely possible we're trying for an invalid row...
407    return Cap_can_goto ? tgoto(cursor_address, x, y) : "";
408 } // end: tg2
409 \f
410 /*######  Exit/Interrput routines  #######################################*/
411
412         /*
413          * Reset the tty, if necessary */
414 static void at_eoj (void) {
415    if (Ttychanged) {
416       tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original);
417       if (keypad_local) putp(keypad_local);
418       putp(tg2(0, Screen_rows));
419       putp("\n");
420 #ifdef OFF_SCROLLBK
421       if (exit_ca_mode) {
422          // this next will also replace top's most recent screen with the
423          // original display contents that were visible at our invocation
424          putp(exit_ca_mode);
425       }
426 #endif
427       putp(Cap_curs_norm);
428       putp(Cap_clr_eol);
429 #ifndef RMAN_IGNORED
430       putp(Cap_smam);
431 #endif
432    }
433    fflush(stdout);
434 #ifndef OFF_STDERROR
435    /* we gotta reverse the stderr redirect which was employed during start up
436       and needed because the two libnuma 'weak' functions were useless to us! */
437    if (-1 < Stderr_save) {
438       dup2(Stderr_save, fileno(stderr));
439       close(Stderr_save);
440       Stderr_save = -1;      // we'll be ending soon anyway but what the heck
441    }
442 #endif
443 } // end: at_eoj
444
445
446         /*
447          * The real program end */
448 static void bye_bye (const char *str) __attribute__((__noreturn__));
449 static void bye_bye (const char *str) {
450    sigset_t ss;
451
452 // POSIX.1 async-signal-safe: sigfillset, sigprocmask, pthread_sigmask
453    sigfillset(&ss);
454 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
455    pthread_sigmask(SIG_BLOCK, &ss, NULL);
456 #else
457    sigprocmask(SIG_BLOCK, &ss, NULL);
458 #endif
459    at_eoj();                 // restore tty in preparation for exit
460 #ifdef ATEOJ_RPTSTD
461 {
462    if (!str && !Frames_signal && Ttychanged) { fprintf(stderr,
463       "\n%s's Summary report:"
464       "\n\tProgram"
465       "\n\t   %s"
466       "\n\t   Hertz = %u (%u bytes, %u-bit time)"
467       "\n\t   Stat_reap->cpus->total = %d, Stat_reap->numa->total = %d"
468       "\n\t   Pids_itms_tot = %d, sizeof(struct pids_result) = %d, pids stack size = %d"
469       "\n\t   SCREENMAX = %d, ROWMINSIZ = %d, ROWMAXSIZ = %d"
470       "\n\t   PACKAGE = '%s', LOCALEDIR = '%s'"
471       "\n\tTerminal: %s"
472       "\n\t   device = %s, ncurses = v%s"
473       "\n\t   max_colors = %d, max_pairs = %d"
474       "\n\t   Cap_can_goto = %s"
475       "\n\t   Screen_cols = %d, Screen_rows = %d"
476       "\n\t   Max_lines = %d, most recent Pseudo_size = %u"
477 #ifndef OFF_STDIOLBF
478       "\n\t   Stdout_buf = %u, BUFSIZ = %u"
479 #endif
480       "\n\tWindows and Curwin->"
481       "\n\t   sizeof(WIN_t) = %u, GROUPSMAX = %d"
482       "\n\t   winname = %s, grpname = %s"
483 #ifdef CASEUP_HEXES
484       "\n\t   winflags = %08X, maxpflgs = %d"
485 #else
486       "\n\t   winflags = x%08x, maxpflgs = %d"
487 #endif
488       "\n\t   sortindx = %d, maxtasks = %d"
489       "\n\t   varcolsz = %d, winlines = %d"
490       "\n\t   strlen(columnhdr) = %d"
491       "\n\t   current fieldscur = %d, maximum fieldscur = %d"
492       "\n"
493       , __func__
494       , PACKAGE_STRING
495       , (unsigned)Hertz, (unsigned)sizeof(Hertz), (unsigned)sizeof(Hertz) * 8
496       , Stat_reap->cpus->total, Stat_reap->numa->total
497       , Pids_itms_tot, (int)sizeof(struct pids_result), (int)(sizeof(struct pids_result) * Pids_itms_tot)
498       , (int)SCREENMAX, (int)ROWMINSIZ, (int)ROWMAXSIZ
499       , PACKAGE, LOCALEDIR
500 #ifdef PRETENDNOCAP
501       , "dumb"
502 #else
503       , termname()
504 #endif
505       , ttyname(STDOUT_FILENO), NCURSES_VERSION
506       , max_colors, max_pairs
507       , Cap_can_goto ? "yes" : "No!"
508       , Screen_cols, Screen_rows
509       , Max_lines, (unsigned)Pseudo_size
510 #ifndef OFF_STDIOLBF
511       , (unsigned)sizeof(Stdout_buf), (unsigned)BUFSIZ
512 #endif
513       , (unsigned)sizeof(WIN_t), GROUPSMAX
514       , Curwin->rc.winname, Curwin->grpname
515       , Curwin->rc.winflags, Curwin->maxpflgs
516       , Curwin->rc.sortindx, Curwin->rc.maxtasks
517       , Curwin->varcolsz, Curwin->winlines
518       , (int)strlen(Curwin->columnhdr)
519       , EU_MAXPFLGS, mlen(Curwin->rc.fieldscur)
520       );
521    }
522 }
523 #endif // end: ATEOJ_RPTSTD
524
525    // there's lots of signal-unsafe stuff in the following ...
526    if (Frames_signal != BREAK_sig) {
527 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
528       /* can not execute any cleanup from a sibling thread and
529          we will violate proper indentation to minimize impact */
530       if (pthread_equal(Thread_id_main, pthread_self())) {
531 #endif
532 #ifdef THREADED_CPU
533       pthread_cancel(Thread_id_cpus);
534       pthread_join(Thread_id_cpus, NULL);
535       sem_destroy(&Semaphore_cpus_end);
536       sem_destroy(&Semaphore_cpus_beg);
537 #endif
538 #ifdef THREADED_MEM
539       pthread_cancel(Thread_id_memory);
540       pthread_join(Thread_id_memory, NULL);
541       sem_destroy(&Semaphore_memory_end);
542       sem_destroy(&Semaphore_memory_beg);
543 #endif
544 #ifdef THREADED_TSK
545       pthread_cancel(Thread_id_tasks);
546       pthread_join(Thread_id_tasks, NULL);
547       sem_destroy(&Semaphore_tasks_end);
548       sem_destroy(&Semaphore_tasks_beg);
549 #endif
550       procps_pids_unref(&Pids_ctx);
551       procps_stat_unref(&Stat_ctx);
552       procps_meminfo_unref(&Mem_ctx);
553 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
554       }
555 #endif
556    }
557
558    /* we'll only have a 'str' if called by error_exit() |
559       and parse_args(), never from a signal handler ... | */
560    if (str) {
561       fputs(str, stderr);
562       exit(EXIT_FAILURE);
563    }
564    /* this could happen when called from several places |
565       including that sig_endpgm().  thus we must use an |
566       async-signal-safe write function just in case ... |
567       (thanks: Shaohua Zhan shaohua.zhan@windriver.com) | */
568    if (Batch)
569       write(fileno(stdout), "\n", sizeof("\n") - 1);
570
571    exit(EXIT_SUCCESS);
572 } // end: bye_bye
573
574
575         /*
576          * Standard error handler to normalize the look of all err output */
577 static void error_exit (const char *str) __attribute__((__noreturn__));
578 static void error_exit (const char *str) {
579    static char buf[MEDBUFSIZ];
580
581    Frames_signal = BREAK_off;
582    /* we'll use our own buffer so callers can still use fmtmk() and, after
583       twelve long years, 2013 was the year we finally eliminated the leading
584       tab character -- now our message can get lost in screen clutter too! */
585    snprintf(buf, sizeof(buf), "%s: %s\n", Myname, str);
586    bye_bye(buf);
587 } // end: error_exit
588
589
590         /*
591          * Catches all remaining signals not otherwise handled */
592 static void sig_abexit (int sig) __attribute__((__noreturn__));
593 static void sig_abexit (int sig) {
594    sigset_t ss;
595
596 // POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, sigprocmask, pthread_sigmask, raise
597    sigfillset(&ss);
598 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
599    pthread_sigmask(SIG_BLOCK, &ss, NULL);
600 #else
601    sigprocmask(SIG_BLOCK, &ss, NULL);
602 #endif
603    at_eoj();                 // restore tty in preparation for exit
604    fprintf(stderr, N_fmt(EXIT_signals_fmt)
605       , sig, signal_number_to_name(sig), Myname);
606    signal(sig, SIG_DFL);     // allow core dumps, if applicable
607    sigemptyset(&ss);
608    sigaddset(&ss, sig);
609 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
610    pthread_sigmask(SIG_UNBLOCK, &ss, NULL);
611 #else
612    sigprocmask(SIG_UNBLOCK, &ss, NULL);
613 #endif
614    raise(sig);               // ( plus set proper return code )
615    _exit(EXIT_FAILURE);      // if default sig action is ignore
616 } // end: sig_abexit
617
618
619         /*
620          * Catches:
621          *    SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
622          *    SIGUSR1 and SIGUSR2 */
623 static void sig_endpgm (int dont_care_sig) __attribute__((__noreturn__));
624 static void sig_endpgm (int dont_care_sig) {
625    Frames_signal = BREAK_sig;
626    bye_bye(NULL);
627    (void)dont_care_sig;
628 } // end: sig_endpgm
629
630
631         /*
632          * Catches:
633          *    SIGTSTP, SIGTTIN and SIGTTOU */
634 static void sig_paused (int dont_care_sig) {
635 // POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise
636    if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original))
637       error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
638    if (keypad_local) putp(keypad_local);
639    putp(tg2(0, Screen_rows));
640    putp(Cap_curs_norm);
641 #ifndef RMAN_IGNORED
642    putp(Cap_smam);
643 #endif
644    // tcdrain(STDOUT_FILENO) was not reliable prior to ncurses-5.9.20121017,
645    // so we'll risk POSIX's wrath with good ol' fflush, lest 'Stopped' gets
646    // co-mingled with our most recent output...
647    fflush(stdout);
648    raise(SIGSTOP);
649    // later, after SIGCONT...
650    if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw))
651       error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
652 #ifndef RMAN_IGNORED
653    putp(Cap_rmam);
654 #endif
655    if (keypad_xmit) putp(keypad_xmit);
656    putp(Cursor_state);
657    Frames_signal = BREAK_sig;
658    (void)dont_care_sig;
659 } // end: sig_paused
660
661
662         /*
663          * Catches:
664          *    SIGCONT and SIGWINCH */
665 static void sig_resize (int dont_care_sig) {
666 // POSIX.1 async-signal-safe: tcdrain
667    tcdrain(STDOUT_FILENO);
668    Frames_signal = BREAK_sig;
669    (void)dont_care_sig;
670 } // end: sig_resize
671 \f
672 /*######  Special UTF-8 Multi-Byte support  ##############################*/
673
674         /* Support for NLS translated multi-byte strings */
675 static char UTF8_tab[] = {
676    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F
677    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F
678    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
679    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F
680    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
681    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F
682    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
683    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F
684   -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F
685   -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F
686   -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF
687   -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF
688   -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF, 0xC2 = begins 2
689    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF
690    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF, 0xE0 = begins 3
691    4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF, 0xF0 = begins 4
692 };                                                 //            ( 0xF5 & beyond invalid )
693
694
695         /*
696          * Accommodate any potential differences between some multibyte
697          * character sequence and the screen columns needed to print it */
698 static inline int utf8_cols (const unsigned char *p, int n) {
699 #ifndef OFF_XTRAWIDE
700    wchar_t wc;
701
702    if (n > 1) {
703       (void)mbtowc(&wc, (const char *)p, n);
704       // allow a zero as valid, as with a 'combining acute accent'
705       if ((n = wcwidth(wc)) < 0) n = 1;
706    }
707    return n;
708 #else
709    (void)p; (void)n;
710    return 1;
711 #endif
712 } // end: utf8_cols
713
714
715         /*
716          * Determine difference between total bytes versus printable
717          * characters in that passed, potentially multi-byte, string */
718 static int utf8_delta (const char *str) {
719    const unsigned char *p = (const unsigned char *)str;
720    int clen, cnum = 0;
721
722    while (*p) {
723       // -1 represents a decoding error, pretend it's untranslated ...
724       if (0 > (clen = UTF8_tab[*p])) return 0;
725       cnum += utf8_cols(p, clen);
726       p += clen;
727    }
728    return (int)((const char *)p - str) - cnum;
729 } // end: utf8_delta
730
731
732         /*
733          * Determine a physical end within a potential multi-byte string
734          * where maximum printable chars could be accommodated in width */
735 static int utf8_embody (const char *str, int width) {
736    const unsigned char *p = (const unsigned char *)str;
737    int clen, cnum = 0;
738
739    if (width > 0) {
740       while (*p) {
741          // -1 represents a decoding error, pretend it's untranslated ...
742          if (0 > (clen = UTF8_tab[*p])) return width;
743          if (width < (cnum += utf8_cols(p, clen))) break;
744          p += clen;
745       }
746    }
747    return (int)((const char *)p - str);
748 } // end: utf8_embody
749
750
751         /*
752          * Like the regular justify_pad routine but this guy
753          * can accommodate the multi-byte translated strings */
754 static const char *utf8_justify (const char *str, int width, int justr) {
755    static char l_fmt[]  = "%-*.*s%s", r_fmt[] = "%*.*s%s";
756    static char buf[SCREENMAX];
757    char tmp[SCREENMAX];
758
759    snprintf(tmp, sizeof(tmp), "%.*s", utf8_embody(str, width), str);
760    width += utf8_delta(tmp);
761    snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, tmp, COLPADSTR);
762    return buf;
763 } // end: utf8_justify
764
765
766         /*
767          * Returns a physical or logical column number given a
768          * multi-byte string and a target column value */
769 static int utf8_proper_col (const char *str, int col, int tophysical) {
770    const unsigned char *p = (const unsigned char *)str;
771    int clen, tlen = 0, cnum = 0;
772
773    while (*p) {
774       // -1 represents a decoding error, don't encourage repositioning ...
775       if (0 > (clen = UTF8_tab[*p])) return col;
776       if (cnum + 1 > col && tophysical) break;
777       p += clen;
778       tlen += clen;
779       if (tlen > col && !tophysical) break;
780       ++cnum;
781    }
782    return tophysical ? tlen : cnum;
783 } // end: utf8_proper_col
784 \f
785 /*######  Misc Color/Display support  ####################################*/
786
787         /*
788          * Make the appropriate caps/color strings for a window/field group.
789          * note: we avoid the use of background color so as to maximize
790          *       compatibility with the user's xterm settings */
791 static void capsmk (WIN_t *q) {
792    /* macro to test if a basic (non-color) capability is valid
793          thanks: Floyd Davidson <floyd@ptialaska.net> */
794  #define tIF(s)  s ? s : ""
795    /* macro to make compatible with netbsd-curses too
796          thanks: rofl0r <retnyg@gmx.net> */
797  #define tPM(a,b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0)
798    static int capsdone = 0;
799
800    // we must NOT disturb our 'empty' terminfo strings!
801    if (Batch) return;
802
803    // these are the unchangeable puppies, so we only do 'em once
804    if (!capsdone) {
805       STRLCPY(Cap_clr_eol, tIF(clr_eol))
806       STRLCPY(Cap_clr_eos, tIF(clr_eos))
807       STRLCPY(Cap_clr_scr, tIF(clear_screen))
808       // due to the leading newline, the following must be used with care
809       snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos));
810       STRLCPY(Cap_curs_huge, tIF(cursor_visible))
811       STRLCPY(Cap_curs_norm, tIF(cursor_normal))
812       STRLCPY(Cap_curs_hide, tIF(cursor_invisible))
813       STRLCPY(Cap_home, tIF(cursor_home))
814       STRLCPY(Cap_norm, tIF(exit_attribute_mode))
815       STRLCPY(Cap_reverse, tIF(enter_reverse_mode))
816 #ifndef RMAN_IGNORED
817       if (!eat_newline_glitch) {
818          STRLCPY(Cap_rmam, tIF(exit_am_mode))
819          STRLCPY(Cap_smam, tIF(enter_am_mode))
820          if (!*Cap_rmam || !*Cap_smam) {
821             *Cap_rmam = '\0';
822             *Cap_smam = '\0';
823             if (auto_right_margin)
824                Cap_avoid_eol = 1;
825          }
826          putp(Cap_rmam);
827       }
828 #endif
829       snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm, tIF(orig_pair));
830       snprintf(Caps_endline, sizeof(Caps_endline), "%s%s", Caps_off, Cap_clr_eol);
831       if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1;
832       capsdone = 1;
833    }
834
835    /* the key to NO run-time costs for configurable colors -- we spend a
836       little time with the user now setting up our terminfo strings, and
837       the job's done until he/she/it has a change-of-heart */
838    STRLCPY(q->cap_bold, CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode))
839    if (CHKw(q, Show_COLORS) && max_colors > 0) {
840       STRLCPY(q->capclr_sum, tPM(set_a_foreground, q->rc.summclr))
841       snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
842          , tPM(set_a_foreground, q->rc.msgsclr), Cap_reverse);
843       snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s"
844          , tPM(set_a_foreground, q->rc.msgsclr), q->cap_bold);
845       snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s"
846          , tPM(set_a_foreground, q->rc.headclr), Cap_reverse);
847       snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s"
848          , Caps_off, tPM(set_a_foreground, q->rc.taskclr));
849    } else {
850       q->capclr_sum[0] = '\0';
851 #ifdef USE_X_COLHDR
852       snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
853          , Cap_reverse, q->cap_bold);
854 #else
855       STRLCPY(q->capclr_msg, Cap_reverse)
856 #endif
857       STRLCPY(q->capclr_pmt, q->cap_bold)
858       STRLCPY(q->capclr_hdr, Cap_reverse)
859       STRLCPY(q->capclr_rownorm, Cap_norm)
860    }
861
862    // composite(s), so we do 'em outside and after the if
863    snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s"
864       , q->capclr_rownorm, CHKw(q, Show_HIBOLD) ? q->cap_bold : Cap_reverse);
865  #undef tIF
866  #undef tPM
867 } // end: capsmk
868
869
870 static struct msg_node {
871    char msg[SMLBUFSIZ];
872    struct msg_node *prev;
873 } Msg_tab[BOT_MSGSMAX];
874
875 static struct msg_node *Msg_this = Msg_tab;
876
877         /*
878          * Show an error message (caller may include '\a' for sound) */
879 static void show_msg (const char *str) {
880    STRLCPY(Msg_this->msg, str);
881    if (++Msg_this > &Msg_tab[BOT_MSGSMAX - 1]) Msg_this = Msg_tab;
882
883    PUTT("%s%s %.*s %s%s%s"
884       , tg2(0, Msg_row)
885       , Curwin->capclr_msg
886       , utf8_embody(str, Screen_cols - 2)
887       , str
888       , Cap_curs_hide
889       , Caps_off
890       , Cap_clr_eol);
891    fflush(stdout);
892    usleep(MSG_USLEEP);
893 } // end: show_msg
894
895
896         /*
897          * Show an input prompt + larger cursor (if possible) */
898 static int show_pmt (const char *str) {
899    char buf[MEDBUFSIZ];
900    int len;
901
902    snprintf(buf, sizeof(buf), "%.*s", utf8_embody(str, Screen_cols - 2), str);
903    len = utf8_delta(buf);
904 #ifdef PRETENDNOCAP
905    PUTT("\n%s%s%.*s %s%s%s"
906 #else
907    PUTT("%s%s%.*s %s%s%s"
908 #endif
909       , tg2(0, Msg_row)
910       , Curwin->capclr_pmt
911       , (Screen_cols - 2) + len
912       , buf
913       , Cap_curs_huge
914       , Caps_off
915       , Cap_clr_eol);
916    fflush(stdout);
917    len = strlen(buf) - len;
918    // +1 for the space we added or -1 for the cursor...
919    return (len + 1 < Screen_cols) ? len + 1 : Screen_cols - 1;
920 } // end: show_pmt
921
922
923         /*
924          * Create and print the optional scroll coordinates message */
925 static void show_scroll (void) {
926    char tmp1[SMLBUFSIZ];
927 #ifndef SCROLLVAR_NO
928    char tmp2[SMLBUFSIZ];
929 #endif
930    int totpflgs = Curwin->totpflgs;
931    int begpflgs = Curwin->begpflg + 1;
932
933 #ifndef USE_X_COLHDR
934    if (CHKw(Curwin, Show_HICOLS)) {
935       totpflgs -= 2;
936       if (ENUpos(Curwin, Curwin->rc.sortindx) < Curwin->begpflg) begpflgs -= 2;
937    }
938 #endif
939    if (1 > totpflgs) totpflgs = 1;
940    if (1 > begpflgs) begpflgs = 1;
941    snprintf(tmp1, sizeof(tmp1), N_fmt(SCROLL_coord_fmt), Curwin->begtask + 1, PIDSmaxt, begpflgs, totpflgs);
942 #ifndef SCROLLVAR_NO
943    if (Curwin->varcolbeg) {
944       snprintf(tmp2, sizeof(tmp2), " + %d", Curwin->varcolbeg);
945       scat(tmp1, tmp2);
946    }
947 #endif
948    PUTT("%s%s  %.*s%s", tg2(0, Msg_row), Caps_off, Screen_cols - 3, tmp1, Cap_clr_eol);
949 } // end: show_scroll
950
951
952         /*
953          * Show lines with specially formatted elements, but only output
954          * what will fit within the current screen width.
955          *    Our special formatting consists of:
956          *       "some text <_delimiter_> some more text <_delimiter_>...\n"
957          *    Where <_delimiter_> is a two byte combination consisting of a
958          *    tilde followed by an ascii digit in the range of 1 - 8.
959          *       examples: ~1, ~5, ~8, etc.
960          *    The tilde is effectively stripped and the next digit
961          *    converted to an index which is then used to select an
962          *    'attribute' from a capabilities table.  That attribute
963          *    is then applied to the *preceding* substring.
964          * Once recognized, the delimiter is replaced with a null character
965          * and viola, we've got a substring ready to output!  Strings or
966          * substrings without delimiters will receive the Cap_norm attribute.
967          *
968          * Caution:
969          *    This routine treats all non-delimiter bytes as displayable
970          *    data subject to our screen width marching orders.  If callers
971          *    embed non-display data like tabs or terminfo strings in our
972          *    glob, a line will truncate incorrectly at best.  Worse case
973          *    would be truncation of an embedded tty escape sequence.
974          *
975          *    Tabs must always be avoided or our efforts are wasted and
976          *    lines will wrap.  To lessen but not eliminate the risk of
977          *    terminfo string truncation, such non-display stuff should
978          *    be placed at the beginning of a "short" line. */
979 static void show_special (int interact, const char *glob) {
980   /* note: the following is for documentation only,
981            the real captab is now found in a group's WIN_t !
982      +------------------------------------------------------+
983      | char *captab[] = {                 :   Cap's = Index |
984      |   Cap_norm, Cap_norm,              =   \000, \001,   |
985      |   cap_bold, capclr_sum,            =   \002, \003,   |
986      |   capclr_msg, capclr_pmt,          =   \004, \005,   |
987      |   capclr_hdr,                      =   \006,         |
988      |   capclr_rowhigh,                  =   \007,         |
989      |   capclr_rownorm  };               =   \010 [octal!] |
990      +------------------------------------------------------+ */
991   /* ( Pssst, after adding the termcap transitions, row may )
992      ( exceed 300+ bytes, even in an 80x24 terminal window! )
993      ( Shown here are the former buffer size specifications )
994      ( char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]. )
995      ( So now we use larger buffers and a little protection )
996      ( against overrunning them with this 'lin_end - glob'. )
997
998      ( That was uncovered during 'Inspect' development when )
999      ( this guy was being considered for a supporting role. )
1000      ( However, such an approach was abandoned. As a result )
1001      ( this function is called only with a glob under top's )
1002      ( control and never containing any 'raw/binary' chars! ) */
1003    char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMINSIZ];
1004    char *rp, *lin_end, *sub_beg, *sub_end;
1005    int room;
1006
1007    // handle multiple lines passed in a bunch
1008    while ((lin_end = strchr(glob, '\n'))) {
1009     #define myMIN(a,b) (((a) < (b)) ? (a) : (b))
1010       size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -3);
1011
1012       // create a local copy we can extend and otherwise abuse
1013       memcpy(lin, glob, lessor);
1014       // zero terminate this part and prepare to parse substrings
1015       lin[lessor] = '\0';
1016       room = Screen_cols;
1017       sub_beg = sub_end = lin;
1018       *(rp = row) = '\0';
1019
1020       while (*sub_beg) {
1021          int ch = *sub_end;
1022          if ('~' == ch) ch = *(sub_end + 1) - '0';
1023          switch (ch) {
1024             case 0:                    // no end delim, captab makes normal
1025                // only possible when '\n' was NOT preceded with a '~#' sequence
1026                // ( '~1' thru '~8' is valid range, '~0' is never actually used )
1027                *(sub_end + 1) = '\0';  // extend str end, then fall through
1028                *(sub_end + 2) = '\0';  // ( +1 optimization for usual path )
1029             // fall through
1030             case 1: case 2: case 3: case 4:
1031             case 5: case 6: case 7: case 8:
1032                *sub_end = '\0';
1033                snprintf(tmp, sizeof(tmp), "%s%.*s%s"
1034                   , Curwin->captab[ch], utf8_embody(sub_beg, room), sub_beg, Caps_off);
1035                rp = scat(rp, tmp);
1036                room -= (sub_end - sub_beg);
1037                room += utf8_delta(sub_beg);
1038                sub_beg = (sub_end += 2);
1039                break;
1040             default:                   // nothin' special, just text
1041                ++sub_end;
1042          }
1043          if (0 >= room) break;         // skip substrings that won't fit
1044       }
1045
1046       if (interact) PUTT("%s%s\n", row, Cap_clr_eol);
1047       else PUFF("%s%s\n", row, Caps_endline);
1048       glob = ++lin_end;                // point to next line (maybe)
1049
1050     #undef myMIN
1051    } // end: while 'lines'
1052
1053    /* If there's anything left in the glob (by virtue of no trailing '\n'),
1054       it probably means caller wants to retain cursor position on this final
1055       line.  That, in turn, means we're interactive and so we'll just do our
1056       'fit-to-screen' thingy while also leaving room for the cursor... */
1057    if (*glob) PUTT("%.*s", utf8_embody(glob, Screen_cols - 1), glob);
1058 } // end: show_special
1059 \f
1060 /*######  Low Level Memory/Keyboard/File I/O support  ####################*/
1061
1062         /*
1063          * Handle our own memory stuff without the risk of leaving the
1064          * user's terminal in an ugly state should things go sour. */
1065
1066 static void *alloc_c (size_t num) {
1067    void *pv;
1068
1069    if (!num) ++num;
1070    if (!(pv = calloc(1, num)))
1071       error_exit(N_txt(FAIL_alloc_c_txt));
1072    return pv;
1073 } // end: alloc_c
1074
1075
1076 static void *alloc_r (void *ptr, size_t num) {
1077    void *pv;
1078
1079    if (!num) ++num;
1080    if (!(pv = realloc(ptr, num)))
1081       error_exit(N_txt(FAIL_alloc_r_txt));
1082    return pv;
1083 } // end: alloc_r
1084
1085
1086 static char *alloc_s (const char *str) {
1087    return strcpy(alloc_c(strlen(str) +1), str);
1088 } // end: alloc_s
1089
1090
1091         /*
1092          * An 'I/O available' routine which will detect raw single byte |
1093          * unsolicited keyboard input which was susceptible to SIGWINCH |
1094          * interrupts (or any other signal).  He'll also support timout |
1095          * in the absence of any user keystrokes or a signal interrupt. | */
1096 static inline int ioa (struct timespec *ts) {
1097    fd_set fs;
1098    int rc;
1099
1100    FD_ZERO(&fs);
1101    FD_SET(STDIN_FILENO, &fs);
1102
1103 #ifdef SIGNALS_LESS // conditional comments are silly, but help in documenting
1104    // hold here until we've got keyboard input, any signal except SIGWINCH
1105    // or (optionally) we timeout with nanosecond granularity
1106 #else
1107    // hold here until we've got keyboard input, any signal (including SIGWINCH)
1108    // or (optionally) we timeout with nanosecond granularity
1109 #endif
1110    rc = pselect(STDIN_FILENO + 1, &fs, NULL, NULL, ts, &Sigwinch_set);
1111
1112    if (rc < 0) rc = 0;
1113    return rc;
1114 } // end: ioa
1115
1116
1117         /*
1118          * This routine isolates ALL user INPUT and ensures that we
1119          * wont be mixing I/O from stdio and low-level read() requests */
1120 static int ioch (int ech, char *buf, unsigned cnt) {
1121    int rc = -1;
1122
1123 #ifdef TERMIOS_ONLY
1124    if (ech) {
1125       tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_tweaked);
1126       rc = read(STDIN_FILENO, buf, cnt);
1127       tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw);
1128    } else {
1129       if (ioa(NULL))
1130          rc = read(STDIN_FILENO, buf, cnt);
1131    }
1132 #else
1133    (void)ech;
1134    if (ioa(NULL))
1135       rc = read(STDIN_FILENO, buf, cnt);
1136 #endif
1137
1138    // zero means EOF, might happen if we erroneously get detached from terminal
1139    if (0 == rc) bye_bye(NULL);
1140
1141    // it may have been the beginning of a lengthy escape sequence
1142    tcflush(STDIN_FILENO, TCIFLUSH);
1143
1144    // note: we do NOT produce a valid 'string'
1145    return rc;
1146 } // end: ioch
1147
1148
1149 #define IOKEY_INIT 0
1150 #define IOKEY_ONCE 1
1151 #define IOKEY_NEXT 2
1152
1153         /*
1154          * Support for single or multiple keystroke input AND
1155          * escaped cursor motion keys.
1156          * note: we support more keys than we currently need, in case
1157          *       we attract new consumers in the future */
1158 static int iokey (int action) {
1159    static struct {
1160       const char *str;
1161       int key;
1162    } tinfo_tab[] = {
1163       { NULL, kbd_BKSP  }, { NULL, kbd_INS   }, { NULL, kbd_DEL   }, { NULL, kbd_LEFT  },
1164       { NULL, kbd_DOWN  }, { NULL, kbd_UP    }, { NULL, kbd_RIGHT }, { NULL, kbd_HOME  },
1165       { NULL, kbd_PGDN  }, { NULL, kbd_PGUP  }, { NULL, kbd_END   }, { NULL, kbd_BTAB  },
1166          // remainder are alternatives for above, just in case...
1167          // ( the h,j,k,l entries are the vim cursor motion keys )
1168       { "\033h",    kbd_LEFT  }, { "\033j",    kbd_DOWN  }, /* meta+      h,j */
1169       { "\033k",    kbd_UP    }, { "\033l",    kbd_RIGHT }, /* meta+      k,l */
1170       { "\033\010", kbd_HOME  }, { "\033\012", kbd_PGDN  }, /* ctrl+meta+ h,j */
1171       { "\033\013", kbd_PGUP  }, { "\033\014", kbd_END   }, /* ctrl+meta+ k,l */
1172       { "\xC3\xA8", kbd_LEFT  }, { "\xC3\xAA", kbd_DOWN  }, /* meta+      h,j (some xterms) */
1173       { "\xC3\xAB", kbd_UP    }, { "\xC3\xAC", kbd_RIGHT }, /* meta+      k,l (some xterms) */
1174       { "\xC2\x88", kbd_HOME  }, { "\xC2\x8A", kbd_PGDN  }, /* ctrl+meta+ h,j (some xterms) */
1175       { "\xC2\x8B", kbd_PGUP  }, { "\xC2\x8C", kbd_END   }, /* ctrl+meta+ k,l (some xterms) */
1176       { "\033\011", kbd_BTAB  }
1177    };
1178 #ifdef TERMIOS_ONLY
1179    char buf[SMLBUFSIZ], *pb;
1180 #else
1181    static char buf[MEDBUFSIZ];
1182    static int pos, len;
1183    char *pb;
1184 #endif
1185    int i;
1186
1187    if (action == IOKEY_INIT) {
1188     #define tOk(s)  s ? s : ""
1189       tinfo_tab[0].str  = tOk(key_backspace);
1190       tinfo_tab[1].str  = tOk(key_ic);
1191       tinfo_tab[2].str  = tOk(key_dc);
1192       tinfo_tab[3].str  = tOk(key_left);
1193       tinfo_tab[4].str  = tOk(key_down);
1194       tinfo_tab[5].str  = tOk(key_up);
1195       tinfo_tab[6].str  = tOk(key_right);
1196       tinfo_tab[7].str  = tOk(key_home);
1197       tinfo_tab[8].str  = tOk(key_npage);
1198       tinfo_tab[9].str  = tOk(key_ppage);
1199       tinfo_tab[10].str = tOk(key_end);
1200       tinfo_tab[11].str = tOk(back_tab);
1201       // next is critical so returned results match bound terminfo keys
1202       putp(tOk(keypad_xmit));
1203       // ( converse keypad_local issued at pause/pgm end, just in case )
1204       return 0;
1205     #undef tOk
1206    }
1207
1208    if (action == IOKEY_ONCE) {
1209       memset(buf, '\0', sizeof(buf));
1210       if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1211    }
1212
1213 #ifndef TERMIOS_ONLY
1214    if (action == IOKEY_NEXT) {
1215       if (pos < len)
1216          return buf[pos++];            // exhaust prior keystrokes
1217       pos = len = 0;
1218       memset(buf, '\0', sizeof(buf));
1219       if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1220       if (!iscntrl(buf[0])) {          // no need for translation
1221          len = strlen(buf);
1222          pos = 1;
1223          return buf[0];
1224       }
1225    }
1226 #endif
1227
1228    /* some emulators implement 'key repeat' too well and we get duplicate
1229       key sequences -- so we'll focus on the last escaped sequence, while
1230       also allowing use of the meta key... */
1231    if (!(pb = strrchr(buf, '\033'))) pb = buf;
1232    else if (pb > buf && '\033' == *(pb - 1)) --pb;
1233
1234    for (i = 0; i < MAXTBL(tinfo_tab); i++)
1235       if (!strcmp(tinfo_tab[i].str, pb))
1236          return tinfo_tab[i].key;
1237
1238    // no match, so we'll return single non-escaped keystrokes only
1239    if (buf[0] == '\033' && buf[1]) return -1;
1240    return buf[0];
1241 } // end: iokey
1242
1243
1244 #ifdef TERMIOS_ONLY
1245         /*
1246          * Get line oriented interactive input from the user,
1247          * using native tty support */
1248 static char *ioline (const char *prompt) {
1249    static const char ws[] = "\b\f\n\r\t\v\x1b\x9b";  // 0x1b + 0x9b are escape
1250    static char buf[MEDBUFSIZ];
1251    char *p;
1252
1253    show_pmt(prompt);
1254    memset(buf, '\0', sizeof(buf));
1255    ioch(1, buf, sizeof(buf)-1);
1256
1257    if ((p = strpbrk(buf, ws))) *p = '\0';
1258    // note: we DO produce a vaid 'string'
1259    return buf;
1260 } // end: ioline
1261
1262 #else
1263         /*
1264          * Get some line oriented interactive input from the ol' user,
1265          * going way, way beyond that native tty support by providing:
1266          * . true input line editing, not just a destructive backspace
1267          * . an input limit sensitive to the current screen dimensions
1268          * . an ability to recall prior strings for editing & re-input */
1269 static char *ioline (const char *prompt) {
1270  #define setLEN    ( len = strlen(buf) - utf8_delta(buf) )
1271  #define setPOS(X) ( pos = utf8_embody(buf, X) )
1272  #define utfCHR(X) ( (unsigned char *)&buf[X] )
1273  #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] )
1274  #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) )
1275  #define movBKW    { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; }
1276  #define chkCUR    { if (cur < 0) cur = 0; if (cur > len) cur = len; }
1277     // thank goodness ol' memmove will safely allow strings to overlap
1278  #define sqzSTR  { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \
1279        if (!utfCOL(pos + i)) i += utfTOT(pos + i); \
1280        memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \
1281        memset(&buf[bufMAX - i], '\0', i); }
1282  #define expSTR(X)  if (bufNXT < bufMAX && scrNXT < Screen_cols) { \
1283        memmove(&buf[pos + X], &buf[pos], bufMAX - pos); }
1284  #define savMAX  50
1285  #define bufNXT  ( pos + 4 )           // four equals longest utf8 str
1286  #define scrNXT  ( beg + len + 2 )     // two due to multi-column char
1287  #define bufMAX  ((int)sizeof(buf)-2)  // -1 for '\0' string delimeter
1288    static char buf[MEDBUFSIZ+1];       // +1 for '\0' string delimeter
1289    static int ovt;
1290    int beg,           // the physical column where input began, buf[0]
1291        cur,           // the logical current column/insertion position
1292        len,           // the logical input length, thus the end column
1293        pos,           // the physical position in the buffer currently
1294        key, i;
1295    struct lin_s {
1296       struct lin_s *bkw;               // pointer for older saved strs
1297       struct lin_s *fwd;               // pointer for newer saved strs
1298       char *str;                       // an actual saved input string
1299    };
1300    static struct lin_s *anchor, *plin;
1301
1302    if (!anchor) {
1303       anchor = alloc_c(sizeof(struct lin_s));
1304       anchor->str = alloc_s("");       // the top-of-stack (empty str)
1305    }
1306    plin = anchor;
1307    cur = len = pos = 0;
1308    beg = show_pmt(prompt);
1309    memset(buf, '\0', sizeof(buf));
1310    // this may not work under a gui emulator (but linux console is ok)
1311    putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1312
1313    do {
1314       fflush(stdout);
1315       key = iokey(IOKEY_NEXT);
1316       switch (key) {
1317          case 0:
1318             buf[0] = '\0';
1319             return buf;
1320          case kbd_ESC:
1321             buf[0] = kbd_ESC;
1322             return buf;
1323          case kbd_ENTER:
1324          case kbd_BTAB: case kbd_PGUP: case kbd_PGDN:
1325             continue;
1326          case kbd_INS:
1327             ovt = !ovt;
1328             putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1329             break;
1330          case kbd_DEL:
1331             sqzSTR
1332             break;
1333          case kbd_BKSP:
1334             if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; }
1335             break;
1336          case kbd_LEFT:
1337             if (0 < cur) { movBKW; cur -= utfCOL(pos); }
1338             break;
1339          case kbd_RIGHT:
1340             if (cur < len) cur += utfCOL(pos);
1341             break;
1342          case kbd_HOME:
1343             cur = pos = 0;
1344             break;
1345          case kbd_END:
1346             cur = len;
1347             pos = strlen(buf);
1348             break;
1349          case kbd_UP:
1350             if (plin->bkw) {
1351                plin = plin->bkw;
1352                memset(buf, '\0', sizeof(buf));
1353                memccpy(buf, plin->str, '\0', bufMAX);
1354                cur = setLEN;
1355                pos = strlen(buf);
1356             }
1357             break;
1358          case kbd_DOWN:
1359             if (plin->fwd) plin = plin->fwd;
1360             memset(buf, '\0', sizeof(buf));
1361             memccpy(buf, plin->str, '\0', bufMAX);
1362             cur = setLEN;
1363             pos = strlen(buf);
1364             break;
1365          default:                      // what we REALLY wanted (maybe)
1366             if (bufNXT < bufMAX && scrNXT < Screen_cols && strlen(buf) < bufMAX) {
1367                int tot = UTF8_tab[(unsigned char)key],
1368                    sav = pos;
1369                if (tot < 1) tot = 1;
1370                if (!ovt) { expSTR(tot); }
1371                else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); }
1372                buf[pos++] = key;
1373                while (tot > 1) {
1374                  key = iokey(IOKEY_NEXT);
1375                  buf[pos++] = key;
1376                  --tot;
1377                }
1378                cur += utfCOL(sav);
1379             }
1380             break;
1381       }
1382       setLEN;
1383       chkCUR;
1384       setPOS(cur);
1385       putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf));
1386 #ifdef OVERTYPE_SEE
1387       putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' '));
1388 #endif
1389       putp(tg2(beg + cur, Msg_row));
1390    } while (key != kbd_ENTER);
1391
1392    // weed out duplicates, including empty strings (top-of-stack)...
1393    for (i = 0, plin = anchor; ; i++) {
1394 #ifdef RECALL_FIXED
1395       if (!STRCMP(plin->str, buf))     // if matched, retain original order
1396          return buf;
1397 #else
1398       if (!STRCMP(plin->str, buf)) {   // if matched, rearrange stack order
1399          if (i > 1) {                  // but not null str or if already #2
1400             if (plin->bkw)             // splice around this matched string
1401                plin->bkw->fwd = plin->fwd; // if older exists link to newer
1402             plin->fwd->bkw = plin->bkw;    // newer linked to older or NULL
1403             anchor->bkw->fwd = plin;   // stick matched on top of former #2
1404             plin->bkw = anchor->bkw;   // keep empty string at top-of-stack
1405             plin->fwd = anchor;        // then prepare to be the 2nd banana
1406             anchor->bkw = plin;        // by sliding us in below the anchor
1407          }
1408          return buf;
1409       }
1410 #endif
1411       if (!plin->bkw) break;           // let i equal total stacked strings
1412       plin = plin->bkw;                // ( with plin representing bottom )
1413    }
1414    if (i < savMAX)
1415       plin = alloc_c(sizeof(struct lin_s));
1416    else {                              // when a new string causes overflow
1417       plin->fwd->bkw = NULL;           // make next-to-last string new last
1418       free(plin->str);                 // and toss copy but keep the struct
1419    }
1420    plin->str = alloc_s(buf);           // copy user's new unique input line
1421    plin->bkw = anchor->bkw;            // keep empty string as top-of-stack
1422    if (plin->bkw)                      // did we have some already stacked?
1423       plin->bkw->fwd = plin;           // yep, so point prior to new string
1424    plin->fwd = anchor;                 // and prepare to be a second banana
1425    anchor->bkw = plin;                 // by sliding it in as new number 2!
1426
1427    return buf;                         // protect our copy, return original
1428  #undef setLEN
1429  #undef setPOS
1430  #undef utfCHR
1431  #undef utfTOT
1432  #undef utfCOL
1433  #undef movBKW
1434  #undef chkCUR
1435  #undef sqzSTR
1436  #undef expSTR
1437  #undef savMAX
1438  #undef bufNXT
1439  #undef scrNXT
1440  #undef bufMAX
1441 } // end: ioline
1442 #endif
1443
1444
1445         /*
1446          * Make locale unaware float (but maybe restrict to whole numbers). */
1447 static int mkfloat (const char *str, float *num, int whole) {
1448    char tmp[SMLBUFSIZ], *ep;
1449
1450    if (whole) {
1451       *num = (float)strtol(str, &ep, 0);
1452       if (ep != str && *ep == '\0' && *num < INT_MAX)
1453          return 1;
1454       return 0;
1455    }
1456    snprintf(tmp, sizeof(tmp), "%s", str);
1457    *num = strtof(tmp, &ep);
1458    if (*ep != '\0') {
1459       // fallback - try to swap the floating point separator
1460       if (*ep == '.') *ep = ',';
1461       else if (*ep == ',') *ep = '.';
1462       *num = strtof(tmp, &ep);
1463    }
1464    if (ep != tmp && *ep == '\0' && *num < INT_MAX)
1465       return 1;
1466    return 0;
1467 } // end: mkfloat
1468
1469
1470         /*
1471          * This routine provides the i/o in support of files whose size
1472          * cannot be determined in advance.  Given a stream pointer, he'll
1473          * try to slurp in the whole thing and return a dynamically acquired
1474          * buffer supporting that single string glob.
1475          *
1476          * He always creates a buffer at least READMINSZ big, possibly
1477          * all zeros (an empty string), even if the file wasn't read. */
1478 static int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread) {
1479    char chunk[4096*16];
1480    size_t num;
1481
1482    *bread = 0;
1483    *bsize = READMINSZ;
1484    *baddr = alloc_c(READMINSZ);
1485    if (fp) {
1486       while (0 < (num = fread(chunk, 1, sizeof(chunk), fp))) {
1487          *baddr = alloc_r(*baddr, num + *bsize);
1488          memcpy(*baddr + *bread, chunk, num);
1489          *bread += num;
1490          *bsize += num;
1491       };
1492       *(*baddr + *bread) = '\0';
1493       return ferror(fp);
1494    }
1495    return ENOENT;
1496 } // end: readfile
1497 \f
1498 /*######  Small Utility routines  ########################################*/
1499
1500 #define GET_NUM_BAD  INT_MIN
1501 #define GET_NUM_ESC (INT_MIN + 1)
1502 #define GET_NUM_NOT (INT_MIN + 2)
1503
1504         /*
1505          * Get a float from the user */
1506 static float get_float (const char *prompt) {
1507    char *line;
1508    float f;
1509
1510    line = ioline(prompt);
1511    if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1512    if (!line[0]) return GET_NUM_NOT;
1513    // note: we're not allowing negative floats
1514    if (!mkfloat(line, &f, 0) || f < 0) {
1515       show_msg(N_txt(BAD_numfloat_txt));
1516       return GET_NUM_BAD;
1517    }
1518    return f;
1519 } // end: get_float
1520
1521
1522         /*
1523          * Get an integer from the user, returning INT_MIN for error */
1524 static int get_int (const char *prompt) {
1525    char *line;
1526    float f;
1527
1528    line = ioline(prompt);
1529    if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1530    if (!line[0]) return GET_NUM_NOT;
1531    // note: we've got to allow negative ints (renice)
1532    if (!mkfloat(line, &f, 1)) {
1533       show_msg(N_txt(BAD_integers_txt));
1534       return GET_NUM_BAD;
1535    }
1536    return (int)f;
1537 } // end: get_int
1538
1539
1540         /*
1541          * Make a hex value, and maybe suppress zeroes. */
1542 static inline const char *hex_make (long num, int noz) {
1543    static char buf[SMLBUFSIZ];
1544    int i;
1545
1546 #ifdef CASEUP_HEXES
1547    snprintf(buf, sizeof(buf), "%08lX", num);
1548 #else
1549    snprintf(buf, sizeof(buf), "%08lx", num);
1550 #endif
1551    if (noz)
1552       for (i = 0; buf[i]; i++)
1553          if ('0' == buf[i])
1554             buf[i] = '.';
1555    return buf;
1556 } // end: hex_make
1557
1558
1559         /*
1560          * Validate the passed string as a user name or number,
1561          * and/or update the window's 'u/U' selection stuff. */
1562 static const char *user_certify (WIN_t *q, const char *str, char typ) {
1563    struct passwd *pwd;
1564    char *endp;
1565    uid_t num;
1566
1567    Monpidsidx = 0;
1568    q->usrseltyp = 0;
1569    q->usrselflg = 1;
1570    if (*str) {
1571       if ('!' == *str) { ++str; q->usrselflg = 0; }
1572       num = (uid_t)strtoul(str, &endp, 0);
1573       if ('\0' == *endp) {
1574          pwd = getpwuid(num);
1575          if (!pwd) {
1576          /* allow foreign users, from e.g within chroot
1577           ( thanks Dr. Werner Fink <werner@suse.de> ) */
1578             q->usrseluid = num;
1579             q->usrseltyp = typ;
1580             return NULL;
1581          }
1582       } else
1583          pwd = getpwnam(str);
1584       if (!pwd) return N_txt(BAD_username_txt);
1585       q->usrseluid = pwd->pw_uid;
1586       q->usrseltyp = typ;
1587    }
1588    return NULL;
1589 } // end: user_certify
1590 \f
1591 /*######  Basic Formatting support  ######################################*/
1592
1593         /*
1594          * Just do some justify stuff, then add post column padding. */
1595 static inline const char *justify_pad (const char *str, int width, int justr) {
1596    static char l_fmt[]  = "%-*.*s%s", r_fmt[] = "%*.*s%s";
1597    static char buf[SCREENMAX];
1598
1599    snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, str, COLPADSTR);
1600    return buf;
1601 } // end: justify_pad
1602
1603
1604         /*
1605          * Make and then justify a single character. */
1606 static inline const char *make_chr (const char ch, int width, int justr) {
1607    static char buf[SMLBUFSIZ];
1608
1609    snprintf(buf, sizeof(buf), "%c", ch);
1610    return justify_pad(buf, width, justr);
1611 } // end: make_chr
1612
1613
1614         /*
1615          * Make and then justify an integer NOT subject to scaling,
1616          * and include a visual clue should tuncation be necessary. */
1617 static inline const char *make_num (long num, int width, int justr, int col, int noz) {
1618    static char buf[SMLBUFSIZ];
1619
1620    buf[0] = '\0';
1621    if (noz && Rc.zero_suppress && 0 == num)
1622       goto end_justifies;
1623
1624    if (width < snprintf(buf, sizeof(buf), "%ld", num)) {
1625       if (width <= 0 || (size_t)width >= sizeof(buf))
1626          width = sizeof(buf)-1;
1627       buf[width-1] = COLPLUSCH;
1628       buf[width] = '\0';
1629       AUTOX_COL(col);
1630    }
1631 end_justifies:
1632    return justify_pad(buf, width, justr);
1633 } // end: make_num
1634
1635
1636         /*
1637          * Make and then justify a character string,
1638          * and include a visual clue should tuncation be necessary. */
1639 static inline const char *make_str (const char *str, int width, int justr, int col) {
1640    static char buf[SCREENMAX];
1641
1642    if (width < snprintf(buf, sizeof(buf), "%s", str)) {
1643       if (width <= 0 || (size_t)width >= sizeof(buf))
1644          width = sizeof(buf)-1;
1645       buf[width-1] = COLPLUSCH;
1646       buf[width] = '\0';
1647       AUTOX_COL(col);
1648    }
1649    return justify_pad(buf, width, justr);
1650 } // end: make_str
1651
1652
1653         /*
1654          * Make and then justify a potentially multi-byte character string,
1655          * and include a visual clue should tuncation be necessary. */
1656 static inline const char *make_str_utf8 (const char *str, int width, int justr, int col) {
1657    static char buf[SCREENMAX];
1658    int delta = utf8_delta(str);
1659
1660    if (width + delta < snprintf(buf, sizeof(buf), "%s", str)) {
1661       snprintf(buf, sizeof(buf), "%.*s%c", utf8_embody(str, width-1), str, COLPLUSCH);
1662       delta = utf8_delta(buf);
1663       AUTOX_COL(col);
1664    }
1665    return justify_pad(buf, width + delta, justr);
1666 } // end: make_str_utf8
1667
1668
1669         /*
1670          * Do some scaling then justify stuff.
1671          * We'll interpret 'num' as a kibibytes quantity and try to
1672          * format it to reach 'target' while also fitting 'width'. */
1673 static const char *scale_mem (int target, float num, int width, int justr) {
1674    //                               SK_Kb   SK_Mb      SK_Gb      SK_Tb      SK_Pb      SK_Eb
1675 #ifdef BOOST_MEMORY
1676    static const char *fmttab[] =  { "%.0f", "%#.1f%c", "%#.3f%c", "%#.3f%c", "%#.3f%c", NULL };
1677 #else
1678    static const char *fmttab[] =  { "%.0f", "%.1f%c",  "%.1f%c",  "%.1f%c",  "%.1f%c",  NULL };
1679 #endif
1680    static char buf[SMLBUFSIZ];
1681    char *psfx;
1682    int i;
1683
1684    buf[0] = '\0';
1685    if (Rc.zero_suppress && 0 >= num)
1686       goto end_justifies;
1687
1688    for (i = SK_Kb, psfx = Scaled_sfxtab; i < SK_Eb; psfx++, i++) {
1689       if (i >= target
1690       && (width >= snprintf(buf, sizeof(buf), fmttab[i], num, *psfx)))
1691          goto end_justifies;
1692       num /= 1024.0;
1693    }
1694
1695    // well shoot, this outta' fit...
1696    snprintf(buf, sizeof(buf), "?");
1697 end_justifies:
1698    return justify_pad(buf, width, justr);
1699 } // end: scale_mem
1700
1701
1702         /*
1703          * Do some scaling then justify stuff. */
1704 static const char *scale_num (float num, int width, int justr) {
1705    static char buf[SMLBUFSIZ];
1706    char *psfx;
1707
1708    buf[0] = '\0';
1709    if (Rc.zero_suppress && 0 >= num)
1710       goto end_justifies;
1711    if (width >= snprintf(buf, sizeof(buf), "%.0f", num))
1712       goto end_justifies;
1713
1714    for (psfx = Scaled_sfxtab; 0 < *psfx; psfx++) {
1715       num /= 1024.0;
1716       if (width >= snprintf(buf, sizeof(buf), "%.1f%c", num, *psfx))
1717          goto end_justifies;
1718       if (width >= snprintf(buf, sizeof(buf), "%.0f%c", num, *psfx))
1719          goto end_justifies;
1720    }
1721
1722    // well shoot, this outta' fit...
1723    snprintf(buf, sizeof(buf), "?");
1724 end_justifies:
1725    return justify_pad(buf, width, justr);
1726 } // end: scale_num
1727
1728
1729         /*
1730          * Make and then justify a percentage, with decreasing precision. */
1731 static const char *scale_pcnt (float num, int width, int justr, int xtra) {
1732    static char buf[SMLBUFSIZ];
1733
1734    buf[0] = '\0';
1735    if (Rc.zero_suppress && 0 >= num)
1736       goto end_justifies;
1737    if (xtra) {
1738       if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1739          goto end_justifies;
1740       if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1741          goto end_justifies;
1742       goto carry_on;
1743    }
1744 #ifdef BOOST_PERCNT
1745    if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1746       goto end_justifies;
1747    if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1748       goto end_justifies;
1749    (void)xtra;
1750 #endif
1751 carry_on:
1752    if (width >= snprintf(buf, sizeof(buf), "%#.1f", num))
1753       goto end_justifies;
1754    if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, num))
1755       goto end_justifies;
1756
1757    // well shoot, this outta' fit...
1758    snprintf(buf, sizeof(buf), "?");
1759 end_justifies:
1760    return justify_pad(buf, width, justr);
1761 } // end: scale_pcnt
1762
1763
1764 #define TICS_AS_SECS  0
1765 #define TICS_AS_MINS  1
1766 #define TICS_AS_HOUR  2
1767 #define TICS_AS_DAY1  3
1768 #define TICS_AS_DAY2  4
1769 #define TICS_AS_WEEK  5
1770 #define TICS_AS_LAST  6
1771
1772         /*
1773          * Do some scaling stuff.
1774          * Try to format 'tics' to reach 'target' while also
1775          * fitting in 'width', then justify it. */
1776 static const char *scale_tics (TIC_t tics, int width, int justr, int target) {
1777 #ifdef CASEUP_SUFIX
1778  #define HH "%luH"                              // nls_maybe
1779  #define DD "%luD"
1780  #define WW "%luW"
1781 #else
1782  #define HH "%luh"                              // nls_maybe
1783  #define DD "%lud"
1784  #define WW "%luw"
1785 #endif
1786    static char buf[SMLBUFSIZ];
1787    TIC_t nt;            // for speed on 64-bit
1788 #ifdef SCALE_FORMER
1789    unsigned long cc;    // centiseconds
1790    unsigned long nn;    // multi-purpose whatever
1791 #else
1792    unsigned long cent, secs, mins, hour, days, week;
1793 #endif
1794
1795    buf[0] = '\0';
1796    nt  = (tics * 100ull) / Hertz;               // lots of room for any time
1797    if (Rc.zero_suppress && 0 >= nt)
1798       goto end_justifies;
1799
1800 #ifdef SCALE_FORMER
1801    cc  = nt % 100;                              // centiseconds past second
1802    nt /= 100;                                   // total seconds
1803    nn  = nt % 60;                               // seconds past the minute
1804    nt /= 60;                                    // total minutes
1805    if (target < TICS_AS_MINS
1806    && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu.%02lu", nt, nn, cc)))
1807       goto end_justifies;
1808    if (target < TICS_AS_HOUR
1809    && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu", nt, nn)))
1810       goto end_justifies;
1811    nn  = nt % 60;                               // minutes past the hour
1812    nt /= 60;                                    // total hours
1813    if (width >= snprintf(buf, sizeof(buf), "%llu,%02lu", nt, nn))
1814       goto end_justifies;
1815    nn = nt;                                     // now also hours
1816    if (width >= snprintf(buf, sizeof(buf), HH, nn))
1817       goto end_justifies;
1818    nn /= 24;                                    // now days
1819    if (width >= snprintf(buf, sizeof(buf), DD, nn))
1820       goto end_justifies;
1821    nn /= 7;                                     // now weeks
1822    if (width >= snprintf(buf, sizeof(buf), WW, nn))
1823       goto end_justifies;
1824 #else
1825  #define mmLIMIT 360                            // arbitrary 6 hours
1826  #define hhLIMIT 96                             // arbitrary 4 days
1827  #define ddLIMIT 14                             // arbitrary 2 weeks
1828
1829    cent = (nt % 100);                           // cent past secs
1830    secs = (nt /= 100);                          // total secs
1831    mins = (nt /= 60);                           // total mins
1832    hour = (nt /= 60);                           // total hour
1833    days = (nt /=  24);                          // totat days
1834    week = (nt / 7);                             // total week
1835
1836    if (Rc.tics_scaled > target)
1837       target += Rc.tics_scaled - target;
1838
1839    switch (target) {
1840       case TICS_AS_SECS:
1841          if (mins < mmLIMIT + 1) {
1842             if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu.%02lu", mins, secs % 60, cent))
1843                goto end_justifies;
1844          }
1845       case TICS_AS_MINS:                        // fall through
1846          if (mins < mmLIMIT + 1) {
1847             if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu", mins, secs % 60))
1848                goto end_justifies;
1849          }
1850       case TICS_AS_HOUR:                        // fall through
1851          if (hour < hhLIMIT + 1) {
1852             if (width >= snprintf(buf, sizeof(buf), "%lu,%02lu", hour, mins % 60))
1853                goto end_justifies;
1854          }
1855       case TICS_AS_DAY1:                        // fall through
1856          if (days < ddLIMIT + 1) {
1857             if (width >= snprintf(buf, sizeof(buf), DD "+" HH, days, hour % 24))
1858                goto end_justifies;
1859 #ifdef SCALE_POSTFX
1860             if (width >= snprintf(buf, sizeof(buf), DD "+%lu", days, hour % 24))
1861                goto end_justifies;
1862 #endif
1863       case TICS_AS_DAY2:                        // fall through
1864             if (width >= snprintf(buf, sizeof(buf), DD, days))
1865                goto end_justifies;
1866          }
1867       case TICS_AS_WEEK:                        // fall through
1868          if (width >= snprintf(buf, sizeof(buf), WW "+" DD, week, days % 7))
1869             goto end_justifies;
1870 #ifdef SCALE_POSTFX
1871          if (width >= snprintf(buf, sizeof(buf), WW "+%lu", week, days % 7))
1872             goto end_justifies;
1873 #endif
1874       case TICS_AS_LAST:                        // fall through
1875          if (width >= snprintf(buf, sizeof(buf), WW, week))
1876             goto end_justifies;
1877       default:                                  // fall through
1878          break;
1879    }
1880  #undef mmLIMIT
1881  #undef hhLIMIT
1882  #undef ddLIMIT
1883 #endif
1884
1885    // well shoot, this outta' fit...
1886    snprintf(buf, sizeof(buf), "?");
1887
1888 end_justifies:
1889    return justify_pad(buf, width, justr);
1890  #undef HH
1891  #undef DD
1892  #undef WW
1893 } // end: scale_tics
1894 \f
1895 /*######  Fields Management support  #####################################*/
1896
1897         /* These are our gosh darn 'Fields' !
1898            They MUST be kept in sync with pflags !! */
1899 static struct {
1900    int           width;         // field width, if applicable
1901    int           scale;         // scaled target, if applicable
1902    const int     align;         // the default column alignment flag
1903    enum pids_item item;         // the new libproc item enum identifier
1904 } Fieldstab[] = {
1905    // these identifiers reflect the default column alignment but they really
1906    // contain the WIN_t flag used to check/change justification at run-time!
1907  #define A_left  Show_JRSTRS       /* toggled with lower case 'j' */
1908  #define A_right Show_JRNUMS       /* toggled with upper case 'J' */
1909
1910 /* .width anomalies:
1911         a -1 width represents variable width columns
1912         a  0 width represents columns set once at startup (see zap_fieldstab)
1913
1914      .width  .scale  .align    .item
1915      ------  ------  --------  ------------------- */
1916    {     0,     -1,  A_right,  PIDS_ID_PID         },  // s_int    EU_PID
1917    {     0,     -1,  A_right,  PIDS_ID_PPID        },  // s_int    EU_PPD
1918    {     5,     -1,  A_right,  PIDS_ID_EUID        },  // u_int    EU_UED
1919    {     8,     -1,  A_left,   PIDS_ID_EUSER       },  // str      EU_UEN
1920    {     5,     -1,  A_right,  PIDS_ID_RUID        },  // u_int    EU_URD
1921    {     8,     -1,  A_left,   PIDS_ID_RUSER       },  // str      EU_URN
1922    {     5,     -1,  A_right,  PIDS_ID_SUID        },  // u_int    EU_USD
1923    {     8,     -1,  A_left,   PIDS_ID_SUSER       },  // str      EU_USN
1924    {     5,     -1,  A_right,  PIDS_ID_EGID        },  // u_int    EU_GID
1925    {     8,     -1,  A_left,   PIDS_ID_EGROUP      },  // str      EU_GRP
1926    {     0,     -1,  A_right,  PIDS_ID_PGRP        },  // s_int    EU_PGD
1927    {     8,     -1,  A_left,   PIDS_TTY_NAME       },  // str      EU_TTY
1928    {     0,     -1,  A_right,  PIDS_ID_TPGID       },  // s_int    EU_TPG
1929    {     0,     -1,  A_right,  PIDS_ID_SESSION     },  // s_int    EU_SID
1930    {     3,     -1,  A_right,  PIDS_PRIORITY       },  // s_int    EU_PRI
1931    {     3,     -1,  A_right,  PIDS_NICE           },  // s_int    EU_NCE
1932    {     3,     -1,  A_right,  PIDS_NLWP           },  // s_int    EU_THD
1933    {     0,     -1,  A_right,  PIDS_PROCESSOR      },  // s_int    EU_CPN
1934    {     5,     -1,  A_right,  PIDS_TICS_ALL_DELTA },  // u_int    EU_CPU
1935    {     6,     -1,  A_right,  PIDS_TICS_ALL       },  // ull_int  EU_TME
1936    {     9,     -1,  A_right,  PIDS_TICS_ALL       },  // ull_int  EU_TM2
1937    {     5,     -1,  A_right,  PIDS_MEM_RES        },  // ul_int   EU_MEM
1938    {     7,  SK_Kb,  A_right,  PIDS_MEM_VIRT       },  // ul_int   EU_VRT
1939    {     6,  SK_Kb,  A_right,  PIDS_VM_SWAP        },  // ul_int   EU_SWP
1940    {     6,  SK_Kb,  A_right,  PIDS_MEM_RES        },  // ul_int   EU_RES
1941    {     6,  SK_Kb,  A_right,  PIDS_MEM_CODE       },  // ul_int   EU_COD
1942    {     7,  SK_Kb,  A_right,  PIDS_MEM_DATA       },  // ul_int   EU_DAT
1943    {     6,  SK_Kb,  A_right,  PIDS_MEM_SHR        },  // ul_int   EU_SHR
1944    {     4,     -1,  A_right,  PIDS_FLT_MAJ        },  // ul_int   EU_FL1
1945    {     4,     -1,  A_right,  PIDS_FLT_MIN        },  // ul_int   EU_FL2
1946    {     4,     -1,  A_right,  PIDS_noop           },  // ul_int   EU_DRT ( always 0 w/ since 2.6 )
1947    {     1,     -1,  A_right,  PIDS_STATE          },  // s_ch     EU_STA
1948    {    -1,     -1,  A_left,   PIDS_CMD            },  // str      EU_CMD
1949    {    10,     -1,  A_left,   PIDS_WCHAN_NAME     },  // str      EU_WCH
1950    {     8,     -1,  A_left,   PIDS_FLAGS          },  // ul_int   EU_FLG
1951    {    -1,     -1,  A_left,   PIDS_CGROUP         },  // str      EU_CGR
1952    {    -1,     -1,  A_left,   PIDS_SUPGIDS        },  // str      EU_SGD
1953    {    -1,     -1,  A_left,   PIDS_SUPGROUPS      },  // str      EU_SGN
1954    {     0,     -1,  A_right,  PIDS_ID_TGID        },  // s_int    EU_TGD
1955    {     5,     -1,  A_right,  PIDS_OOM_ADJ        },  // s_int    EU_OOA
1956    {     4,     -1,  A_right,  PIDS_OOM_SCORE      },  // s_int    EU_OOM
1957    {    -1,     -1,  A_left,   PIDS_ENVIRON        },  // str      EU_ENV
1958    {     3,     -1,  A_right,  PIDS_FLT_MAJ_DELTA  },  // s_int    EU_FV1
1959    {     3,     -1,  A_right,  PIDS_FLT_MIN_DELTA  },  // s_int    EU_FV2
1960    {     6,  SK_Kb,  A_right,  PIDS_VM_USED        },  // ul_int   EU_USE
1961    {    10,     -1,  A_right,  PIDS_NS_IPC         },  // ul_int   EU_NS1
1962    {    10,     -1,  A_right,  PIDS_NS_MNT         },  // ul_int   EU_NS2
1963    {    10,     -1,  A_right,  PIDS_NS_NET         },  // ul_int   EU_NS3
1964    {    10,     -1,  A_right,  PIDS_NS_PID         },  // ul_int   EU_NS4
1965    {    10,     -1,  A_right,  PIDS_NS_USER        },  // ul_int   EU_NS5
1966    {    10,     -1,  A_right,  PIDS_NS_UTS         },  // ul_int   EU_NS6
1967    {     8,     -1,  A_left,   PIDS_LXCNAME        },  // str      EU_LXC
1968    {     6,  SK_Kb,  A_right,  PIDS_VM_RSS_ANON    },  // ul_int   EU_RZA
1969    {     6,  SK_Kb,  A_right,  PIDS_VM_RSS_FILE    },  // ul_int   EU_RZF
1970    {     6,  SK_Kb,  A_right,  PIDS_VM_RSS_LOCKED  },  // ul_int   EU_RZL
1971    {     6,  SK_Kb,  A_right,  PIDS_VM_RSS_SHARED  },  // ul_int   EU_RZS
1972    {    -1,     -1,  A_left,   PIDS_CGNAME         },  // str      EU_CGN
1973    {     0,     -1,  A_right,  PIDS_PROCESSOR_NODE },  // s_int    EU_NMA
1974    {     5,     -1,  A_right,  PIDS_ID_LOGIN       },  // s_int    EU_LID
1975    {    -1,     -1,  A_left,   PIDS_EXE            },  // str      EU_EXE
1976    {     6,  SK_Kb,  A_right,  PIDS_SMAP_RSS       },  // ul_int   EU_RSS
1977    {     6,  SK_Kb,  A_right,  PIDS_SMAP_PSS       },  // ul_int   EU_PSS
1978    {     6,  SK_Kb,  A_right,  PIDS_SMAP_PSS_ANON  },  // ul_int   EU_PZA
1979    {     6,  SK_Kb,  A_right,  PIDS_SMAP_PSS_FILE  },  // ul_int   EU_PZF
1980    {     6,  SK_Kb,  A_right,  PIDS_SMAP_PSS_SHMEM },  // ul_int   EU_PZS
1981    {     6,  SK_Kb,  A_right,  PIDS_SMAP_PRV_TOTAL },  // ul_int   EU_USS
1982    {     6,     -1,  A_right,  PIDS_IO_READ_BYTES  },  // ul_int   EU_IRB
1983    {     5,     -1,  A_right,  PIDS_IO_READ_OPS    },  // ul_int   EU_IRO
1984    {     6,     -1,  A_right,  PIDS_IO_WRITE_BYTES },  // ul_int   EU_IWB
1985    {     5,     -1,  A_right,  PIDS_IO_WRITE_OPS   },  // ul_int   EU_IWO
1986    {     5,     -1,  A_right,  PIDS_AUTOGRP_ID     },  // s_int    EU_AGI
1987    {     4,     -1,  A_right,  PIDS_AUTOGRP_NICE   },  // s_int    EU_AGN
1988    {     7,     -1,  A_right,  PIDS_TICS_BEGAN     },  // ull_int  EU_TM3
1989    {     7,     -1,  A_right,  PIDS_TIME_ELAPSED   },  // real     EU_TM4
1990    {     6,     -1,  A_right,  PIDS_UTILIZATION    },  // real     EU_CUU
1991    {     7,     -1,  A_right,  PIDS_UTILIZATION_C  },  // real     EU_CUC
1992    {    10,     -1,  A_right,  PIDS_NS_CGROUP      },  // ul_int   EU_NS7
1993    {    10,     -1,  A_right,  PIDS_NS_TIME        }   // ul_int   EU_NS8
1994 #define eu_LAST  EU_NS8
1995 // xtra Fieldstab 'pseudo pflag' entries for the newlib interface . . . . . . .
1996 #define eu_CMDLINE     eu_LAST +1
1997 #define eu_TICS_ALL_C  eu_LAST +2
1998 #define eu_ID_FUID     eu_LAST +3
1999 #define eu_TREE_HID    eu_LAST +4
2000 #define eu_TREE_LVL    eu_LAST +5
2001 #define eu_TREE_ADD    eu_LAST +6
2002 #define eu_CMDLINE_V   eu_LAST +7
2003 #define eu_ENVIRON_V   eu_LAST +8
2004    , {  -1, -1, -1,  PIDS_CMDLINE     }  // str      ( if Show_CMDLIN, eu_CMDLINE    )
2005    , {  -1, -1, -1,  PIDS_TICS_ALL_C  }  // ull_int  ( if Show_CTIMES, eu_TICS_ALL_C )
2006    , {  -1, -1, -1,  PIDS_ID_FUID     }  // u_int    ( if a usrseltyp, eu_ID_FUID    )
2007    , {  -1, -1, -1,  PIDS_extra       }  // s_ch     ( if Show_FOREST, eu_TREE_HID   )
2008    , {  -1, -1, -1,  PIDS_extra       }  // s_int    ( if Show_FOREST, eu_TREE_LVL   )
2009    , {  -1, -1, -1,  PIDS_extra       }  // s_int    ( if Show_FOREST, eu_TREE_ADD   )
2010    , {  -1, -1, -1,  PIDS_CMDLINE_V   }  // strv     ( if Ctrlk,       eu_CMDLINE_V  )
2011    , {  -1, -1, -1,  PIDS_ENVIRON_V   }  // strv     ( if CtrlN,       eu_ENVIRON_V  )
2012  #undef A_left
2013  #undef A_right
2014 };
2015
2016
2017         /*
2018          * A calibrate_fields() *Helper* function which refreshes
2019          * all that cached screen geometry plus related variables */
2020 static void adj_geometry (void) {
2021    static size_t pseudo_max = 0;
2022    static int w_set = 0, w_cols = 0, w_rows = 0;
2023    struct winsize wz;
2024
2025    Screen_cols = columns;    // <term.h>
2026    Screen_rows = lines;      // <term.h>
2027
2028    if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz)
2029    && 0 < wz.ws_col && 0 < wz.ws_row) {
2030       Screen_cols = wz.ws_col;
2031       Screen_rows = wz.ws_row;
2032    }
2033
2034 #ifndef RMAN_IGNORED
2035    // be crudely tolerant of crude tty emulators
2036    if (Cap_avoid_eol) Screen_cols--;
2037 #endif
2038
2039    // we might disappoint some folks (but they'll deserve it)
2040    if (Screen_cols > SCREENMAX) Screen_cols = SCREENMAX;
2041    if (Screen_cols < W_MIN_COL) Screen_cols = W_MIN_COL;
2042
2043    if (!w_set) {
2044       if (Width_mode > 0)              // -w with arg, we'll try to honor
2045          w_cols = Width_mode;
2046       else
2047       if (Width_mode < 0) {            // -w without arg, try environment
2048          char *env_columns = getenv("COLUMNS"),
2049               *env_lines = getenv("LINES"),
2050               *ep;
2051          if (env_columns && *env_columns) {
2052             long t, tc = 0;
2053             t = strtol(env_columns, &ep, 0);
2054             if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tc = t;
2055             if (0 < tc) w_cols = (int)tc;
2056          }
2057          if (env_lines && *env_lines) {
2058             long t, tr = 0;
2059             t = strtol(env_lines, &ep, 0);
2060             if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tr = t;
2061             if (0 < tr) w_rows = (int)tr;
2062          }
2063          if (!w_cols) w_cols = SCREENMAX;
2064          if (w_cols && w_cols < W_MIN_COL) w_cols = W_MIN_COL;
2065          if (w_rows && w_rows < W_MIN_ROW) w_rows = W_MIN_ROW;
2066       }
2067       if (w_cols > SCREENMAX) w_cols = SCREENMAX;
2068       w_set = 1;
2069    }
2070
2071    /* keep our support for output optimization in sync with current reality
2072       note: when we're in Batch mode, we don't really need a Pseudo_screen
2073             and when not Batch, our buffer will contain 1 extra 'line' since
2074             Msg_row is never represented -- but it's nice to have some space
2075             between us and the great-beyond... */
2076    if (Batch) {
2077       if (w_cols) Screen_cols = w_cols;
2078       Screen_rows = w_rows ? w_rows : INT_MAX;
2079       Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2080    } else {
2081       const int max_rows = INT_MAX / (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2082       if (w_cols && w_cols < Screen_cols) Screen_cols = w_cols;
2083       if (w_rows && w_rows < Screen_rows) Screen_rows = w_rows;
2084       if (Screen_rows < 0 || Screen_rows > max_rows) Screen_rows = max_rows;
2085       Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ) * Screen_rows;
2086    }
2087    // we'll only grow our Pseudo_screen, never shrink it
2088    if (pseudo_max < Pseudo_size) {
2089       pseudo_max = Pseudo_size;
2090       Pseudo_screen = alloc_r(Pseudo_screen, pseudo_max);
2091    }
2092    // ensure each row is repainted (just in case)
2093    PSU_CLREOS(0);
2094
2095    // prepare to customize potential cpu/memory graphs
2096    if (Curwin->rc.double_up) {
2097       int num = (Curwin->rc.double_up + 1);
2098       int pfx = (Curwin->rc.double_up < 2) ? GRAPH_prefix_std : GRAPH_prefix_abv;
2099
2100       Graph_cpus->length = (Screen_cols - (ADJOIN_space * Curwin->rc.double_up) - (num * (pfx + GRAPH_suffix))) / num;
2101       if (Graph_cpus->length > GRAPH_length_max) Graph_cpus->length = GRAPH_length_max;
2102       if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min;
2103
2104 #ifdef TOG4_MEM_1UP
2105       Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix));
2106 #else
2107       Graph_mems->length = (Screen_cols - ADJOIN_space - (2 * (pfx + GRAPH_suffix))) / 2;
2108 #endif
2109       if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max;
2110       if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min;
2111
2112 #if !defined(TOG4_MEM_FIX) && !defined(TOG4_MEM_1UP)
2113       if (num > 2) {
2114        #define cpuGRAPH  ( GRAPH_prefix_abv + Graph_cpus->length + GRAPH_suffix )
2115        #define nxtGRAPH  ( cpuGRAPH + ADJOIN_space )
2116          int len = cpuGRAPH;
2117          for (;;) {
2118             if (len + nxtGRAPH > GRAPH_length_max) break;
2119             len += nxtGRAPH;
2120          }
2121          len -= (GRAPH_prefix_abv + ADJOIN_space);
2122          Graph_mems->length = len;
2123        #undef cpuGRAPH
2124        #undef nxtGRAPH
2125       }
2126 #endif
2127    } else {
2128       Graph_cpus->length = Screen_cols - (GRAPH_prefix_std + GRAPH_length_max + GRAPH_suffix);
2129       if (Graph_cpus->length >= 0) Graph_cpus->length = GRAPH_length_max;
2130       else Graph_cpus->length = Screen_cols - GRAPH_prefix_std - GRAPH_suffix;
2131       if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min;
2132 #ifdef TOG4_MEM_1UP
2133       Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix));
2134       if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max;
2135       if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min;
2136 #else
2137       Graph_mems->length = Graph_cpus->length;
2138 #endif
2139    }
2140    Graph_cpus->adjust = (float)Graph_cpus->length / 100.0;
2141    Graph_cpus->style  = Curwin->rc.graph_cpus;
2142
2143    Graph_mems->adjust = (float)Graph_mems->length / 100.0;
2144    Graph_mems->style  = Curwin->rc.graph_mems;
2145
2146    fflush(stdout);
2147    Frames_signal = BREAK_off;
2148 } // end: adj_geometry
2149
2150
2151         /*
2152          * A calibrate_fields() *Helper* function to build the actual
2153          * column headers & ensure necessary item enumerators support */
2154 static void build_headers (void) {
2155  #define ckITEM(f) do { Pids_itms[f] = Fieldstab[f].item; } while (0)
2156  #define ckCMDS(w) do { if (CHKw(w, Show_CMDLIN)) ckITEM(eu_CMDLINE); } while (0)
2157    FLG_t f;
2158    char *s;
2159    WIN_t *w = Curwin;
2160 #ifdef EQUCOLHDRYES
2161    int x, hdrmax = 0;
2162 #endif
2163    int i;
2164
2165    // ensure fields not visible incur no significant library costs
2166    for (i = 0; i < MAXTBL(Fieldstab); i++)
2167       Pids_itms[i] = PIDS_extra;
2168
2169    ckITEM(EU_PID);      // these 2 fields may not display,
2170    ckITEM(EU_STA);      // yet we'll always need them both
2171    ckITEM(EU_CMD);      // this is used with 'Y' (inspect)
2172
2173    do {
2174       if (VIZISw(w)) {
2175          memset((s = w->columnhdr), 0, sizeof(w->columnhdr));
2176          if (Rc.mode_altscr) s = scat(s, fmtmk("%d", w->winnum));
2177
2178          for (i = 0; i < w->maxpflgs; i++) {
2179             f = w->procflgs[i];
2180 #ifdef USE_X_COLHDR
2181             if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2182                s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_msg));
2183                w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_msg);
2184             }
2185 #else
2186             if (EU_MAXPFLGS <= f) continue;
2187 #endif
2188             ckITEM(f);
2189             switch (f) {
2190                case EU_CMD:
2191                   ckCMDS(w);
2192                   break;
2193                case EU_CPU:
2194                // cpu calculations depend on number of threads
2195                   ckITEM(EU_THD);
2196                   break;
2197                case EU_TME:
2198                case EU_TM2:
2199                // for 'cumulative' times, we'll need equivalent of cutime & cstime
2200                   if (CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C);
2201                   break;
2202                default:
2203                   break;
2204             }
2205             s = scat(s, utf8_justify(N_col(f)
2206                , VARcol(f) ? w->varcolsz : Fieldstab[f].width
2207                , CHKw(w, Fieldstab[f].align)));
2208 #ifdef USE_X_COLHDR
2209             if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2210                s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_hdr));
2211                w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_hdr);
2212             }
2213 #endif
2214          }
2215 #ifdef EQUCOLHDRYES
2216          // prepare to even out column header lengths...
2217          if (hdrmax + w->hdrcaplen < (x = strlen(w->columnhdr))) hdrmax = x - w->hdrcaplen;
2218 #endif
2219          // for 'busy' only processes, we'll need elapsed tics
2220          if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU);
2221          // with forest view mode, we'll need pid, tgid, ppid & start_time...
2222 #ifndef TREE_VCPUOFF
2223          if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); ckITEM(eu_TREE_ADD); }
2224 #else
2225          if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); }
2226 #endif
2227          // for 'u/U' filtering we need these too (old top forgot that, oops)
2228          if (w->usrseltyp) { ckITEM(EU_UED); ckITEM(EU_URD); ckITEM(EU_USD); ckITEM(eu_ID_FUID); }
2229
2230          // we must also accommodate an out of view sort field...
2231          f = w->rc.sortindx;
2232          if (EU_CMD == f) ckCMDS(w);
2233          else ckITEM(f);
2234
2235          // lastly, accommodate any special non-display 'tagged' needs...
2236          i = 0;
2237          while (Bot_item[i] > BOT_DELIMIT) {
2238             ckITEM(Bot_item[i]);
2239             ++i;
2240          }
2241       } // end: VIZISw(w)
2242
2243       if (Rc.mode_altscr) w = w->next;
2244    } while (w != Curwin);
2245
2246 #ifdef EQUCOLHDRYES
2247    /* now we can finally even out column header lengths
2248       (we're assuming entire columnhdr was memset to '\0') */
2249    if (Rc.mode_altscr && SCREENMAX > Screen_cols)
2250       for (i = 0; i < GROUPSMAX; i++) {
2251          w = &Winstk[i];
2252          if (CHKw(w, Show_TASKON))
2253             if (hdrmax + w->hdrcaplen > (x = strlen(w->columnhdr)))
2254                memset(&w->columnhdr[x], ' ', hdrmax + w->hdrcaplen - x);
2255       }
2256 #endif
2257
2258  #undef ckITEM
2259  #undef ckCMDS
2260 } // end: build_headers
2261
2262
2263         /*
2264          * This guy coordinates the activities surrounding the maintenance of
2265          * each visible window's columns headers plus item enumerators needed */
2266 static void calibrate_fields (void) {
2267    FLG_t f;
2268    char *s;
2269    const char *h;
2270    WIN_t *w = Curwin;
2271    int i, varcolcnt, len, rc;
2272
2273    adj_geometry();
2274
2275    do {
2276       if (VIZISw(w)) {
2277          w->hdrcaplen = 0;   // really only used with USE_X_COLHDR
2278          // build window's pflgsall array, establish upper bounds for maxpflgs
2279          for (i = 0, w->totpflgs = 0; i < EU_MAXPFLGS; i++) {
2280             if (FLDviz(w, i)) {
2281                f = FLDget(w, i);
2282 #ifdef USE_X_COLHDR
2283                w->pflgsall[w->totpflgs++] = f;
2284 #else
2285                if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2286                   w->pflgsall[w->totpflgs++] = EU_XON;
2287                   w->pflgsall[w->totpflgs++] = f;
2288                   w->pflgsall[w->totpflgs++] = EU_XOF;
2289                } else
2290                   w->pflgsall[w->totpflgs++] = f;
2291 #endif
2292             }
2293          }
2294          if (!w->totpflgs) w->pflgsall[w->totpflgs++] = EU_PID;
2295
2296          /* build a preliminary columns header not to exceed screen width
2297             while accounting for a possible leading window number */
2298          w->varcolsz = varcolcnt = 0;
2299          *(s = w->columnhdr) = '\0';
2300          if (Rc.mode_altscr) s = scat(s, " ");
2301          for (i = 0; i + w->begpflg < w->totpflgs; i++) {
2302             f = w->pflgsall[i + w->begpflg];
2303             w->procflgs[i] = f;
2304 #ifndef USE_X_COLHDR
2305             if (EU_MAXPFLGS <= f) continue;
2306 #endif
2307             h = N_col(f);
2308             len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2309             // oops, won't fit -- we're outta here...
2310             if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2311             if (VARcol(f)) { ++varcolcnt; w->varcolsz += strlen(h); }
2312             s = scat(s, fmtmk("%*.*s", len, len, h));
2313          }
2314 #ifndef USE_X_COLHDR
2315          if (i >= 1 && EU_XON == w->procflgs[i - 1]) --i;
2316 #endif
2317
2318          /* establish the final maxpflgs and prepare to grow the variable column
2319             heading(s) via varcolsz - it may be a fib if their pflags weren't
2320             encountered, but that's ok because they won't be displayed anyway */
2321          w->maxpflgs = i;
2322          w->varcolsz += Screen_cols - strlen(w->columnhdr);
2323          if (varcolcnt) w->varcolsz /= varcolcnt;
2324
2325          /* establish the field where all remaining fields would still
2326             fit within screen width, including a leading window number */
2327          *(s = w->columnhdr) = '\0';
2328          if (Rc.mode_altscr) s = scat(s, " ");
2329          w->endpflg = 0;
2330          for (i = w->totpflgs - 1; -1 < i; i--) {
2331             f = w->pflgsall[i];
2332 #ifndef USE_X_COLHDR
2333             if (EU_MAXPFLGS <= f) { w->endpflg = i; continue; }
2334 #endif
2335             h = N_col(f);
2336             len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2337             if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2338             s = scat(s, fmtmk("%*.*s", len, len, h));
2339             w->endpflg = i;
2340          }
2341 #ifndef USE_X_COLHDR
2342          if (EU_XOF == w->pflgsall[w->endpflg]) ++w->endpflg;
2343 #endif
2344       } // end: if (VIZISw(w))
2345
2346       if (Rc.mode_altscr) w = w->next;
2347    } while (w != Curwin);
2348
2349    build_headers();
2350
2351    if ((rc = procps_pids_reset(Pids_ctx, Pids_itms, Pids_itms_tot)))
2352       error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
2353 } // end: calibrate_fields
2354
2355
2356         /*
2357          * Display each field represented in the current window's fieldscur
2358          * array along with its description.  Mark with bold and a leading
2359          * asterisk those fields associated with the "on" or "active" state.
2360          *
2361          * Special highlighting will be accorded the "focus" field with such
2362          * highlighting potentially extended to include the description.
2363          *
2364          * Below is the current Fieldstab space requirement and how
2365          * we apportion it.  The xSUFX is considered sacrificial,
2366          * something we can reduce or do without.
2367          *            0        1         2         3
2368          *            12345678901234567890123456789012
2369          *            * HEADING = Longest Description!
2370          *      xPRFX ----------______________________ xSUFX
2371          *    ( xPRFX has pos 2 & 10 for 'extending' when at minimums )
2372          *
2373          * The first 4 screen rows are reserved for explanatory text, and
2374          * the maximum number of columns is Screen_cols / xPRFX + 1 space
2375          * between columns.  Thus, for example, with 42 fields a tty will
2376          * still remain useable under these extremes:
2377          *       rows       columns     what's
2378          *       tty  top   tty  top    displayed
2379          *       ---  ---   ---  ---    ------------------
2380          *        46   42    10    1    xPRFX only
2381          *        46   42    32    1    full xPRFX + xSUFX
2382          *         6    2   231   21    xPRFX only
2383          *        10    6   231    7    full xPRFX + xSUFX
2384          */
2385 static void display_fields (int focus, int extend) {
2386  #define mkERR { putp("\n"); putp(N_txt(XTRA_winsize_txt)); return; }
2387  #define mxCOL ( (Screen_cols / 11) > 0 ? (Screen_cols / 11) : 1 )
2388  #define yRSVD  4
2389  #define xEQUS  2                      // length of suffix beginning '= '
2390  #define xSUFX  22                     // total suffix length, incl xEQUS
2391  #define xPRFX (10 + xadd)
2392  #define xTOTL (xPRFX + xSUFX)
2393    static int col_sav, row_sav;
2394    WIN_t *w = Curwin;                  // avoid gcc bloat with a local copy
2395    int i;                              // utility int (a row, tot cols, ix)
2396    int smax;                           // printable width of xSUFX
2397    int xadd = 0;                       // spacing between data columns
2398    int cmax = Screen_cols;             // total data column width
2399    int rmax = Screen_rows - yRSVD;     // total useable rows
2400
2401    i = (EU_MAXPFLGS % mxCOL) ? 1 : 0;
2402    if (rmax < i + (EU_MAXPFLGS / mxCOL)) mkERR;
2403    i = EU_MAXPFLGS / rmax;
2404    if (EU_MAXPFLGS % rmax) ++i;
2405    if (i > 1) { cmax /= i; xadd = 1; }
2406    if (cmax > xTOTL) cmax = xTOTL;
2407    smax = cmax - xPRFX;
2408    if (smax < 0) mkERR;
2409
2410    /* we'll go the extra distance to avoid any potential screen flicker
2411       which occurs under some terminal emulators (but it was our fault) */
2412    if (col_sav != Screen_cols || row_sav != Screen_rows) {
2413       col_sav = Screen_cols;
2414       row_sav = Screen_rows;
2415       putp(Cap_clr_eos);
2416    }
2417    fflush(stdout);
2418
2419    for (i = 0; i < EU_MAXPFLGS; ++i) {
2420       int b = FLDviz(w, i), x = (i / rmax) * cmax, y = (i % rmax) + yRSVD;
2421       const char *e = (i == focus && extend) ? w->capclr_hdr : "";
2422       FLG_t f = FLDget(w, i);
2423       char sbuf[xSUFX*4];                        // 4 = max multi-byte
2424       int xcol, xfld;
2425
2426       /* prep sacrificial suffix (allowing for beginning '= ')
2427          note: width passed to 'utf8_embody' may go negative, but he'll be just fine */
2428       snprintf(sbuf, sizeof(sbuf), "= %.*s", utf8_embody(N_fld(f), smax - xEQUS), N_fld(f));
2429       // obtain translated deltas (if any) ...
2430       xcol = utf8_delta(fmtmk("%.*s", utf8_embody(N_col(f), 8), N_col(f)));
2431       xfld = utf8_delta(sbuf + xEQUS);           // ignore beginning '= '
2432
2433       PUTT("%s%c%s%s %s%-*.*s%s%s%s %-*.*s%s"
2434          , tg2(x, y)
2435          , b ? '*' : ' '
2436          , b ? w->cap_bold : Cap_norm
2437          , e
2438          , i == focus ? w->capclr_hdr : ""
2439          , 8 + xcol, 8 + xcol
2440          , N_col(f)
2441          , Cap_norm
2442          , b ? w->cap_bold : ""
2443          , e
2444          , smax + xfld, smax + xfld
2445          , sbuf
2446          , Cap_norm);
2447    }
2448
2449    putp(Caps_off);
2450  #undef mkERR
2451  #undef mxCOL
2452  #undef yRSVD
2453  #undef xEQUS
2454  #undef xSUFX
2455  #undef xPRFX
2456  #undef xTOTL
2457 } // end: display_fields
2458
2459
2460         /*
2461          * Manage all fields aspects (order/toggle/sort), for all windows. */
2462 static void fields_utility (void) {
2463 #ifndef SCROLLVAR_NO
2464  #define unSCRL  { w->begpflg = w->varcolbeg = 0; OFFw(w, Show_HICOLS); }
2465 #else
2466  #define unSCRL  { w->begpflg = 0; OFFw(w, Show_HICOLS); }
2467 #endif
2468  #define swapEM  { int c; unSCRL; c = w->rc.fieldscur[i]; \
2469        w->rc.fieldscur[i] = *p; *p = c; p = &w->rc.fieldscur[i]; }
2470  #define spewFI  { int *t; f = w->rc.sortindx; t = msch(w->rc.fieldscur, ENUcvt(f, FLDon), EU_MAXPFLGS); \
2471        if (!t) t = msch(w->rc.fieldscur, ENUcvt(f, FLDoff), EU_MAXPFLGS); \
2472        i = (t) ? (int)(t - w->rc.fieldscur) : 0; }
2473    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
2474    const char *h = NULL;
2475    int *p = NULL;
2476    int i, key;
2477    FLG_t f;
2478
2479    spewFI
2480 signify_that:
2481    putp(Cap_clr_scr);
2482    adj_geometry();
2483
2484    do {
2485       if (!h) h = N_col(f);
2486       putp(Cap_home);
2487       show_special(1, fmtmk(N_unq(FIELD_header_fmt)
2488          , w->grpname, CHKw(w, Show_FOREST) ? N_txt(FOREST_views_txt) : h));
2489       display_fields(i, (p != NULL));
2490       fflush(stdout);
2491
2492       if (Frames_signal) goto signify_that;
2493       key = iokey(IOKEY_ONCE);
2494       if (key < 1) goto signify_that;
2495
2496       switch (key) {
2497          case kbd_UP:
2498             if (i > 0) { --i; if (p) swapEM }
2499             break;
2500          case kbd_DOWN:
2501             if (i + 1 < EU_MAXPFLGS) { ++i; if (p) swapEM }
2502             break;
2503          case kbd_LEFT:
2504          case kbd_ENTER:
2505             p = NULL;
2506             break;
2507          case kbd_RIGHT:
2508             p = &w->rc.fieldscur[i];
2509             break;
2510          case kbd_HOME:
2511          case kbd_PGUP:
2512             if (!p) i = 0;
2513             break;
2514          case kbd_END:
2515          case kbd_PGDN:
2516             if (!p) i = EU_MAXPFLGS - 1;
2517             break;
2518          case kbd_SPACE:
2519          case 'd':
2520             if (!p) { FLDtog(w, i); unSCRL }
2521             break;
2522          case 's':
2523 #ifdef TREE_NORESET
2524             if (!p && !CHKw(w, Show_FOREST)) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL }
2525 #else
2526             if (!p) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL; OFFw(w, Show_FOREST); }
2527 #endif
2528             break;
2529          case 'a':
2530          case 'w':
2531             Curwin = w = ('a' == key) ? w->next : w->prev;
2532             spewFI
2533             h = NULL;
2534             p = NULL;
2535             break;
2536          default:                 // keep gcc happy
2537             break;
2538       }
2539    } while (key != 'q' && key != kbd_ESC);
2540
2541    // signal that we just corrupted entire screen
2542    Frames_signal = BREAK_screen;
2543  #undef unSCRL
2544  #undef swapEM
2545  #undef spewFI
2546 } // end: fields_utility
2547
2548
2549         /*
2550          * This routine takes care of auto sizing field widths
2551          * if/when the user sets Rc.fixed_widest to -1.  Along the
2552          * way he reinitializes some things for the next frame. */
2553 static inline void widths_resize (void) {
2554    int i;
2555
2556    // next var may also be set by the guys that actually truncate stuff
2557    Autox_found = 0;
2558    for (i = 0; i < EU_MAXPFLGS; i++) {
2559       if (Autox_array[i]) {
2560          Fieldstab[i].width++;
2561          Autox_array[i] = 0;
2562          Autox_found = 1;
2563       }
2564    }
2565    // trigger a call to calibrate_fields (via zap_fieldstab)
2566    if (Autox_found) Frames_signal = BREAK_autox;
2567 } // end: widths_resize
2568
2569
2570         /*
2571          * This routine exists just to consolidate most of the messin'
2572          * around with the Fieldstab array and some related stuff. */
2573 static void zap_fieldstab (void) {
2574 #ifdef WIDEN_COLUMN
2575  #define maX(E) ( (wtab[E].wnls > wtab[E].wmin) \
2576   ? wtab[E].wnls : wtab[E].wmin )
2577    static struct {
2578       int wmin;         // minimum field width (-1 == variable width)
2579       int wnls;         // translated header column requirements
2580       int watx;         // +1 == non-scalable auto sized columns
2581    } wtab[EU_MAXPFLGS];
2582 #endif
2583    static int once;
2584    int i, digits;
2585    char buf[8];
2586
2587    if (!once) {
2588       Fieldstab[EU_CPN].width = 1;
2589       Fieldstab[EU_NMA].width = 2;
2590       Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2591          = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2592          = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = 5;
2593       if (5 < (digits = (int)procps_pid_length())) {
2594          if (10 < digits) error_exit(N_txt(FAIL_widepid_txt));
2595          Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2596             = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2597             = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = digits;
2598       }
2599 #ifdef WIDEN_COLUMN
2600       // identify our non-scalable auto sized columns
2601       wtab[EU_UED].watx = wtab[EU_UEN].watx = wtab[EU_URD].watx
2602          = wtab[EU_URN].watx = wtab[EU_USD].watx = wtab[EU_USN].watx
2603          = wtab[EU_GID].watx = wtab[EU_GRP].watx = wtab[EU_TTY].watx
2604          = wtab[EU_WCH].watx = wtab[EU_NS1].watx = wtab[EU_NS2].watx
2605          = wtab[EU_NS3].watx = wtab[EU_NS4].watx = wtab[EU_NS5].watx
2606          = wtab[EU_NS6].watx = wtab[EU_NS7].watx = wtab[EU_NS8].watx
2607          = wtab[EU_LXC].watx = wtab[EU_LID].watx
2608          = +1;
2609       /* establish translatable header 'column' requirements
2610          and ensure .width reflects the widest value */
2611       for (i = 0; i < EU_MAXPFLGS; i++) {
2612          wtab[i].wmin = Fieldstab[i].width;
2613          wtab[i].wnls = (int)strlen(N_col(i)) - utf8_delta(N_col(i));
2614          if (wtab[i].wmin != -1)
2615             Fieldstab[i].width = maX(i);
2616       }
2617 #endif
2618       once = 1;
2619    }
2620
2621    Cpu_pmax = 99.9;
2622    if (Rc.mode_irixps && Cpu_cnt > 1 && !Thread_mode) {
2623       Cpu_pmax = 100.0 * Cpu_cnt;
2624       if (Cpu_cnt > 1000) {
2625          if (Cpu_pmax > 9999999.0) Cpu_pmax = 9999999.0;
2626       } else if (Cpu_cnt > 100) {
2627          if (Cpu_cnt > 999999.0) Cpu_pmax = 999999.0;
2628       } else if (Cpu_cnt > 10) {
2629          if (Cpu_pmax > 99999.0) Cpu_pmax = 99999.0;
2630       } else {
2631          if (Cpu_pmax > 999.9) Cpu_pmax = 999.9;
2632       }
2633    }
2634
2635 #ifdef WIDEN_COLUMN
2636    digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2637    if (wtab[EU_CPN].wmin < digits) {
2638       if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2639       wtab[EU_CPN].wmin = digits;
2640       Fieldstab[EU_CPN].width = maX(EU_CPN);
2641    }
2642    digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2643    if (wtab[EU_NMA].wmin < digits) {
2644       wtab[EU_NMA].wmin = digits;
2645       Fieldstab[EU_NMA].width = maX(EU_NMA);
2646    }
2647
2648    // and accommodate optional wider non-scalable columns (maybe)
2649    if (!AUTOX_MODE) {
2650       for (i = 0; i < EU_MAXPFLGS; i++) {
2651          if (wtab[i].watx)
2652             Fieldstab[i].width = Rc.fixed_widest ? Rc.fixed_widest + maX(i) : maX(i);
2653       }
2654    }
2655 #else
2656    digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2657    if (1 < digits) {
2658       if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2659       Fieldstab[EU_CPN].width = digits;
2660    }
2661    digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2662    if (2 < digits)
2663       Fieldstab[EU_NMA].width = digits;
2664
2665    // and accommodate optional wider non-scalable columns (maybe)
2666    if (!AUTOX_MODE) {
2667       Fieldstab[EU_UED].width = Fieldstab[EU_URD].width
2668          = Fieldstab[EU_USD].width = Fieldstab[EU_GID].width
2669          = Rc.fixed_widest ? 5 + Rc.fixed_widest : 5;
2670       Fieldstab[EU_UEN].width = Fieldstab[EU_URN].width
2671          = Fieldstab[EU_USN].width = Fieldstab[EU_GRP].width
2672          = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2673       Fieldstab[EU_TTY].width = Fieldstab[EU_LXC].width
2674          = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2675       Fieldstab[EU_WCH].width
2676          = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2677       // the initial namespace fields
2678       for (i = EU_NS1; i <= EU_NS6; i++)
2679          Fieldstab[i].width
2680             = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2681       // the later namespace additions
2682       for (i = EU_NS7; i <= EU_NS8; i++)
2683          Fieldstab[i].width
2684             = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2685    }
2686 #endif
2687
2688    /* plus user selectable scaling */
2689    Fieldstab[EU_VRT].scale = Fieldstab[EU_SWP].scale
2690       = Fieldstab[EU_RES].scale = Fieldstab[EU_COD].scale
2691       = Fieldstab[EU_DAT].scale = Fieldstab[EU_SHR].scale
2692       = Fieldstab[EU_USE].scale = Fieldstab[EU_RZA].scale
2693       = Fieldstab[EU_RZF].scale = Fieldstab[EU_RZL].scale
2694       = Fieldstab[EU_RZS].scale = Fieldstab[EU_RSS].scale
2695       = Fieldstab[EU_PSS].scale = Fieldstab[EU_PZA].scale
2696       = Fieldstab[EU_PZF].scale = Fieldstab[EU_PZS].scale
2697       = Fieldstab[EU_USS].scale = Rc.task_mscale;
2698
2699    // lastly, ensure we've got proper column headers...
2700    calibrate_fields();
2701  #undef maX
2702 } // end: zap_fieldstab
2703 \f
2704 /*######  Library Interface (as separate threads)  #######################*/
2705
2706         /*
2707          * This guy's responsible for interfacing with the library <stat> API
2708          * and reaping all cpu or numa node tics.
2709          * ( his task is now embarassingly small under the new api ) */
2710 static void *cpus_refresh (void *unused) {
2711    enum stat_reap_type which;
2712
2713    do {
2714 #ifdef THREADED_CPU
2715       sem_wait(&Semaphore_cpus_beg);
2716 #endif
2717       which = STAT_REAP_CPUS_ONLY;
2718       if (CHKw(Curwin, View_CPUNOD))
2719          which = STAT_REAP_NUMA_NODES_TOO;
2720
2721       Stat_reap = procps_stat_reap(Stat_ctx, which, Stat_items, MAXTBL(Stat_items));
2722       if (!Stat_reap)
2723          error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
2724 #ifndef PRETEND0NUMA
2725       // adapt to changes in total numa nodes (assuming it's even possible)
2726       if (Stat_reap->numa->total && Stat_reap->numa->total != Numa_node_tot) {
2727          Numa_node_tot = Stat_reap->numa->total;
2728          Numa_node_sel = -1;
2729       }
2730 #endif
2731       if (Stat_reap->cpus->total && Stat_reap->cpus->total != Cpu_cnt) {
2732          Cpu_cnt = Stat_reap->cpus->total;
2733 #ifdef PRETEND48CPU
2734          Cpu_cnt = 48;
2735 #endif
2736       }
2737 #ifdef PRETENDECORE
2738 {  int i, x;
2739    x = Cpu_cnt - (Cpu_cnt / 4);
2740    for (i = 0; i < Cpu_cnt; i++)
2741       Stat_reap->cpus->stacks[i]->head[stat_COR_TYP].result.s_int = (i < x) ? P_CORE : E_CORE;
2742 }
2743 #endif
2744 #ifdef THREADED_CPU
2745       sem_post(&Semaphore_cpus_end);
2746    } while (1);
2747 #else
2748    } while (0);
2749 #endif
2750    return NULL;
2751    (void)unused;
2752 } // end: cpus_refresh
2753
2754
2755         /*
2756          * This serves as our interface to the memory portion of libprocps.
2757          * The sampling frequency is reduced in order to minimize overhead. */
2758 static void *memory_refresh (void *unused) {
2759    static time_t sav_secs;
2760    time_t cur_secs;
2761
2762    do {
2763 #ifdef THREADED_MEM
2764       sem_wait(&Semaphore_memory_beg);
2765 #endif
2766       if (Frames_signal)
2767          sav_secs = 0;
2768       cur_secs = time(NULL);
2769
2770       if (3 <= cur_secs - sav_secs) {
2771          if (!(Mem_stack = procps_meminfo_select(Mem_ctx, Mem_items, MAXTBL(Mem_items))))
2772             error_exit(fmtmk(N_fmt(LIB_errormem_fmt), __LINE__, strerror(errno)));
2773          sav_secs = cur_secs;
2774       }
2775 #ifdef THREADED_MEM
2776       sem_post(&Semaphore_memory_end);
2777    } while (1);
2778 #else
2779    } while (0);
2780 #endif
2781    return NULL;
2782    (void)unused;
2783 } // end: memory_refresh
2784
2785
2786         /*
2787          * This guy's responsible for interfacing with the library <pids> API
2788          * then refreshing the WIN_t ptr arrays, growing them as appropirate. */
2789 static void *tasks_refresh (void *unused) {
2790  #define nALIGN(n,m) (((n + m - 1) / m) * m)     // unconditionally align
2791  #define nALGN2(n,m) ((n + m - 1) & ~(m - 1))    // with power of 2 align
2792  #define n_reap  Pids_reap->counts->total
2793    static double uptime_sav;
2794    static int n_alloc = -1;                      // size of windows stacks arrays
2795    double uptime_cur;
2796    float et;
2797    int i, what;
2798
2799    do {
2800 #ifdef THREADED_TSK
2801       sem_wait(&Semaphore_tasks_beg);
2802 #endif
2803       procps_uptime(&uptime_cur, NULL);
2804       et = uptime_cur - uptime_sav;
2805       if (et < 0.01) et = 0.005;
2806       uptime_sav = uptime_cur;
2807       // if in Solaris mode, adjust our scaling for all cpus
2808       Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : Cpu_cnt));
2809
2810       what = Thread_mode ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;
2811       if (Monpidsidx) {
2812          what |= PIDS_SELECT_PID;
2813          Pids_reap = procps_pids_select(Pids_ctx, (unsigned *)Monpids, Monpidsidx, what);
2814       } else
2815          Pids_reap = procps_pids_reap(Pids_ctx, what);
2816       if (!Pids_reap)
2817          error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
2818
2819       // now refresh each window's stacks pointer array...
2820       if (n_alloc < n_reap) {
2821 //       n_alloc = nALIGN(n_reap, 100);
2822          n_alloc = nALGN2(n_reap, 128);
2823          for (i = 0; i < GROUPSMAX; i++) {
2824             Winstk[i].ppt = alloc_r(Winstk[i].ppt, sizeof(void *) * n_alloc);
2825             memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2826          }
2827       } else {
2828          for (i = 0; i < GROUPSMAX; i++)
2829             memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2830       }
2831 #ifdef THREADED_TSK
2832       sem_post(&Semaphore_tasks_end);
2833    } while (1);
2834 #else
2835    } while (0);
2836 #endif
2837    return NULL;
2838    (void)unused;
2839  #undef nALIGN
2840  #undef nALGN2
2841  #undef n_reap
2842 } // end: tasks_refresh
2843 \f
2844 /*######  Inspect Other Output  ##########################################*/
2845
2846         /*
2847          * HOWTO Extend the top 'inspect' functionality:
2848          *
2849          * To exploit the 'Y' interactive command, one must add entries to
2850          * the top personal configuration file.  Such entries simply reflect
2851          * a file to be read or command/pipeline to be executed whose results
2852          * will then be displayed in a separate scrollable window.
2853          *
2854          * Entries beginning with a '#' character are ignored, regardless of
2855          * content.  Otherwise they consist of the following 3 elements, each
2856          * of which must be separated by a tab character (thus 2 '\t' total):
2857          *     type:  literal 'file' or 'pipe'
2858          *     name:  selection shown on the Inspect screen
2859          *     fmts:  string representing a path or command
2860          *
2861          * The two types of Inspect entries are not interchangeable.
2862          * Those designated 'file' will be accessed using fopen/fread and must
2863          * reference a single file in the 'fmts' element.  Entries specifying
2864          * 'pipe' will employ popen/fread, their 'fmts' element could contain
2865          * many pipelined commands and, none can be interactive.
2866          *
2867          * Here are some examples of both types of inspection entries.
2868          * The first entry will be ignored due to the initial '#' character.
2869          * For clarity, the pseudo tab depictions (^I) are surrounded by an
2870          * extra space but the actual tabs would not be.
2871          *
2872          *     # pipe ^I Sockets ^I lsof -n -P -i 2>&1
2873          *     pipe ^I Open Files ^I lsof -P -p %d 2>&1
2874          *     file ^I NUMA Info ^I /proc/%d/numa_maps
2875          *     pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr
2876          *
2877          * Caution:  If the output contains unprintable characters they will
2878          * be displayed in either the ^I notation or hexidecimal <FF> form.
2879          * This applies to tab characters as well.  So if one wants a more
2880          * accurate display, any tabs should be expanded within the 'fmts'.
2881          *
2882          * The following example takes what could have been a 'file' entry
2883          * but employs a 'pipe' instead so as to expand the tabs.
2884          *
2885          *     # next would have contained '\t' ...
2886          *     # file ^I <your_name> ^I /proc/%d/status
2887          *     # but this will eliminate embedded '\t' ...
2888          *     pipe ^I <your_name> ^I cat /proc/%d/status | expand -
2889          *
2890          * Note: If a pipe such as the following was established, one must
2891          * use Ctrl-C to terminate that pipe in order to review the results.
2892          * This is the single occasion where a '^C' will not terminate top.
2893          *
2894          *     pipe ^I Trace ^I /usr/bin/strace -p %d 2>&1
2895          */
2896
2897         /*
2898          * Our driving table support, the basis for generalized inspection,
2899          * built at startup (if at all) from rcfile or demo entries. */
2900 struct I_ent {
2901    void (*func)(char *, int);     // a pointer to file/pipe/demo function
2902    char *type;                    // the type of entry ('file' or 'pipe')
2903    char *name;                    // the selection label for display
2904    char *fmts;                    // format string to build path or command
2905    int   farg;                    // 1 = '%d' in fmts, 0 = not (future use)
2906    const char *caps;              // not really caps, show_special() delim's
2907    char *fstr;                    // entry's current/active search string
2908    int   flen;                    // above's strlen, without call overhead
2909 };
2910 struct I_struc {
2911    int demo;                      // do NOT save table entries in rcfile
2912    int total;                     // total I_ent table entries
2913    char *raw;                     // all entries for 'W', incl '#' & blank
2914    struct I_ent *tab;
2915 };
2916 static struct I_struc Inspect;
2917
2918 static char   **Insp_p;           // pointers to each line start
2919 static int      Insp_nl;          // total lines, total Insp_p entries
2920 static int      Insp_utf8;        // treat Insp_buf as translatable, else raw
2921 static char    *Insp_buf;         // the results from insp_do_file/pipe
2922 static size_t   Insp_bufsz;       // allocated size of Insp_buf
2923 static size_t   Insp_bufrd;       // bytes actually in Insp_buf
2924 static struct I_ent *Insp_sel;    // currently selected Inspect entry
2925
2926         // Our 'make status line' macro
2927 #define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \
2928    const char *_p; \
2929    _sz += utf8_delta(txt); \
2930    _p = fmtmk("%-*.*s", _sz, _sz, txt); \
2931    PUTT("%s%s%.*s%s", tg2(0, (Msg_row = 3)), Curwin->capclr_hdr \
2932       , utf8_embody(_p, Screen_cols), _p, Cap_clr_eol); \
2933    putp(Caps_off); fflush(stdout); }
2934
2935         // Our 'row length' macro, equivalent to a strlen() call
2936 #define INSP_RLEN(idx) (int)(Insp_p[idx +1] - Insp_p[idx] -1)
2937
2938         // Our 'busy/working' macro
2939 #define INSP_BUSY(enu)  { INSP_MKSL(0, N_txt(enu)) }
2940
2941
2942         /*
2943          * Establish the number of lines present in the Insp_buf glob plus
2944          * build the all important row start array.  It is that array that
2945          * others will rely on since we dare not try to use strlen() on what
2946          * is potentially raw binary data.  Who knows what some user might
2947          * name as a file or include in a pipeline (scary, ain't it?). */
2948 static void insp_cnt_nl (void) {
2949    char *beg = Insp_buf;
2950    char *cur = Insp_buf;
2951    char *end = Insp_buf + Insp_bufrd + 1;
2952
2953 #ifdef INSP_SAVEBUF
2954 {
2955    static int n = 1;
2956    char fn[SMLBUFSIZ];
2957    FILE *fd;
2958    snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++);
2959    fd = fopen(fn, "w");
2960    if (fd) {
2961       fwrite(Insp_buf, 1, Insp_bufrd, fd);
2962       fclose(fd);
2963    }
2964 }
2965 #endif
2966    Insp_p = alloc_c(sizeof(char *) * 2);
2967
2968    for (Insp_nl = 0; beg < end; beg++) {
2969       if (*beg == '\n') {
2970          Insp_p[Insp_nl++] = cur;
2971          // keep our array ahead of next potential need (plus the 2 above)
2972          Insp_p = alloc_r(Insp_p, (sizeof(char *) * (Insp_nl +3)));
2973          cur = beg +1;
2974       }
2975    }
2976    Insp_p[0] = Insp_buf;
2977    Insp_p[Insp_nl++] = cur;
2978    Insp_p[Insp_nl] = end;
2979    if ((end - cur) == 1)          // if there's an eof null delimiter,
2980       --Insp_nl;                  // don't count it as a new line
2981 } // end: insp_cnt_nl
2982
2983
2984 #ifndef INSP_OFFDEMO
2985         /*
2986          * The pseudo output DEMO utility. */
2987 static void insp_do_demo (char *fmts, int pid) {
2988    (void)fmts; (void)pid;
2989    /* next will put us on a par with the real file/pipe read buffers
2990     ( and also avoid a harmless, but evil sounding, valgrind warning ) */
2991    Insp_bufsz = READMINSZ + strlen(N_txt(YINSP_dstory_txt));
2992    Insp_buf   = alloc_c(Insp_bufsz);
2993    Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_dstory_txt));
2994    insp_cnt_nl();
2995 } // end: insp_do_demo
2996 #endif
2997
2998
2999         /*
3000          * The generalized FILE utility. */
3001 static void insp_do_file (char *fmts, int pid) {
3002    char buf[LRGBUFSIZ];
3003    FILE *fp;
3004    int rc;
3005
3006    snprintf(buf, sizeof(buf), fmts, pid);
3007    fp = fopen(buf, "r");
3008    rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
3009    if (fp) fclose(fp);
3010    if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
3011       , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
3012    insp_cnt_nl();
3013 } // end: insp_do_file
3014
3015
3016         /*
3017          * The generalized PIPE utility. */
3018 static void insp_do_pipe (char *fmts, int pid) {
3019    char buf[LRGBUFSIZ];
3020    struct sigaction sa;
3021    FILE *fp;
3022    int rc;
3023
3024    memset(&sa, 0, sizeof(sa));
3025    sigemptyset(&sa.sa_mask);
3026    sa.sa_handler = SIG_IGN;
3027    sigaction(SIGINT, &sa, NULL);
3028
3029    snprintf(buf, sizeof(buf), fmts, pid);
3030    fp = popen(buf, "r");
3031    rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
3032    if (fp) pclose(fp);
3033    if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
3034       , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
3035    insp_cnt_nl();
3036
3037    sa.sa_handler = sig_endpgm;
3038    sigaction(SIGINT, &sa, NULL);
3039 } // end: insp_do_pipe
3040
3041
3042         /*
3043          * This guy is a *Helper* function serving the following two masters:
3044          *   insp_find_str() - find the next Insp_sel->fstr match
3045          *   insp_mkrow_...  - highlight any Insp_sel->fstr matches in-view
3046          * If Insp_sel->fstr is found in the designated row, he returns the
3047          * offset from the start of the row, otherwise he returns a huge
3048          * integer so traditional fencepost usage can be employed. */
3049 static inline int insp_find_ofs (int col, int row) {
3050  #define begFS (int)(fnd - Insp_p[row])
3051    char *p, *fnd = NULL;
3052
3053    if (Insp_sel->fstr[0]) {
3054       // skip this row, if there's no chance of a match
3055       if (memchr(Insp_p[row], Insp_sel->fstr[0], INSP_RLEN(row))) {
3056          for ( ; col < INSP_RLEN(row); col++) {
3057             if (!*(p = Insp_p[row] + col))       // skip any empty strings
3058                continue;
3059             fnd = STRSTR(p, Insp_sel->fstr);     // with binary data, each
3060             if (fnd)                             // row may have '\0'.  so
3061                break;                            // our scans must be done
3062             col += strlen(p);                    // as individual strings.
3063          }
3064          if (fnd && fnd < Insp_p[row + 1])       // and, we must watch out
3065             return begFS;                        // for potential overrun!
3066       }
3067    }
3068    return INT_MAX;
3069  #undef begFS
3070 } // end: insp_find_ofs
3071
3072
3073         /*
3074          * This guy supports the inspect 'L' and '&' search provisions
3075          * and returns the row and *optimal* column for viewing any match
3076          * ( we'll always opt for left column justification since any )
3077          * ( preceding ctrl chars appropriate an unpredictable amount ) */
3078 static void insp_find_str (int ch, int *col, int *row) {
3079  #define reDUX (found) ? N_txt(WORD_another_txt) : ""
3080    static int found;
3081
3082    if ((ch == '&' || ch == 'n') && !Insp_sel->fstr[0]) {
3083       show_msg(N_txt(FIND_no_next_txt));
3084       return;
3085    }
3086    if (ch == 'L' || ch == '/') {
3087       char *str = ioline(N_txt(GET_find_str_txt));
3088       if (*str == kbd_ESC) return;
3089       snprintf(Insp_sel->fstr, FNDBUFSIZ, "%s", str);
3090       Insp_sel->flen = strlen(Insp_sel->fstr);
3091       found = 0;
3092    }
3093    if (Insp_sel->fstr[0]) {
3094       int xx, yy;
3095
3096       INSP_BUSY(YINSP_waitin_txt);
3097       for (xx = *col, yy = *row; yy < Insp_nl; ) {
3098          xx = insp_find_ofs(xx, yy);
3099          if (xx < INSP_RLEN(yy)) {
3100             found = 1;
3101             if (xx == *col &&  yy == *row) {     // matched where we were!
3102                ++xx;                             // ( was the user maybe )
3103                continue;                         // ( trying to fool us? )
3104             }
3105             *col = xx;
3106             *row = yy;
3107             return;
3108          }
3109          xx = 0;
3110          ++yy;
3111       }
3112       show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Insp_sel->fstr));
3113    }
3114  #undef reDUX
3115 } // end: insp_find_str
3116
3117
3118         /*
3119          * This guy is a *Helper* function responsible for positioning a
3120          * single row in the current 'X axis', then displaying the results.
3121          * Along the way, he makes sure control characters and/or unprintable
3122          * characters display in a less-like fashion:
3123          *    '^A'    for control chars
3124          *    '<BC>'  for other unprintable stuff
3125          * Those will be highlighted with the current windows's capclr_msg,
3126          * while visible search matches display with capclr_hdr for emphasis.
3127          * ( we hide ugly plumbing in macros to concentrate on the algorithm ) */
3128 static void insp_mkrow_raw (int col, int row) {
3129  #define maxSZ ( Screen_cols - to )
3130  #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } }
3131  #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3132     fr += Insp_sel->flen -1; to += Insp_sel->flen; hicap = 0; }
3133 #ifndef INSP_JUSTNOT
3134  #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3135     PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 2; hicap = 1; }
3136  #define mkUNP { const char *p = fmtmk("<%02X>", uch); \
3137     PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 4; hicap = 1; }
3138 #else
3139  #define mkCTL { if ((to += 2) <= Screen_cols) \
3140     PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; }
3141  #define mkUNP { if ((to += 4) <= Screen_cols) \
3142     PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; }
3143 #endif
3144  #define mkSTD { capNO; if (++to <= Screen_cols) { static char _str[2]; \
3145     _str[0] = uch; putp(_str); } }
3146    unsigned char tline[SCREENMAX];
3147    int fr, to, ofs;
3148    int hicap = 0;
3149
3150    if (col < INSP_RLEN(row))
3151       memcpy(tline, Insp_p[row] + col, sizeof(tline));
3152    else tline[0] = '\n';
3153
3154    for (fr = 0, to = 0, ofs = 0; to < Screen_cols; fr++) {
3155       if (!ofs)
3156          ofs = insp_find_ofs(col + fr, row);
3157       if (col + fr < ofs) {
3158          unsigned char uch = tline[fr];
3159          if (uch == '\n')   break;     // a no show  (he,he)
3160          if (uch > 126)     mkUNP      // show as: '<AB>'
3161          else if (uch < 32) mkCTL      // show as:  '^C'
3162          else               mkSTD      // a show off (he,he)
3163       } else {              mkFND      // a big show (he,he)
3164          ofs = 0;
3165       }
3166       if (col + fr >= INSP_RLEN(row)) break;
3167    }
3168    capNO;
3169    putp(Cap_clr_eol);
3170
3171  #undef maxSZ
3172  #undef capNO
3173  #undef mkFND
3174  #undef mkCTL
3175  #undef mkUNP
3176  #undef mkSTD
3177 } // end: insp_mkrow_raw
3178
3179
3180         /*
3181          * This guy is a *Helper* function responsible for positioning a
3182          * single row in the current 'X axis' within a multi-byte string
3183          * then displaying the results. Along the way he ensures control
3184          * characters will then be displayed in two positions like '^A'.
3185          * ( assuming they can even get past those 'gettext' utilities ) */
3186 static void insp_mkrow_utf8 (int col, int row) {
3187  #define maxSZ ( Screen_cols - to )
3188  #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3189     fr += Insp_sel->flen; to += Insp_sel->flen; }
3190 #ifndef INSP_JUSTNOT
3191  #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3192     PUTT("%s%.*s%s", Curwin->capclr_msg, maxSZ, p, Caps_off); to += 2; }
3193 #else
3194  #define mkCTL { if ((to += 2) <= Screen_cols) \
3195     PUTT("%s^%c%s", Curwin->capclr_msg, uch + '@', Caps_off); }
3196 #endif
3197  #define mkNUL { buf1[0] = ' '; doPUT(buf1) }
3198  #define doPUT(buf) if ((to += cno) <= Screen_cols) putp(buf);
3199    static char buf1[2], buf2[3], buf3[4], buf4[5];
3200    unsigned char tline[BIGBUFSIZ];
3201    int fr, to, ofs;
3202
3203    col = utf8_proper_col(Insp_p[row], col, 1);
3204    if (col < INSP_RLEN(row))
3205       memcpy(tline, Insp_p[row] + col, sizeof(tline));
3206    else tline[0] = '\n';
3207
3208    for (fr = 0, to = 0, ofs = 0; to < Screen_cols; ) {
3209       if (!ofs)
3210          ofs = insp_find_ofs(col + fr, row);
3211       if (col + fr < ofs) {
3212          unsigned char uch = tline[fr];
3213          int bno = UTF8_tab[uch];
3214          int cno = utf8_cols(&tline[fr++], bno);
3215          switch (bno) {
3216             case 1:
3217                if (uch == '\n') break;
3218                if (uch < 32) mkCTL
3219                else if (uch == 127) mkNUL
3220                else { buf1[0] = uch; doPUT(buf1) }
3221                break;
3222             case 2:
3223                buf2[0] = uch; buf2[1] = tline[fr++];
3224                doPUT(buf2)
3225                break;
3226             case 3:
3227                buf3[0] = uch; buf3[1] = tline[fr++]; buf3[2] = tline[fr++];
3228                doPUT(buf3)
3229                break;
3230             case 4:
3231                buf4[0] = uch; buf4[1] = tline[fr++]; buf4[2] = tline[fr++]; buf4[3] = tline[fr++];
3232                doPUT(buf4)
3233                break;
3234             default:
3235                mkNUL
3236                break;
3237          }
3238       } else {
3239          mkFND
3240          ofs = 0;
3241       }
3242       if (col + fr >= INSP_RLEN(row)) break;
3243    }
3244    putp(Cap_clr_eol);
3245
3246  #undef maxSZ
3247  #undef mkFND
3248  #undef mkCTL
3249  #undef mkNUL
3250  #undef doPUT
3251 } // end: insp_mkrow_utf8
3252
3253
3254         /*
3255          * This guy is an insp_view_choice() *Helper* function who displays
3256          * a page worth of of the user's damages.  He also creates a status
3257          * line based on maximum digits for the current selection's lines and
3258          * hozizontal position (so it serves to inform, not distract, by
3259          * otherwise being jumpy). */
3260 static inline void insp_show_pgs (int col, int row, int max) {
3261    char buf[SMLBUFSIZ];
3262    void (*mkrow_func)(int, int);
3263    int r = snprintf(buf, sizeof(buf), "%d", Insp_nl);
3264    int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols);
3265    int l = row +1, ls = Insp_nl;
3266
3267    if (!Insp_bufrd)
3268       l = ls = 0;
3269    snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt)
3270       , Insp_sel->name
3271       , r, l, r, ls
3272       , c, col + 1, c, col + Screen_cols
3273       , (unsigned long)Insp_bufrd);
3274    INSP_MKSL(0, buf);
3275
3276    mkrow_func = Insp_utf8 ? insp_mkrow_utf8 : insp_mkrow_raw;
3277
3278    for ( ; max && row < Insp_nl; row++) {
3279       putp("\n");
3280       mkrow_func(col, row);
3281       --max;
3282    }
3283
3284    if (max)
3285       putp(Cap_nl_clreos);
3286 } // end: insp_show_pgs
3287
3288
3289         /*
3290          * This guy is responsible for displaying the Insp_buf contents and
3291          * managing all scrolling/locate requests until the user gives up. */
3292 static int insp_view_choice (struct pids_stack *p) {
3293 #ifdef INSP_SLIDE_1
3294  #define hzAMT  1
3295 #else
3296  #define hzAMT  8
3297 #endif
3298  #define maxLN (Screen_rows - (Msg_row +1))
3299  #define makHD(b1,b2) { \
3300     snprintf(b1, sizeof(b1), "%d", PID_VAL(EU_PID, s_int, p)); \
3301     snprintf(b2, sizeof(b2), "%s", PID_VAL(EU_CMD, str, p)); }
3302  #define makFS(dst) { if (Insp_sel->flen < 22) \
3303        snprintf(dst, sizeof(dst), "%s", Insp_sel->fstr); \
3304     else snprintf(dst, sizeof(dst), "%.19s...", Insp_sel->fstr); }
3305    char buf[LRGBUFSIZ];
3306    int key, curlin = 0, curcol = 0;
3307
3308 signify_that:
3309    putp(Cap_clr_scr);
3310    adj_geometry();
3311
3312    for (;;) {
3313       char pid[6], cmd[64];
3314
3315       if (curcol < 0) curcol = 0;
3316       if (curlin >= Insp_nl) curlin = Insp_nl -1;
3317       if (curlin < 0) curlin = 0;
3318
3319       makFS(buf)
3320       makHD(pid,cmd)
3321       putp(Cap_home);
3322       show_special(1, fmtmk(N_unq(YINSP_hdview_fmt)
3323          , pid, cmd, (Insp_sel->fstr[0]) ? buf : " N/A "));   // nls_maybe
3324       insp_show_pgs(curcol, curlin, maxLN);
3325       fflush(stdout);
3326       /* fflush(stdin) didn't do the trick, so we'll just dip a little deeper
3327          lest repeated <Enter> keys produce immediate re-selection in caller */
3328       tcflush(STDIN_FILENO, TCIFLUSH);
3329
3330       if (Frames_signal) goto signify_that;
3331       key = iokey(IOKEY_ONCE);
3332       if (key < 1) goto signify_that;
3333
3334       switch (key) {
3335          case kbd_ENTER:          // must force new iokey()
3336             key = INT_MAX;        // fall through !
3337          case kbd_ESC:
3338          case 'q':
3339             putp(Cap_clr_scr);
3340             return key;
3341          case kbd_LEFT:
3342             curcol -= hzAMT;
3343             break;
3344          case kbd_RIGHT:
3345             curcol += hzAMT;
3346             break;
3347          case kbd_UP:
3348             --curlin;
3349             break;
3350          case kbd_DOWN:
3351             ++curlin;
3352             break;
3353          case kbd_PGUP:
3354          case 'b':
3355             curlin -= maxLN -1;   // keep 1 line for reference
3356             break;
3357          case kbd_PGDN:
3358          case kbd_SPACE:
3359             curlin += maxLN -1;   // ditto
3360             break;
3361          case kbd_HOME:
3362          case 'g':
3363             curcol = curlin = 0;
3364             break;
3365          case kbd_END:
3366          case 'G':
3367             curcol = 0;
3368             curlin = Insp_nl - maxLN;
3369             break;
3370          case 'L':
3371          case '&':
3372          case '/':
3373          case 'n':
3374             if (!Insp_utf8)
3375                insp_find_str(key, &curcol, &curlin);
3376             else {
3377                int tmpcol = utf8_proper_col(Insp_p[curlin], curcol, 1);
3378                insp_find_str(key, &tmpcol, &curlin);
3379                curcol = utf8_proper_col(Insp_p[curlin], tmpcol, 0);
3380             }
3381             // must re-hide cursor in case a prompt for a string makes it huge
3382             putp((Cursor_state = Cap_curs_hide));
3383             break;
3384          case '=':
3385             snprintf(buf, sizeof(buf), "%s: %s", Insp_sel->type, Insp_sel->fmts);
3386             INSP_MKSL(1, buf);    // show an extended SL
3387             if (iokey(IOKEY_ONCE) < 1)
3388                goto signify_that;
3389             break;
3390          default:                 // keep gcc happy
3391             break;
3392       }
3393    }
3394  #undef hzAMT
3395  #undef maxLN
3396  #undef makHD
3397  #undef makFS
3398 } // end: insp_view_choice
3399
3400
3401         /*
3402          * This is the main Inspect routine, responsible for:
3403          *   1) validating the passed pid (required, but not always used)
3404          *   2) presenting/establishing the target selection
3405          *   3) arranging to fill Insp_buf (via the Inspect.tab[?].func)
3406          *   4) invoking insp_view_choice for viewing/scrolling/searching
3407          *   5) cleaning up the dynamically acquired memory afterwards */
3408 static void inspection_utility (int pid) {
3409  #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \
3410       Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \
3411       for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \
3412          snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \
3413          strncat(dst, _s, (sizeof(dst) - 1) - strlen(dst)); } }
3414    char sels[SCREENMAX];
3415    static int sel;
3416    int i, key;
3417    struct pids_stack *p;
3418
3419    for (i = 0, p = NULL; i < PIDSmaxt; i++)
3420       if (pid == PID_VAL(EU_PID, s_int, Curwin->ppt[i])) {
3421          p = Curwin->ppt[i];
3422          break;
3423       }
3424    if (!p) {
3425       show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid));
3426       return;
3427    }
3428    // must re-hide cursor since the prompt for a pid made it huge
3429    putp((Cursor_state = Cap_curs_hide));
3430 signify_that:
3431    putp(Cap_clr_scr);
3432    adj_geometry();
3433
3434    key = INT_MAX;
3435    do {
3436       mkSEL(sels);
3437       putp(Cap_home);
3438       show_special(1, fmtmk(N_unq(YINSP_hdsels_fmt)
3439          , pid, PID_VAL(EU_CMD, str, Curwin->ppt[i]), sels));
3440       INSP_MKSL(0, " ");
3441
3442       if (Frames_signal) goto signify_that;
3443       if (key == INT_MAX) key = iokey(IOKEY_ONCE);
3444       if (key < 1) goto signify_that;
3445
3446       switch (key) {
3447          case 'q':
3448          case kbd_ESC:
3449             break;
3450          case kbd_END:
3451             sel = 0;              // fall through !
3452          case kbd_LEFT:
3453             if (--sel < 0) sel = Inspect.total -1;
3454             key = INT_MAX;
3455             break;
3456          case kbd_HOME:
3457             sel = Inspect.total;  // fall through !
3458          case kbd_RIGHT:
3459             if (++sel >= Inspect.total) sel = 0;
3460             key = INT_MAX;
3461             break;
3462          case kbd_ENTER:
3463             INSP_BUSY(!strcmp("file", Inspect.tab[sel].type)
3464                ? YINSP_waitin_txt : YINSP_workin_txt);
3465             Insp_sel = &Inspect.tab[sel];
3466             Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid);
3467             Insp_utf8 = utf8_delta(Insp_buf);
3468             key = insp_view_choice(p);
3469             free(Insp_buf);
3470             free(Insp_p);
3471             break;
3472          default:
3473             goto signify_that;
3474       }
3475    } while (key != 'q' && key != kbd_ESC);
3476
3477    // signal that we just corrupted entire screen
3478    Frames_signal = BREAK_screen;
3479  #undef mkSEL
3480 } // end: inspection_utility
3481
3482 #undef INSP_MKSL
3483 #undef INSP_RLEN
3484 #undef INSP_BUSY
3485 \f
3486 /*######  Other Filtering  ###############################################*/
3487
3488         /*
3489          * This sructure is hung from a WIN_t when other filtering is active */
3490 struct osel_s {
3491    struct osel_s *nxt;                         // the next criteria or NULL.
3492    int (*rel)(const char *, const char *);     // relational strings compare
3493    char *(*sel)(const char *, const char *);   // for selection str compares
3494    char *raw;                                  // raw user input (dup check)
3495    char *val;                                  // value included or excluded
3496    int   ops;                                  // filter delimiter/operation
3497    int   inc;                                  // include == 1, exclude == 0
3498    int   enu;                                  // field (procflag) to filter
3499    int   typ;                                  // typ used to set: rel & sel
3500 };
3501
3502         /*
3503          * A function to parse, validate and build a single 'other filter' */
3504 static const char *osel_add (WIN_t *q, int ch, char *glob, int push) {
3505    int (*rel)(const char *, const char *);
3506    char *(*sel)(const char *, const char *);
3507    char raw[MEDBUFSIZ], ops, *pval;
3508    struct osel_s *osel;
3509    int inc, enu;
3510
3511    if (ch == 'o') {
3512       rel   = strcasecmp;
3513       sel   = strcasestr;
3514    } else {
3515       rel   = strcmp;
3516       sel   = strstr;
3517    }
3518
3519    if (!snprintf(raw, sizeof(raw), "%s", glob))
3520       return NULL;
3521    for (osel = q->osel_1st; osel; ) {
3522       if (!strcmp(osel->raw, raw))             // #1: is criteria duplicate?
3523          return N_txt(OSEL_errdups_txt);
3524       osel = osel->nxt;
3525    }
3526    if (*glob != '!') inc = 1;                  // #2: is it include/exclude?
3527    else { ++glob; inc = 0; }
3528
3529    if (!(pval = strpbrk(glob, "<=>")))         // #3: do we see a delimiter?
3530       return fmtmk(N_fmt(OSEL_errdelm_fmt)
3531          , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3532    ops = *(pval);
3533    *(pval++) = '\0';
3534
3535    for (enu = 0; enu < EU_MAXPFLGS; enu++)     // #4: is this a valid field?
3536       if (!STRCMP(N_col(enu), glob)) break;
3537    if (enu == EU_MAXPFLGS)
3538       return fmtmk(N_fmt(XTRA_badflds_fmt), glob);
3539
3540    if (!(*pval))                               // #5: did we get some value?
3541       return fmtmk(N_fmt(OSEL_errvalu_fmt)
3542          , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3543
3544    osel = alloc_c(sizeof(struct osel_s));
3545    osel->typ = ch;
3546    osel->inc = inc;
3547    osel->enu = enu;
3548    osel->ops = ops;
3549    if (ops == '=') osel->val = alloc_s(pval);
3550    else osel->val = alloc_s(justify_pad(pval, Fieldstab[enu].width, Fieldstab[enu].align));
3551    osel->rel = rel;
3552    osel->sel = sel;
3553    osel->raw = alloc_s(raw);
3554
3555    if (push) {
3556       // a LIFO queue was used when we're interactive
3557       osel->nxt = q->osel_1st;
3558       q->osel_1st = osel;
3559    } else {
3560       // a FIFO queue must be employed for the rcfile
3561       if (!q->osel_1st)
3562          q->osel_1st = osel;
3563       else {
3564          struct osel_s *prev, *walk = q->osel_1st;
3565          do {
3566             prev = walk;
3567             walk = walk->nxt;
3568          } while (walk);
3569          prev->nxt = osel;
3570       }
3571    }
3572    q->osel_tot += 1;
3573
3574    return NULL;
3575 } // end: osel_add
3576
3577
3578         /*
3579          * A function to turn off entire other filtering in the given window */
3580 static void osel_clear (WIN_t *q) {
3581    struct osel_s *osel = q->osel_1st;
3582
3583    while (osel) {
3584       struct osel_s *nxt = osel->nxt;
3585       free(osel->val);
3586       free(osel->raw);
3587       free(osel);
3588       osel = nxt;
3589    }
3590    q->osel_tot = 0;
3591    q->osel_1st = NULL;
3592 } // end: osel_clear
3593
3594
3595         /*
3596          * Determine if there are matching values or relationships among the
3597          * other criteria in this passed window -- it's called from only one
3598          * place, and likely inlined even without the directive */
3599 static inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str) {
3600    struct osel_s *osel = q->osel_1st;
3601
3602    while (osel) {
3603       if (osel->enu == enu) {
3604          int r;
3605          switch (osel->ops) {
3606             case '<':                          // '<' needs the r < 0 unless
3607                r = osel->rel(str, osel->val);  // '!' which needs an inverse
3608                if ((r >= 0 && osel->inc) || (r < 0 && !osel->inc)) return 0;
3609                break;
3610             case '>':                          // '>' needs the r > 0 unless
3611                r = osel->rel(str, osel->val);  // '!' which needs an inverse
3612                if ((r <= 0 && osel->inc) || (r > 0 && !osel->inc)) return 0;
3613                break;
3614             default:
3615             {  char *p = osel->sel(str, osel->val);
3616                if ((!p && osel->inc) || (p && !osel->inc)) return 0;
3617             }
3618                break;
3619          }
3620       }
3621       osel = osel->nxt;
3622    }
3623    return 1;
3624 } // end: osel_matched
3625 \f
3626 /*######  Startup routines  ##############################################*/
3627
3628         /*
3629          * No matter what *they* say, we handle the really really BIG and
3630          * IMPORTANT stuff upon which all those lessor functions depend! */
3631 static void before (char *me) {
3632  #define doALL STAT_REAP_NUMA_NODES_TOO
3633    int i, rc;
3634    int linux_version_code = procps_linux_version();
3635
3636    atexit(close_stdout);
3637
3638    // setup our program name
3639    Myname = strrchr(me, '/');
3640    if (Myname) ++Myname; else Myname = me;
3641
3642    // accommodate nls/gettext potential translations
3643    // ( must 'setlocale' before our libproc called )
3644    initialize_nls();
3645
3646    // is /proc mounted?
3647    fatal_proc_unmounted(NULL, 0);
3648
3649 #ifndef OFF_STDERROR
3650    /* there's a chance that damn libnuma may spew to stderr so we gotta
3651       make sure he does not corrupt poor ol' top's first output screen!
3652       Yes, he provides some overridable 'weak' functions to change such
3653       behavior but we can't exploit that since we don't follow a normal
3654       ld route to symbol resolution (we use that dlopen() guy instead)! */
3655    Stderr_save = dup(fileno(stderr));
3656    if (-1 < Stderr_save && freopen("/dev/null", "w", stderr))
3657       ;                           // avoid -Wunused-result
3658 #endif
3659
3660    // establish some cpu particulars
3661    Hertz = procps_hertz_get();
3662    Cpu_States_fmts = N_unq(STATE_lin2x6_fmt);
3663    if (linux_version_code >= LINUX_VERSION(2, 6, 11))
3664       Cpu_States_fmts = N_unq(STATE_lin2x7_fmt);
3665
3666    // get the total cpus (and, if possible, numa node total)
3667    if ((rc = procps_stat_new(&Stat_ctx)))
3668       Restrict_some = Cpu_cnt = 1;
3669    else {
3670       if (!(Stat_reap = procps_stat_reap(Stat_ctx, doALL, Stat_items, MAXTBL(Stat_items))))
3671          error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
3672 #ifndef PRETEND0NUMA
3673       Numa_node_tot = Stat_reap->numa->total;
3674 #endif
3675       Cpu_cnt = Stat_reap->cpus->total;
3676 #ifdef PRETEND48CPU
3677       Cpu_cnt = 48;
3678 #endif
3679    }
3680
3681    // prepare for memory stats from new library API ...
3682    if ((rc = procps_meminfo_new(&Mem_ctx)))
3683       Restrict_some = 1;
3684
3685    // establish max depth for newlib pids stack (# of result structs)
3686    Pids_itms = alloc_c(sizeof(enum pids_item) * MAXTBL(Fieldstab));
3687    if (PIDS_noop != 0)
3688       for (i = 0; i < MAXTBL(Fieldstab); i++)
3689          Pids_itms[i] = PIDS_noop;
3690    Pids_itms_tot = MAXTBL(Fieldstab);
3691    // we will identify specific items in the build_headers() function
3692    if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot)))
3693       error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
3694
3695 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
3696 {  struct sigaction sa;
3697    Thread_id_main = pthread_self();
3698    /* in case any of our threads have been enabled, they'll inherit this mask
3699       with everything blocked. therefore, signals go to the main thread (us). */
3700    sigfillset(&sa.sa_mask);
3701    pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
3702 }
3703 #endif
3704
3705 #ifdef THREADED_CPU
3706    if (0 != sem_init(&Semaphore_cpus_beg, 0, 0)
3707    || (0 != sem_init(&Semaphore_cpus_end, 0, 0)))
3708       error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3709    if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL))
3710       error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3711    pthread_setname_np(Thread_id_cpus, "update cpus");
3712 #endif
3713 #ifdef THREADED_MEM
3714    if (0 != sem_init(&Semaphore_memory_beg, 0, 0)
3715    || (0 != sem_init(&Semaphore_memory_end, 0, 0)))
3716       error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3717    if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL))
3718       error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3719    pthread_setname_np(Thread_id_memory, "update memory");
3720 #endif
3721 #ifdef THREADED_TSK
3722    if (0 != sem_init(&Semaphore_tasks_beg, 0, 0)
3723    || (0 != sem_init(&Semaphore_tasks_end, 0, 0)))
3724       error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3725    if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL))
3726       error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3727    pthread_setname_np(Thread_id_tasks, "update tasks");
3728 #endif
3729    // lastly, establish support for graphing cpus & memory
3730    Graph_cpus = alloc_c(sizeof(struct graph_parms));
3731    Graph_mems = alloc_c(sizeof(struct graph_parms));
3732  #undef doALL
3733 } // end: before
3734
3735
3736         /*
3737          * A configs_file *Helper* function responsible for transorming
3738          * a 3.2.8 - 3.3.17 format 'fieldscur' into our integer based format */
3739 static int cfg_xform (WIN_t *q, char *flds, const char *defs) {
3740  #define CVTon(c) ((c) |= 0x80)
3741    static struct {
3742       int old, new;
3743    } flags_tab[] = {
3744     #define old_View_NOBOLD  0x000001
3745     #define old_VISIBLE_tsk  0x000008
3746     #define old_Qsrt_NORMAL  0x000010
3747     #define old_Show_HICOLS  0x000200
3748     #define old_Show_THREAD  0x010000
3749       { old_View_NOBOLD, View_NOBOLD },
3750       { old_VISIBLE_tsk, Show_TASKON },
3751       { old_Qsrt_NORMAL, Qsrt_NORMAL },
3752       { old_Show_HICOLS, Show_HICOLS },
3753       { old_Show_THREAD, 0           }
3754     #undef old_View_NOBOLD
3755     #undef old_VISIBLE_tsk
3756     #undef old_Qsrt_NORMAL
3757     #undef old_Show_HICOLS
3758     #undef old_Show_THREAD
3759    };
3760    static char null_flds[] = "abcdefghijklmnopqrstuvwxyz";
3761    static const char fields_src[] = CVT_FORMER;
3762    char fields_dst[PFLAGSSIZ], *p1, *p2;
3763    int c, f, i, x, *pn;
3764
3765    if (Rc.id == 'a') {
3766       // first we'll touch up this window's winflags ...
3767       x = q->rc.winflags;
3768       q->rc.winflags = 0;
3769       for (i = 0; i < MAXTBL(flags_tab); i++) {
3770          if (x & flags_tab[i].old) {
3771             x &= ~flags_tab[i].old;
3772             q->rc.winflags |= flags_tab[i].new;
3773          }
3774       }
3775       q->rc.winflags |= x;
3776
3777       // now let's convert old top's more limited fields ...
3778       f = strlen(flds);
3779       if (f >= CVT_FLDMAX)
3780          return 1;
3781       strcpy(fields_dst, fields_src);
3782       /* all other fields represent the 'on' state with a capitalized version
3783          of a particular qwerty key.  for the 2 additional suse out-of-memory
3784          fields it makes perfect sense to do the exact opposite, doesn't it?
3785          in any case, we must turn them 'off' temporarily ... */
3786       if ((p1 = strchr(flds, '[')))  *p1 = '{';
3787       if ((p2 = strchr(flds, '\\'))) *p2 = '|';
3788       for (i = 0; i < f; i++) {
3789          c = flds[i];
3790          x = tolower(c) - 'a';
3791          if (x < 0 || x >= CVT_FLDMAX)
3792             return 1;
3793          fields_dst[i] = fields_src[x];
3794          if (isupper(c))
3795             CVTon(fields_dst[i]);
3796       }
3797       // if we turned any suse only fields off, turn 'em back on OUR way ...
3798       if (p1) CVTon(fields_dst[p1 - flds]);
3799       if (p2) CVTon(fields_dst[p2 - flds]);
3800
3801       // next, we must adjust the old sort field enum ...
3802       x = q->rc.sortindx;
3803       c = null_flds[x];
3804       q->rc.sortindx = 0;
3805       if ((p1 = memchr(flds, c, CVT_FLDMAX))
3806       || ((p1 = memchr(flds, toupper(c), CVT_FLDMAX)))) {
3807          x = p1 - flds;
3808          q->rc.sortindx = (fields_dst[x] & 0x7f) - FLD_OFFSET;
3809       }
3810       // now we're in a 3.3.0 format (soon to be transformed) ...
3811       strcpy(flds, fields_dst);
3812    }
3813
3814    // lastly, let's attend to the 3.3.0 - 3.3.17 fieldcurs format ...
3815    pn = &q->rc.fieldscur[0];
3816    x = strlen(defs);
3817    for (i = 0; i < x; i++) {
3818       f  = ((unsigned char)flds[i] & 0x7f);
3819       f  = f << 1;
3820       if ((unsigned char)flds[i] & 0x80) f |= FLDon;
3821       *(pn + i) = f;
3822    }
3823
3824    return 0;
3825  #undef CVTon
3826 } // end: cfg_xform
3827
3828
3829         /*
3830          * A configs_file *Helper* function responsible for reading
3831          * and validating a configuration file's 'Inspection' entries */
3832 static int config_insp (FILE *fp, char *buf, size_t size) {
3833    int i;
3834
3835    // we'll start off with a 'potential' blank or empty line
3836    // ( only realized if we end up with Inspect.total > 0 )
3837    if (!buf[0] || buf[0] != '\n') Inspect.raw = alloc_s("\n");
3838    else Inspect.raw = alloc_c(1);
3839
3840    for (i = 0;;) {
3841     #define iT(element) Inspect.tab[i].element
3842     #define nxtLINE { buf[0] = '\0'; continue; }
3843       size_t lraw = strlen(Inspect.raw) +1;
3844       int n, x;
3845       char *s1, *s2, *s3;
3846
3847       if (i < 0 || (size_t)i >= INT_MAX / sizeof(struct I_ent)) break;
3848       if (lraw >= INT_MAX - size) break;
3849
3850       if (!buf[0] && !fgets(buf, size, fp)) break;
3851       lraw += strlen(buf) +1;
3852       Inspect.raw = alloc_r(Inspect.raw, lraw);
3853       strcat(Inspect.raw, buf);
3854
3855       if (buf[0] == '#' || buf[0] == '\n') nxtLINE;
3856       Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_ent) * (i + 1));
3857
3858       // part of this is used in a show_special() call, so let's sanitize it
3859       for (n = 0, x = strlen(buf); n < x; n++) {
3860          if ((buf[n] != '\t' && buf[n] != '\n')
3861           && (buf[n] < ' ')) {
3862             buf[n] = '.';
3863             Rc_questions = 1;
3864          }
3865       }
3866       if (!(s1 = strtok(buf, "\t\n")))  { Rc_questions = 1; nxtLINE; }
3867       if (!(s2 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3868       if (!(s3 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3869
3870       switch (toupper(buf[0])) {
3871          case 'F':
3872             iT(func) = insp_do_file;
3873             break;
3874          case 'P':
3875             iT(func) = insp_do_pipe;
3876             break;
3877          default:
3878             Rc_questions = 1;
3879             nxtLINE;
3880       }
3881       iT(type) = alloc_s(s1);
3882       iT(name) = alloc_s(s2);
3883       iT(fmts) = alloc_s(s3);
3884       iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0;
3885       iT(fstr) = alloc_c(FNDBUFSIZ);
3886       iT(flen) = 0;
3887
3888       buf[0] = '\0';
3889       ++i;
3890     #undef iT
3891     #undef nxtLINE
3892    } // end: for ('inspect' entries)
3893
3894    Inspect.total = i;
3895 #ifndef INSP_OFFDEMO
3896    if (!Inspect.total) {
3897     #define mkS(n) N_txt(YINSP_demo ## n ## _txt)
3898       const char *sels[] = { mkS(01), mkS(02), mkS(03) };
3899       Inspect.total = Inspect.demo = MAXTBL(sels);
3900       Inspect.tab = alloc_c(sizeof(struct I_ent) * Inspect.total);
3901       for (i = 0; i < Inspect.total; i++) {
3902          Inspect.tab[i].type = alloc_s(N_txt(YINSP_deqtyp_txt));
3903          Inspect.tab[i].name = alloc_s(sels[i]);
3904          Inspect.tab[i].func = insp_do_demo;
3905          Inspect.tab[i].fmts = alloc_s(N_txt(YINSP_deqfmt_txt));
3906          Inspect.tab[i].fstr = alloc_c(FNDBUFSIZ);
3907       }
3908     #undef mkS
3909    }
3910 #endif
3911    return 0;
3912 } // end: config_insp
3913
3914
3915         /*
3916          * A configs_file *Helper* function responsible for reading
3917          * and validating a configuration file's 'Other Filter' entries */
3918 static int config_osel (FILE *fp, char *buf, size_t size) {
3919    int i, ch, tot, wno, begun;
3920    char *p;
3921
3922    for (begun = 0;;) {
3923       if (!fgets(buf, size, fp)) return 0;
3924       if (buf[0] == '\n') continue;
3925       // whoa, must be an 'inspect' entry
3926       if (!begun && !strstr(buf, Osel_delim_1_txt))
3927          return 0;
3928       // ok, we're now beginning
3929       if (!begun && strstr(buf, Osel_delim_1_txt)) {
3930          begun = 1;
3931          continue;
3932       }
3933       // this marks the end of our stuff
3934       if (begun && strstr(buf, Osel_delim_2_txt))
3935          break;
3936
3937       if (2 != sscanf(buf, Osel_window_fmts, &wno, &tot))
3938          goto end_oops;
3939       if (wno < 0 || wno >= GROUPSMAX) goto end_oops;
3940       if (tot < 0) goto end_oops;
3941
3942       for (i = 0; i < tot; i++) {
3943          if (!fgets(buf, size, fp)) return 1;
3944          if (1 > sscanf(buf, Osel_filterI_fmt, &ch)) goto end_oops;
3945          if ((p = strchr(buf, '\n'))) *p = '\0';
3946          if (!(p = strstr(buf, OSEL_FILTER))) goto end_oops;
3947          p += sizeof(OSEL_FILTER) - 1;
3948          if (osel_add(&Winstk[wno], ch, p, 0)) goto end_oops;
3949       }
3950    }
3951    // let's prime that buf for the next guy...
3952    fgets(buf, size, fp);
3953    return 0;
3954
3955 end_oops:
3956    Rc_questions = 1;
3957    return 1;
3958 } // end: config_osel
3959
3960
3961         /*
3962          * A configs_reads *Helper* function responsible for processing
3963          * a configuration file (personal or system-wide default) */
3964 static const char *configs_file (FILE *fp, const char *name, float *delay) {
3965    char fbuf[LRGBUFSIZ];
3966    int i, n, tmp_whole, tmp_fract;
3967    const char *p = NULL;
3968
3969    p = fmtmk(N_fmt(RC_bad_files_fmt), name);
3970    (void)fgets(fbuf, sizeof(fbuf), fp);     // ignore eyecatcher
3971    if (6 != fscanf(fp
3972       , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
3973       , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_whole, &tmp_fract, &i)) {
3974          return p;
3975    }
3976    if (Rc.id < 'a' || Rc.id > RCF_VERSION_ID)
3977       return p;
3978    if (strchr("bcde", Rc.id))
3979       return p;
3980    if (Rc.mode_altscr < 0 || Rc.mode_altscr > 1)
3981       return p;
3982    if (Rc.mode_irixps < 0 || Rc.mode_irixps > 1)
3983       return p;
3984    if (tmp_whole < 0)
3985       return p;
3986    // you saw that, right?  (fscanf stickin' it to 'i')
3987    if (i < 0 || i >= GROUPSMAX)
3988       return p;
3989    Curwin = &Winstk[i];
3990    // this may be ugly, but it keeps us locale independent...
3991    *delay = (float)tmp_whole + (float)tmp_fract / 1000;
3992
3993    for (i = 0 ; i < GROUPSMAX; i++) {
3994       static const char *def_flds[] = { DEF_FORMER, JOB_FORMER, MEM_FORMER, USR_FORMER };
3995       int j, x;
3996       WIN_t *w = &Winstk[i];
3997       p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, name);
3998
3999       if (1 != fscanf(fp, "%3s\tfieldscur=", w->rc.winname))
4000          return p;
4001       if (Rc.id < RCF_XFORMED_ID)
4002          fscanf(fp, "%s\n", fbuf);
4003       else {
4004          for (j = 0; ; j++)
4005             if (1 != fscanf(fp, "%d", &w->rc.fieldscur[j]))
4006                break;
4007       }
4008
4009       // be tolerant of missing release 3.3.10 graph modes additions
4010       if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
4011                          ", double_up=%d, combine_cpus=%d, core_types=%d\n"
4012          , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems
4013          , &w->rc.double_up, &w->rc.combine_cpus, &w->rc.core_types))
4014             return p;
4015       if (w->rc.sortindx < 0 || w->rc.sortindx >= EU_MAXPFLGS)
4016          return p;
4017       if (w->rc.maxtasks < 0)
4018          return p;
4019       if (w->rc.graph_cpus < 0 || w->rc.graph_cpus > 2)
4020          return p;
4021       if (w->rc.graph_mems < 0 || w->rc.graph_mems > 2)
4022          return p;
4023       if (w->rc.double_up < 0 || w->rc.double_up >= ADJOIN_limit)
4024          return p;
4025       // can't check upper bounds until Cpu_cnt is known
4026       if (w->rc.combine_cpus < 0)
4027          return p;
4028       if (w->rc.core_types < 0 || w->rc.core_types > E_CORES_ONLY)
4029          return p;
4030
4031       if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
4032          , &w->rc.summclr, &w->rc.msgsclr, &w->rc.headclr, &w->rc.taskclr))
4033             return p;
4034       // would prefer to use 'max_colors', but it isn't available yet...
4035       if (w->rc.summclr < 0 || w->rc.summclr > 255) return p;
4036       if (w->rc.msgsclr < 0 || w->rc.msgsclr > 255) return p;
4037       if (w->rc.headclr < 0 || w->rc.headclr > 255) return p;
4038       if (w->rc.taskclr < 0 || w->rc.taskclr > 255) return p;
4039
4040       switch (Rc.id) {
4041          case 'a':                          // 3.2.8 (former procps)
4042          // fall through
4043          case 'f':                          // 3.3.0 thru 3.3.3 (ng)
4044             SETw(w, Show_JRNUMS);
4045          // fall through
4046          case 'g':                          // from 3.3.4 thru 3.3.8
4047             if (Rc.id > 'a') scat(fbuf, RCF_PLUS_H);
4048          // fall through
4049          case 'h':                          // this is release 3.3.9
4050             w->rc.graph_cpus = w->rc.graph_mems = 0;
4051             // these next 2 are really global, but best documented here
4052             Rc.summ_mscale = Rc.task_mscale = SK_Kb;
4053          // fall through
4054          case 'i':                          // from 3.3.10 thru 3.3.16
4055             if (Rc.id > 'a') scat(fbuf, RCF_PLUS_J);
4056             w->rc.double_up = w->rc.combine_cpus = 0;
4057          // fall through
4058          case 'j':                          // this is release 3.3.17
4059             if (cfg_xform(w, fbuf, def_flds[i]))
4060                return p;
4061             Rc.tics_scaled = 0;
4062          // fall through
4063          case 'k':                          // current RCF_VERSION_ID
4064          // fall through
4065          default:
4066             if (mlen(w->rc.fieldscur) < EU_MAXPFLGS)
4067                return p;
4068             for (x = 0; x < EU_MAXPFLGS; x++) {
4069                FLG_t f = FLDget(w, x);
4070                if (f >= EU_MAXPFLGS || f < 0)
4071                   return p;
4072             }
4073             break;
4074       }
4075       // ensure there's been no manual alteration of fieldscur
4076       for (n = 0 ; n < EU_MAXPFLGS; n++) {
4077          if (&w->rc.fieldscur[n] != msch(w->rc.fieldscur, w->rc.fieldscur[n], EU_MAXPFLGS))
4078             return p;
4079       }
4080    } // end: for (GROUPSMAX)
4081
4082    // any new addition(s) last, for older rcfiles compatibility...
4083    (void)fscanf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
4084       , &Rc.fixed_widest, &Rc.summ_mscale, &Rc.task_mscale, &Rc.zero_suppress,  &Rc.tics_scaled);
4085    if (Rc.fixed_widest < -1 || Rc.fixed_widest > SCREENMAX)
4086       Rc.fixed_widest = 0;
4087    if (Rc.summ_mscale < 0   || Rc.summ_mscale > SK_Eb)
4088       Rc.summ_mscale = 0;
4089    if (Rc.task_mscale < 0   || Rc.task_mscale > SK_Pb)
4090       Rc.task_mscale = 0;
4091    if (Rc.zero_suppress < 0 || Rc.zero_suppress > 1)
4092       Rc.zero_suppress = 0;
4093    if (Rc.tics_scaled < 0 || Rc.tics_scaled > TICS_AS_LAST)
4094       Rc.tics_scaled = 0;
4095
4096    // prepare to warn that older top can no longer read rcfile ...
4097    if (Rc.id != RCF_VERSION_ID)
4098       Rc_compatibilty = 1;
4099
4100    // lastly, let's process any optional glob(s) ...
4101    // (darn, must do osel 1st even though alphabetically 2nd)
4102    fbuf[0] = '\0';
4103    config_osel(fp, fbuf, sizeof(fbuf));
4104    config_insp(fp, fbuf, sizeof(fbuf));
4105
4106    return NULL;
4107 } // end: configs_file
4108
4109
4110         /*
4111          * A configs_reads *Helper* function responsible for ensuring the
4112          * complete path was established, otherwise force the 'W' to fail */
4113 static int configs_path (const char *const fmts, ...) __attribute__((format(printf,1,2)));
4114 static int configs_path (const char *const fmts, ...) {
4115    int len;
4116    va_list ap;
4117
4118    va_start(ap, fmts);
4119    len = vsnprintf(Rc_name, sizeof(Rc_name), fmts, ap);
4120    va_end(ap);
4121    if (len <= 0 || (size_t)len >= sizeof(Rc_name)) {
4122       Rc_name[0] = '\0';
4123       len = 0;
4124    }
4125    return len;
4126 } // end: configs_path
4127
4128
4129         /*
4130          * Try reading up to 3 rcfiles
4131          * 1. 'SYS_RCRESTRICT' contains two lines consisting of the secure
4132          *     mode switch and an update interval.  Its presence limits what
4133          *     ordinary users are allowed to do.
4134          * 2. 'Rc_name' contains multiple lines - both global & per window.
4135          *       line 1 : an eyecatcher and creating program/alias name
4136          *       line 2 : an id, Mode_altcsr, Mode_irixps, Delay_time, Curwin.
4137          *     For each of the 4 windows:
4138          *       lines a: contains w->winname, fieldscur
4139          *       line  b: contains w->winflags, sortindx, maxtasks, etc
4140          *       line  c: contains w->summclr, msgsclr, headclr, taskclr
4141          *     global   : miscellaneous additional settings
4142          *     Any remaining lines are devoted to the optional entries
4143          *     supporting the 'Other Filter' and 'Inspect' provisions.
4144          * 3. 'SYS_RCDEFAULTS' system-wide defaults if 'Rc_name' absent
4145          *     format is identical to #2 above */
4146 static void configs_reads (void) {
4147    float tmp_delay = DEF_DELAY;
4148    const char *p, *p_home;
4149    FILE *fp;
4150
4151    fp = fopen(SYS_RCRESTRICT, "r");
4152    if (fp) {
4153       char fbuf[SMLBUFSIZ];
4154       if (fgets(fbuf, sizeof(fbuf), fp)) {     // sys rc file, line 1
4155          Secure_mode = 1;
4156          if (fgets(fbuf, sizeof(fbuf), fp))    // sys rc file, line 2
4157             sscanf(fbuf, "%f", &Rc.delay_time);
4158       }
4159       fclose(fp);
4160    }
4161
4162    Rc_name[0] = '\0'; // "fopen() shall fail if pathname is an empty string."
4163    // attempt to use the legacy file first, if we cannot access that file, use
4164    // the new XDG basedir locations (XDG_CONFIG_HOME or HOME/.config) instead.
4165    p_home = getenv("HOME");
4166    if (!p_home || p_home[0] != '/') {
4167       const struct passwd *const pwd = getpwuid(getuid());
4168       if (!pwd || !(p_home = pwd->pw_dir) || p_home[0] != '/') {
4169          p_home = NULL;
4170       }
4171    }
4172    if (p_home)
4173       configs_path("%s/.%src", p_home, Myname);
4174
4175    if (!(fp = fopen(Rc_name, "r"))) {
4176       p = getenv("XDG_CONFIG_HOME");
4177       // ensure the path we get is absolute, fallback otherwise.
4178       if (!p || p[0] != '/') {
4179          if (!p_home) goto system_default;
4180          p = fmtmk("%s/.config", p_home);
4181          (void)mkdir(p, 0700);
4182       }
4183       if (!configs_path("%s/procps", p)) goto system_default;
4184       (void)mkdir(Rc_name, 0700);
4185       if (!configs_path("%s/procps/%src", p, Myname)) goto system_default;
4186       fp = fopen(Rc_name, "r");
4187    }
4188
4189    if (fp) {
4190       p = configs_file(fp, Rc_name, &tmp_delay);
4191       fclose(fp);
4192       if (p) goto default_or_error;
4193    } else {
4194 system_default:
4195       fp = fopen(SYS_RCDEFAULTS, "r");
4196       if (fp) {
4197          p = configs_file(fp, SYS_RCDEFAULTS, &tmp_delay);
4198          fclose(fp);
4199          if (p) goto default_or_error;
4200       }
4201    }
4202
4203    // lastly, establish the true runtime secure mode and delay time
4204    if (!getuid()) Secure_mode = 0;
4205    if (!Secure_mode) Rc.delay_time = tmp_delay;
4206    return;
4207
4208 default_or_error:
4209 #ifdef RCFILE_NOERR
4210 {  RCF_t rcdef = DEF_RCFILE;
4211    int i;
4212    Rc = rcdef;
4213    for (i = 0 ; i < GROUPSMAX; i++)
4214       Winstk[i].rc  = Rc.win[i];
4215 }
4216 #else
4217    error_exit(p);
4218 #endif
4219 } // end: configs_reads
4220
4221
4222         /*
4223          * Parse command line arguments.
4224          * Note: it's assumed that the rc file(s) have already been read
4225          *       and our job is to see if any of those options are to be
4226          *       overridden -- we'll force some on and negate others in our
4227          *       best effort to honor the loser's (oops, user's) wishes... */
4228 static void parse_args (int argc, char **argv) {
4229     static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1";
4230     static const struct option lopts[] = {
4231        { "batch-mode",        no_argument,       NULL, 'b' },
4232        { "cmdline-toggle",    no_argument,       NULL, 'c' },
4233        { "delay",             required_argument, NULL, 'd' },
4234        { "scale-summary-mem", required_argument, NULL, 'E' },
4235        { "scale-task-mem",    required_argument, NULL, 'e' },
4236        { "threads-show",      no_argument,       NULL, 'H' },
4237        { "help",              no_argument,       NULL, 'h' },
4238        { "idle-toggle",       no_argument,       NULL, 'i' },
4239        { "iterations",        required_argument, NULL, 'n' },
4240        { "list-fields",       no_argument,       NULL, 'O' },
4241        { "sort-override",     required_argument, NULL, 'o' },
4242        { "pid",               required_argument, NULL, 'p' },
4243        { "accum-time-toggle", no_argument,       NULL, 'S' },
4244        { "secure-mode",       no_argument,       NULL, 's' },
4245        { "filter-any-user",   required_argument, NULL, 'U' },
4246        { "filter-only-euser", required_argument, NULL, 'u' },
4247        { "version",           no_argument,       NULL, 'V' },
4248        { "width",             optional_argument, NULL, 'w' },
4249        { "single-cpu-toggle", no_argument,       NULL, '1' },
4250        { NULL, 0, NULL, 0 }
4251    };
4252    float tmp_delay = FLT_MAX;
4253    int ch;
4254
4255    while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) {
4256       int i;
4257       float tmp;
4258       char *cp = optarg;
4259
4260 #ifndef GETOPTFIX_NO
4261       /* first, let's plug some awful gaps in the getopt implementation,
4262          especially relating to short options with (optional) arguments! */
4263       if (!cp && optind < argc && argv[optind][0] != '-')
4264          cp = argv[optind++];
4265       if (cp) {
4266          if (*cp == '=') ++cp;
4267          /* here, if we're actually accessing argv[argc], we'll rely on
4268             the required NULL delimiter which yields an error_exit next */
4269          if (*cp == '\0') cp = argv[optind++];
4270          if (!cp) error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
4271       }
4272 #endif
4273       switch (ch) {
4274          case '1':   // ensure behavior identical to run-time toggle
4275             if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM);
4276             else TOGw(Curwin, View_CPUSUM);
4277             OFFw(Curwin, View_CPUNOD);
4278             SETw(Curwin, View_STATES);
4279             break;
4280          case 'b':
4281             Batch = 1;
4282             break;
4283          case 'c':
4284             TOGw(Curwin, Show_CMDLIN);
4285             break;
4286          case 'd':
4287             if (!mkfloat(cp, &tmp_delay, 0))
4288                error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp));
4289             if (0 > tmp_delay)
4290                error_exit(N_txt(DELAY_badarg_txt));
4291             continue;
4292          case 'E':
4293          {  const char *get = "kmgtpe", *got;
4294             if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4295                error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4296             Rc.summ_mscale = (int)(got - get);
4297          }  continue;
4298          case 'e':
4299          {  const char *get = "kmgtp", *got;
4300             if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4301                error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4302             Rc.task_mscale = (int)(got - get);
4303          }  continue;
4304          case 'H':
4305             Thread_mode = 1;
4306             break;
4307          case 'h':
4308             puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname));
4309             bye_bye(NULL);
4310          case 'i':
4311             TOGw(Curwin, Show_IDLEPS);
4312             Curwin->rc.maxtasks = 0;
4313             break;
4314          case 'n':
4315             if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp)
4316                error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp));
4317             Loops = (int)tmp;
4318             continue;
4319          case 'O':
4320             for (i = 0; i < EU_MAXPFLGS; i++)
4321                puts(N_col(i));
4322             bye_bye(NULL);
4323          case 'o':
4324             if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; }
4325             else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; }
4326             for (i = 0; i < EU_MAXPFLGS; i++)
4327                if (!STRCMP(cp, N_col(i))) break;
4328             if (i == EU_MAXPFLGS)
4329                error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp));
4330             OFFw(Curwin, Show_FOREST);
4331             Curwin->rc.sortindx = i;
4332             continue;
4333          case 'p':
4334          {  int pid; char *p;
4335             if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4336             do {
4337                if (Monpidsidx >= MONPIDMAX)
4338                   error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX));
4339                if (1 != sscanf(cp, "%d", &pid)
4340                || strpbrk(cp, "+-."))
4341                   error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp));
4342                if (!pid) pid = getpid();
4343                for (i = 0; i < Monpidsidx; i++)
4344                   if (Monpids[i] == pid) goto next_pid;
4345                Monpids[Monpidsidx++] = pid;
4346             next_pid:
4347                if (!(p = strchr(cp, ','))) break;
4348                cp = p + 1;
4349             } while (*cp);
4350          }  continue;
4351          case 'S':
4352             TOGw(Curwin, Show_CTIMES);
4353             break;
4354          case 's':
4355             Secure_mode = 1;
4356             break;
4357          case 'U':
4358          case 'u':
4359          {  const char *errmsg;
4360             if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4361             if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg);
4362          }  continue;
4363          case 'V':
4364             puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING));
4365             bye_bye(NULL);
4366          case 'w':
4367             tmp = -1;
4368             if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX))
4369                error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp));
4370             Width_mode = (int)tmp;
4371             continue;
4372          default:
4373             /* we'll rely on getopt for any error message while
4374                forcing an EXIT_FAILURE with an empty string ... */
4375             bye_bye("");
4376       } // end: switch (ch)
4377 #ifndef GETOPTFIX_NO
4378       if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp));
4379 #endif
4380    } // end: while getopt_long
4381
4382    if (optind < argc)
4383       error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind]));
4384
4385    // fixup delay time, maybe...
4386    if (FLT_MAX > tmp_delay) {
4387       if (Secure_mode)
4388          error_exit(N_txt(DELAY_secure_txt));
4389       Rc.delay_time = tmp_delay;
4390    }
4391 } // end: parse_args
4392
4393
4394         /*
4395          * Establish a robust signals environment */
4396 static void signals_set (void) {
4397  #ifndef SIGRTMAX       // not available on hurd, maybe others too
4398   #define SIGRTMAX 32
4399  #endif
4400    int i;
4401    struct sigaction sa;
4402
4403    memset(&sa, 0, sizeof(sa));
4404    sigemptyset(&sa.sa_mask);
4405    // with user position preserved through SIGWINCH, we must avoid SA_RESTART
4406    sa.sa_flags = 0;
4407    for (i = SIGRTMAX; i; i--) {
4408       switch (i) {
4409          case SIGHUP:
4410             if (Batch)
4411                sa.sa_handler = SIG_IGN;
4412             else
4413                sa.sa_handler = sig_endpgm;
4414             break;
4415          case SIGALRM: case SIGINT:  case SIGPIPE:
4416          case SIGQUIT: case SIGTERM: case SIGUSR1:
4417          case SIGUSR2:
4418             sa.sa_handler = sig_endpgm;
4419             break;
4420          case SIGTSTP: case SIGTTIN: case SIGTTOU:
4421             sa.sa_handler = sig_paused;
4422             break;
4423          case SIGCONT: case SIGWINCH:
4424             sa.sa_handler = sig_resize;
4425             break;
4426          default:
4427             sa.sa_handler = sig_abexit;
4428             break;
4429          case SIGKILL: case SIGSTOP:
4430          // because uncatchable, fall through
4431          case SIGCHLD: // we can't catch this
4432             continue;  // when opening a pipe
4433       }
4434       sigaction(i, &sa, NULL);
4435    }
4436 } // end: signals_set
4437
4438
4439         /*
4440          * Set up the terminal attributes */
4441 static void whack_terminal (void) {
4442    static char dummy[] = "dumb";
4443    struct termios tmptty;
4444
4445    // the curses part...
4446    if (Batch) {
4447       setupterm(dummy, STDOUT_FILENO, NULL);
4448       return;
4449    }
4450 #ifdef PRETENDNOCAP
4451    setupterm(dummy, STDOUT_FILENO, NULL);
4452 #else
4453    setupterm(NULL, STDOUT_FILENO, NULL);
4454 #endif
4455    // our part...
4456    if (-1 == tcgetattr(STDIN_FILENO, &Tty_original))
4457       error_exit(N_txt(FAIL_tty_get_txt));
4458    // ok, haven't really changed anything but we do have our snapshot
4459    Ttychanged = 1;
4460
4461    // first, a consistent canonical mode for interactive line input
4462    tmptty = Tty_original;
4463    tmptty.c_lflag |= (ECHO | ECHOCTL | ECHOE | ICANON | ISIG);
4464    tmptty.c_lflag &= ~NOFLSH;
4465    tmptty.c_oflag &= ~TAB3;
4466    tmptty.c_iflag |= BRKINT;
4467    tmptty.c_iflag &= ~IGNBRK;
4468    if (key_backspace && 1 == strlen(key_backspace))
4469       tmptty.c_cc[VERASE] = *key_backspace;
4470 #ifdef TERMIOS_ONLY
4471    if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4472       error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4473    tcgetattr(STDIN_FILENO, &Tty_tweaked);
4474 #endif
4475    // lastly, a nearly raw mode for unsolicited single keystrokes
4476    tmptty.c_lflag &= ~(ECHO | ECHOCTL | ECHOE | ICANON);
4477    tmptty.c_cc[VMIN] = 1;
4478    tmptty.c_cc[VTIME] = 0;
4479    if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4480       error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4481    tcgetattr(STDIN_FILENO, &Tty_raw);
4482
4483 #ifndef OFF_STDIOLBF
4484    // thanks anyway stdio, but we'll manage buffering at the frame level...
4485    setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf));
4486 #endif
4487 #ifdef OFF_SCROLLBK
4488    // this has the effect of disabling any troublesome scrollback buffer...
4489    if (enter_ca_mode) putp(enter_ca_mode);
4490 #endif
4491    // and don't forget to ask iokey to initialize his tinfo_tab
4492    iokey(IOKEY_INIT);
4493 } // end: whack_terminal
4494 \f
4495 /*######  Windows/Field Groups support  #################################*/
4496
4497         /*
4498          * Value a window's name and make the associated group name. */
4499 static void win_names (WIN_t *q, const char *name) {
4500    /* note: sprintf/snprintf results are "undefined" when src==dst,
4501             according to C99 & POSIX.1-2001 (thanks adc) */
4502    if (q->rc.winname != name)
4503       snprintf(q->rc.winname, sizeof(q->rc.winname), "%s", name);
4504    snprintf(q->grpname, sizeof(q->grpname), "%d:%s", q->winnum, name);
4505 } // end: win_names
4506
4507
4508         /*
4509          * This guy just resets (normalizes) a single window
4510          * and he ensures pid monitoring is no longer active. */
4511 static void win_reset (WIN_t *q) {
4512          SETw(q, Show_IDLEPS | Show_TASKON);
4513 #ifndef SCROLLVAR_NO
4514          q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->varcolbeg = q->focus_pid = 0;
4515 #else
4516          q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->focus_pid = 0;
4517 #endif
4518          mkVIZoff(q)
4519          osel_clear(q);
4520          q->findstr[0] = '\0';
4521          q->rc.combine_cpus = 0;
4522          q->rc.core_types = 0;
4523
4524          // these next guys are global, not really windows based
4525          Monpidsidx = 0;
4526          Rc.tics_scaled = 0;
4527          BOT_TOSS;
4528 } // end: win_reset
4529
4530
4531         /*
4532          * Display a window/field group (ie. make it "current"). */
4533 static WIN_t *win_select (int ch) {
4534    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
4535
4536    /* if there's no ch, it means we're supporting the external interface,
4537       so we must try to get our own darn ch by begging the user... */
4538    if (!ch) {
4539       show_pmt(N_txt(CHOOSE_group_txt));
4540       if (1 > (ch = iokey(IOKEY_ONCE))) return w;
4541    }
4542    switch (ch) {
4543       case 'a':                   // we don't carry 'a' / 'w' in our
4544          w = w->next;             // pmt - they're here for a good
4545          break;                   // friend of ours -- wins_colors.
4546       case 'w':                   // (however those letters work via
4547          w = w->prev;             // the pmt too but gee, end-loser
4548          break;                   // should just press the darn key)
4549       case '1': case '2' : case '3': case '4':
4550          w = &Winstk[ch - '1'];
4551          break;
4552       default:                    // keep gcc happy
4553          break;
4554    }
4555    Curwin = w;
4556    return Curwin;
4557 } // end: win_select
4558
4559
4560         /*
4561          * Just warn the user when a command can't be honored. */
4562 static int win_warn (int what) {
4563    switch (what) {
4564       case Warn_ALT:
4565          show_msg(N_txt(DISABLED_cmd_txt));
4566          break;
4567       case Warn_VIZ:
4568          show_msg(fmtmk(N_fmt(DISABLED_win_fmt), Curwin->grpname));
4569          break;
4570       default:                    // keep gcc happy
4571          break;
4572    }
4573    /* we gotta' return false 'cause we're somewhat well known within
4574       macro society, by way of that sassy little tertiary operator... */
4575    return 0;
4576 } // end: win_warn
4577
4578
4579         /*
4580          * Change colors *Helper* function to save/restore settings;
4581          * ensure colors will show; and rebuild the terminfo strings. */
4582 static void wins_clrhlp (WIN_t *q, int save) {
4583    static int flgssav, summsav, msgssav, headsav, tasksav;
4584
4585    if (save) {
4586       flgssav = q->rc.winflags; summsav = q->rc.summclr;
4587       msgssav = q->rc.msgsclr;  headsav = q->rc.headclr; tasksav = q->rc.taskclr;
4588       SETw(q, Show_COLORS);
4589    } else {
4590       q->rc.winflags = flgssav; q->rc.summclr = summsav;
4591       q->rc.msgsclr = msgssav;  q->rc.headclr = headsav; q->rc.taskclr = tasksav;
4592    }
4593    capsmk(q);
4594 } // end: wins_clrhlp
4595
4596
4597         /*
4598          * Change colors used in display */
4599 static void wins_colors (void) {
4600  #define kbdABORT  'q'
4601  #define kbdAPPLY  kbd_ENTER
4602    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
4603    int clr = w->rc.taskclr, *pclr = &w->rc.taskclr;
4604    char tgt = 'T';
4605    int key;
4606
4607    if (0 >= max_colors) {
4608       show_msg(N_txt(COLORS_nomap_txt));
4609       return;
4610    }
4611    wins_clrhlp(w, 1);
4612    putp((Cursor_state = Cap_curs_huge));
4613 signify_that:
4614    putp(Cap_clr_scr);
4615    adj_geometry();
4616
4617    do {
4618       putp(Cap_home);
4619       // this string is well above ISO C89's minimum requirements!
4620       show_special(1, fmtmk(N_unq(COLOR_custom_fmt)
4621          , w->grpname
4622          , CHKw(w, View_NOBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4623          , CHKw(w, Show_COLORS) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4624          , CHKw(w, Show_HIBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4625          , tgt, max_colors, clr, w->grpname));
4626       putp(Cap_clr_eos);
4627       fflush(stdout);
4628
4629       if (Frames_signal) goto signify_that;
4630       key = iokey(IOKEY_ONCE);
4631       if (key < 1) goto signify_that;
4632       if (key == kbd_ESC) break;
4633
4634       switch (key) {
4635          case 'S':
4636             pclr = &w->rc.summclr;
4637             clr = *pclr;
4638             tgt = key;
4639             break;
4640          case 'M':
4641             pclr = &w->rc.msgsclr;
4642             clr = *pclr;
4643             tgt = key;
4644             break;
4645          case 'H':
4646             pclr = &w->rc.headclr;
4647             clr = *pclr;
4648             tgt = key;
4649             break;
4650          case 'T':
4651             pclr = &w->rc.taskclr;
4652             clr = *pclr;
4653             tgt = key;
4654             break;
4655          case '0': case '1': case '2': case '3':
4656          case '4': case '5': case '6': case '7':
4657             clr = key - '0';
4658             *pclr = clr;
4659             break;
4660          case kbd_UP:
4661             ++clr;
4662             if (clr >= max_colors) clr = 0;
4663             *pclr = clr;
4664             break;
4665          case kbd_DOWN:
4666             --clr;
4667             if (clr < 0) clr = max_colors - 1;
4668             *pclr = clr;
4669             break;
4670          case 'B':
4671             TOGw(w, View_NOBOLD);
4672             break;
4673          case 'b':
4674             TOGw(w, Show_HIBOLD);
4675             break;
4676          case 'z':
4677             TOGw(w, Show_COLORS);
4678             break;
4679          case 'a':
4680          case 'w':
4681             wins_clrhlp((w = win_select(key)), 1);
4682             clr = w->rc.taskclr, pclr = &w->rc.taskclr;
4683             tgt = 'T';
4684             break;
4685          default:
4686             break;                // keep gcc happy
4687       }
4688       capsmk(w);
4689    } while (key != kbdAPPLY && key != kbdABORT);
4690
4691    if (key == kbdABORT || key == kbd_ESC) wins_clrhlp(w, 0);
4692    // signal that we just corrupted entire screen
4693    Frames_signal = BREAK_screen;
4694  #undef kbdABORT
4695  #undef kbdAPPLY
4696 } // end: wins_colors
4697
4698
4699         /*
4700          * Manipulate flag(s) for all our windows. */
4701 static void wins_reflag (int what, int flg) {
4702    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
4703
4704    do {
4705       switch (what) {
4706          case Flags_TOG:
4707             TOGw(w, flg);
4708             break;
4709          case Flags_SET:          // Ummmm, i can't find anybody
4710             SETw(w, flg);         // who uses Flags_set ...
4711             break;
4712          case Flags_OFF:
4713             OFFw(w, flg);
4714             break;
4715          default:                 // keep gcc happy
4716             break;
4717       }
4718          /* a flag with special significance -- user wants to rebalance
4719             display so we gotta' off some stuff then force on two flags... */
4720       if (EQUWINS_xxx == flg)
4721          win_reset(w);
4722
4723       w = w->next;
4724    } while (w != Curwin);
4725 } // end: wins_reflag
4726
4727
4728         /*
4729          * Set up the raw/incomplete field group windows --
4730          * they'll be finished off after startup completes.
4731          * [ and very likely that will override most/all of our efforts ]
4732          * [               --- life-is-NOT-fair ---                     ] */
4733 static void wins_stage_1 (void) {
4734    WIN_t *w;
4735    int i;
4736
4737    for (i = 0; i < GROUPSMAX; i++) {
4738       w = &Winstk[i];
4739       w->winnum = i + 1;
4740       w->rc = Rc.win[i];
4741       w->captab[0] = Cap_norm;
4742       w->captab[1] = Cap_norm;
4743       w->captab[2] = w->cap_bold;
4744       w->captab[3] = w->capclr_sum;
4745       w->captab[4] = w->capclr_msg;
4746       w->captab[5] = w->capclr_pmt;
4747       w->captab[6] = w->capclr_hdr;
4748       w->captab[7] = w->capclr_rowhigh;
4749       w->captab[8] = w->capclr_rownorm;
4750       w->next = w + 1;
4751       w->prev = w - 1;
4752    }
4753
4754    // fixup the circular chains...
4755    Winstk[GROUPSMAX - 1].next = &Winstk[0];
4756    Winstk[0].prev = &Winstk[GROUPSMAX - 1];
4757    Curwin = Winstk;
4758
4759    for (i = 1; i < BOT_MSGSMAX; i++)
4760       Msg_tab[i].prev = &Msg_tab[i - 1];
4761    Msg_tab[0].prev = &Msg_tab[BOT_MSGSMAX -1];
4762 } // end: wins_stage_1
4763
4764
4765         /*
4766          * This guy just completes the field group windows after the
4767          * rcfiles have been read and command line arguments parsed.
4768          * And since he's the cabose of startup, he'll also tidy up
4769          * a few final things... */
4770 static void wins_stage_2 (void) {
4771    int i;
4772
4773    for (i = 0; i < GROUPSMAX; i++) {
4774       win_names(&Winstk[i], Winstk[i].rc.winname);
4775       capsmk(&Winstk[i]);
4776       Winstk[i].findstr = alloc_c(FNDBUFSIZ);
4777       Winstk[i].findlen = 0;
4778       if (Winstk[i].rc.combine_cpus >= Cpu_cnt)
4779          Winstk[i].rc.combine_cpus = 0;
4780       if (CHKw(&Winstk[i], (View_CPUSUM | View_CPUNOD)))
4781          Winstk[i].rc.double_up = 0;
4782    }
4783    if (!Batch)
4784       putp((Cursor_state = Cap_curs_hide));
4785    else {
4786       OFFw(Curwin, View_SCROLL);
4787       signal(SIGHUP, SIG_IGN);    // allow running under nohup
4788    }
4789    // fill in missing Fieldstab members and build each window's columnhdr
4790    zap_fieldstab();
4791
4792    // with preserved 'other filters' & command line 'user filters',
4793    // we must ensure that we always have a visible task on row one.
4794    mkVIZrow1
4795
4796    // lastly, initialize a signal set used to throttle one troublesome signal
4797    sigemptyset(&Sigwinch_set);
4798 #ifdef SIGNALS_LESS
4799    sigaddset(&Sigwinch_set, SIGWINCH);
4800 #endif
4801 } // end: wins_stage_2
4802
4803
4804         /*
4805          * Determine if this task matches the 'u/U' selection
4806          * criteria for a given window */
4807 static inline int wins_usrselect (const WIN_t *q, int idx) {
4808   // a tailored 'results stack value' extractor macro
4809  #define rSv(E)  PID_VAL(E, u_int, p)
4810    struct pids_stack *p = q->ppt[idx];
4811
4812    switch (q->usrseltyp) {
4813       case 0:                                    // uid selection inactive
4814          return 1;
4815       case 'U':                                  // match any uid
4816          if (rSv(EU_URD)     == (unsigned)q->usrseluid) return q->usrselflg;
4817          if (rSv(EU_USD)     == (unsigned)q->usrseluid) return q->usrselflg;
4818          if (rSv(eu_ID_FUID) == (unsigned)q->usrseluid) return q->usrselflg;
4819       // fall through...
4820       case 'u':                                  // match effective uid
4821          if (rSv(EU_UED)     == (unsigned)q->usrseluid) return q->usrselflg;
4822       // fall through...
4823       default:                                   // no match...
4824          ;
4825    }
4826    return !q->usrselflg;
4827  #undef rSv
4828 } // end: wins_usrselect
4829 \f
4830 /*######  Forest View support  ###########################################*/
4831
4832         /*
4833          * We try keeping most existing code unaware of these activities |
4834          * ( plus, maintain alphabetical order within carefully chosen ) |
4835          * ( names beginning forest_a, forest_b, forest_c and forest_d ) |
4836          * ( with each name exactly 1 letter more than its predecessor ) | */
4837 static struct pids_stack **Seed_ppt;        // temporary win ppt pointer |
4838 static struct pids_stack **Tree_ppt;        // forest_begin resizes this |
4839 static int Tree_idx;                        // frame_make resets to zero |
4840         /* those next two support collapse/expand children. the Hide_pid |
4841            array holds parent pids whose children have been manipulated. |
4842            positive pid values represent parents with collapsed children |
4843            while a negative pid value means children have been expanded. |
4844            ( both of these are managed under the 'keys_task()' routine ) | */
4845 static int *Hide_pid;                       // collapsible process array |
4846 static int  Hide_tot;                       // total used in above array |
4847
4848         /*
4849          * This little recursive guy was the real forest view workhorse. |
4850          * He fills in the Tree_ppt array and also sets the child indent |
4851          * level which is stored in an 'extra' result struct as a u_int. | */
4852 static void forest_adds (const int self, int level) {
4853   // tailored 'results stack value' extractor macros
4854  #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X])
4855   // if xtra-procps-debug.h active, can't use PID_VAL with assignment
4856  #define rSv_Lvl  Tree_ppt[Tree_idx]->head[eu_TREE_LVL].result.s_int
4857    int i;
4858
4859    if (Tree_idx < PIDSmaxt) {               // immunize against insanity |
4860       if (level > 100) level = 101;         // our arbitrary nests limit |
4861       Tree_ppt[Tree_idx] = Seed_ppt[self];  // add this as root or child |
4862       rSv_Lvl = level;                      // while recording its level |
4863       ++Tree_idx;
4864 #ifdef TREE_SCANALL
4865       for (i = 0; i < PIDSmaxt; i++) {
4866          if (i == self) continue;
4867 #else
4868       for (i = self + 1; i < PIDSmaxt; i++) {
4869 #endif
4870          if (rSv(EU_PID, self) == rSv(EU_TGD, i)
4871          || (rSv(EU_PID, self) == rSv(EU_PPD, i) && rSv(EU_PID, i) == rSv(EU_TGD, i)))
4872             forest_adds(i, level + 1);      // got one child any others?
4873       }
4874    }
4875  #undef rSv
4876  #undef rSv_Lvl
4877 } // end: forest_adds
4878
4879
4880         /*
4881          * This function is responsible for making that stacks ptr array |
4882          * a forest display in that designated window. After completion, |
4883          * he'll replace that original window ppt array with a specially |
4884          * ordered forest view version. He'll also mark hidden children! | */
4885 static void forest_begin (WIN_t *q) {
4886    static int hwmsav;
4887    int i, j;
4888
4889    Seed_ppt = q->ppt;                          // avoid passing pointers |
4890    if (!Tree_idx) {                            // do just once per frame |
4891       if (hwmsav < PIDSmaxt) {                 // grow, but never shrink |
4892          hwmsav = PIDSmaxt;
4893          Tree_ppt = alloc_r(Tree_ppt, sizeof(void *) * hwmsav);
4894       }
4895
4896 #ifndef TREE_SCANALL
4897       if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt
4898          , PIDS_TICS_BEGAN, PIDS_SORT_ASCEND)))
4899             error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
4900 #endif
4901       for (i = 0; i < PIDSmaxt; i++) {         // avoid hidepid distorts |
4902          if (!PID_VAL(eu_TREE_LVL, s_int, Seed_ppt[i])) // parents lvl 0 |
4903             forest_adds(i, 0);                 // add parents + children |
4904       }
4905
4906       /* we use up to three additional 'PIDS_extra' results in our stack |
4907             eu_TREE_HID (s_ch) :  where 'x' == collapsed & 'z' == unseen |
4908             eu_TREE_LVL (s_int):  where level number is stored (0 - 100) |
4909             eu_TREE_ADD (u_int):  where a children's tics stored (maybe) | */
4910       for (i = 0; i < Hide_tot; i++) {
4911
4912         // if have xtra-procps-debug.h, cannot use PID_VAL w/ assignment |
4913        #define rSv(E,T,X)  Tree_ppt[X]->head[E].result.T
4914        #define rSv_Pid(X)  rSv(EU_PID, s_int, X)
4915        #define rSv_Lvl(X)  rSv(eu_TREE_LVL, s_int, X)
4916        #define rSv_Hid(X)  rSv(eu_TREE_HID, s_ch, X)
4917         /* next 2 aren't needed if TREE_VCPUOFF but they cost us nothing |
4918            & the EU_CPU slot will now always be present (even if it's 0) | */
4919        #define rSv_Add(X)  rSv(eu_TREE_ADD, u_int, X)
4920        #define rSv_Cpu(X)  rSv(EU_CPU, u_int, X)
4921
4922          if (Hide_pid[i] > 0) {
4923             for (j = 0; j < PIDSmaxt; j++) {
4924                if (rSv_Pid(j) == Hide_pid[i]) {
4925                   int parent = j;
4926                   int children = 0;
4927                   int level = rSv_Lvl(parent);
4928                   while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > level) {
4929                      ++j;
4930                      rSv_Hid(j) = 'z';
4931 #ifndef TREE_VCPUOFF
4932                      rSv_Add(parent) += rSv_Cpu(j);
4933 #endif
4934                      children = 1;
4935                   }
4936                   /* if any children found (& collapsed) mark the parent |
4937                      ( when children aren't found don't negate the pid ) |
4938                      ( to prevent future scans since who's to say such ) |
4939                      ( tasks will not fork more children in the future ) | */
4940                   if (children) rSv_Hid(parent) = 'x';
4941                   // this will force a check of next Hide_pid[i], if any |
4942                   j = PIDSmaxt + 1;
4943                }
4944             }
4945             // if a target task disappeared prevent any further scanning |
4946             if (j == PIDSmaxt) Hide_pid[i] = -Hide_pid[i];
4947          }
4948        #undef rSv
4949        #undef rSv_Pid
4950        #undef rSv_Lvl
4951        #undef rSv_Hid
4952        #undef rSv_Add
4953        #undef rSv_Cpu
4954       }
4955    } // end: !Tree_idx
4956    memcpy(Seed_ppt, Tree_ppt, sizeof(void *) * PIDSmaxt);
4957 } // end: forest_begin
4958
4959
4960         /*
4961          * When there's a 'focus_pid' established for a window, this guy |
4962          * determines that window's 'focus_beg' plus 'focus_end' values. |
4963          * But, if the pid can no longer be found, he'll turn off focus! | */
4964 static void forest_config (WIN_t *q) {
4965   // tailored 'results stack value' extractor macro
4966  #define rSv(x) PID_VAL(eu_TREE_LVL, s_int, q->ppt[(x)])
4967    int i, level = 0;
4968
4969    for (i = 0; i < PIDSmaxt; i++) {
4970       if (q->focus_pid == PID_VAL(EU_PID, s_int, q->ppt[i])) {
4971          level = rSv(i);
4972          q->focus_beg = i;
4973          break;
4974       }
4975    }
4976    if (i == PIDSmaxt)
4977       q->focus_pid = q->begtask = 0;
4978    else {
4979 #ifdef FOCUS_TREE_X
4980       q->focus_lvl = rSv(i);
4981 #endif
4982       while (i+1 < PIDSmaxt && rSv(i+1) > level)
4983          ++i;
4984       q->focus_end = i + 1;  // make 'focus_end' a proper fencpost
4985       // watch out for newly forked/cloned tasks 'above' us ...
4986       if (q->begtask < q->focus_beg) {
4987          q->begtask = q->focus_beg;
4988          mkVIZoff(q)
4989       }
4990 #ifdef FOCUS_HARD_Y
4991       // if some task 'above' us ended, try to maintain focus
4992       // ( but allow scrolling when there are many children )
4993       if (q->begtask > q->focus_beg
4994       && (SCREEN_ROWS > (q->focus_end - q->focus_beg))) {
4995          q->begtask = q->focus_beg;
4996          mkVIZoff(q)
4997       }
4998 #endif
4999    }
5000  #undef rSv
5001 } // end: forest_config
5002
5003
5004         /*
5005          * This guy adds the artwork to either 'cmd' or 'cmdline' values |
5006          * if we are in forest view mode otherwise he just returns them. | */
5007 static inline const char *forest_display (const WIN_t *q, int idx) {
5008   // tailored 'results stack value' extractor macros
5009  #define rSv(E)   PID_VAL(E, str, p)
5010  #define rSv_Lvl  PID_VAL(eu_TREE_LVL, s_int, p)
5011  #define rSv_Hid  PID_VAL(eu_TREE_HID, s_ch, p)
5012 #ifndef SCROLLVAR_NO
5013    static char buf[MAXBUFSIZ];
5014 #else
5015    static char buf[ROWMINSIZ];
5016 #endif
5017    struct pids_stack *p = q->ppt[idx];
5018    const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD);
5019    int level = rSv_Lvl;
5020
5021 #ifdef FOCUS_TREE_X
5022    if (q->focus_pid) {
5023       if (idx >= q->focus_beg && idx < q->focus_end)
5024          level -= q->focus_lvl;
5025    }
5026 #endif
5027    if (!CHKw(q, Show_FOREST) || level == 0) return which;
5028 #ifndef TREE_VWINALL
5029    if (q == Curwin)            // note: the following is NOT indented
5030 #endif
5031    if (rSv_Hid == 'x') {
5032 #ifdef TREE_VALTMRK
5033       snprintf(buf, sizeof(buf), "%*s%s", (4 * level), "`+ ", which);
5034 #else
5035       snprintf(buf, sizeof(buf), "+%*s%s", ((4 * level) - 1), "`- ", which);
5036 #endif
5037       return buf;
5038    }
5039    if (level > 100) {
5040       snprintf(buf, sizeof(buf), "%400s%s", " +  ", which);
5041       return buf;
5042    }
5043 #ifndef FOCUS_VIZOFF
5044    if (q->focus_pid) snprintf(buf, sizeof(buf), "|%*s%s", ((4 * level) - 1), "`- ", which);
5045    else snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
5046 #else
5047    snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
5048 #endif
5049    return buf;
5050  #undef rSv
5051  #undef rSv_Lvl
5052  #undef rSv_Hid
5053 } // end: forest_display
5054 \f
5055 /*######  Special Separate Bottom Window support  ########################*/
5056
5057         /*
5058          * This guy actually draws the parsed strings |
5059          * including adding a highlight if necessary. | */
5060 static void bot_do (const char *str, int focus) {
5061    char *cap = Cap_norm;
5062
5063    while (*str == ' ') putchar(*(str++));
5064    if (focus)
5065 #ifdef BOT_STRV_OFF
5066       cap = Cap_reverse;
5067 #else
5068       cap = strchr(str, Bot_sep) ? Curwin->capclr_msg : Cap_reverse;
5069 #endif
5070    putp(fmtmk("%s%s%s", cap, str, Cap_norm));
5071 } // end: bot_do
5072
5073
5074         /*
5075          * This guy draws that bottom window's header |
5076          * then parses/arranges to show the contents. |
5077          * ( returns relative # of elements printed ) | */
5078 static int bot_focus_str (const char *hdr, const char *str) {
5079  #define maxRSVD ( Screen_rows - 1 )
5080    char *beg, *end;
5081    char tmp[BIGBUFSIZ];
5082    int n, x;
5083
5084    if (str) {
5085       // we're a little careless with overhead here (it's a one time cost)
5086       memset(Bot_buf, '\0', sizeof(Bot_buf));
5087       n = strlen(str);
5088       if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5089       if (!*str || !strcmp(str, "-")) strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5090       else memccpy(Bot_buf, str, '\0', n);
5091       Bot_rsvd = 1 + BOT_RSVD + ((strlen(Bot_buf) - utf8_delta(Bot_buf)) / Screen_cols);
5092       if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5093       // somewhere down call chain fmtmk() may be used, so we'll old school it
5094       snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5095          , tg2(0, SCREEN_ROWS)
5096          , Curwin->capclr_hdr
5097          , Screen_cols + utf8_delta(hdr)
5098          , hdr);
5099       putp(tmp);
5100    }
5101    // now fmtmk is safe to use ...
5102    putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5103
5104    beg = &Bot_buf[0];
5105    x = BOT_UNFOCUS;
5106
5107    while (*beg) {
5108       if (!(end = strchr(beg, Bot_sep)))
5109          end = beg + strlen(beg);
5110       if ((n = end - beg) >= sizeof(tmp))
5111          n = sizeof(tmp) - 1;
5112       memccpy(tmp, beg, '\0', n);
5113       tmp[n] = '\0';
5114       bot_do(tmp, (++x == Bot_indx));
5115       if (*(beg += n))
5116          putchar(*(beg++));
5117       while (*beg == ' ') putchar(*(beg++));
5118    }
5119    return x;
5120  #undef maxRSVD
5121 } // end: bot_focus_str
5122
5123
5124         /*
5125          * This guy draws that bottom window's header |
5126          * & parses/arranges to show vector contents. |
5127          * ( returns relative # of elements printed ) | */
5128 static int bot_focus_strv (const char *hdr, const char **strv) {
5129  #define maxRSVD ( Screen_rows - 1 )
5130    static int nsav;
5131    char tmp[SCREENMAX], *p;
5132    int i, n, x;
5133
5134    if (strv) {
5135       // we're a little careless with overhead here (it's a one time cost)
5136       memset(Bot_buf, '\0', sizeof(Bot_buf));
5137       n = (char *)&strv[0] - strv[0];
5138       if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5139       memcpy(Bot_buf, strv[0], n);
5140       if ((!Bot_buf[0] || !strcmp(Bot_buf, "-")) && n <= sizeof(char *))
5141          strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5142       for (nsav= 0, p = Bot_buf, x = 0; strv[nsav] != NULL; nsav++) {
5143          p += strlen(strv[nsav]) + 1;
5144          if ((p - Bot_buf) >= sizeof(Bot_buf))
5145             break;
5146          x += utf8_delta(strv[nsav]);
5147       }
5148       n  = (p - Bot_buf) - (x + 1);
5149       Bot_rsvd = 1 + BOT_RSVD + (n / Screen_cols);
5150       if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5151       // somewhere down call chain fmtmk() may be used, so we'll old school it
5152       snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5153          , tg2(0, SCREEN_ROWS)
5154          , Curwin->capclr_hdr
5155          , Screen_cols + utf8_delta(hdr)
5156          , hdr);
5157       putp(tmp);
5158    }
5159    // now fmtmk is safe to use ...
5160    putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5161
5162    p = Bot_buf;
5163    x = BOT_UNFOCUS;
5164
5165    for (i = 0; i < nsav; i++) {
5166       bot_do(p, (++x == Bot_indx));
5167       p += strlen(p) + 1;
5168       putchar(' ');
5169    }
5170    return x;
5171  #undef maxRSVD
5172 } // end: bot_focus_strv
5173
5174
5175 static struct {
5176    enum pflag this;
5177    enum namespace_type that;
5178 } ns_tab[] = {
5179    // careful, cgroup & time were late additions ...
5180    { EU_NS7, PROCPS_NS_CGROUP }, { EU_NS1, PROCPS_NS_IPC  },
5181    { EU_NS2, PROCPS_NS_MNT    }, { EU_NS3, PROCPS_NS_NET  },
5182    { EU_NS4, PROCPS_NS_PID    }, { EU_NS8, PROCPS_NS_TIME },
5183    { EU_NS5, PROCPS_NS_USER   }, { EU_NS6, PROCPS_NS_UTS  }
5184 };
5185
5186
5187         /*
5188          * A helper function that will gather various |
5189          * stuff for dislay by the bot_item_show guy. | */
5190 static void *bot_item_hlp (struct pids_stack *p) {
5191    static char buf[BIGBUFSIZ];
5192    char tmp[SMLBUFSIZ], *b;
5193    struct msg_node *m;
5194    int i;
5195
5196    switch (Bot_what) {
5197       case BOT_MSG_LOG:
5198          *(b = &buf[0]) = '\0';
5199          m = Msg_this->prev;
5200          do {
5201             if (m->msg[0]) {
5202                b = scat(b, m->msg);
5203                if (m != Msg_this && m->prev->msg[0]) {
5204                   // caller itself may have used fmtmk, so we'll old school it ...
5205                   snprintf(tmp, sizeof(tmp), "%c ", BOT_SEP_SMI);
5206                   b = scat(b, tmp);
5207                }
5208             }
5209             m = m->prev;
5210          } while (m != Msg_this->prev);
5211          return buf;
5212       case BOT_ITEM_NS:
5213          *(b = &buf[0]) = '\0';
5214          for (i = 0; i < MAXTBL(ns_tab); i++) {
5215             // caller itself may have used fmtmk, so we'll old school it ...
5216             snprintf(tmp, sizeof(tmp), "%s: %-10lu"
5217                , procps_ns_get_name(ns_tab[i].that)
5218                , PID_VAL(ns_tab[i].this, ul_int, p));
5219             b = scat(b, tmp);
5220             if (i < (MAXTBL(ns_tab) - 1)) b = scat(b, ", ");
5221          }
5222          return buf;
5223       case eu_CMDLINE_V:
5224       case eu_ENVIRON_V:
5225          return p->head[Bot_item[0]].result.strv;
5226       default:
5227          return p->head[Bot_item[0]].result.str;
5228    }
5229 } // end: bot_item_hlp
5230
5231
5232         /*
5233          * This guy manages that bottom margin window |
5234          * which shows various process related stuff. | */
5235 static void bot_item_show (void) {
5236  #define mkHDR  fmtmk(Bot_head, Bot_task, PID_VAL(EU_CMD, str, p))
5237    struct pids_stack *p;
5238    int i;
5239
5240    for (i = 0; i < PIDSmaxt; i++) {
5241       p = Curwin->ppt[i];
5242       if (Bot_task == PID_VAL(EU_PID, s_int, p))
5243          break;
5244    }
5245    if (i < PIDSmaxt) {
5246       Bot_focus_func(mkHDR, bot_item_hlp(p));
5247    }
5248 #ifdef BOT_DEAD_ZAP
5249    else
5250       BOT_TOSS;
5251 #else
5252    BOT_KEEP;
5253 #endif
5254  #undef mkHDR
5255 } // end: bot_item_show
5256
5257
5258         /*
5259          * This guy can toggle between displaying the |
5260          * bottom window or arranging to turn it off. | */
5261 static void bot_item_toggle (int what, const char *head, char sep) {
5262    int i;
5263
5264    // if already targeted, assume user wants to turn it off ...
5265    if (Bot_what == what) {
5266       BOT_TOSS;
5267    } else {
5268       // accommodate transition from larger to smaller window
5269       Bot_rsvd = 0;
5270       switch (what) {
5271          case BOT_ITEM_NS:
5272             for (i = 0; i < MAXTBL(ns_tab); i++)
5273                Bot_item[i] = ns_tab[i].this;
5274             Bot_item[i] = BOT_DELIMIT;
5275             Bot_focus_func = (BOT_f)bot_focus_str;
5276             break;
5277          case eu_CMDLINE_V:
5278          case eu_ENVIRON_V:
5279             Bot_item[0] = what;
5280             Bot_item[1] = BOT_DELIMIT;
5281             Bot_focus_func = (BOT_f)bot_focus_strv;
5282             break;
5283          default:
5284             Bot_item[0] = what;
5285             Bot_item[1] = BOT_DELIMIT;
5286             Bot_focus_func = (BOT_f)bot_focus_str;
5287             break;
5288       }
5289       Bot_sep = sep;
5290       Bot_what = what;
5291       Bot_indx = BOT_UNFOCUS;
5292       Bot_head = (char *)head;
5293       Bot_show_func = bot_item_show;
5294       Bot_task = PID_VAL(EU_PID, s_int, Curwin->ppt[Curwin->begtask]);
5295    }
5296 } // end: bot_item_toggle
5297 \f
5298 /*######  Interactive Input Tertiary support  ############################*/
5299
5300   /*
5301    * This section exists so as to offer some function naming freedom
5302    * while also maintaining the strict alphabetical order protocol
5303    * within each section. */
5304
5305         /*
5306          * This guy is a *Helper* function serving the following two masters:
5307          *   find_string() - find the next match in a given window
5308          *   task_show()   - highlight all matches currently in-view
5309          * If q->findstr is found in the designated buffer, he returns the
5310          * offset from the start of the buffer, otherwise he returns -1. */
5311 static inline int find_ofs (const WIN_t *q, const char *buf) {
5312    char *fnd;
5313
5314    if (q->findstr[0] && (fnd = STRSTR(buf, q->findstr)))
5315       return (int)(fnd - buf);
5316    return -1;
5317 } // end: find_ofs
5318
5319
5320
5321    /* This is currently the only true prototype required by top.
5322       It is placed here, instead of top.h, to avoid one compiler
5323       warning when the top_nls.c source was compiled separately. */
5324 static const char *task_show (const WIN_t *q, int idx);
5325
5326 static void find_string (int ch) {
5327  #define reDUX (found) ? N_txt(WORD_another_txt) : ""
5328    static int found;
5329    int i;
5330
5331    if ('&' == ch && !Curwin->findstr[0]) {
5332       show_msg(N_txt(FIND_no_next_txt));
5333       return;
5334    }
5335    if ('L' == ch) {
5336       char *str = ioline(N_txt(GET_find_str_txt));
5337       if (*str == kbd_ESC) return;
5338       snprintf(Curwin->findstr, FNDBUFSIZ, "%s", str);
5339       Curwin->findlen = strlen(Curwin->findstr);
5340       found = 0;
5341    }
5342    if (Curwin->findstr[0]) {
5343       SETw(Curwin, NOPRINT_xxx);
5344       for (i = Curwin->begtask; i < PIDSmaxt; i++) {
5345          const char *row = task_show(Curwin, i);
5346          if (*row && -1 < find_ofs(Curwin, row)) {
5347             found = 1;
5348             if (i == Curwin->begtask) continue;
5349             Curwin->begtask = i;
5350             return;
5351          }
5352       }
5353       show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Curwin->findstr));
5354    }
5355  #undef reDUX
5356 } // end: find_string
5357
5358
5359 static void help_view (void) {
5360    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
5361    char key = 1;
5362
5363    putp((Cursor_state = Cap_curs_huge));
5364 signify_that:
5365    putp(Cap_clr_scr);
5366    adj_geometry();
5367
5368    show_special(1, fmtmk(N_unq(KEYS_helpbas_fmt)
5369       , PACKAGE_STRING
5370       , w->grpname
5371       , CHKw(w, Show_CTIMES) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5372       , Rc.delay_time
5373       , Secure_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5374       , Secure_mode ? "" : N_unq(KEYS_helpext_fmt)));
5375    putp(Cap_clr_eos);
5376    fflush(stdout);
5377
5378    if (Frames_signal) goto signify_that;
5379    key = iokey(IOKEY_ONCE);
5380    if (key < 1) goto signify_that;
5381
5382    switch (key) {
5383       // these keys serve the primary help screen
5384       case kbd_ESC: case 'q':
5385          break;
5386       case '?': case 'h': case 'H':
5387          do {
5388 signify_this:
5389             putp(Cap_clr_scr);
5390             adj_geometry();
5391             show_special(1, fmtmk(N_unq(WINDOWS_help_fmt)
5392                , w->grpname
5393                , Winstk[0].rc.winname, Winstk[1].rc.winname
5394                , Winstk[2].rc.winname, Winstk[3].rc.winname));
5395             putp(Cap_clr_eos);
5396             fflush(stdout);
5397             if (Frames_signal || (key = iokey(IOKEY_ONCE)) < 1)
5398                goto signify_this;
5399             else w = win_select(key);
5400          // these keys serve the secondary help screen
5401          } while (key != kbd_ENTER && key != kbd_ESC && key != 'q');
5402          break;
5403       default:
5404          goto signify_that;
5405    }
5406    // signal that we just corrupted entire screen
5407    Frames_signal = BREAK_screen;
5408 } // end: help_view
5409
5410
5411 static void other_filters (int ch) {
5412    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
5413    const char *txt, *p;
5414    char *glob;
5415
5416    switch (ch) {
5417       case 'o':
5418       case 'O':
5419          if (ch == 'o') txt = N_txt(OSEL_casenot_txt);
5420          else txt = N_txt(OSEL_caseyes_txt);
5421          glob = ioline(fmtmk(N_fmt(OSEL_prompts_fmt), w->osel_tot + 1, txt));
5422          if (*glob == kbd_ESC || *glob == '\0')
5423             return;
5424          if ((p = osel_add(w, ch, glob, 1))) {
5425             show_msg(p);
5426             return;
5427          }
5428          break;
5429       case kbd_CtrlO:
5430          if (VIZCHKw(w)) {
5431             char buf[SCREENMAX], **pp;
5432             struct osel_s *osel;
5433             int i;
5434
5435             i = 0;
5436             osel = w->osel_1st;
5437             pp = alloc_c((w->osel_tot + 1) * sizeof(char *));
5438             while (osel && i < w->osel_tot) {
5439                pp[i++] = osel->raw;
5440                osel = osel->nxt;
5441             }
5442             buf[0] = '\0';
5443             for ( ; i > 0; )
5444                strncat(buf, fmtmk("%s'%s'", " + " , pp[--i]), sizeof(buf) - (strlen(buf) + 1));
5445             if (buf[0]) p = buf + strspn(buf, " + ");
5446             else p = N_txt(WORD_noneone_txt);
5447             ioline(fmtmk(N_fmt(OSEL_statlin_fmt), p));
5448             free(pp);
5449          }
5450          break;
5451       default:                    // keep gcc happy
5452          break;
5453    }
5454 } // end: other_filters
5455
5456
5457 static void write_rcfile (void) {
5458    FILE *fp;
5459    int i, j, n;
5460
5461    if (Rc_questions) {
5462       show_pmt(N_txt(XTRA_warncfg_txt));
5463       if ('y' != tolower(iokey(IOKEY_ONCE)))
5464          return;
5465       Rc_questions = 0;
5466    }
5467    if (Rc_compatibilty) {
5468       show_pmt(N_txt(XTRA_warnold_txt));
5469       if ('y' != tolower(iokey(IOKEY_ONCE)))
5470          return;
5471       Rc_compatibilty = 0;
5472    }
5473    if (!(fp = fopen(Rc_name, "w"))) {
5474       show_msg(fmtmk(N_fmt(FAIL_rc_open_fmt), Rc_name, strerror(errno)));
5475       return;
5476    }
5477    fprintf(fp, "%s's " RCF_EYECATCHER, Myname);
5478    fprintf(fp, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
5479       , RCF_VERSION_ID
5480       , Rc.mode_altscr, Rc.mode_irixps
5481         // this may be ugly, but it keeps us locale independent...
5482       , (int)Rc.delay_time, (int)((Rc.delay_time - (int)Rc.delay_time) * 1000)
5483       , (int)(Curwin - Winstk));
5484
5485    for (i = 0 ; i < GROUPSMAX; i++) {
5486       n = mlen(Winstk[i].rc.fieldscur);
5487       fprintf(fp, "%s\tfieldscur=", Winstk[i].rc.winname);
5488       for (j = 0; j < n; j++) {
5489          if (j && !(j % FLD_ROWMAX) && j < n)
5490             fprintf(fp, "\n\t\t  ");
5491          fprintf(fp, "%4d ", (int)Winstk[i].rc.fieldscur[j]);
5492       }
5493       fprintf(fp, "\n");
5494       fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
5495                   ", double_up=%d, combine_cpus=%d, core_types=%d\n"
5496          , Winstk[i].rc.winflags, Winstk[i].rc.sortindx, Winstk[i].rc.maxtasks
5497          , Winstk[i].rc.graph_cpus, Winstk[i].rc.graph_mems, Winstk[i].rc.double_up
5498          , Winstk[i].rc.combine_cpus, Winstk[i].rc.core_types);
5499       fprintf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
5500          , Winstk[i].rc.summclr, Winstk[i].rc.msgsclr
5501          , Winstk[i].rc.headclr, Winstk[i].rc.taskclr);
5502    }
5503
5504    // any new addition(s) last, for older rcfiles compatibility...
5505    fprintf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
5506       , Rc.fixed_widest, Rc.summ_mscale, Rc.task_mscale, Rc.zero_suppress, Rc.tics_scaled);
5507
5508    if (Winstk[0].osel_tot + Winstk[1].osel_tot
5509      + Winstk[2].osel_tot + Winstk[3].osel_tot) {
5510       fprintf(fp, "\n");
5511       fprintf(fp, Osel_delim_1_txt);
5512       for (i = 0 ; i < GROUPSMAX; i++) {
5513          struct osel_s *osel = Winstk[i].osel_1st;
5514          if (osel) {
5515             fprintf(fp, Osel_window_fmts, i, Winstk[i].osel_tot);
5516             do {
5517                fprintf(fp, Osel_filterO_fmt, osel->typ, osel->raw);
5518                osel = osel->nxt;
5519             } while (osel);
5520          }
5521       }
5522       fprintf(fp, Osel_delim_2_txt);
5523    }
5524
5525    if (Inspect.raw && strcmp(Inspect.raw, "\n"))
5526       fputs(Inspect.raw, fp);
5527
5528    fclose(fp);
5529    show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name));
5530 } // end: write_rcfile
5531 \f
5532 /*######  Interactive Input Secondary support (do_key helpers)  ##########*/
5533
5534   /*
5535    *  These routines exist just to keep the do_key() function
5536    *  a reasonably modest size. */
5537
5538 static void keys_global (int ch) {
5539    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
5540    int i, num, def, pid;
5541
5542    switch (ch) {
5543       case '?':
5544       case 'h':
5545          help_view();
5546          break;
5547       case 'B':
5548          TOGw(w, View_NOBOLD);
5549          capsmk(w);
5550          break;
5551       case 'd':
5552       case 's':
5553          if (Secure_mode)
5554             show_msg(N_txt(NOT_onsecure_txt));
5555          else {
5556             float tmp =
5557                get_float(fmtmk(N_fmt(DELAY_change_fmt), Rc.delay_time));
5558             if (tmp > -1) Rc.delay_time = tmp;
5559          }
5560          break;
5561       case 'E':
5562          if (++Rc.summ_mscale > SK_Eb) Rc.summ_mscale = SK_Kb;
5563          break;
5564       case 'e':
5565          if (++Rc.task_mscale > SK_Pb) Rc.task_mscale = SK_Kb;
5566          break;
5567       case 'f':
5568          fields_utility();
5569          break;
5570       case 'g':
5571          win_select(0);
5572          break;
5573       case 'H':
5574          Thread_mode = !Thread_mode;
5575          if (!CHKw(w, View_STATES))
5576             show_msg(fmtmk(N_fmt(THREADS_show_fmt)
5577                , Thread_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5578          for (i = 0 ; i < GROUPSMAX; i++)
5579             Winstk[i].begtask = Winstk[i].focus_pid = 0;
5580          // force an extra procs refresh to avoid %cpu distortions...
5581          Pseudo_row = PROC_XTRA;
5582          break;
5583       case 'I':
5584          if (Cpu_cnt > 1) {
5585             Rc.mode_irixps = !Rc.mode_irixps;
5586             show_msg(fmtmk(N_fmt(IRIX_curmode_fmt)
5587                , Rc.mode_irixps ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5588          } else
5589             show_msg(N_txt(NOT_smp_cpus_txt));
5590          break;
5591       case 'k':
5592          if (Secure_mode)
5593             show_msg(N_txt(NOT_onsecure_txt));
5594          else {
5595             num = SIGTERM;
5596             def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5597             pid = get_int(fmtmk(N_txt(GET_pid2kill_fmt), def));
5598             if (pid > GET_NUM_ESC) {
5599                char *str;
5600                if (pid == GET_NUM_NOT) pid = def;
5601                str = ioline(fmtmk(N_fmt(GET_sigs_num_fmt), pid, SIGTERM));
5602                if (*str != kbd_ESC) {
5603                   if (*str) num = signal_name_to_number(str);
5604                   if (Frames_signal) break;
5605                   if (0 < num && kill(pid, num))
5606                      show_msg(fmtmk(N_fmt(FAIL_signals_fmt)
5607                         , pid, num, strerror(errno)));
5608                   else if (0 > num) show_msg(N_txt(BAD_signalid_txt));
5609                }
5610             }
5611          }
5612          break;
5613       case 'r':
5614          if (Secure_mode)
5615             show_msg(N_txt(NOT_onsecure_txt));
5616          else {
5617             def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5618             pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5619             if (pid > GET_NUM_ESC) {
5620                if (pid == GET_NUM_NOT) pid = def;
5621                num = get_int(fmtmk(N_fmt(GET_nice_num_fmt), pid));
5622                if (num > GET_NUM_NOT
5623                && setpriority(PRIO_PROCESS, (unsigned)pid, num))
5624                   show_msg(fmtmk(N_fmt(FAIL_re_nice_fmt)
5625                      , pid, num, strerror(errno)));
5626             }
5627          }
5628          break;
5629       case 'X':
5630          num = get_int(fmtmk(N_fmt(XTRA_fixwide_fmt), Rc.fixed_widest));
5631          if (num > GET_NUM_NOT) {
5632             if (num >= 0 && num <= SCREENMAX) Rc.fixed_widest = num;
5633             else Rc.fixed_widest = -1;
5634          }
5635          break;
5636       case 'Y':
5637          if (!Inspect.total)
5638 #ifndef INSP_OFFDEMO
5639             ioline(N_txt(YINSP_noent1_txt));
5640 #else
5641             ioline(N_txt(YINSP_noent2_txt));
5642 #endif
5643          else {
5644             def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5645             pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def));
5646             if (pid > GET_NUM_ESC) {
5647                if (pid == GET_NUM_NOT) pid = def;
5648                if (pid) inspection_utility(pid);
5649             }
5650          }
5651          break;
5652       case 'Z':
5653          wins_colors();
5654          break;
5655       case '0':
5656          Rc.zero_suppress = !Rc.zero_suppress;
5657          break;
5658       case kbd_CtrlE:
5659 #ifndef SCALE_FORMER
5660          Rc.tics_scaled++;
5661          if (Rc.tics_scaled > TICS_AS_LAST)
5662             Rc.tics_scaled = 0;
5663 #endif
5664          break;
5665       case kbd_CtrlG:
5666          bot_item_toggle(EU_CGR, N_fmt(X_BOT_ctlgrp_fmt), BOT_SEP_SLS);
5667          break;
5668       case kbd_CtrlI:
5669          if (BOT_PRESENT) {
5670             ++Bot_indx;
5671             if (Bot_indx > Bot_focus_func(NULL, NULL))
5672                Bot_indx = BOT_UNFOCUS;
5673          }
5674          break;
5675       case kbd_CtrlK:
5676          // with string vectors, the 'separator' may serve a different purpose
5677          bot_item_toggle(eu_CMDLINE_V, N_fmt(X_BOT_cmdlin_fmt), BOT_SEP_SPC);
5678          break;
5679       case kbd_CtrlL:
5680          bot_item_toggle(BOT_MSG_LOG, N_txt(X_BOT_msglog_txt), BOT_SEP_SMI);
5681          break;
5682       case kbd_CtrlN:
5683          // with string vectors, the 'separator' may serve a different purpose
5684          bot_item_toggle(eu_ENVIRON_V, N_fmt(X_BOT_envirn_fmt), BOT_SEP_SPC);
5685          break;
5686       case kbd_CtrlP:
5687          bot_item_toggle(BOT_ITEM_NS, N_fmt(X_BOT_namesp_fmt), BOT_SEP_CMA);
5688          break;
5689       case kbd_CtrlR:
5690          if (Secure_mode)
5691             show_msg(N_txt(NOT_onsecure_txt));
5692          else {
5693             def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5694             pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5695             if (pid > GET_NUM_ESC) {
5696                int fd;
5697                if (pid == GET_NUM_NOT) pid = def;
5698                num = get_int(fmtmk(N_fmt(AGNI_valueof_fmt), pid));
5699                if (num > GET_NUM_NOT) {
5700                   if (num < -20 || num > +19)
5701                      show_msg(N_txt(AGNI_invalid_txt));
5702                   else if (0 > (fd = open(fmtmk("/proc/%d/autogroup", pid), O_WRONLY)))
5703                      show_msg(fmtmk(N_fmt(AGNI_notopen_fmt), strerror(errno)));
5704                   else {
5705                      char buf[TNYBUFSIZ];
5706                      snprintf(buf, sizeof(buf), "%d", num);
5707                      if (0 >= write(fd, buf, strlen(buf)))
5708                         show_msg(fmtmk(N_fmt(AGNI_nowrite_fmt), strerror(errno)));
5709                      close(fd);
5710                   }
5711                }
5712             }
5713          }
5714          break;
5715       case kbd_CtrlU:
5716          bot_item_toggle(EU_SGN, N_fmt(X_BOT_supgrp_fmt), BOT_SEP_CMA);
5717          break;
5718       case kbd_BTAB:
5719          if (BOT_PRESENT) {
5720             --Bot_indx;
5721             num = Bot_focus_func(NULL, NULL);
5722             if (Bot_indx <= BOT_UNFOCUS)
5723                Bot_indx = num + 1;
5724          }
5725          break;
5726       case kbd_ENTER:             // these two have the effect of waking us
5727       case kbd_SPACE:             // from 'pselect', refreshing the display
5728          break;                   // and updating any hot-plugged resources
5729       default:                    // keep gcc happy
5730          break;
5731    }
5732 } // end: keys_global
5733
5734
5735 static void keys_summary (int ch) {
5736    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
5737
5738    if (Restrict_some && ch != 'C') {
5739       show_msg(N_txt(X_RESTRICTED_txt));
5740       return;
5741    }
5742    switch (ch) {
5743       case '!':
5744          if (CHKw(w, View_CPUSUM) || CHKw(w, View_CPUNOD))
5745             show_msg(N_txt(XTRA_modebad_txt));
5746          else {
5747             if (!w->rc.combine_cpus) w->rc.combine_cpus = 1;
5748             else w->rc.combine_cpus *= 2;
5749             if (w->rc.combine_cpus >= Cpu_cnt) w->rc.combine_cpus = 0;
5750             w->rc.core_types = 0;
5751          }
5752          break;
5753       case '1':
5754          if (CHKw(w, View_CPUNOD)) OFFw(w, View_CPUSUM);
5755          else TOGw(w, View_CPUSUM);
5756          OFFw(w, View_CPUNOD);
5757          SETw(w, View_STATES);
5758          w->rc.double_up = 0;
5759          w->rc.core_types = 0;
5760          break;
5761       case '2':
5762          if (!Numa_node_tot)
5763             show_msg(N_txt(NUMA_nodenot_txt));
5764          else {
5765             if (Numa_node_sel < 0) TOGw(w, View_CPUNOD);
5766             if (!CHKw(w, View_CPUNOD)) SETw(w, View_CPUSUM);
5767             SETw(w, View_STATES);
5768             Numa_node_sel = -1;
5769             w->rc.double_up = 0;
5770             w->rc.core_types = 0;
5771          }
5772          break;
5773       case '3':
5774          if (!Numa_node_tot)
5775             show_msg(N_txt(NUMA_nodenot_txt));
5776          else {
5777             int num = get_int(fmtmk(N_fmt(NUMA_nodeget_fmt), Numa_node_tot -1));
5778             if (num > GET_NUM_NOT) {
5779                if (num >= 0 && num < Numa_node_tot) {
5780                   Numa_node_sel = num;
5781                   SETw(w, View_CPUNOD | View_STATES);
5782                   OFFw(w, View_CPUSUM);
5783                   w->rc.double_up = 0;
5784                   w->rc.core_types = 0;
5785                } else
5786                   show_msg(N_txt(NUMA_nodebad_txt));
5787             }
5788          }
5789          break;
5790       case '4':
5791          w->rc.double_up += 1;
5792          if ((w->rc.double_up >= ADJOIN_limit)
5793          || ((w->rc.double_up >= Cpu_cnt)))
5794             w->rc.double_up = 0;
5795          if ((w->rc.double_up > 1)
5796          && (!w->rc.graph_cpus))
5797             w->rc.double_up = 0;
5798          OFFw(w, (View_CPUSUM | View_CPUNOD));
5799          break;
5800 #ifndef CORE_TYPE_NO
5801       case '5':
5802          if (!CHKw(w, View_STATES)
5803          || ((CHKw(w, View_CPUSUM | View_CPUNOD))
5804          || ((w->rc.combine_cpus))))
5805             show_msg(N_txt(XTRA_modebad_txt));
5806          else {
5807             int scanned;
5808             for (scanned = 0; scanned < Cpu_cnt; scanned++)
5809                if (CPU_VAL(stat_COR_TYP, scanned) == E_CORE)
5810                   break;
5811             if (scanned < Cpu_cnt) {
5812                w->rc.core_types += 1;
5813                if (w->rc.core_types > E_CORES_ONLY)
5814                   w->rc.core_types = 0;
5815            } else w->rc.core_types = 0;
5816          }
5817          break;
5818 #endif
5819       case 'C':
5820          VIZTOGw(w, View_SCROLL);
5821          break;
5822       case 'l':
5823          TOGw(w, View_LOADAV);
5824          break;
5825       case 'm':
5826          if (!CHKw(w, View_MEMORY))
5827             SETw(w, View_MEMORY);
5828          else if (++w->rc.graph_mems > 2) {
5829             w->rc.graph_mems = 0;
5830             OFFw(w, View_MEMORY);
5831          }
5832          break;
5833       case 't':
5834          if (!CHKw(w, View_STATES))
5835             SETw(w, View_STATES);
5836          else if (++w->rc.graph_cpus > 2) {
5837             w->rc.graph_cpus = 0;
5838             OFFw(w, View_STATES);
5839          }
5840          if ((w->rc.double_up > 1)
5841          && (!w->rc.graph_cpus))
5842             w->rc.double_up = 0;
5843          break;
5844       default:                    // keep gcc happy
5845          break;
5846    }
5847 } // end: keys_summary
5848
5849
5850 static void keys_task (int ch) {
5851    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
5852
5853    switch (ch) {
5854       case '#':
5855       case 'n':
5856          if (VIZCHKw(w)) {
5857             int num = get_int(fmtmk(N_fmt(GET_max_task_fmt), w->rc.maxtasks));
5858             if (num > GET_NUM_NOT) {
5859                if (-1 < num ) w->rc.maxtasks = num;
5860                else show_msg(N_txt(BAD_max_task_txt));
5861             }
5862          }
5863          break;
5864       case '<':
5865 #ifdef TREE_NORESET
5866          if (CHKw(w, Show_FOREST)) break;
5867 #endif
5868          if (VIZCHKw(w)) {
5869             FLG_t *p = w->procflgs + w->maxpflgs - 1;
5870             while (p > w->procflgs && *p != w->rc.sortindx) --p;
5871             if (*p == w->rc.sortindx) {
5872                --p;
5873 #ifndef USE_X_COLHDR
5874                if (EU_MAXPFLGS < *p) --p;
5875 #endif
5876                if (p >= w->procflgs) {
5877                   w->rc.sortindx = *p;
5878 #ifndef TREE_NORESET
5879                   OFFw(w, Show_FOREST);
5880 #endif
5881                }
5882             }
5883          }
5884          break;
5885       case '>':
5886 #ifdef TREE_NORESET
5887          if (CHKw(w, Show_FOREST)) break;
5888 #endif
5889          if (VIZCHKw(w)) {
5890             FLG_t *p = w->procflgs + w->maxpflgs - 1;
5891             while (p > w->procflgs && *p != w->rc.sortindx) --p;
5892             if (*p == w->rc.sortindx) {
5893                ++p;
5894 #ifndef USE_X_COLHDR
5895                if (EU_MAXPFLGS < *p) ++p;
5896 #endif
5897                if (p < w->procflgs + w->maxpflgs) {
5898                   w->rc.sortindx = *p;
5899 #ifndef TREE_NORESET
5900                   OFFw(w, Show_FOREST);
5901 #endif
5902                }
5903             }
5904          }
5905          break;
5906       case 'b':
5907          TOGw(w, Show_HIBOLD);
5908          capsmk(w);
5909          break;
5910       case 'c':
5911          VIZTOGw(w, Show_CMDLIN);
5912          break;
5913       case 'F':
5914          if (VIZCHKw(w)) {
5915             if (CHKw(w, Show_FOREST)) {
5916                int n = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5917                if (w->focus_pid == n) w->focus_pid = 0;
5918                else w->focus_pid = n;
5919             }
5920          }
5921          break;
5922       case 'i':
5923       {  static WIN_t *w_sav;
5924          static int beg_sav;
5925          if (w_sav != w) { beg_sav = 0; w_sav = w; }
5926          if (CHKw(w, Show_IDLEPS)) { beg_sav = w->begtask; w->begtask = 0; }
5927          else { w->begtask = beg_sav; beg_sav = 0; }
5928       }
5929          VIZTOGw(w, Show_IDLEPS);
5930          break;
5931       case 'J':
5932          VIZTOGw(w, Show_JRNUMS);
5933          break;
5934       case 'j':
5935          VIZTOGw(w, Show_JRSTRS);
5936          break;
5937       case 'R':
5938 #ifdef TREE_NORESET
5939          if (!CHKw(w, Show_FOREST)) VIZTOGw(w, Qsrt_NORMAL);
5940 #else
5941          if (VIZCHKw(w)) {
5942             TOGw(w, Qsrt_NORMAL);
5943             OFFw(w, Show_FOREST);
5944          }
5945 #endif
5946          break;
5947       case 'S':
5948          if (VIZCHKw(w)) {
5949             TOGw(w, Show_CTIMES);
5950             show_msg(fmtmk(N_fmt(TIME_accumed_fmt) , CHKw(w, Show_CTIMES)
5951                ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5952          }
5953          break;
5954       case 'O':
5955       case 'o':
5956       case kbd_CtrlO:
5957          if (VIZCHKw(w)) other_filters(ch);
5958          break;
5959       case 'U':
5960       case 'u':
5961          if (VIZCHKw(w)) {
5962             const char *errmsg, *str = ioline(N_txt(GET_user_ids_txt));
5963             if (*str != kbd_ESC
5964             && (errmsg = user_certify(w, str, ch)))
5965                 show_msg(errmsg);
5966          }
5967          break;
5968       case 'V':
5969          if (VIZCHKw(w)) {
5970             TOGw(w, Show_FOREST);
5971             if (!ENUviz(w, EU_CMD))
5972                show_msg(fmtmk(N_fmt(FOREST_modes_fmt) , CHKw(w, Show_FOREST)
5973                   ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5974             if (!CHKw(w, Show_FOREST)) w->focus_pid = 0;
5975          }
5976          break;
5977       case 'v':
5978          if (VIZCHKw(w)) {
5979             if (CHKw(w, Show_FOREST)) {
5980                int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5981 #ifdef TREE_VPROMPT
5982                int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid));
5983                if (got < GET_NUM_NOT) break;
5984                if (got > GET_NUM_NOT) pid = got;
5985 #endif
5986                for (i = 0; i < Hide_tot; i++) {
5987                   if (Hide_pid[i] == pid || Hide_pid[i] == -pid) {
5988                      Hide_pid[i] = -Hide_pid[i];
5989                      break;
5990                   }
5991                }
5992                if (i == Hide_tot) {
5993                   static int totsav;
5994                   if (Hide_tot >= totsav) {
5995                      totsav += 128;
5996                      Hide_pid = alloc_r(Hide_pid, sizeof(int) * totsav);
5997                   }
5998                   Hide_pid[Hide_tot++] = pid;
5999                } else {
6000                   // if everything's expanded, let's empty the array ...
6001                   for (i = 0; i < Hide_tot; i++)
6002                      if (Hide_pid[i] > 0) break;
6003                   if (i == Hide_tot) Hide_tot = 0;
6004                }
6005             }
6006          }
6007          break;
6008       case 'x':
6009          if (VIZCHKw(w)) {
6010 #ifdef USE_X_COLHDR
6011             TOGw(w, Show_HICOLS);
6012             capsmk(w);
6013 #else
6014             if (ENUviz(w, w->rc.sortindx)) {
6015                TOGw(w, Show_HICOLS);
6016                if (ENUpos(w, w->rc.sortindx) < w->begpflg) {
6017                   if (CHKw(w, Show_HICOLS)) w->begpflg += 2;
6018                   else w->begpflg -= 2;
6019                   if (0 > w->begpflg) w->begpflg = 0;
6020                }
6021                capsmk(w);
6022             }
6023 #endif
6024          }
6025          break;
6026       case 'y':
6027          if (VIZCHKw(w)) {
6028             TOGw(w, Show_HIROWS);
6029             capsmk(w);
6030          }
6031          break;
6032       case 'z':
6033          if (VIZCHKw(w)) {
6034             TOGw(w, Show_COLORS);
6035             capsmk(w);
6036          }
6037          break;
6038       default:                    // keep gcc happy
6039          break;
6040    }
6041 } // end: keys_task
6042
6043
6044 static void keys_window (int ch) {
6045    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
6046
6047    switch (ch) {
6048       case '+':
6049          if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx);
6050          Hide_tot = 0;
6051          break;
6052       case '-':
6053          if (ALTCHKw) TOGw(w, Show_TASKON);
6054          break;
6055       case '=':
6056          win_reset(w);
6057          Hide_tot = 0;
6058          break;
6059       case '_':
6060          if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON);
6061          break;
6062       case '&':
6063       case 'L':
6064          if (VIZCHKw(w)) find_string(ch);
6065          break;
6066       case 'A':
6067          Rc.mode_altscr = !Rc.mode_altscr;
6068          break;
6069       case 'a':
6070       case 'w':
6071          if (ALTCHKw) win_select(ch);
6072          break;
6073       case 'G':
6074          if (ALTCHKw) {
6075             char tmp[SMLBUFSIZ];
6076             STRLCPY(tmp, ioline(fmtmk(N_fmt(NAME_windows_fmt), w->rc.winname)));
6077             if (tmp[0] && tmp[0] != kbd_ESC) win_names(w, tmp);
6078          }
6079          break;
6080       case kbd_UP:
6081          if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(-1)
6082          break;
6083       case kbd_DOWN:
6084          if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(+1)
6085          break;
6086 #ifdef USE_X_COLHDR // ------------------------------------
6087       case kbd_LEFT:
6088 #ifndef SCROLLVAR_NO
6089          if (VIZCHKw(w)) {
6090             if (VARleft(w))
6091                w->varcolbeg -= SCROLLAMT;
6092             else if (0 < w->begpflg)
6093                w->begpflg -= 1;
6094          }
6095 #else
6096          if (VIZCHKw(w)) if (0 < w->begpflg) w->begpflg -= 1;
6097 #endif
6098          break;
6099       case kbd_RIGHT:
6100 #ifndef SCROLLVAR_NO
6101          if (VIZCHKw(w)) {
6102             if (VARright(w)) {
6103                w->varcolbeg += SCROLLAMT;
6104                if (0 > w->varcolbeg) w->varcolbeg = 0;
6105             } else if (w->begpflg + 1 < w->totpflgs)
6106                w->begpflg += 1;
6107          }
6108 #else
6109          if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) w->begpflg += 1;
6110 #endif
6111          break;
6112 #else  // USE_X_COLHDR ------------------------------------
6113       case kbd_LEFT:
6114 #ifndef SCROLLVAR_NO
6115          if (VIZCHKw(w)) {
6116             if (VARleft(w))
6117                w->varcolbeg -= SCROLLAMT;
6118             else if (0 < w->begpflg) {
6119                w->begpflg -= 1;
6120                if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6121             }
6122          }
6123 #else
6124          if (VIZCHKw(w)) if (0 < w->begpflg) {
6125             w->begpflg -= 1;
6126             if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6127          }
6128 #endif
6129          break;
6130       case kbd_RIGHT:
6131 #ifndef SCROLLVAR_NO
6132          if (VIZCHKw(w)) {
6133             if (VARright(w)) {
6134                w->varcolbeg += SCROLLAMT;
6135                if (0 > w->varcolbeg) w->varcolbeg = 0;
6136             } else if (w->begpflg + 1 < w->totpflgs) {
6137                if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6138                   w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6139                else w->begpflg += 1;
6140             }
6141          }
6142 #else
6143          if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) {
6144             if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6145                w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6146             else w->begpflg += 1;
6147          }
6148 #endif
6149          break;
6150 #endif // USE_X_COLHDR ------------------------------------
6151       case kbd_PGUP:
6152          if (VIZCHKw(w)) {
6153             if (CHKw(w, Show_IDLEPS) && 0 < w->begtask) {
6154                mkVIZrowX(-(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6155             }
6156          }
6157          break;
6158       case kbd_PGDN:
6159          if (VIZCHKw(w)) {
6160             if (CHKw(w, Show_IDLEPS) && w->begtask < PIDSmaxt - 1) {
6161                mkVIZrowX(+(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6162             }
6163          }
6164          break;
6165       case kbd_HOME:
6166 #ifndef SCROLLVAR_NO
6167          if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = w->varcolbeg = 0;
6168 #else
6169          if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = 0;
6170 #endif
6171          break;
6172       case kbd_END:
6173          if (VIZCHKw(w)) {
6174             if (CHKw(w, Show_IDLEPS)) {
6175                mkVIZrowX((PIDSmaxt - w->winlines) + 1)
6176                w->begpflg = w->endpflg;
6177 #ifndef SCROLLVAR_NO
6178                w->varcolbeg = 0;
6179 #endif
6180             }
6181          }
6182          break;
6183       default:                    // keep gcc happy
6184          break;
6185    }
6186 } // end: keys_window
6187
6188
6189 static void keys_xtra (int ch) {
6190 // const char *xmsg;
6191    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
6192
6193 #ifdef TREE_NORESET
6194    if (CHKw(w, Show_FOREST)) return;
6195 #else
6196    OFFw(w, Show_FOREST);
6197 #endif
6198    /* these keys represent old-top compatibility --
6199       they're grouped here so that if users could ever be weaned,
6200       we would just whack do_key's key_tab entry and this function... */
6201    switch (ch) {
6202       case 'M':
6203          w->rc.sortindx = EU_MEM;
6204 //       xmsg = "Memory";
6205          break;
6206       case 'N':
6207          w->rc.sortindx = EU_PID;
6208 //       xmsg = "Numerical";
6209          break;
6210       case 'P':
6211          w->rc.sortindx = EU_CPU;
6212 //       xmsg = "CPU";
6213          break;
6214       case 'T':
6215          w->rc.sortindx = EU_TM2;
6216 //       xmsg = "Time";
6217          break;
6218       default:                    // keep gcc happy
6219          break;
6220    }
6221 // some have objected to this message, so we'll just keep silent...
6222 // show_msg(fmtmk("%s sort compatibility key honored", xmsg));
6223 } // end: keys_xtra
6224 \f
6225 /*######  Tertiary summary display support (summary_show helpers)  #######*/
6226
6227         /*
6228          * note how alphabetical order is maintained within carefully chosen |
6229          * function names such as: (s)sum_see, (t)sum_tics, and (u)sum_unify |
6230          * with every name exactly 1 letter more than the preceding function |
6231          * ( surely, this must make us run much more efficiently. amirite? ) | */
6232
6233 struct rx_st {
6234    float pcnt_one, pcnt_two, pcnt_tot;
6235    char graph[MEDBUFSIZ];
6236 };
6237
6238         /*
6239          * A *Helper* function to produce the actual cpu & memory graphs for |
6240          * these functions -- sum_tics (tertiary) and do_memory (secondary). |
6241          * (sorry about the name, but it keeps the above comment commitment) | */
6242 static struct rx_st *sum_rx (struct graph_parms *these) {
6243    static struct {
6244       const char *part1, *part2, *style;
6245    } gtab[] = {
6246       { "%-.*s~7", "%-.*s~8", Graph_bars },
6247       { "%-.*s~4", "%-.*s~6", Graph_blks }
6248    };
6249    static __thread struct rx_st rx;
6250    char buf1[SMLBUFSIZ], buf2[SMLBUFSIZ], buf3[MEDBUFSIZ];
6251    int ix, num1, num2, width;
6252    float scale = 0.0;
6253
6254    if (these->total > 0)
6255       scale = 100.0 / these->total;
6256    rx.pcnt_one = scale * these->part1;
6257    rx.pcnt_two = scale * these->part2;
6258    if (rx.pcnt_one + rx.pcnt_two > 100.0 || rx.pcnt_two < 0)
6259       rx.pcnt_two = 0;
6260    rx.pcnt_tot = rx.pcnt_one + rx.pcnt_two;
6261
6262    num1 = (int)((rx.pcnt_one * these->adjust) + .5);
6263    num2 = (int)((rx.pcnt_two * these->adjust) + .5);
6264    if (num1 + num2 > these->length) {
6265       if (num1 > these->length) num1 = these->length;
6266       num2 = these->length - num1;
6267    }
6268
6269    width = these->length;
6270    buf1[0] = buf2[0] = buf3[0] = '\0';
6271    ix = these->style - 1;     // now relative to zero
6272    if (num1) {
6273       snprintf(buf1, sizeof(buf1), gtab[ix].part1, num1, gtab[ix].style);
6274       width += 2;
6275    }
6276    if (num2) {
6277       snprintf(buf2, sizeof(buf2), gtab[ix].part2, num2, gtab[ix].style);
6278       width += 2;
6279    }
6280    snprintf(buf3, sizeof(buf3), "%s%s", buf1, buf2);
6281    // 'width' has accounted for any show_special directives embedded above
6282    snprintf(rx.graph, sizeof(rx.graph), "[~1%-*.*s] ~1", width, width, buf3);
6283
6284    return &rx;
6285 } // end: sum_rx
6286
6287
6288         /*
6289          * A *Helper* function to show multiple lines of summary information |
6290          * as a single line. We return the number of lines actually printed. | */
6291 static inline int sum_see (const char *str, int nobuf) {
6292    static char row[ROWMAXSIZ];
6293    static int tog;
6294    char *p;
6295
6296    p = scat(row, str);
6297    if (!str[0]) goto flush_it;
6298    if (Curwin->rc.double_up
6299    && (!nobuf)) {
6300       if (++tog <= Curwin->rc.double_up) {
6301          scat(p, Adjoin_sp);
6302          return 0;
6303       }
6304    }
6305 flush_it:
6306    if (!row[0]) return 0;
6307    scat(p, "\n");
6308    show_special(0, row);
6309    row[0] = '\0';
6310    tog = 0;
6311    return 1;
6312 } // end: sum_see
6313
6314
6315         /*
6316          * State display *Helper* function to calculate plus display (maybe) |
6317          * the percentages for a single cpu.  In this way, we'll support the |
6318          * following environments without (hopefully) that usual code bloat: |
6319          *    1) single cpu platforms (no matter the paucity of these types) |
6320          *    2) modest smp boxes with ample room for each cpu's percentages |
6321          *    3) massive smp guys leaving little or no room for that process |
6322          *       display and thus requiring the '1', '4', or '!' cpu toggles |
6323          * ( we return the number of lines printed, as reported by sum_see ) | */
6324 static int sum_tics (struct stat_stack *this, const char *pfx, int nobuf) {
6325   // tailored 'results stack value' extractor macros
6326  #define qSv(E)  STAT_VAL(E, s_int, this, Stat_ctx)
6327  #define rSv(E)  TIC_VAL(E, this)
6328    SIC_t idl_frme, tot_frme;
6329    struct rx_st *rx;
6330    float scale;
6331
6332 #ifndef CORE_TYPE_NO
6333    if (Curwin->rc.core_types == P_CORES_ONLY && qSv(stat_COR_TYP) != P_CORE) return 0;
6334    if (Curwin->rc.core_types == E_CORES_ONLY && qSv(stat_COR_TYP) != E_CORE) return 0;
6335 #endif
6336    idl_frme = rSv(stat_IL);
6337    tot_frme = rSv(stat_SUM_TOT);
6338    if (1 > tot_frme) idl_frme = tot_frme = 1;
6339    scale = 100.0 / (float)tot_frme;
6340
6341    /* display some kinda' cpu state percentages
6342       (who or what is explained by the passed prefix) */
6343    if (Curwin->rc.graph_cpus) {
6344       Graph_cpus->total = tot_frme;
6345       Graph_cpus->part1 = rSv(stat_SUM_USR);
6346       Graph_cpus->part2 = rSv(stat_SUM_SYS);
6347       rx = sum_rx(Graph_cpus);
6348       if (Curwin->rc.double_up > 1)
6349          return sum_see(fmtmk("%s~3%3.0f%s", pfx, rx->pcnt_tot, rx->graph), nobuf);
6350       else {
6351          return sum_see(fmtmk("%s ~3%#5.1f~2/%-#5.1f~3 %3.0f%s"
6352             , pfx, rx->pcnt_one, rx->pcnt_two, rx->pcnt_tot
6353             , rx->graph)
6354             , nobuf);
6355       }
6356    } else {
6357       return sum_see(fmtmk(Cpu_States_fmts, pfx
6358          , (float)rSv(stat_US) * scale, (float)rSv(stat_SY) * scale
6359          , (float)rSv(stat_NI) * scale, (float)idl_frme * scale
6360          , (float)rSv(stat_IO) * scale, (float)rSv(stat_IR) * scale
6361          , (float)rSv(stat_SI) * scale, (float)rSv(stat_ST) * scale), nobuf);
6362    }
6363  #undef qSv
6364  #undef rSv
6365 } // end: sum_tics
6366
6367
6368         /*
6369          * Cpu *Helper* function to combine additional cpu statistics in our |
6370          * efforts to reduce the total number of processors that'll be shown |
6371          * ( we return the number of lines printed, as reported by sum_see ) | */
6372 static int sum_unify (struct stat_stack *this, int nobuf) {
6373   // a tailored 'results stack value' extractor macro
6374  #define rSv(E,T)  STAT_VAL(E, T, this, Stat_ctx)
6375    static struct stat_result stack[MAXTBL(Stat_items)];
6376    static struct stat_stack accum = { &stack[0] };
6377    static int ix, beg;
6378    char pfx[16];
6379    int n;
6380
6381    // entries for stat_ID & stat_NU are unused
6382    stack[stat_US].result.sl_int += rSv(stat_US, sl_int);
6383    stack[stat_SY].result.sl_int += rSv(stat_SY, sl_int);
6384    stack[stat_NI].result.sl_int += rSv(stat_NI, sl_int);
6385    stack[stat_IL].result.sl_int += rSv(stat_IL, sl_int);
6386    stack[stat_IO].result.sl_int += rSv(stat_IO, sl_int);
6387    stack[stat_IR].result.sl_int += rSv(stat_IR, sl_int);
6388    stack[stat_SI].result.sl_int += rSv(stat_SI, sl_int);
6389    stack[stat_ST].result.sl_int += rSv(stat_ST, sl_int);
6390    stack[stat_SUM_USR].result.sl_int += rSv(stat_SUM_USR, sl_int);
6391    stack[stat_SUM_SYS].result.sl_int += rSv(stat_SUM_SYS, sl_int);
6392    stack[stat_SUM_TOT].result.sl_int += rSv(stat_SUM_TOT, sl_int);
6393
6394    if (!ix) beg = rSv(stat_ID, s_int);
6395    if (nobuf || ix >= Curwin->rc.combine_cpus) {
6396       snprintf(pfx, sizeof(pfx), "%-7.7s:", fmtmk("%d-%d", beg, rSv(stat_ID, s_int)));
6397       n = sum_tics(&accum, pfx, nobuf);
6398       memset(&stack, 0, sizeof(stack));
6399       ix = 0;
6400       return n;
6401    }
6402    ++ix;
6403    return 0;
6404  #undef rSv
6405 } // end: sum_unify
6406 \f
6407 /*######  Secondary summary display support (summary_show helpers)  ######*/
6408
6409         /*
6410          * A helper function that displays cpu and/or numa node stuff |
6411          * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6412 static void do_cpus (void) {
6413  #define noMAS (Msg_row + 1 >= SCREEN_ROWS - 1)
6414  #define eachCPU(x)  N_fmt(WORD_eachcpu_fmt), 'u', x
6415    char tmp[MEDBUFSIZ];
6416    int i;
6417
6418    if (CHKw(Curwin, View_CPUNOD)) {
6419       if (Numa_node_sel < 0) {
6420 numa_oops:
6421          /*
6422           * display the 1st /proc/stat line, then the nodes (if room) ... */
6423          Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6424          // display each cpu node's states
6425          for (i = 0; i < Numa_node_tot; i++) {
6426             struct stat_stack *nod_ptr = Stat_reap->numa->stacks[i];
6427             if (NOD_VAL(stat_NU, i) == STAT_NODE_INVALID) continue;
6428             if (noMAS) break;
6429             snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), NOD_VAL(stat_ID, i));
6430             Msg_row += sum_tics(nod_ptr, tmp, 1);
6431          }
6432       } else {
6433          /*
6434           * display the node summary, then the associated cpus (if room) ... */
6435          for (i = 0; i < Numa_node_tot; i++) {
6436             if (Numa_node_sel == NOD_VAL(stat_ID, i)
6437             && (NOD_VAL(stat_NU, i) != STAT_NODE_INVALID)) break;
6438          }
6439          if (i == Numa_node_tot) {
6440             Numa_node_sel = -1;
6441             goto numa_oops;
6442          }
6443          snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), Numa_node_sel);
6444          Msg_row += sum_tics(Stat_reap->numa->stacks[Numa_node_sel], tmp, 1);
6445 #ifdef PRETEND48CPU
6446  #define deLIMIT Stat_reap->cpus->total
6447 #else
6448  #define deLIMIT Cpu_cnt
6449 #endif
6450          for (i = 0; i < deLIMIT; i++) {
6451             if (Numa_node_sel == CPU_VAL(stat_NU, i)) {
6452                if (noMAS) break;
6453                snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i)));
6454                Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, 1);
6455             }
6456          }
6457  #undef deLIMIT
6458       }
6459
6460    } else if (!CHKw(Curwin, View_CPUSUM)) {
6461       /*
6462        * display each cpu's states separately, screen height permitting ... */
6463 #ifdef PRETEND48CPU
6464       int j;
6465       if (Curwin->rc.combine_cpus) {
6466          for (i = 0, j = 0; i < Cpu_cnt; i++) {
6467             Stat_reap->cpus->stacks[j]->head[stat_ID].result.s_int = i;
6468             Msg_row += sum_unify(Stat_reap->cpus->stacks[j], (i+1 >= Cpu_cnt));
6469             if (++j >= Stat_reap->cpus->total) j = 0;
6470             if (noMAS) break;
6471          }
6472       } else {
6473          for (i = 0, j = 0; i < Cpu_cnt; i++) {
6474             snprintf(tmp, sizeof(tmp), eachCPU(i));
6475             Msg_row += sum_tics(Stat_reap->cpus->stacks[j], tmp, (i+1 >= Cpu_cnt));
6476             if (++j >= Stat_reap->cpus->total) j = 0;
6477             if (noMAS) break;
6478          }
6479       }
6480 #else
6481       if (Curwin->rc.combine_cpus) {
6482          for (i = 0; i < Cpu_cnt; i++) {
6483             Msg_row += sum_unify(Stat_reap->cpus->stacks[i], (i+1 >= Cpu_cnt));
6484             if (noMAS) break;
6485          }
6486       } else {
6487          for (i = 0; i < Cpu_cnt; i++) {
6488 #ifndef CORE_TYPE_NO
6489  #ifdef CORE_TYPE_LO
6490             char ctab[] = { 'u', 'e', 'p' };
6491  #else
6492             char ctab[] = { 'u', 'E', 'P' };
6493  #endif
6494             int cid = CPU_VAL(stat_ID, i), typ = CPU_VAL(stat_COR_TYP, i);
6495             char chr = Curwin->rc.core_types ? ctab[typ] : 'u' ;
6496             snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), chr, cid);
6497 #else
6498             snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i)));
6499 #endif
6500             Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, (i+1 >= Cpu_cnt));
6501             if (noMAS) break;
6502          }
6503       }
6504 #endif
6505
6506    } else {
6507       /*
6508        * display just the 1st /proc/stat line ... */
6509       Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6510    }
6511
6512    // coax this guy into flushing any pending cpu stuff ...
6513    Msg_row += sum_see("", 1);
6514  #undef noMAS
6515  #undef eachCPU
6516 } // end: do_cpus
6517
6518
6519         /*
6520          * A helper function which will display the memory/swap stuff |
6521          * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6522 static void do_memory (void) {
6523  #define bfT(n)  buftab[n].buf
6524  #define scT(e)  scaletab[Rc.summ_mscale]. e
6525  #define mkM(x) (float) x / scT(div)
6526  #define prT(b,z) { if (9 < snprintf(b, 10, scT(fmts), z)) b[8] = '+'; }
6527 #ifdef TOG4_MEM_1UP
6528  #define mem2UP 1
6529 #else
6530  #define mem2UP 0
6531 #endif
6532    static struct {
6533       float div;
6534       const char *fmts;
6535       const char *label;
6536    } scaletab[] = {
6537       { 1, "%.0f ", NULL },                             // kibibytes
6538 #ifdef BOOST_MEMORY
6539       { 1024.0, "%#.3f ", NULL },                       // mebibytes
6540       { 1024.0*1024, "%#.3f ", NULL },                  // gibibytes
6541       { 1024.0*1024*1024, "%#.3f ", NULL },             // tebibytes
6542       { 1024.0*1024*1024*1024, "%#.3f ", NULL },        // pebibytes
6543       { 1024.0*1024*1024*1024*1024, "%#.3f ", NULL }    // exbibytes
6544 #else
6545       { 1024.0, "%#.1f ", NULL },                       // mebibytes
6546       { 1024.0*1024, "%#.1f ", NULL },                  // gibibytes
6547       { 1024.0*1024*1024, "%#.1f ", NULL },             // tebibytes
6548       { 1024.0*1024*1024*1024, "%#.1f ", NULL },        // pebibytes
6549       { 1024.0*1024*1024*1024*1024, "%#.1f ", NULL }    // exbibytes
6550 #endif
6551    };
6552    struct { //                                            0123456789
6553       // snprintf contents of each buf (after SK_Kb):    'nnnn.nnn 0'
6554       // & prT macro might replace space at buf[8] with: -------> +
6555       char buf[10]; // MEMORY_lines_fmt provides for 8+1 bytes
6556    } buftab[8];
6557    char row[ROWMINSIZ];
6558    long my_qued, my_misc, my_used;
6559    struct rx_st *rx;
6560
6561    if (!scaletab[0].label) {
6562       scaletab[0].label = N_txt(AMT_kilobyte_txt);
6563       scaletab[1].label = N_txt(AMT_megabyte_txt);
6564       scaletab[2].label = N_txt(AMT_gigabyte_txt);
6565       scaletab[3].label = N_txt(AMT_terabyte_txt);
6566       scaletab[4].label = N_txt(AMT_petabyte_txt);
6567       scaletab[5].label = N_txt(AMT_exxabyte_txt);
6568    }
6569    my_qued = MEM_VAL(mem_BUF) + MEM_VAL(mem_QUE);
6570
6571    if (Curwin->rc.graph_mems) {
6572       my_used = MEM_VAL(mem_TOT) - MEM_VAL(mem_FRE) - my_qued;
6573 #ifdef MEMGRAPH_OLD
6574       my_misc = my_qued;
6575 #else
6576       my_misc = MEM_VAL(mem_TOT) - MEM_VAL(mem_AVL) - my_used;
6577 #endif
6578       Graph_mems->total = MEM_VAL(mem_TOT);
6579       Graph_mems->part1 = my_used;
6580       Graph_mems->part2 = my_misc;
6581       rx = sum_rx(Graph_mems);
6582 #ifdef TOG4_MEM_1UP
6583       prT(bfT(0), mkM(MEM_VAL(mem_TOT)));
6584       snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6585          , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph);
6586 #else
6587       if (Curwin->rc.double_up > 1)
6588          snprintf(row, sizeof(row), "%s %s~3%3.0f%s"
6589             , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, rx->graph);
6590       else {
6591          prT(bfT(0), mkM(MEM_VAL(mem_TOT)));
6592          snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6593             , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph);
6594       }
6595 #endif
6596       Msg_row += sum_see(row, mem2UP);
6597
6598       Graph_mems->total = MEM_VAL(swp_TOT);
6599       Graph_mems->part1 = 0;
6600       Graph_mems->part2 = MEM_VAL(swp_USE);
6601       rx = sum_rx(Graph_mems);
6602 #ifdef TOG4_MEM_1UP
6603       prT(bfT(1), mkM(MEM_VAL(swp_TOT)));
6604       snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6605          , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph);
6606 #else
6607       if (Curwin->rc.double_up > 1)
6608          snprintf(row, sizeof(row), "%s %s~3%3.0f%s"
6609             , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, rx->graph);
6610       else {
6611          prT(bfT(1), mkM(MEM_VAL(swp_TOT)));
6612          snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6613             , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph);
6614       }
6615 #endif
6616       Msg_row += sum_see(row, 1);
6617
6618    } else {
6619       prT(bfT(0), mkM(MEM_VAL(mem_TOT))); prT(bfT(1), mkM(MEM_VAL(mem_FRE)));
6620       prT(bfT(2), mkM(MEM_VAL(mem_USE))); prT(bfT(3), mkM(my_qued));
6621       prT(bfT(4), mkM(MEM_VAL(swp_TOT))); prT(bfT(5), mkM(MEM_VAL(swp_FRE)));
6622       prT(bfT(6), mkM(MEM_VAL(swp_USE))); prT(bfT(7), mkM(MEM_VAL(mem_AVL)));
6623
6624       snprintf(row, sizeof(row), N_unq(MEMORY_line1_fmt)
6625          , scT(label), N_txt(WORD_abv_mem_txt), bfT(0), bfT(1), bfT(2), bfT(3));
6626       Msg_row += sum_see(row, mem2UP);
6627
6628       snprintf(row, sizeof(row), N_unq(MEMORY_line2_fmt)
6629          , scT(label), N_txt(WORD_abv_swp_txt), bfT(4), bfT(5), bfT(6), bfT(7)
6630          , N_txt(WORD_abv_mem_txt));
6631       Msg_row += sum_see(row, 1);
6632    }
6633  #undef bfT
6634  #undef scT
6635  #undef mkM
6636  #undef prT
6637  #undef mem2UP
6638 } // end: do_memory
6639 \f
6640 /*######  Main Screen routines  ##########################################*/
6641
6642         /*
6643          * Process keyboard input during the main loop */
6644 static void do_key (int ch) {
6645    static struct {
6646       void (*func)(int ch);
6647       char keys[SMLBUFSIZ];
6648    } key_tab[] = {
6649       { keys_global,
6650          { '?', 'B', 'd', 'E', 'e', 'f', 'g', 'H', 'h'
6651          , 'I', 'k', 'r', 's', 'X', 'Y', 'Z', '0'
6652          , kbd_CtrlE, kbd_CtrlG, kbd_CtrlI, kbd_CtrlK, kbd_CtrlL
6653          , kbd_CtrlN, kbd_CtrlP, kbd_CtrlR, kbd_CtrlU
6654          , kbd_ENTER, kbd_SPACE, kbd_BTAB, '\0' } },
6655       { keys_summary,
6656  #ifdef CORE_TYPE_NO
6657          { '!', '1', '2', '3', '4', 'C', 'l', 'm', 't', '\0' } },
6658  #else
6659          { '!', '1', '2', '3', '4', '5', 'C', 'l', 'm', 't', '\0' } },
6660  #endif
6661       { keys_task,
6662          { '#', '<', '>', 'b', 'c', 'F', 'i', 'J', 'j', 'n', 'O', 'o'
6663          , 'R', 'S', 'U', 'u', 'V', 'v', 'x', 'y', 'z'
6664          , kbd_CtrlO, '\0' } },
6665       { keys_window,
6666          { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w'
6667          , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN
6668          , kbd_HOME, kbd_END, '\0' } },
6669       { keys_xtra,
6670          { 'M', 'N', 'P', 'T', '\0'} }
6671    };
6672    int i;
6673
6674    Frames_signal = BREAK_off;
6675    switch (ch) {
6676       case 0:                // ignored (always)
6677       case kbd_ESC:          // ignored (sometimes)
6678          goto all_done;
6679       case 'q':              // no return from this guy
6680          bye_bye(NULL);
6681       case 'W':              // no need for rebuilds
6682          write_rcfile();
6683          goto all_done;
6684       default:               // and now, the real work...
6685          // and just in case 'Monpids' is active but matched no processes ...
6686          if (!PIDSmaxt && ch != '=') goto all_done;
6687          for (i = 0; i < MAXTBL(key_tab); ++i)
6688             if (strchr(key_tab[i].keys, ch)) {
6689                key_tab[i].func(ch);
6690                if (Frames_signal == BREAK_off)
6691                   Frames_signal = BREAK_kbd;
6692                /* due to the proliferation of the need for 'mkVIZrow1', |
6693                   aside from 'wins_stage_2' use, we'll now issue it one |
6694                   time here. there will remain several places where the |
6695                   companion 'mkVIZrowX' macro is issued, thus the check |
6696                   for a value already in 'begnext' in this conditional. | */
6697                if (CHKw(Curwin, Show_TASKON) && !mkVIZyes)
6698                   mkVIZrow1
6699                goto all_done;
6700             }
6701    };
6702    /* The Frames_signal above will force a rebuild of column headers.
6703       It's NOT simply lazy programming.  Below are some keys that may
6704       require new column headers and/or new library item enumerators:
6705          'A' - likely
6706          'c' - likely when !Mode_altscr, maybe when Mode_altscr
6707          'F' - likely
6708          'f' - likely
6709          'g' - likely
6710          'H' - likely
6711          'I' - likely
6712          'J' - always
6713          'j' - always
6714          'Z' - likely, if 'Curwin' changed when !Mode_altscr
6715          '-' - likely (restricted to Mode_altscr)
6716          '_' - likely (restricted to Mode_altscr)
6717          '=' - maybe, but only when Mode_altscr
6718          '+' - likely (restricted to Mode_altscr)
6719          PLUS, likely for FOUR of the EIGHT cursor motion keys (scrolled)
6720       ( At this point we have a human being involved and so have all the time )
6721       ( in the world.  We can afford a few extra cpu cycles every now & then! )
6722     */
6723
6724    show_msg(N_txt(UNKNOWN_cmds_txt));
6725 all_done:
6726    putp((Cursor_state = Cap_curs_hide));
6727 } // end: do_key
6728
6729
6730         /*
6731          * In support of a new frame:
6732          *    1) Display uptime and load average (maybe)
6733          *    2) Display task/cpu states (maybe)
6734          *    3) Display memory & swap usage (maybe) */
6735 static void summary_show (void) {
6736  #define isROOM(f,n) (CHKw(Curwin, f) && Msg_row + (n) < SCREEN_ROWS - 1)
6737
6738    if (Restrict_some) {
6739 #ifdef THREADED_TSK
6740       sem_wait(&Semaphore_tasks_end);
6741 #endif
6742       // Display Task States only
6743       if (isROOM(View_STATES, 1)) {
6744          show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6745             , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6746             , PIDSmaxt, Pids_reap->counts->running
6747             , Pids_reap->counts->sleeping + Pids_reap->counts->other
6748             , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6749          Msg_row += 1;
6750       }
6751       return;
6752    }
6753
6754    // Display Uptime and Loadavg
6755    if (isROOM(View_LOADAV, 1)) {
6756       if (!Rc.mode_altscr)
6757          show_special(0, fmtmk(LOADAV_line, Myname, procps_uptime_sprint()));
6758       else
6759          show_special(0, fmtmk(CHKw(Curwin, Show_TASKON)? LOADAV_line_alt : LOADAV_line
6760             , Curwin->grpname, procps_uptime_sprint()));
6761       Msg_row += 1;
6762    } // end: View_LOADAV
6763
6764 #ifdef THREADED_CPU
6765       sem_wait(&Semaphore_cpus_end);
6766 #endif
6767 #ifdef THREADED_TSK
6768       sem_wait(&Semaphore_tasks_end);
6769 #endif
6770    // Display Task and Cpu(s) States
6771    if (isROOM(View_STATES, 2)) {
6772       show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6773          , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6774          , PIDSmaxt, Pids_reap->counts->running
6775          , Pids_reap->counts->sleeping + Pids_reap->counts->other
6776          , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6777       Msg_row += 1;
6778
6779       do_cpus();
6780    }
6781
6782 #ifdef THREADED_MEM
6783    sem_wait(&Semaphore_memory_end);
6784 #endif
6785    // Display Memory and Swap stats
6786    if (isROOM(View_MEMORY, 2)) {
6787       do_memory();
6788    }
6789
6790  #undef isROOM
6791 } // end: summary_show
6792
6793
6794         /*
6795          * Build the information for a single task row and
6796          * display the results or return them to the caller. */
6797 static const char *task_show (const WIN_t *q, int idx) {
6798   // a tailored 'results stack value' extractor macro
6799  #define rSv(E,T)  PID_VAL(E, T, p)
6800 #ifndef SCROLLVAR_NO
6801  #define makeVAR(S)  { const char *pv = S; \
6802     if (!q->varcolbeg) cp = make_str(pv, q->varcolsz, Js, AUTOX_NO); \
6803     else cp = make_str(q->varcolbeg < (int)strlen(pv) ? pv + q->varcolbeg : "", q->varcolsz, Js, AUTOX_NO); }
6804  #define varUTF8(S)  { const char *pv = S; \
6805     if (!q->varcolbeg) cp = make_str_utf8(pv, q->varcolsz, Js, AUTOX_NO); \
6806     else cp = make_str_utf8((q->varcolbeg < ((int)strlen(pv) - utf8_delta(pv))) \
6807     ? pv + utf8_embody(pv, q->varcolbeg) : "", q->varcolsz, Js, AUTOX_NO); }
6808 #else
6809  #define makeVAR(S)  { cp = make_str(S, q->varcolsz, Js, AUTOX_NO); }
6810  #define varUTF8(S)  { cp = make_str_utf8(S, q->varcolsz, Js, AUTOX_NO); }
6811 #endif
6812    struct pids_stack *p = q->ppt[idx];
6813    static char rbuf[ROWMINSIZ];
6814    char *rp;
6815    int x;
6816
6817    /* we use up to three additional 'PIDS_extra' results in our stacks
6818          eu_TREE_HID (s_ch) : where 'x' == collapsed and 'z' == unseen
6819          eu_TREE_LVL (s_int): where a level number is stored (0 - 100)
6820          eu_TREE_ADD (u_int): where children's tics are stored (maybe) */
6821 #ifndef TREE_VWINALL
6822    if (q == Curwin)            // note: the following is NOT indented
6823 #endif
6824    if (CHKw(q, Show_FOREST) && rSv(eu_TREE_HID, s_ch)  == 'z')
6825       return "";
6826
6827    // we must begin a row with a possible window number in mind...
6828    *(rp = rbuf) = '\0';
6829    if (Rc.mode_altscr) rp = scat(rp, " ");
6830
6831    for (x = 0; x < q->maxpflgs; x++) {
6832       const char *cp = NULL;
6833       FLG_t       i = q->procflgs[x];
6834       #define S   Fieldstab[i].scale        // these used to be variables
6835       #define W   Fieldstab[i].width        // but it's much better if we
6836       #define Js  CHKw(q, Show_JRSTRS)      // represent them as #defines
6837       #define Jn  CHKw(q, Show_JRNUMS)      // and only exec code if used
6838
6839    /* except for the XOF/XON pseudo flags the following case labels are grouped
6840       by result type according to capacity (small -> large) and then ordered by
6841       additional processing requirements (as in plain, scaled, decorated, etc.) */
6842
6843       switch (i) {
6844 #ifndef USE_X_COLHDR
6845          // these 2 aren't real procflgs, they're used in column highlighting!
6846          case EU_XOF:
6847          case EU_XON:
6848             cp = NULL;
6849             if (!CHKw(q, NOPRINT_xxx)) {
6850                /* treat running tasks specially - entire row may get highlighted
6851                   so we needn't turn it on and we MUST NOT turn it off */
6852                if (!('R' == rSv(EU_STA, s_ch) && CHKw(q, Show_HIROWS)))
6853                   cp = (EU_XON == i ? q->capclr_rowhigh : q->capclr_rownorm);
6854             }
6855             break;
6856 #endif
6857    /* s_ch, make_chr */
6858          case EU_STA:        // PIDS_STATE
6859             cp = make_chr(rSv(EU_STA, s_ch), W, Js);
6860             break;
6861    /* s_int, make_num with auto width */
6862          case EU_LID:        // PIDS_ID_LOGIN
6863             cp = make_num(rSv(EU_LID, s_int), W, Jn, EU_LID, 0);
6864             break;
6865    /* s_int, make_num without auto width */
6866          case EU_AGI:        // PIDS_AUTOGRP_ID
6867          case EU_CPN:        // PIDS_PROCESSOR
6868          case EU_NMA:        // PIDS_PROCESSOR_NODE
6869          case EU_PGD:        // PIDS_ID_PGRP
6870          case EU_PID:        // PIDS_ID_PID
6871          case EU_PPD:        // PIDS_ID_PPID
6872          case EU_SID:        // PIDS_ID_SESSION
6873          case EU_TGD:        // PIDS_ID_TGID
6874          case EU_THD:        // PIDS_NLWP
6875          case EU_TPG:        // PIDS_ID_TPGID
6876             cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 0);
6877             break;
6878    /* s_int, make_num without auto width, but with zero supression */
6879          case EU_AGN:        // PIDS_AUTOGRP_NICE
6880          case EU_NCE:        // PIDS_NICE
6881          case EU_OOA:        // PIDS_OOM_ADJ
6882          case EU_OOM:        // PIDS_OOM_SCORE
6883             cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 1);
6884             break;
6885    /* s_int, scale_num */
6886          case EU_FV1:        // PIDS_FLT_MAJ_DELTA
6887          case EU_FV2:        // PIDS_FLT_MIN_DELTA
6888             cp = scale_num(rSv(i, s_int), W, Jn);
6889             break;
6890    /* s_int, make_num or make_str */
6891          case EU_PRI:        // PIDS_PRIORITY
6892             if (-99 > rSv(EU_PRI, s_int) || 999 < rSv(EU_PRI, s_int))
6893                cp = make_str("rt", W, Jn, AUTOX_NO);
6894             else
6895                cp = make_num(rSv(EU_PRI, s_int), W, Jn, AUTOX_NO, 0);
6896             break;
6897    /* s_int, scale_pcnt with special handling */
6898          case EU_CPU:        // PIDS_TICS_ALL_DELTA
6899          {  float u = (float)rSv(EU_CPU, u_int);
6900             int n = rSv(EU_THD, s_int);
6901             if (Restrict_some) {
6902                cp = justify_pad("?", W, Jn);
6903                break;
6904             }
6905 #ifndef TREE_VCPUOFF
6906  #ifndef TREE_VWINALL
6907             if (q == Curwin) // note: the following is NOT indented
6908  #endif
6909             if (CHKw(q, Show_FOREST)) u += rSv(eu_TREE_ADD, u_int);
6910             u *= Frame_etscale;
6911             /* technically, eu_TREE_HID is only valid if Show_FOREST is active
6912                but its zeroed out slot will always be present now */
6913             if (rSv(eu_TREE_HID, s_ch) != 'x' && u > 100.0 * n) u = 100.0 * n;
6914 #else
6915             u *= Frame_etscale;
6916             /* process can't use more %cpu than number of threads it has
6917              ( thanks Jaromir Capik <jcapik@redhat.com> ) */
6918             if (u > 100.0 * n) u = 100.0 * n;
6919 #endif
6920             if (u > Cpu_pmax) u = Cpu_pmax;
6921             cp = scale_pcnt(u, W, Jn, 0);
6922          }
6923             break;
6924    /* ull_int, scale_pcnt for 'utilization' */
6925          case EU_CUU:        // PIDS_UTILIZATION
6926          case EU_CUC:        // PIDS_UTILIZATION_C
6927             if (Restrict_some) {
6928                cp = justify_pad("?", W, Jn);
6929                break;
6930             }
6931             cp = scale_pcnt(rSv(i, real), W, Jn, 1);
6932             break;
6933    /* u_int, make_num with auto width */
6934          case EU_GID:        // PIDS_ID_EGID
6935          case EU_UED:        // PIDS_ID_EUID
6936          case EU_URD:        // PIDS_ID_RUID
6937          case EU_USD:        // PIDS_ID_SUID
6938             cp = make_num(rSv(i, u_int), W, Jn, i, 0);
6939             break;
6940    /* ul_int, make_num with auto width and zero supression */
6941          case EU_NS1:        // PIDS_NS_IPC
6942          case EU_NS2:        // PIDS_NS_MNT
6943          case EU_NS3:        // PIDS_NS_NET
6944          case EU_NS4:        // PIDS_NS_PID
6945          case EU_NS5:        // PIDS_NS_USER
6946          case EU_NS6:        // PIDS_NS_UTS
6947          case EU_NS7:        // PIDS_NS_CGROUP
6948          case EU_NS8:        // PIDS_NS_TIME
6949             cp = make_num(rSv(i, ul_int), W, Jn, i, 1);
6950             break;
6951    /* ul_int, scale_mem */
6952          case EU_COD:        // PIDS_MEM_CODE
6953          case EU_DAT:        // PIDS_MEM_DATA
6954          case EU_DRT:        // PIDS_noop, really # pgs, but always 0 since 2.6
6955          case EU_PZA:        // PIDS_SMAP_PSS_ANON
6956          case EU_PZF:        // PIDS_SMAP_PSS_FILE
6957          case EU_PZS:        // PIDS_SMAP_PSS_SHMEM
6958          case EU_PSS:        // PIDS_SMAP_PSS
6959          case EU_RES:        // PIDS_MEM_RES
6960          case EU_RSS:        // PIDS_SMAP_RSS
6961          case EU_RZA:        // PIDS_VM_RSS_ANON
6962          case EU_RZF:        // PIDS_VM_RSS_FILE
6963          case EU_RZL:        // PIDS_VM_RSS_LOCKED
6964          case EU_RZS:        // PIDS_VM_RSS_SHARED
6965          case EU_SHR:        // PIDS_MEM_SHR
6966          case EU_SWP:        // PIDS_VM_SWAP
6967          case EU_USE:        // PIDS_VM_USED
6968          case EU_USS:        // PIDS_SMAP_PRV_TOTAL
6969          case EU_VRT:        // PIDS_MEM_VIRT
6970             cp = scale_mem(S, rSv(i, ul_int), W, Jn);
6971             break;
6972    /* ul_int, scale_num */
6973          case EU_FL1:        // PIDS_FLT_MAJ
6974          case EU_FL2:        // PIDS_FLT_MIN
6975          case EU_IRB:        // PIDS_IO_READ_BYTES
6976          case EU_IRO:        // PIDS_IO_READ_OPS
6977          case EU_IWB:        // PIDS_IO_WRITE_BYTES
6978          case EU_IWO:        // PIDS_IO_WRITE_OPS
6979             cp = scale_num(rSv(i, ul_int), W, Jn);
6980             break;
6981    /* ul_int, scale_pcnt */
6982          case EU_MEM:        // derive from PIDS_MEM_RES
6983             if (Restrict_some) {
6984                cp = justify_pad("?", W, Jn);
6985                break;
6986             }
6987             cp = scale_pcnt((float)rSv(EU_MEM, ul_int) * 100 / MEM_VAL(mem_TOT), W, Jn, 0);
6988             break;
6989    /* ul_int, make_str with special handling */
6990          case EU_FLG:        // PIDS_FLAGS
6991             cp = make_str(hex_make(rSv(EU_FLG, ul_int), 1), W, Js, AUTOX_NO);
6992             break;
6993    /* ull_int, scale_tics (try 'minutes:seconds.hundredths') */
6994          case EU_TM2:        // PIDS_TICS_ALL
6995          case EU_TME:        // PIDS_TICS_ALL
6996          {  TIC_t t;
6997             if (CHKw(q, Show_CTIMES)) t = rSv(eu_TICS_ALL_C, ull_int);
6998             else t = rSv(i, ull_int);
6999             cp = scale_tics(t, W, Jn, TICS_AS_SECS);
7000          }
7001             break;
7002    /* ull_int, scale_tics (try 'minutes:seconds') */
7003          case EU_TM3:        // PIDS_TICS_BEGAN
7004             cp = scale_tics(rSv(EU_TM3, ull_int), W, Jn, TICS_AS_MINS);
7005             break;
7006    /* real, scale_tics (try 'hour,minutes') */
7007          case EU_TM4:        // PIDS_TIME_ELAPSED
7008             cp = scale_tics(rSv(EU_TM4, real) * Hertz, W, Jn, TICS_AS_HOUR);
7009             break;
7010    /* str, make_str (all AUTOX yes) */
7011          case EU_LXC:        // PIDS_LXCNAME
7012          case EU_TTY:        // PIDS_TTY_NAME
7013          case EU_WCH:        // PIDS_WCHAN_NAME
7014             cp = make_str(rSv(i, str), W, Js, i);
7015             break;
7016    /* str, make_str_utf8 (all AUTOX yes) */
7017          case EU_GRP:        // PIDS_ID_EGROUP
7018          case EU_UEN:        // PIDS_ID_EUSER
7019          case EU_URN:        // PIDS_ID_RUSER
7020          case EU_USN:        // PIDS_ID_SUSER
7021             cp = make_str_utf8(rSv(i, str), W, Js, i);
7022             break;
7023    /* str, make_str_utf8 with varialbe width */
7024          case EU_CGN:        // PIDS_CGNAME
7025          case EU_CGR:        // PIDS_CGROUP
7026          case EU_ENV:        // PIDS_ENVIRON
7027          case EU_EXE:        // PIDS_EXE
7028          case EU_SGN:        // PIDS_SUPGROUPS
7029             varUTF8(rSv(i, str))
7030             break;
7031    /* str, make_str with varialbe width */
7032          case EU_SGD:        // PIDS_SUPGIDS
7033             makeVAR(rSv(EU_SGD, str))
7034             break;
7035    /* str, make_str with varialbe width + additional decoration */
7036          case EU_CMD:        // PIDS_CMD or PIDS_CMDLINE
7037             varUTF8(forest_display(q, idx))
7038             break;
7039          default:            // keep gcc happy
7040             continue;
7041       } // end: switch 'procflag'
7042
7043       if (cp) {
7044          if (q->osel_tot && !osel_matched(q, i, cp)) return "";
7045          rp = scat(rp, cp);
7046       }
7047       #undef S
7048       #undef W
7049       #undef Js
7050       #undef Jn
7051    } // end: for 'maxpflgs'
7052
7053    if (!CHKw(q, NOPRINT_xxx)) {
7054       const char *cap = ((CHKw(q, Show_HIROWS) && 'R' == rSv(EU_STA, s_ch)))
7055          ? q->capclr_rowhigh : q->capclr_rownorm;
7056       char *row = rbuf;
7057       int ofs;
7058       /* since we can't predict what the search string will be and,
7059          considering what a single space search request would do to
7060          potential buffer needs, when any matches are found we skip
7061          normal output routing and send all of the results directly
7062          to the terminal (and we sound asthmatic: poof, putt, puff) */
7063       if (-1 < (ofs = find_ofs(q, row))) {
7064          POOF("\n", cap);
7065          do {
7066             row[ofs] = '\0';
7067             PUTT("%s%s%s%s", row, q->capclr_hdr, q->findstr, cap);
7068             row += (ofs + q->findlen);
7069             ofs = find_ofs(q, row);
7070          } while (-1 < ofs);
7071          PUTT("%s%s", row, Caps_endline);
7072          // with a corrupted rbuf, ensure row is 'counted' by window_show
7073          rbuf[0] = '!';
7074       } else
7075          PUFF("\n%s%s%s", cap, row, Caps_endline);
7076    }
7077    return rbuf;
7078  #undef rSv
7079  #undef makeVAR
7080  #undef varUTF8
7081 } // end: task_show
7082
7083
7084         /*
7085          * A window_show *Helper* function ensuring that a window 'begtask' |
7086          * represents a visible process (not any hidden/filtered-out task). |
7087          * In reality this function is called exclusively for the 'current' |
7088          * window and only after available user keystroke(s) are processed. |
7089          * Note: it's entirely possible there are NO visible tasks to show! | */
7090 static void window_hlp (void) {
7091    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
7092    int i, reversed;
7093    int beg = w->focus_pid ? w->focus_beg : 0;
7094    int end = w->focus_pid ? w->focus_end : PIDSmaxt;
7095
7096    SETw(w, NOPRINT_xxx);
7097    w->begtask += w->begnext;
7098    // next 'if' will force a forward scan ...
7099    if (w->begtask <= beg) { w->begtask = beg; w->begnext = +1; }
7100    else if (w->begtask >= end) w->begtask = end - 1;
7101
7102    reversed = 0;
7103    // potentially scroll forward ...
7104    if (w->begnext > 0) {
7105 fwd_redux:
7106       for (i = w->begtask; i < end; i++) {
7107          if (wins_usrselect(w, i)
7108          && (*task_show(w, i)))
7109             break;
7110       }
7111       if (i < end) {
7112          w->begtask = i;
7113          goto wrap_up;
7114       }
7115       // no luck forward, so let's try backward
7116       w->begtask = end - 1;
7117    }
7118
7119    // potentially scroll backward ...
7120    for (i = w->begtask; i > beg; i--) {
7121       if (wins_usrselect(w, i)
7122       && (*task_show(w, i)))
7123          break;
7124    }
7125    w->begtask = i;
7126
7127    // reached the top, but maybe this guy ain't visible
7128    if (w->begtask == beg && !reversed) {
7129       if (!(wins_usrselect(w, beg))
7130       || (!(*task_show(w, beg)))) {
7131          reversed = 1;
7132          goto fwd_redux;
7133       }
7134    }
7135
7136 wrap_up:
7137    mkVIZoff(w)
7138    OFFw(w, NOPRINT_xxx);
7139 } // end: window_hlp
7140
7141
7142         /*
7143          * Squeeze as many tasks as we can into a single window,
7144          * after sorting the passed proc table. */
7145 static int window_show (WIN_t *q, int wmax) {
7146  #define sORDER  CHKw(q, Qsrt_NORMAL) ? PIDS_SORT_DESCEND : PIDS_SORT_ASCEND
7147  /* the isBUSY macro determines if a task is 'active' --
7148     it returns true if some cpu was used since the last sample.
7149     ( actual 'running' tasks will be a subset of those selected ) */
7150  #define isBUSY(x)   (0 < PID_VAL(EU_CPU, u_int, (x)))
7151  #define winMIN(a,b) (((a) < (b)) ? (a) : (b))
7152    int i, lwin, numtasks;
7153
7154    // Display Column Headings -- and distract 'em while we sort (maybe)
7155    PUFF("\n%s%s%s", q->capclr_hdr, q->columnhdr, Caps_endline);
7156    // and just in case 'Monpids' is active but matched no processes ...
7157    if (!PIDSmaxt) return 1;                         // 1 for the column header
7158
7159    if (CHKw(q, Show_FOREST)) {
7160       forest_begin(q);
7161       if (q->focus_pid) forest_config(q);
7162    } else {
7163       enum pids_item item = Fieldstab[q->rc.sortindx].item;
7164       if (item == PIDS_CMD && CHKw(q, Show_CMDLIN))
7165          item = PIDS_CMDLINE;
7166       else if (item == PIDS_TICS_ALL && CHKw(q, Show_CTIMES))
7167          item = PIDS_TICS_ALL_C;
7168       if (!(procps_pids_sort(Pids_ctx, q->ppt , PIDSmaxt, item, sORDER)))
7169          error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
7170    }
7171
7172    if (mkVIZyes) window_hlp();
7173    else OFFw(q, NOPRINT_xxx);
7174
7175    i = q->begtask;
7176    lwin = 1;                                        // 1 for the column header
7177    wmax = winMIN(wmax, q->winlines + 1);            // ditto for winlines, too
7178    numtasks = q->focus_pid ? winMIN(q->focus_end, PIDSmaxt) : PIDSmaxt;
7179
7180    /* the least likely scenario is also the most costly, so we'll try to avoid
7181       checking some stuff with each iteration and check it just once... */
7182    if (CHKw(q, Show_IDLEPS) && !q->usrseltyp)
7183       while (i < numtasks && lwin < wmax) {
7184          if (*task_show(q, i++))
7185             ++lwin;
7186       }
7187    else
7188       while (i < numtasks && lwin < wmax) {
7189          if ((CHKw(q, Show_IDLEPS) || isBUSY(q->ppt[i]))
7190          && wins_usrselect(q, i)
7191          && *task_show(q, i))
7192             ++lwin;
7193          ++i;
7194       }
7195
7196    return lwin;
7197  #undef sORDER
7198  #undef isBUSY
7199  #undef winMIN
7200 } // end: window_show
7201 \f
7202 /*######  Entry point plus two  ##########################################*/
7203
7204         /*
7205          * This guy's just a *Helper* function who apportions the
7206          * remaining amount of screen real estate under multiple windows */
7207 static void frame_hlp (int wix, int max) {
7208    int i, size, wins;
7209
7210    // calc remaining number of visible windows
7211    for (i = wix, wins = 0; i < GROUPSMAX; i++)
7212       if (CHKw(&Winstk[i], Show_TASKON))
7213          ++wins;
7214
7215    if (!wins) wins = 1;
7216    // deduct 1 line/window for the columns heading
7217    size = (max - wins) / wins;
7218
7219    /* for subject window, set WIN_t winlines to either the user's
7220       maxtask (1st choice) or our 'foxized' size calculation
7221       (foxized  adj. -  'fair and balanced') */
7222    Winstk[wix].winlines =
7223       Winstk[wix].rc.maxtasks ? Winstk[wix].rc.maxtasks : size;
7224 } // end: frame_hlp
7225
7226
7227         /*
7228          * Initiate the Frame Display Update cycle at someone's whim!
7229          * This routine doesn't do much, mostly he just calls others.
7230          *
7231          * (Whoa, wait a minute, we DO caretake those row guys, plus)
7232          * (we CALCULATE that IMPORTANT Max_lines thingy so that the)
7233          * (*subordinate* functions invoked know WHEN the user's had)
7234          * (ENOUGH already.  And at Frame End, it SHOULD be apparent)
7235          * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring)
7236          * (that those auto-sized columns are addressed, know what I)
7237          * (mean?  Huh, "doesn't DO MUCH"!  Never, EVER think or say)
7238          * (THAT about THIS function again, Ok?  Good that's better.)
7239          *
7240          * (ps. we ARE the UNEQUALED justification KING of COMMENTS!)
7241          * (No, I don't mean significance/relevance, only alignment.)
7242          */
7243 static void frame_make (void) {
7244    WIN_t *w = Curwin;             // avoid gcc bloat with a local copy
7245    int i, scrlins;
7246
7247    // check auto-sized width increases from the last iteration...
7248    if (AUTOX_MODE && Autox_found)
7249       widths_resize();
7250
7251    /* deal with potential signal(s) since the last time around
7252       plus any input which may change 'tasks_refresh' needs... */
7253    if (Frames_signal) {
7254       if (Frames_signal == BREAK_sig
7255       || (Frames_signal == BREAK_screen))
7256          BOT_TOSS;
7257       zap_fieldstab();
7258    }
7259
7260 #ifdef THREADED_TSK
7261    sem_post(&Semaphore_tasks_beg);
7262 #else
7263    tasks_refresh(NULL);
7264 #endif
7265
7266    if (!Restrict_some) {
7267 #ifdef THREADED_CPU
7268       sem_post(&Semaphore_cpus_beg);
7269 #else
7270       cpus_refresh(NULL);
7271 #endif
7272 #ifdef THREADED_MEM
7273       sem_post(&Semaphore_memory_beg);
7274 #else
7275       memory_refresh(NULL);
7276 #endif
7277    }
7278
7279    // whoa either first time or thread/task mode change, (re)prime the pump...
7280    if (Pseudo_row == PROC_XTRA) {
7281       usleep(LIB_USLEEP);
7282 #ifdef THREADED_TSK
7283       sem_wait(&Semaphore_tasks_end);
7284       sem_post(&Semaphore_tasks_beg);
7285 #else
7286       tasks_refresh(NULL);
7287 #endif
7288       putp(Cap_clr_scr);
7289    } else
7290       putp(Batch ? "\n\n" : Cap_home);
7291
7292    Tree_idx = Pseudo_row = Msg_row = scrlins = 0;
7293    summary_show();
7294    Max_lines = (SCREEN_ROWS - Msg_row) - 1;
7295
7296    // we're now on Msg_row so clear out any residual messages ...
7297    putp(Cap_clr_eol);
7298
7299    if (!Rc.mode_altscr) {
7300       // only 1 window to show so, piece o' cake
7301       w->winlines = w->rc.maxtasks ? w->rc.maxtasks : Max_lines;
7302       scrlins = window_show(w, Max_lines);
7303    } else {
7304       // maybe NO window is visible but assume, pieces o' cakes
7305       for (i = 0 ; i < GROUPSMAX; i++) {
7306          if (CHKw(&Winstk[i], Show_TASKON)) {
7307             frame_hlp(i, Max_lines - scrlins);
7308             scrlins += window_show(&Winstk[i], Max_lines - scrlins);
7309          }
7310          if (Max_lines <= scrlins) break;
7311       }
7312    }
7313
7314    /* clear to end-of-screen - critical if last window is 'idleps off'
7315       (main loop must iterate such that we're always called before sleep) */
7316    if (!Batch && scrlins < Max_lines) {
7317       if (!BOT_PRESENT)
7318          putp(Cap_nl_clreos);
7319       else {
7320          for (i = scrlins + Msg_row + 1; i < SCREEN_ROWS; i++) {
7321             putp(tg2(0, i));
7322             putp(Cap_clr_eol);
7323          }
7324       }
7325       PSU_CLREOS(Pseudo_row);
7326    }
7327
7328    if (CHKw(w, View_SCROLL) && VIZISw(Curwin)) show_scroll();
7329    if (Bot_show_func) Bot_show_func();
7330    fflush(stdout);
7331
7332    /* we'll deem any terminal not supporting tgoto as dumb and disable
7333       the normal non-interactive output optimization... */
7334    if (!Cap_can_goto) PSU_CLREOS(0);
7335 } // end: frame_make
7336
7337
7338         /*
7339          * duh... */
7340 int main (int argc, char *argv[]) {
7341    before(*argv);
7342                                         //                 +-------------+
7343    wins_stage_1();                      //                 top (sic) slice
7344    configs_reads();                     //                 > spread etc, <
7345    parse_args(argc, argv);              //                 > onions etc, <
7346    signals_set();                       //                 > lean stuff, <
7347    whack_terminal();                    //                 > more stuff. <
7348    wins_stage_2();                      //                 as bottom slice
7349                                         //                 +-------------+
7350
7351    for (;;) {
7352       struct timespec ts;
7353
7354       frame_make();
7355
7356       if (0 < Loops) --Loops;
7357       if (!Loops) bye_bye(NULL);
7358
7359       ts.tv_sec = Rc.delay_time;
7360       ts.tv_nsec = (Rc.delay_time - (int)Rc.delay_time) * 1000000000;
7361
7362       if (Batch)
7363          pselect(0, NULL, NULL, NULL, &ts, NULL);
7364       else {
7365          if (ioa(&ts))
7366             do_key(iokey(IOKEY_ONCE));
7367       }
7368            /* note: that above ioa routine exists to consolidate all logic
7369                     which is susceptible to signal interrupt and must then
7370                     produce a screen refresh. in this main loop frame_make
7371                     assumes responsibility for such refreshes. other logic
7372                     in contact with users must deal more obliquely with an
7373                     interrupt/refresh (hint: Frames_signal + return code)!
7374
7375                     (everything is perfectly justified plus right margins)
7376                     (are completely filled, but of course it must be luck)
7377             */
7378    }
7379    return 0;
7380 } // end: main