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