1 /* top.c - Source file: show Linux processes */
3 * Copyright © 2002-2023 Jim Warner <james.warner@comcast.net
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.
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>
28 #include <semaphore.h>
34 #include <term.h> // foul sob, defines all sorts of stuff...
43 #include <sys/ioctl.h>
44 #include <sys/resource.h>
45 #include <sys/select.h> // also available via <sys/types.h>
48 #include <sys/types.h> // also available via <stdlib.h>
50 #include "fileutils.h"
62 /*###### Miscellaneous global stuff ####################################*/
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
68 Tty_tweaked, // for interactive 'line' input
70 Tty_raw; // for unsolicited input
71 static int Ttychanged = 0;
73 /* Last established cursor state/shape */
74 static const char *Cursor_state = "";
76 /* Program name used in error messages and local 'rc' file name */
79 /* Our constant sigset, so we need initialize it but once */
80 static sigset_t Sigwinch_set;
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;
88 /* SMP, Irix/Solaris mode, Linux 2.5.xx support (and beyond) */
91 static float Cpu_pmax;
92 static const char *Cpu_States_fmts;
94 /* Specific process id monitoring support */
95 static int Monpids [MONPIDMAX+1] = { 0 };
96 static int Monpidsidx = 0;
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;
105 /* These 'SCREEN_ROWS', 'BOT_ and 'Bot_' guys are used
106 in managing the special separate bottom 'window' ... */
107 #define SCREEN_ROWS ( Screen_rows - Bot_rsvd )
108 #define BOT_PRESENT ( Bot_what != 0 )
109 #define BOT_ITEMMAX 10 // Bot_item array's max size
110 #define BOT_MSGSMAX 10 // total entries for Msg_tab
111 #define BOT_UNFOCUS -1 // tab focus not established
112 // negative 'item' values won't be seen by build_headers() ...
113 #define BOT_DELIMIT -1 // fencepost with item array
114 #define BOT_ITEM_NS -2 // data for namespaces req'd
115 #define BOT_MSG_LOG -3 // show the most recent msgs
116 // next 4 are used when toggling window contents
117 #define BOT_SEP_CMA ','
118 #define BOT_SEP_SLS '/'
119 #define BOT_SEP_SMI ';'
120 #define BOT_SEP_SPC ' '
121 // 1 for horizontal separator
123 #define BOT_KEEP Bot_show_func = NULL
124 #define BOT_TOSS do { Bot_show_func = NULL; Bot_item[0] = BOT_DELIMIT; \
125 Bot_task = Bot_rsvd = Bot_what = 0; \
126 Bot_indx = BOT_UNFOCUS; \
131 Bot_indx = BOT_UNFOCUS,
132 Bot_item[BOT_ITEMMAX] = { BOT_DELIMIT };
135 Bot_buf[BOTBUFSIZ]; // the 'environ' can be huge
136 typedef int(*BOT_f)(const void *, const void *);
137 static BOT_f Bot_focus_func;
138 static void(*Bot_show_func)(void);
140 /* This is really the number of lines needed to display the summary
141 information (0 - nn), but is used as the relative row where we
142 stick the cursor between frames. */
145 /* Global/Non-windows mode stuff that is NOT persistent */
146 static int Batch = 0, // batch mode, collect no input, dumb output
147 Loops = -1, // number of iterations, -1 loops forever
148 Secure_mode = 0, // set if some functionality restricted
149 Width_mode = 0, // set w/ 'w' - potential output override
150 Thread_mode = 0; // set w/ 'H' - show threads vs. tasks
152 /* Unchangeable cap's stuff built just once (if at all) and
153 thus NOT saved in a WIN_t's RCW_t. To accommodate 'Batch'
154 mode, they begin life as empty strings so the overlying
155 logic need not change ! */
156 static char Cap_clr_eol [CAPBUFSIZ] = "", // global and/or static vars
157 Cap_nl_clreos [CAPBUFSIZ] = "", // are initialized to zeros!
158 Cap_clr_scr [CAPBUFSIZ] = "", // the assignments used here
159 Cap_curs_norm [CAPBUFSIZ] = "", // cost nothing but DO serve
160 Cap_curs_huge [CAPBUFSIZ] = "", // to remind people of those
161 Cap_curs_hide [CAPBUFSIZ] = "", // batch requirements!
162 Cap_clr_eos [CAPBUFSIZ] = "",
163 Cap_home [CAPBUFSIZ] = "",
164 Cap_norm [CAPBUFSIZ] = "",
165 Cap_reverse [CAPBUFSIZ] = "",
166 Caps_off [CAPBUFSIZ] = "",
167 Caps_endline [SMLBUFSIZ] = "";
169 static char Cap_rmam [CAPBUFSIZ] = "",
170 Cap_smam [CAPBUFSIZ] = "";
171 /* set to 1 if writing to the last column would be troublesome
172 (we don't distinguish the lowermost row from the other rows) */
173 static int Cap_avoid_eol = 0;
175 static int Cap_can_goto = 0;
177 /* Some optimization stuff, to reduce output demands...
178 The Pseudo_ guys are managed by adj_geometry and frame_make. They
179 are exploited in a macro and represent 90% of our optimization.
180 The Stdout_buf is transparent to our code and regardless of whose
181 buffer is used, stdout is flushed at frame end or if interactive. */
182 static char *Pseudo_screen;
183 static int Pseudo_row = PROC_XTRA;
184 static size_t Pseudo_size;
186 // less than stdout's normal buffer but with luck mostly '\n' anyway
187 static char Stdout_buf[2048];
190 /* Our four WIN_t's, and which of those is considered the 'current'
191 window (ie. which window is associated with any summ info displayed
192 and to which window commands are directed) */
193 static WIN_t Winstk [GROUPSMAX];
194 static WIN_t *Curwin;
196 /* Frame oriented stuff that can't remain local to any 1 function
197 and/or that would be too cumbersome managed as parms,
198 and/or that are simply more efficiently handled as globals
199 [ 'Frames_...' (plural) stuff persists beyond 1 frame ]
200 [ or are used in response to async signals received ! ] */
201 static volatile int Frames_signal; // time to rebuild all column headers
202 static float Frame_etscale; // so we can '*' vs. '/' WHEN 'pcpu'
204 /* Support for automatically sized fixed-width column expansions.
205 * (hopefully, the macros help clarify/document our new 'feature') */
206 static int Autox_array [EU_MAXPFLGS],
208 #define AUTOX_NO EU_MAXPFLGS
209 #define AUTOX_COL(f) if (EU_MAXPFLGS > f && f >= 0) Autox_array[f] = Autox_found = 1
210 #define AUTOX_MODE (0 > Rc.fixed_widest)
212 /* Support for scale_mem and scale_num (to avoid duplication. */
213 #ifdef CASEUP_SUFIX // nls_maybe
214 static char Scaled_sfxtab[] = { 'K', 'M', 'G', 'T', 'P', 'E', 0 };
216 static char Scaled_sfxtab[] = { 'k', 'm', 'g', 't', 'p', 'e', 0 };
219 /* Support for NUMA Node display plus node expansion and targeting */
221 static int Stderr_save = -1;
223 static int Numa_node_tot;
224 static int Numa_node_sel = -1;
226 /* Support for Graphing of the View_STATES ('t') and View_MEMORY ('m')
227 commands -- which are now both 4-way toggles */
228 #define GRAPH_length_max 100 // the actual bars or blocks
229 #define GRAPH_length_min 10 // the actual bars or blocks
230 #define GRAPH_prefix_std 25 // '.......: 100.0/100.0 100['
231 #define GRAPH_prefix_abv 12 // '.......:100['
232 #define GRAPH_suffix 2 // '] ' (bracket + trailing space)
233 // first 3 more static (adj_geometry), last 3 volatile (sum_tics/do_memory)
235 float adjust; // bars/blocks scaling factor
236 int length; // scaled length (<= GRAPH_length_max)
237 int style; // rc.graph_cpus or rc.graph_mems
238 long total, part1, part2; // elements to be graphed
240 static struct graph_parms *Graph_cpus, *Graph_mems;
241 static const char Graph_blks[] = " ";
242 static const char Graph_bars[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
244 /* Support for 'Other Filters' in the configuration file */
245 static const char Osel_delim_1_txt[] = "begin: saved other filter data -------------------\n";
246 static const char Osel_delim_2_txt[] = "end : saved other filter data -------------------\n";
247 static const char Osel_window_fmts[] = "window #%d, osel_tot=%d\n";
248 #define OSEL_FILTER "filter="
249 static const char Osel_filterO_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%s\n";
250 static const char Osel_filterI_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%*s\n";
252 /* Support for adjoining display (if terminal is wide enough) */
254 static char Adjoin_sp[] = " ";
255 #define ADJOIN_space (sizeof(Adjoin_sp) - 1) // 1 for null
258 static char Adjoin_sp[] = "~1 ~6 ";
260 static char Adjoin_sp[] = " ~6 ~1";
262 #define ADJOIN_space (sizeof(Adjoin_sp) - 5) // 1 for null + 4 unprintable
264 #define ADJOIN_limit 8
266 /* Support for the new library API -- acquired (if necessary)
267 at program startup and referenced throughout our lifetime. */
269 * --- <proc/pids.h> -------------------------------------------------- */
270 static struct pids_info *Pids_ctx;
271 static int Pids_itms_tot; // same as MAXTBL(Fieldstab)
272 static enum pids_item *Pids_itms; // allocated as MAXTBL(Fieldstab)
273 static struct pids_fetch *Pids_reap; // for reap or select
274 #define PIDSmaxt Pids_reap->counts->total // just a little less wordy
275 // pid stack results extractor macro, where e=our EU enum, t=type, s=stack
276 // ( we'll exploit that <proc/pids.h> provided macro as much as possible )
277 // ( but many functions use their own unique tailored version for access )
278 #define PID_VAL(e,t,s) PIDS_VAL(e, t, s, Pids_ctx)
280 * --- <proc/stat.h> -------------------------------------------------- */
281 static struct stat_info *Stat_ctx;
282 static struct stat_reaped *Stat_reap;
283 static enum stat_item Stat_items[] = {
284 STAT_TIC_ID, STAT_TIC_NUMA_NODE,
285 STAT_TIC_DELTA_USER, STAT_TIC_DELTA_SYSTEM,
286 STAT_TIC_DELTA_NICE, STAT_TIC_DELTA_IDLE,
287 STAT_TIC_DELTA_IOWAIT, STAT_TIC_DELTA_IRQ,
288 STAT_TIC_DELTA_SOFTIRQ, STAT_TIC_DELTA_STOLEN,
289 STAT_TIC_SUM_DELTA_USER, STAT_TIC_SUM_DELTA_SYSTEM,
291 STAT_TIC_SUM_DELTA_TOTAL };
293 STAT_TIC_SUM_DELTA_TOTAL, STAT_TIC_TYPE_CORE };
301 stat_SUM_USR, stat_SUM_SYS,
305 stat_SUM_TOT, stat_COR_TYP };
307 // cpu/node stack results extractor macros, where e=rel enum, x=index
308 #define CPU_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->cpus->stacks[x], Stat_ctx)
309 #define NOD_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->numa->stacks[x], Stat_ctx)
310 #define TIC_VAL(e,s) STAT_VAL(e, sl_int, s, Stat_ctx)
311 #define E_CORE 1 // values for stat_COR_TYP itself
312 #define P_CORE 2 // ( 0 = unsure/unknown )
313 #define P_CORES_ONLY 2 // values of rc.core_types toggle, for filtering
314 #define E_CORES_ONLY 3 // ( 0 = Cpu shown, 1 = both CpP & CpE shown )
316 * --- <proc/meminfo.h> ----------------------------------------------- */
317 static struct meminfo_info *Mem_ctx;
318 static struct meminfo_stack *Mem_stack;
319 static enum meminfo_item Mem_items[] = {
320 MEMINFO_MEM_FREE, MEMINFO_MEM_USED, MEMINFO_MEM_TOTAL,
321 MEMINFO_MEM_CACHED_ALL, MEMINFO_MEM_BUFFERS, MEMINFO_MEM_AVAILABLE,
322 MEMINFO_SWAP_TOTAL, MEMINFO_SWAP_FREE, MEMINFO_SWAP_USED };
324 mem_FRE, mem_USE, mem_TOT,
325 mem_QUE, mem_BUF, mem_AVL,
326 swp_TOT, swp_FRE, swp_USE };
327 // mem stack results extractor macro, where e=rel enum
328 #define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx)
330 /* Support for concurrent library updates via
331 multithreaded background processes */
333 static pthread_t Thread_id_cpus;
334 static sem_t Semaphore_cpus_beg, Semaphore_cpus_end;
337 static pthread_t Thread_id_memory;
338 static sem_t Semaphore_memory_beg, Semaphore_memory_end;
341 static pthread_t Thread_id_tasks;
342 static sem_t Semaphore_tasks_beg, Semaphore_tasks_end;
344 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
345 static pthread_t Thread_id_main;
348 /* Support for a namespace with proc mounted subset=pid,
349 ( we'll limit our display to task information only ). */
350 static int Restrict_some = 0;
352 /*###### Tiny useful routine(s) ########################################*/
355 * This routine simply formats whatever the caller wants and
356 * returns a pointer to the resulting 'const char' string... */
357 static const char *fmtmk (const char *fmts, ...) __attribute__((format(printf,1,2)));
358 static const char *fmtmk (const char *fmts, ...) {
359 static char buf[BIGBUFSIZ]; // with help stuff, our buffer
360 va_list va; // requirements now exceed 1k
363 vsnprintf(buf, sizeof(buf), fmts, va);
365 return (const char *)buf;
370 * Interger based fieldscur version of 'strlen' */
371 static inline int mlen (const int *mem) {
374 for (i = 0; mem[i]; i++)
381 * Interger based fieldscur version of 'strchr' */
382 static inline int *msch (const int *mem, int obj, int max) {
385 for (i = 0; i < max; i++)
386 if (*(mem + i) == obj) return (int *)mem + i;
392 * This guy is just our way of avoiding the overhead of the standard
393 * strcat function (should the caller choose to participate) */
394 static inline char *scat (char *dst, const char *src) {
396 while ((*(dst++) = *(src++)));
402 * This guy just facilitates Batch and protects against dumb ttys
403 * -- we'd 'inline' him but he's only called twice per frame,
404 * yet used in many other locations. */
405 static const char *tg2 (int x, int y) {
406 // it's entirely possible we're trying for an invalid row...
407 return Cap_can_goto ? tgoto(cursor_address, x, y) : "";
410 /*###### Exit/Interrput routines #######################################*/
413 * Reset the tty, if necessary */
414 static void at_eoj (void) {
416 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original);
417 if (keypad_local) putp(keypad_local);
418 putp(tg2(0, Screen_rows));
422 // this next will also replace top's most recent screen with the
423 // original display contents that were visible at our invocation
435 /* we gotta reverse the stderr redirect which was employed during start up
436 and needed because the two libnuma 'weak' functions were useless to us! */
437 if (-1 < Stderr_save) {
438 dup2(Stderr_save, fileno(stderr));
440 Stderr_save = -1; // we'll be ending soon anyway but what the heck
447 * The real program end */
448 static void bye_bye (const char *str) __attribute__((__noreturn__));
449 static void bye_bye (const char *str) {
452 // POSIX.1 async-signal-safe: sigfillset, sigprocmask, pthread_sigmask
454 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
455 pthread_sigmask(SIG_BLOCK, &ss, NULL);
457 sigprocmask(SIG_BLOCK, &ss, NULL);
459 at_eoj(); // restore tty in preparation for exit
462 if (!str && !Frames_signal && Ttychanged) { fprintf(stderr,
463 "\n%s's Summary report:"
466 "\n\t Hertz = %u (%u bytes, %u-bit time)"
467 "\n\t Stat_reap->cpus->total = %d, Stat_reap->numa->total = %d"
468 "\n\t Pids_itms_tot = %d, sizeof(struct pids_result) = %d, pids stack size = %d"
469 "\n\t SCREENMAX = %d, ROWMINSIZ = %d, ROWMAXSIZ = %d"
470 "\n\t PACKAGE = '%s', LOCALEDIR = '%s'"
472 "\n\t device = %s, ncurses = v%s"
473 "\n\t max_colors = %d, max_pairs = %d"
474 "\n\t Cap_can_goto = %s"
475 "\n\t Screen_cols = %d, Screen_rows = %d"
476 "\n\t Max_lines = %d, most recent Pseudo_size = %u"
478 "\n\t Stdout_buf = %u, BUFSIZ = %u"
480 "\n\tWindows and Curwin->"
481 "\n\t sizeof(WIN_t) = %u, GROUPSMAX = %d"
482 "\n\t winname = %s, grpname = %s"
484 "\n\t winflags = %08X, maxpflgs = %d"
486 "\n\t winflags = x%08x, maxpflgs = %d"
488 "\n\t sortindx = %d, maxtasks = %d"
489 "\n\t varcolsz = %d, winlines = %d"
490 "\n\t strlen(columnhdr) = %d"
491 "\n\t current fieldscur = %d, maximum fieldscur = %d"
495 , (unsigned)Hertz, (unsigned)sizeof(Hertz), (unsigned)sizeof(Hertz) * 8
496 , Stat_reap->cpus->total, Stat_reap->numa->total
497 , Pids_itms_tot, (int)sizeof(struct pids_result), (int)(sizeof(struct pids_result) * Pids_itms_tot)
498 , (int)SCREENMAX, (int)ROWMINSIZ, (int)ROWMAXSIZ
505 , ttyname(STDOUT_FILENO), NCURSES_VERSION
506 , max_colors, max_pairs
507 , Cap_can_goto ? "yes" : "No!"
508 , Screen_cols, Screen_rows
509 , Max_lines, (unsigned)Pseudo_size
511 , (unsigned)sizeof(Stdout_buf), (unsigned)BUFSIZ
513 , (unsigned)sizeof(WIN_t), GROUPSMAX
514 , Curwin->rc.winname, Curwin->grpname
515 , Curwin->rc.winflags, Curwin->maxpflgs
516 , Curwin->rc.sortindx, Curwin->rc.maxtasks
517 , Curwin->varcolsz, Curwin->winlines
518 , (int)strlen(Curwin->columnhdr)
519 , EU_MAXPFLGS, mlen(Curwin->rc.fieldscur)
523 #endif // end: ATEOJ_RPTSTD
525 // there's lots of signal-unsafe stuff in the following ...
526 if (Frames_signal != BREAK_sig) {
527 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
528 /* can not execute any cleanup from a sibling thread and
529 we will violate proper indentation to minimize impact */
530 if (pthread_equal(Thread_id_main, pthread_self())) {
533 pthread_cancel(Thread_id_cpus);
534 pthread_join(Thread_id_cpus, NULL);
535 sem_destroy(&Semaphore_cpus_end);
536 sem_destroy(&Semaphore_cpus_beg);
539 pthread_cancel(Thread_id_memory);
540 pthread_join(Thread_id_memory, NULL);
541 sem_destroy(&Semaphore_memory_end);
542 sem_destroy(&Semaphore_memory_beg);
545 pthread_cancel(Thread_id_tasks);
546 pthread_join(Thread_id_tasks, NULL);
547 sem_destroy(&Semaphore_tasks_end);
548 sem_destroy(&Semaphore_tasks_beg);
550 procps_pids_unref(&Pids_ctx);
551 procps_stat_unref(&Stat_ctx);
552 procps_meminfo_unref(&Mem_ctx);
553 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
558 /* we'll only have a 'str' if called by error_exit() |
559 and parse_args(), never from a signal handler ... | */
564 /* this could happen when called from several places |
565 including that sig_endpgm(). thus we must use an |
566 async-signal-safe write function just in case ... |
567 (thanks: Shaohua Zhan shaohua.zhan@windriver.com) | */
569 write(fileno(stdout), "\n", sizeof("\n") - 1);
576 * Standard error handler to normalize the look of all err output */
577 static void error_exit (const char *str) __attribute__((__noreturn__));
578 static void error_exit (const char *str) {
579 static char buf[MEDBUFSIZ];
581 Frames_signal = BREAK_off;
582 /* we'll use our own buffer so callers can still use fmtmk() and, after
583 twelve long years, 2013 was the year we finally eliminated the leading
584 tab character -- now our message can get lost in screen clutter too! */
585 snprintf(buf, sizeof(buf), "%s: %s\n", Myname, str);
591 * Catches all remaining signals not otherwise handled */
592 static void sig_abexit (int sig) __attribute__((__noreturn__));
593 static void sig_abexit (int sig) {
596 // POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, sigprocmask, pthread_sigmask, raise
598 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
599 pthread_sigmask(SIG_BLOCK, &ss, NULL);
601 sigprocmask(SIG_BLOCK, &ss, NULL);
603 at_eoj(); // restore tty in preparation for exit
604 fprintf(stderr, N_fmt(EXIT_signals_fmt)
605 , sig, signal_number_to_name(sig), Myname);
606 signal(sig, SIG_DFL); // allow core dumps, if applicable
609 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
610 pthread_sigmask(SIG_UNBLOCK, &ss, NULL);
612 sigprocmask(SIG_UNBLOCK, &ss, NULL);
614 raise(sig); // ( plus set proper return code )
615 _exit(EXIT_FAILURE); // if default sig action is ignore
621 * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
622 * SIGUSR1 and SIGUSR2 */
623 static void sig_endpgm (int dont_care_sig) __attribute__((__noreturn__));
624 static void sig_endpgm (int dont_care_sig) {
625 Frames_signal = BREAK_sig;
633 * SIGTSTP, SIGTTIN and SIGTTOU */
634 static void sig_paused (int dont_care_sig) {
635 // POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise
636 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original))
637 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
638 if (keypad_local) putp(keypad_local);
639 putp(tg2(0, Screen_rows));
644 // tcdrain(STDOUT_FILENO) was not reliable prior to ncurses-5.9.20121017,
645 // so we'll risk POSIX's wrath with good ol' fflush, lest 'Stopped' gets
646 // co-mingled with our most recent output...
649 // later, after SIGCONT...
650 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw))
651 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
655 if (keypad_xmit) putp(keypad_xmit);
657 Frames_signal = BREAK_sig;
664 * SIGCONT and SIGWINCH */
665 static void sig_resize (int dont_care_sig) {
666 // POSIX.1 async-signal-safe: tcdrain
667 tcdrain(STDOUT_FILENO);
668 Frames_signal = BREAK_sig;
672 /*###### Special UTF-8 Multi-Byte support ##############################*/
674 /* Support for NLS translated multi-byte strings */
675 static char UTF8_tab[] = {
676 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F
677 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F
678 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
679 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F
680 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
681 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F
682 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
683 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F
684 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F
685 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F
686 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF
687 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF
688 -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF, 0xC2 = begins 2
689 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF
690 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF, 0xE0 = begins 3
691 4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF, 0xF0 = begins 4
692 }; // ( 0xF5 & beyond invalid )
696 * Accommodate any potential differences between some multibyte
697 * character sequence and the screen columns needed to print it */
698 static inline int utf8_cols (const unsigned char *p, int n) {
703 (void)mbtowc(&wc, (const char *)p, n);
704 // allow a zero as valid, as with a 'combining acute accent'
705 if ((n = wcwidth(wc)) < 0) n = 1;
716 * Determine difference between total bytes versus printable
717 * characters in that passed, potentially multi-byte, string */
718 static int utf8_delta (const char *str) {
719 const unsigned char *p = (const unsigned char *)str;
723 // -1 represents a decoding error, pretend it's untranslated ...
724 if (0 > (clen = UTF8_tab[*p])) return 0;
725 cnum += utf8_cols(p, clen);
728 return (int)((const char *)p - str) - cnum;
733 * Determine a physical end within a potential multi-byte string
734 * where maximum printable chars could be accommodated in width */
735 static int utf8_embody (const char *str, int width) {
736 const unsigned char *p = (const unsigned char *)str;
741 // -1 represents a decoding error, pretend it's untranslated ...
742 if (0 > (clen = UTF8_tab[*p])) return width;
743 if (width < (cnum += utf8_cols(p, clen))) break;
747 return (int)((const char *)p - str);
748 } // end: utf8_embody
752 * Like the regular justify_pad routine but this guy
753 * can accommodate the multi-byte translated strings */
754 static const char *utf8_justify (const char *str, int width, int justr) {
755 static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s";
756 static char buf[SCREENMAX];
759 snprintf(tmp, sizeof(tmp), "%.*s", utf8_embody(str, width), str);
760 width += utf8_delta(tmp);
761 snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, tmp, COLPADSTR);
763 } // end: utf8_justify
767 * Returns a physical or logical column number given a
768 * multi-byte string and a target column value */
769 static int utf8_proper_col (const char *str, int col, int tophysical) {
770 const unsigned char *p = (const unsigned char *)str;
771 int clen, tlen = 0, cnum = 0;
774 // -1 represents a decoding error, don't encourage repositioning ...
775 if (0 > (clen = UTF8_tab[*p])) return col;
776 if (cnum + 1 > col && tophysical) break;
779 if (tlen > col && !tophysical) break;
782 return tophysical ? tlen : cnum;
783 } // end: utf8_proper_col
785 /*###### Misc Color/Display support ####################################*/
788 * Make the appropriate caps/color strings for a window/field group.
789 * note: we avoid the use of background color so as to maximize
790 * compatibility with the user's xterm settings */
791 static void capsmk (WIN_t *q) {
792 /* macro to test if a basic (non-color) capability is valid
793 thanks: Floyd Davidson <floyd@ptialaska.net> */
794 #define tIF(s) s ? s : ""
795 /* macro to make compatible with netbsd-curses too
796 thanks: rofl0r <retnyg@gmx.net> */
797 #define tPM(a,b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0)
798 static int capsdone = 0;
800 // we must NOT disturb our 'empty' terminfo strings!
803 // these are the unchangeable puppies, so we only do 'em once
805 STRLCPY(Cap_clr_eol, tIF(clr_eol))
806 STRLCPY(Cap_clr_eos, tIF(clr_eos))
807 STRLCPY(Cap_clr_scr, tIF(clear_screen))
808 // due to the leading newline, the following must be used with care
809 snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos));
810 STRLCPY(Cap_curs_huge, tIF(cursor_visible))
811 STRLCPY(Cap_curs_norm, tIF(cursor_normal))
812 STRLCPY(Cap_curs_hide, tIF(cursor_invisible))
813 STRLCPY(Cap_home, tIF(cursor_home))
814 STRLCPY(Cap_norm, tIF(exit_attribute_mode))
815 STRLCPY(Cap_reverse, tIF(enter_reverse_mode))
817 if (!eat_newline_glitch) {
818 STRLCPY(Cap_rmam, tIF(exit_am_mode))
819 STRLCPY(Cap_smam, tIF(enter_am_mode))
820 if (!*Cap_rmam || !*Cap_smam) {
823 if (auto_right_margin)
829 snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm, tIF(orig_pair));
830 snprintf(Caps_endline, sizeof(Caps_endline), "%s%s", Caps_off, Cap_clr_eol);
831 if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1;
835 /* the key to NO run-time costs for configurable colors -- we spend a
836 little time with the user now setting up our terminfo strings, and
837 the job's done until he/she/it has a change-of-heart */
838 STRLCPY(q->cap_bold, CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode))
839 if (CHKw(q, Show_COLORS) && max_colors > 0) {
840 STRLCPY(q->capclr_sum, tPM(set_a_foreground, q->rc.summclr))
841 snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
842 , tPM(set_a_foreground, q->rc.msgsclr), Cap_reverse);
843 snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s"
844 , tPM(set_a_foreground, q->rc.msgsclr), q->cap_bold);
845 snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s"
846 , tPM(set_a_foreground, q->rc.headclr), Cap_reverse);
847 snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s"
848 , Caps_off, tPM(set_a_foreground, q->rc.taskclr));
850 q->capclr_sum[0] = '\0';
852 snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
853 , Cap_reverse, q->cap_bold);
855 STRLCPY(q->capclr_msg, Cap_reverse)
857 STRLCPY(q->capclr_pmt, q->cap_bold)
858 STRLCPY(q->capclr_hdr, Cap_reverse)
859 STRLCPY(q->capclr_rownorm, Cap_norm)
862 // composite(s), so we do 'em outside and after the if
863 snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s"
864 , q->capclr_rownorm, CHKw(q, Show_HIBOLD) ? q->cap_bold : Cap_reverse);
870 static struct msg_node {
872 struct msg_node *prev;
873 } Msg_tab[BOT_MSGSMAX];
875 static struct msg_node *Msg_this = Msg_tab;
878 * Show an error message (caller may include '\a' for sound) */
879 static void show_msg (const char *str) {
880 STRLCPY(Msg_this->msg, str);
881 if (++Msg_this > &Msg_tab[BOT_MSGSMAX - 1]) Msg_this = Msg_tab;
883 PUTT("%s%s %.*s %s%s%s"
886 , utf8_embody(str, Screen_cols - 2)
897 * Show an input prompt + larger cursor (if possible) */
898 static int show_pmt (const char *str) {
902 snprintf(buf, sizeof(buf), "%.*s", utf8_embody(str, Screen_cols - 2), str);
903 len = utf8_delta(buf);
905 PUTT("\n%s%s%.*s %s%s%s"
907 PUTT("%s%s%.*s %s%s%s"
911 , (Screen_cols - 2) + len
917 len = strlen(buf) - len;
918 // +1 for the space we added or -1 for the cursor...
919 return (len + 1 < Screen_cols) ? len + 1 : Screen_cols - 1;
924 * Create and print the optional scroll coordinates message */
925 static void show_scroll (void) {
926 char tmp1[SMLBUFSIZ];
928 char tmp2[SMLBUFSIZ];
930 int totpflgs = Curwin->totpflgs;
931 int begpflgs = Curwin->begpflg + 1;
934 if (CHKw(Curwin, Show_HICOLS)) {
936 if (ENUpos(Curwin, Curwin->rc.sortindx) < Curwin->begpflg) begpflgs -= 2;
939 if (1 > totpflgs) totpflgs = 1;
940 if (1 > begpflgs) begpflgs = 1;
941 snprintf(tmp1, sizeof(tmp1), N_fmt(SCROLL_coord_fmt), Curwin->begtask + 1, PIDSmaxt, begpflgs, totpflgs);
943 if (Curwin->varcolbeg) {
944 snprintf(tmp2, sizeof(tmp2), " + %d", Curwin->varcolbeg);
948 PUTT("%s%s %.*s%s", tg2(0, Msg_row), Caps_off, Screen_cols - 3, tmp1, Cap_clr_eol);
949 } // end: show_scroll
953 * Show lines with specially formatted elements, but only output
954 * what will fit within the current screen width.
955 * Our special formatting consists of:
956 * "some text <_delimiter_> some more text <_delimiter_>...\n"
957 * Where <_delimiter_> is a two byte combination consisting of a
958 * tilde followed by an ascii digit in the range of 1 - 8.
959 * examples: ~1, ~5, ~8, etc.
960 * The tilde is effectively stripped and the next digit
961 * converted to an index which is then used to select an
962 * 'attribute' from a capabilities table. That attribute
963 * is then applied to the *preceding* substring.
964 * Once recognized, the delimiter is replaced with a null character
965 * and viola, we've got a substring ready to output! Strings or
966 * substrings without delimiters will receive the Cap_norm attribute.
969 * This routine treats all non-delimiter bytes as displayable
970 * data subject to our screen width marching orders. If callers
971 * embed non-display data like tabs or terminfo strings in our
972 * glob, a line will truncate incorrectly at best. Worse case
973 * would be truncation of an embedded tty escape sequence.
975 * Tabs must always be avoided or our efforts are wasted and
976 * lines will wrap. To lessen but not eliminate the risk of
977 * terminfo string truncation, such non-display stuff should
978 * be placed at the beginning of a "short" line. */
979 static void show_special (int interact, const char *glob) {
980 /* note: the following is for documentation only,
981 the real captab is now found in a group's WIN_t !
982 +------------------------------------------------------+
983 | char *captab[] = { : Cap's = Index |
984 | Cap_norm, Cap_norm, = \000, \001, |
985 | cap_bold, capclr_sum, = \002, \003, |
986 | capclr_msg, capclr_pmt, = \004, \005, |
987 | capclr_hdr, = \006, |
988 | capclr_rowhigh, = \007, |
989 | capclr_rownorm }; = \010 [octal!] |
990 +------------------------------------------------------+ */
991 /* ( Pssst, after adding the termcap transitions, row may )
992 ( exceed 300+ bytes, even in an 80x24 terminal window! )
993 ( Shown here are the former buffer size specifications )
994 ( char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]. )
995 ( So now we use larger buffers and a little protection )
996 ( against overrunning them with this 'lin_end - glob'. )
998 ( That was uncovered during 'Inspect' development when )
999 ( this guy was being considered for a supporting role. )
1000 ( However, such an approach was abandoned. As a result )
1001 ( this function is called only with a glob under top's )
1002 ( control and never containing any 'raw/binary' chars! ) */
1003 char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMINSIZ];
1004 char *rp, *lin_end, *sub_beg, *sub_end;
1007 // handle multiple lines passed in a bunch
1008 while ((lin_end = strchr(glob, '\n'))) {
1009 #define myMIN(a,b) (((a) < (b)) ? (a) : (b))
1010 size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -3);
1012 // create a local copy we can extend and otherwise abuse
1013 memcpy(lin, glob, lessor);
1014 // zero terminate this part and prepare to parse substrings
1017 sub_beg = sub_end = lin;
1022 if ('~' == ch) ch = *(sub_end + 1) - '0';
1024 case 0: // no end delim, captab makes normal
1025 // only possible when '\n' was NOT preceded with a '~#' sequence
1026 // ( '~1' thru '~8' is valid range, '~0' is never actually used )
1027 *(sub_end + 1) = '\0'; // extend str end, then fall through
1028 *(sub_end + 2) = '\0'; // ( +1 optimization for usual path )
1030 case 1: case 2: case 3: case 4:
1031 case 5: case 6: case 7: case 8:
1033 snprintf(tmp, sizeof(tmp), "%s%.*s%s"
1034 , Curwin->captab[ch], utf8_embody(sub_beg, room), sub_beg, Caps_off);
1036 room -= (sub_end - sub_beg);
1037 room += utf8_delta(sub_beg);
1038 sub_beg = (sub_end += 2);
1040 default: // nothin' special, just text
1043 if (0 >= room) break; // skip substrings that won't fit
1046 if (interact) PUTT("%s%s\n", row, Cap_clr_eol);
1047 else PUFF("%s%s\n", row, Caps_endline);
1048 glob = ++lin_end; // point to next line (maybe)
1051 } // end: while 'lines'
1053 /* If there's anything left in the glob (by virtue of no trailing '\n'),
1054 it probably means caller wants to retain cursor position on this final
1055 line. That, in turn, means we're interactive and so we'll just do our
1056 'fit-to-screen' thingy while also leaving room for the cursor... */
1057 if (*glob) PUTT("%.*s", utf8_embody(glob, Screen_cols - 1), glob);
1058 } // end: show_special
1060 /*###### Low Level Memory/Keyboard/File I/O support ####################*/
1063 * Handle our own memory stuff without the risk of leaving the
1064 * user's terminal in an ugly state should things go sour. */
1066 static void *alloc_c (size_t num) {
1070 if (!(pv = calloc(1, num)))
1071 error_exit(N_txt(FAIL_alloc_c_txt));
1076 static void *alloc_r (void *ptr, size_t num) {
1080 if (!(pv = realloc(ptr, num)))
1081 error_exit(N_txt(FAIL_alloc_r_txt));
1086 static char *alloc_s (const char *str) {
1087 return strcpy(alloc_c(strlen(str) +1), str);
1092 * An 'I/O available' routine which will detect raw single byte |
1093 * unsolicited keyboard input which was susceptible to SIGWINCH |
1094 * interrupts (or any other signal). He'll also support timout |
1095 * in the absence of any user keystrokes or a signal interrupt. | */
1096 static inline int ioa (struct timespec *ts) {
1101 FD_SET(STDIN_FILENO, &fs);
1103 #ifdef SIGNALS_LESS // conditional comments are silly, but help in documenting
1104 // hold here until we've got keyboard input, any signal except SIGWINCH
1105 // or (optionally) we timeout with nanosecond granularity
1107 // hold here until we've got keyboard input, any signal (including SIGWINCH)
1108 // or (optionally) we timeout with nanosecond granularity
1110 rc = pselect(STDIN_FILENO + 1, &fs, NULL, NULL, ts, &Sigwinch_set);
1118 * This routine isolates ALL user INPUT and ensures that we
1119 * wont be mixing I/O from stdio and low-level read() requests */
1120 static int ioch (int ech, char *buf, unsigned cnt) {
1125 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_tweaked);
1126 rc = read(STDIN_FILENO, buf, cnt);
1127 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw);
1130 rc = read(STDIN_FILENO, buf, cnt);
1135 rc = read(STDIN_FILENO, buf, cnt);
1138 // zero means EOF, might happen if we erroneously get detached from terminal
1139 if (0 == rc) bye_bye(NULL);
1141 // it may have been the beginning of a lengthy escape sequence
1142 tcflush(STDIN_FILENO, TCIFLUSH);
1144 // note: we do NOT produce a valid 'string'
1149 #define IOKEY_INIT 0
1150 #define IOKEY_ONCE 1
1151 #define IOKEY_NEXT 2
1154 * Support for single or multiple keystroke input AND
1155 * escaped cursor motion keys.
1156 * note: we support more keys than we currently need, in case
1157 * we attract new consumers in the future */
1158 static int iokey (int action) {
1163 { NULL, kbd_BKSP }, { NULL, kbd_INS }, { NULL, kbd_DEL }, { NULL, kbd_LEFT },
1164 { NULL, kbd_DOWN }, { NULL, kbd_UP }, { NULL, kbd_RIGHT }, { NULL, kbd_HOME },
1165 { NULL, kbd_PGDN }, { NULL, kbd_PGUP }, { NULL, kbd_END }, { NULL, kbd_BTAB },
1166 // remainder are alternatives for above, just in case...
1167 // ( the h,j,k,l entries are the vim cursor motion keys )
1168 { "\033h", kbd_LEFT }, { "\033j", kbd_DOWN }, /* meta+ h,j */
1169 { "\033k", kbd_UP }, { "\033l", kbd_RIGHT }, /* meta+ k,l */
1170 { "\033\010", kbd_HOME }, { "\033\012", kbd_PGDN }, /* ctrl+meta+ h,j */
1171 { "\033\013", kbd_PGUP }, { "\033\014", kbd_END }, /* ctrl+meta+ k,l */
1172 { "\xC3\xA8", kbd_LEFT }, { "\xC3\xAA", kbd_DOWN }, /* meta+ h,j (some xterms) */
1173 { "\xC3\xAB", kbd_UP }, { "\xC3\xAC", kbd_RIGHT }, /* meta+ k,l (some xterms) */
1174 { "\xC2\x88", kbd_HOME }, { "\xC2\x8A", kbd_PGDN }, /* ctrl+meta+ h,j (some xterms) */
1175 { "\xC2\x8B", kbd_PGUP }, { "\xC2\x8C", kbd_END }, /* ctrl+meta+ k,l (some xterms) */
1176 { "\033\011", kbd_BTAB }
1179 char buf[SMLBUFSIZ], *pb;
1181 static char buf[MEDBUFSIZ];
1182 static int pos, len;
1187 if (action == IOKEY_INIT) {
1188 #define tOk(s) s ? s : ""
1189 tinfo_tab[0].str = tOk(key_backspace);
1190 tinfo_tab[1].str = tOk(key_ic);
1191 tinfo_tab[2].str = tOk(key_dc);
1192 tinfo_tab[3].str = tOk(key_left);
1193 tinfo_tab[4].str = tOk(key_down);
1194 tinfo_tab[5].str = tOk(key_up);
1195 tinfo_tab[6].str = tOk(key_right);
1196 tinfo_tab[7].str = tOk(key_home);
1197 tinfo_tab[8].str = tOk(key_npage);
1198 tinfo_tab[9].str = tOk(key_ppage);
1199 tinfo_tab[10].str = tOk(key_end);
1200 tinfo_tab[11].str = tOk(back_tab);
1201 // next is critical so returned results match bound terminfo keys
1202 putp(tOk(keypad_xmit));
1203 // ( converse keypad_local issued at pause/pgm end, just in case )
1208 if (action == IOKEY_ONCE) {
1209 memset(buf, '\0', sizeof(buf));
1210 if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1213 #ifndef TERMIOS_ONLY
1214 if (action == IOKEY_NEXT) {
1216 return buf[pos++]; // exhaust prior keystrokes
1218 memset(buf, '\0', sizeof(buf));
1219 if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1220 if (!iscntrl(buf[0])) { // no need for translation
1228 /* some emulators implement 'key repeat' too well and we get duplicate
1229 key sequences -- so we'll focus on the last escaped sequence, while
1230 also allowing use of the meta key... */
1231 if (!(pb = strrchr(buf, '\033'))) pb = buf;
1232 else if (pb > buf && '\033' == *(pb - 1)) --pb;
1234 for (i = 0; i < MAXTBL(tinfo_tab); i++)
1235 if (!strcmp(tinfo_tab[i].str, pb))
1236 return tinfo_tab[i].key;
1238 // no match, so we'll return single non-escaped keystrokes only
1239 if (buf[0] == '\033' && buf[1]) return -1;
1246 * Get line oriented interactive input from the user,
1247 * using native tty support */
1248 static char *ioline (const char *prompt) {
1249 static const char ws[] = "\b\f\n\r\t\v\x1b\x9b"; // 0x1b + 0x9b are escape
1250 static char buf[MEDBUFSIZ];
1254 memset(buf, '\0', sizeof(buf));
1255 ioch(1, buf, sizeof(buf)-1);
1257 if ((p = strpbrk(buf, ws))) *p = '\0';
1258 // note: we DO produce a vaid 'string'
1264 * Get some line oriented interactive input from the ol' user,
1265 * going way, way beyond that native tty support by providing:
1266 * . true input line editing, not just a destructive backspace
1267 * . an input limit sensitive to the current screen dimensions
1268 * . an ability to recall prior strings for editing & re-input */
1269 static char *ioline (const char *prompt) {
1270 #define setLEN ( len = strlen(buf) - utf8_delta(buf) )
1271 #define setPOS(X) ( pos = utf8_embody(buf, X) )
1272 #define utfCHR(X) ( (unsigned char *)&buf[X] )
1273 #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] )
1274 #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) )
1275 #define movBKW { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; }
1276 #define chkCUR { if (cur < 0) cur = 0; if (cur > len) cur = len; }
1277 // thank goodness ol' memmove will safely allow strings to overlap
1278 #define sqzSTR { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \
1279 if (!utfCOL(pos + i)) i += utfTOT(pos + i); \
1280 memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \
1281 memset(&buf[bufMAX - i], '\0', i); }
1282 #define expSTR(X) if (bufNXT < bufMAX && scrNXT < Screen_cols) { \
1283 memmove(&buf[pos + X], &buf[pos], bufMAX - pos); }
1285 #define bufNXT ( pos + 4 ) // four equals longest utf8 str
1286 #define scrNXT ( beg + len + 2 ) // two due to multi-column char
1287 #define bufMAX ((int)sizeof(buf)-2) // -1 for '\0' string delimeter
1288 static char buf[MEDBUFSIZ+1]; // +1 for '\0' string delimeter
1290 int beg, // the physical column where input began, buf[0]
1291 cur, // the logical current column/insertion position
1292 len, // the logical input length, thus the end column
1293 pos, // the physical position in the buffer currently
1296 struct lin_s *bkw; // pointer for older saved strs
1297 struct lin_s *fwd; // pointer for newer saved strs
1298 char *str; // an actual saved input string
1300 static struct lin_s *anchor, *plin;
1303 anchor = alloc_c(sizeof(struct lin_s));
1304 anchor->str = alloc_s(""); // the top-of-stack (empty str)
1307 cur = len = pos = 0;
1308 beg = show_pmt(prompt);
1309 memset(buf, '\0', sizeof(buf));
1310 // this may not work under a gui emulator (but linux console is ok)
1311 putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1315 key = iokey(IOKEY_NEXT);
1324 case kbd_BTAB: case kbd_PGUP: case kbd_PGDN:
1328 putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1334 if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; }
1337 if (0 < cur) { movBKW; cur -= utfCOL(pos); }
1340 if (cur < len) cur += utfCOL(pos);
1352 memset(buf, '\0', sizeof(buf));
1353 memccpy(buf, plin->str, '\0', bufMAX);
1359 if (plin->fwd) plin = plin->fwd;
1360 memset(buf, '\0', sizeof(buf));
1361 memccpy(buf, plin->str, '\0', bufMAX);
1365 default: // what we REALLY wanted (maybe)
1366 if (bufNXT < bufMAX && scrNXT < Screen_cols && strlen(buf) < bufMAX) {
1367 int tot = UTF8_tab[(unsigned char)key],
1369 if (tot < 1) tot = 1;
1370 if (!ovt) { expSTR(tot); }
1371 else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); }
1374 key = iokey(IOKEY_NEXT);
1385 putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf));
1387 putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' '));
1389 putp(tg2(beg + cur, Msg_row));
1390 } while (key != kbd_ENTER);
1392 // weed out duplicates, including empty strings (top-of-stack)...
1393 for (i = 0, plin = anchor; ; i++) {
1395 if (!STRCMP(plin->str, buf)) // if matched, retain original order
1398 if (!STRCMP(plin->str, buf)) { // if matched, rearrange stack order
1399 if (i > 1) { // but not null str or if already #2
1400 if (plin->bkw) // splice around this matched string
1401 plin->bkw->fwd = plin->fwd; // if older exists link to newer
1402 plin->fwd->bkw = plin->bkw; // newer linked to older or NULL
1403 anchor->bkw->fwd = plin; // stick matched on top of former #2
1404 plin->bkw = anchor->bkw; // keep empty string at top-of-stack
1405 plin->fwd = anchor; // then prepare to be the 2nd banana
1406 anchor->bkw = plin; // by sliding us in below the anchor
1411 if (!plin->bkw) break; // let i equal total stacked strings
1412 plin = plin->bkw; // ( with plin representing bottom )
1415 plin = alloc_c(sizeof(struct lin_s));
1416 else { // when a new string causes overflow
1417 plin->fwd->bkw = NULL; // make next-to-last string new last
1418 free(plin->str); // and toss copy but keep the struct
1420 plin->str = alloc_s(buf); // copy user's new unique input line
1421 plin->bkw = anchor->bkw; // keep empty string as top-of-stack
1422 if (plin->bkw) // did we have some already stacked?
1423 plin->bkw->fwd = plin; // yep, so point prior to new string
1424 plin->fwd = anchor; // and prepare to be a second banana
1425 anchor->bkw = plin; // by sliding it in as new number 2!
1427 return buf; // protect our copy, return original
1446 * Make locale unaware float (but maybe restrict to whole numbers). */
1447 static int mkfloat (const char *str, float *num, int whole) {
1448 char tmp[SMLBUFSIZ], *ep;
1451 *num = (float)strtol(str, &ep, 0);
1452 if (ep != str && *ep == '\0' && *num < INT_MAX)
1456 snprintf(tmp, sizeof(tmp), "%s", str);
1457 *num = strtof(tmp, &ep);
1459 // fallback - try to swap the floating point separator
1460 if (*ep == '.') *ep = ',';
1461 else if (*ep == ',') *ep = '.';
1462 *num = strtof(tmp, &ep);
1464 if (ep != tmp && *ep == '\0' && *num < INT_MAX)
1471 * This routine provides the i/o in support of files whose size
1472 * cannot be determined in advance. Given a stream pointer, he'll
1473 * try to slurp in the whole thing and return a dynamically acquired
1474 * buffer supporting that single string glob.
1476 * He always creates a buffer at least READMINSZ big, possibly
1477 * all zeros (an empty string), even if the file wasn't read. */
1478 static int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread) {
1479 char chunk[4096*16];
1484 *baddr = alloc_c(READMINSZ);
1486 while (0 < (num = fread(chunk, 1, sizeof(chunk), fp))) {
1487 *baddr = alloc_r(*baddr, num + *bsize);
1488 memcpy(*baddr + *bread, chunk, num);
1492 *(*baddr + *bread) = '\0';
1498 /*###### Small Utility routines ########################################*/
1500 #define GET_NUM_BAD INT_MIN
1501 #define GET_NUM_ESC (INT_MIN + 1)
1502 #define GET_NUM_NOT (INT_MIN + 2)
1505 * Get a float from the user */
1506 static float get_float (const char *prompt) {
1510 line = ioline(prompt);
1511 if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1512 if (!line[0]) return GET_NUM_NOT;
1513 // note: we're not allowing negative floats
1514 if (!mkfloat(line, &f, 0) || f < 0) {
1515 show_msg(N_txt(BAD_numfloat_txt));
1523 * Get an integer from the user, returning INT_MIN for error */
1524 static int get_int (const char *prompt) {
1528 line = ioline(prompt);
1529 if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1530 if (!line[0]) return GET_NUM_NOT;
1531 // note: we've got to allow negative ints (renice)
1532 if (!mkfloat(line, &f, 1)) {
1533 show_msg(N_txt(BAD_integers_txt));
1541 * Make a hex value, and maybe suppress zeroes. */
1542 static inline const char *hex_make (long num, int noz) {
1543 static char buf[SMLBUFSIZ];
1547 snprintf(buf, sizeof(buf), "%08lX", num);
1549 snprintf(buf, sizeof(buf), "%08lx", num);
1552 for (i = 0; buf[i]; i++)
1560 * Validate the passed string as a user name or number,
1561 * and/or update the window's 'u/U' selection stuff. */
1562 static const char *user_certify (WIN_t *q, const char *str, char typ) {
1571 if ('!' == *str) { ++str; q->usrselflg = 0; }
1572 num = (uid_t)strtoul(str, &endp, 0);
1573 if ('\0' == *endp) {
1574 pwd = getpwuid(num);
1576 /* allow foreign users, from e.g within chroot
1577 ( thanks Dr. Werner Fink <werner@suse.de> ) */
1583 pwd = getpwnam(str);
1584 if (!pwd) return N_txt(BAD_username_txt);
1585 q->usrseluid = pwd->pw_uid;
1589 } // end: user_certify
1591 /*###### Basic Formatting support ######################################*/
1594 * Just do some justify stuff, then add post column padding. */
1595 static inline const char *justify_pad (const char *str, int width, int justr) {
1596 static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s";
1597 static char buf[SCREENMAX];
1599 snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, str, COLPADSTR);
1601 } // end: justify_pad
1605 * Make and then justify a single character. */
1606 static inline const char *make_chr (const char ch, int width, int justr) {
1607 static char buf[SMLBUFSIZ];
1609 snprintf(buf, sizeof(buf), "%c", ch);
1610 return justify_pad(buf, width, justr);
1615 * Make and then justify an integer NOT subject to scaling,
1616 * and include a visual clue should tuncation be necessary. */
1617 static inline const char *make_num (long num, int width, int justr, int col, int noz) {
1618 static char buf[SMLBUFSIZ];
1621 if (noz && Rc.zero_suppress && 0 == num)
1624 if (width < snprintf(buf, sizeof(buf), "%ld", num)) {
1625 if (width <= 0 || (size_t)width >= sizeof(buf))
1626 width = sizeof(buf)-1;
1627 buf[width-1] = COLPLUSCH;
1632 return justify_pad(buf, width, justr);
1637 * Make and then justify a character string,
1638 * and include a visual clue should tuncation be necessary. */
1639 static inline const char *make_str (const char *str, int width, int justr, int col) {
1640 static char buf[SCREENMAX];
1642 if (width < snprintf(buf, sizeof(buf), "%s", str)) {
1643 if (width <= 0 || (size_t)width >= sizeof(buf))
1644 width = sizeof(buf)-1;
1645 buf[width-1] = COLPLUSCH;
1649 return justify_pad(buf, width, justr);
1654 * Make and then justify a potentially multi-byte character string,
1655 * and include a visual clue should tuncation be necessary. */
1656 static inline const char *make_str_utf8 (const char *str, int width, int justr, int col) {
1657 static char buf[SCREENMAX];
1658 int delta = utf8_delta(str);
1660 if (width + delta < snprintf(buf, sizeof(buf), "%s", str)) {
1661 snprintf(buf, sizeof(buf), "%.*s%c", utf8_embody(str, width-1), str, COLPLUSCH);
1662 delta = utf8_delta(buf);
1665 return justify_pad(buf, width + delta, justr);
1666 } // end: make_str_utf8
1670 * Do some scaling then justify stuff.
1671 * We'll interpret 'num' as a kibibytes quantity and try to
1672 * format it to reach 'target' while also fitting 'width'. */
1673 static const char *scale_mem (int target, float num, int width, int justr) {
1674 // SK_Kb SK_Mb SK_Gb SK_Tb SK_Pb SK_Eb
1676 static const char *fmttab[] = { "%.0f", "%#.1f%c", "%#.3f%c", "%#.3f%c", "%#.3f%c", NULL };
1678 static const char *fmttab[] = { "%.0f", "%.1f%c", "%.1f%c", "%.1f%c", "%.1f%c", NULL };
1680 static char buf[SMLBUFSIZ];
1685 if (Rc.zero_suppress && 0 >= num)
1688 for (i = SK_Kb, psfx = Scaled_sfxtab; i < SK_Eb; psfx++, i++) {
1690 && (width >= snprintf(buf, sizeof(buf), fmttab[i], num, *psfx)))
1695 // well shoot, this outta' fit...
1696 snprintf(buf, sizeof(buf), "?");
1698 return justify_pad(buf, width, justr);
1703 * Do some scaling then justify stuff. */
1704 static const char *scale_num (float num, int width, int justr) {
1705 static char buf[SMLBUFSIZ];
1709 if (Rc.zero_suppress && 0 >= num)
1711 if (width >= snprintf(buf, sizeof(buf), "%.0f", num))
1714 for (psfx = Scaled_sfxtab; 0 < *psfx; psfx++) {
1716 if (width >= snprintf(buf, sizeof(buf), "%.1f%c", num, *psfx))
1718 if (width >= snprintf(buf, sizeof(buf), "%.0f%c", num, *psfx))
1722 // well shoot, this outta' fit...
1723 snprintf(buf, sizeof(buf), "?");
1725 return justify_pad(buf, width, justr);
1730 * Make and then justify a percentage, with decreasing precision. */
1731 static const char *scale_pcnt (float num, int width, int justr, int xtra) {
1732 static char buf[SMLBUFSIZ];
1735 if (Rc.zero_suppress && 0 >= num)
1738 if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1740 if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1745 if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1747 if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1752 if (width >= snprintf(buf, sizeof(buf), "%#.1f", num))
1754 if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, num))
1757 // well shoot, this outta' fit...
1758 snprintf(buf, sizeof(buf), "?");
1760 return justify_pad(buf, width, justr);
1761 } // end: scale_pcnt
1764 #define TICS_AS_SECS 0
1765 #define TICS_AS_MINS 1
1766 #define TICS_AS_HOUR 2
1767 #define TICS_AS_DAY1 3
1768 #define TICS_AS_DAY2 4
1769 #define TICS_AS_WEEK 5
1770 #define TICS_AS_LAST 6
1773 * Do some scaling stuff.
1774 * Try to format 'tics' to reach 'target' while also
1775 * fitting in 'width', then justify it. */
1776 static const char *scale_tics (TIC_t tics, int width, int justr, int target) {
1778 #define HH "%luH" // nls_maybe
1782 #define HH "%luh" // nls_maybe
1786 static char buf[SMLBUFSIZ];
1787 TIC_t nt; // for speed on 64-bit
1789 unsigned long cc; // centiseconds
1790 unsigned long nn; // multi-purpose whatever
1792 unsigned long cent, secs, mins, hour, days, week;
1796 nt = (tics * 100ull) / Hertz; // lots of room for any time
1797 if (Rc.zero_suppress && 0 >= nt)
1801 cc = nt % 100; // centiseconds past second
1802 nt /= 100; // total seconds
1803 nn = nt % 60; // seconds past the minute
1804 nt /= 60; // total minutes
1805 if (target < TICS_AS_MINS
1806 && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu.%02lu", nt, nn, cc)))
1808 if (target < TICS_AS_HOUR
1809 && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu", nt, nn)))
1811 nn = nt % 60; // minutes past the hour
1812 nt /= 60; // total hours
1813 if (width >= snprintf(buf, sizeof(buf), "%llu,%02lu", nt, nn))
1815 nn = nt; // now also hours
1816 if (width >= snprintf(buf, sizeof(buf), HH, nn))
1818 nn /= 24; // now days
1819 if (width >= snprintf(buf, sizeof(buf), DD, nn))
1821 nn /= 7; // now weeks
1822 if (width >= snprintf(buf, sizeof(buf), WW, nn))
1825 #define mmLIMIT 360 // arbitrary 6 hours
1826 #define hhLIMIT 96 // arbitrary 4 days
1827 #define ddLIMIT 14 // arbitrary 2 weeks
1829 cent = (nt % 100); // cent past secs
1830 secs = (nt /= 100); // total secs
1831 mins = (nt /= 60); // total mins
1832 hour = (nt /= 60); // total hour
1833 days = (nt /= 24); // totat days
1834 week = (nt / 7); // total week
1836 if (Rc.tics_scaled > target)
1837 target += Rc.tics_scaled - target;
1841 if (mins < mmLIMIT + 1) {
1842 if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu.%02lu", mins, secs % 60, cent))
1845 case TICS_AS_MINS: // fall through
1846 if (mins < mmLIMIT + 1) {
1847 if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu", mins, secs % 60))
1850 case TICS_AS_HOUR: // fall through
1851 if (hour < hhLIMIT + 1) {
1852 if (width >= snprintf(buf, sizeof(buf), "%lu,%02lu", hour, mins % 60))
1855 case TICS_AS_DAY1: // fall through
1856 if (days < ddLIMIT + 1) {
1857 if (width >= snprintf(buf, sizeof(buf), DD "+" HH, days, hour % 24))
1860 if (width >= snprintf(buf, sizeof(buf), DD "+%lu", days, hour % 24))
1863 case TICS_AS_DAY2: // fall through
1864 if (width >= snprintf(buf, sizeof(buf), DD, days))
1867 case TICS_AS_WEEK: // fall through
1868 if (width >= snprintf(buf, sizeof(buf), WW "+" DD, week, days % 7))
1871 if (width >= snprintf(buf, sizeof(buf), WW "+%lu", week, days % 7))
1874 case TICS_AS_LAST: // fall through
1875 if (width >= snprintf(buf, sizeof(buf), WW, week))
1877 default: // fall through
1885 // well shoot, this outta' fit...
1886 snprintf(buf, sizeof(buf), "?");
1889 return justify_pad(buf, width, justr);
1893 } // end: scale_tics
1895 /*###### Fields Management support #####################################*/
1897 /* These are our gosh darn 'Fields' !
1898 They MUST be kept in sync with pflags !! */
1900 int width; // field width, if applicable
1901 int scale; // scaled target, if applicable
1902 const int align; // the default column alignment flag
1903 enum pids_item item; // the new libproc item enum identifier
1905 // these identifiers reflect the default column alignment but they really
1906 // contain the WIN_t flag used to check/change justification at run-time!
1907 #define A_left Show_JRSTRS /* toggled with lower case 'j' */
1908 #define A_right Show_JRNUMS /* toggled with upper case 'J' */
1910 /* .width anomalies:
1911 a -1 width represents variable width columns
1912 a 0 width represents columns set once at startup (see zap_fieldstab)
1914 .width .scale .align .item
1915 ------ ------ -------- ------------------- */
1916 { 0, -1, A_right, PIDS_ID_PID }, // s_int EU_PID
1917 { 0, -1, A_right, PIDS_ID_PPID }, // s_int EU_PPD
1918 { 5, -1, A_right, PIDS_ID_EUID }, // u_int EU_UED
1919 { 8, -1, A_left, PIDS_ID_EUSER }, // str EU_UEN
1920 { 5, -1, A_right, PIDS_ID_RUID }, // u_int EU_URD
1921 { 8, -1, A_left, PIDS_ID_RUSER }, // str EU_URN
1922 { 5, -1, A_right, PIDS_ID_SUID }, // u_int EU_USD
1923 { 8, -1, A_left, PIDS_ID_SUSER }, // str EU_USN
1924 { 5, -1, A_right, PIDS_ID_EGID }, // u_int EU_GID
1925 { 8, -1, A_left, PIDS_ID_EGROUP }, // str EU_GRP
1926 { 0, -1, A_right, PIDS_ID_PGRP }, // s_int EU_PGD
1927 { 8, -1, A_left, PIDS_TTY_NAME }, // str EU_TTY
1928 { 0, -1, A_right, PIDS_ID_TPGID }, // s_int EU_TPG
1929 { 0, -1, A_right, PIDS_ID_SESSION }, // s_int EU_SID
1930 { 3, -1, A_right, PIDS_PRIORITY }, // s_int EU_PRI
1931 { 3, -1, A_right, PIDS_NICE }, // s_int EU_NCE
1932 { 3, -1, A_right, PIDS_NLWP }, // s_int EU_THD
1933 { 0, -1, A_right, PIDS_PROCESSOR }, // s_int EU_CPN
1934 { 5, -1, A_right, PIDS_TICS_ALL_DELTA }, // u_int EU_CPU
1935 { 6, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TME
1936 { 9, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TM2
1937 { 5, -1, A_right, PIDS_MEM_RES }, // ul_int EU_MEM
1938 { 7, SK_Kb, A_right, PIDS_MEM_VIRT }, // ul_int EU_VRT
1939 { 6, SK_Kb, A_right, PIDS_VM_SWAP }, // ul_int EU_SWP
1940 { 6, SK_Kb, A_right, PIDS_MEM_RES }, // ul_int EU_RES
1941 { 6, SK_Kb, A_right, PIDS_MEM_CODE }, // ul_int EU_COD
1942 { 7, SK_Kb, A_right, PIDS_MEM_DATA }, // ul_int EU_DAT
1943 { 6, SK_Kb, A_right, PIDS_MEM_SHR }, // ul_int EU_SHR
1944 { 4, -1, A_right, PIDS_FLT_MAJ }, // ul_int EU_FL1
1945 { 4, -1, A_right, PIDS_FLT_MIN }, // ul_int EU_FL2
1946 { 4, -1, A_right, PIDS_noop }, // ul_int EU_DRT ( always 0 w/ since 2.6 )
1947 { 1, -1, A_right, PIDS_STATE }, // s_ch EU_STA
1948 { -1, -1, A_left, PIDS_CMD }, // str EU_CMD
1949 { 10, -1, A_left, PIDS_WCHAN_NAME }, // str EU_WCH
1950 { 8, -1, A_left, PIDS_FLAGS }, // ul_int EU_FLG
1951 { -1, -1, A_left, PIDS_CGROUP }, // str EU_CGR
1952 { -1, -1, A_left, PIDS_SUPGIDS }, // str EU_SGD
1953 { -1, -1, A_left, PIDS_SUPGROUPS }, // str EU_SGN
1954 { 0, -1, A_right, PIDS_ID_TGID }, // s_int EU_TGD
1955 { 5, -1, A_right, PIDS_OOM_ADJ }, // s_int EU_OOA
1956 { 4, -1, A_right, PIDS_OOM_SCORE }, // s_int EU_OOM
1957 { -1, -1, A_left, PIDS_ENVIRON }, // str EU_ENV
1958 { 3, -1, A_right, PIDS_FLT_MAJ_DELTA }, // s_int EU_FV1
1959 { 3, -1, A_right, PIDS_FLT_MIN_DELTA }, // s_int EU_FV2
1960 { 6, SK_Kb, A_right, PIDS_VM_USED }, // ul_int EU_USE
1961 { 10, -1, A_right, PIDS_NS_IPC }, // ul_int EU_NS1
1962 { 10, -1, A_right, PIDS_NS_MNT }, // ul_int EU_NS2
1963 { 10, -1, A_right, PIDS_NS_NET }, // ul_int EU_NS3
1964 { 10, -1, A_right, PIDS_NS_PID }, // ul_int EU_NS4
1965 { 10, -1, A_right, PIDS_NS_USER }, // ul_int EU_NS5
1966 { 10, -1, A_right, PIDS_NS_UTS }, // ul_int EU_NS6
1967 { 8, -1, A_left, PIDS_LXCNAME }, // str EU_LXC
1968 { 6, SK_Kb, A_right, PIDS_VM_RSS_ANON }, // ul_int EU_RZA
1969 { 6, SK_Kb, A_right, PIDS_VM_RSS_FILE }, // ul_int EU_RZF
1970 { 6, SK_Kb, A_right, PIDS_VM_RSS_LOCKED }, // ul_int EU_RZL
1971 { 6, SK_Kb, A_right, PIDS_VM_RSS_SHARED }, // ul_int EU_RZS
1972 { -1, -1, A_left, PIDS_CGNAME }, // str EU_CGN
1973 { 0, -1, A_right, PIDS_PROCESSOR_NODE }, // s_int EU_NMA
1974 { 5, -1, A_right, PIDS_ID_LOGIN }, // s_int EU_LID
1975 { -1, -1, A_left, PIDS_EXE }, // str EU_EXE
1976 { 6, SK_Kb, A_right, PIDS_SMAP_RSS }, // ul_int EU_RSS
1977 { 6, SK_Kb, A_right, PIDS_SMAP_PSS }, // ul_int EU_PSS
1978 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_ANON }, // ul_int EU_PZA
1979 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_FILE }, // ul_int EU_PZF
1980 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_SHMEM }, // ul_int EU_PZS
1981 { 6, SK_Kb, A_right, PIDS_SMAP_PRV_TOTAL }, // ul_int EU_USS
1982 { 6, -1, A_right, PIDS_IO_READ_BYTES }, // ul_int EU_IRB
1983 { 5, -1, A_right, PIDS_IO_READ_OPS }, // ul_int EU_IRO
1984 { 6, -1, A_right, PIDS_IO_WRITE_BYTES }, // ul_int EU_IWB
1985 { 5, -1, A_right, PIDS_IO_WRITE_OPS }, // ul_int EU_IWO
1986 { 5, -1, A_right, PIDS_AUTOGRP_ID }, // s_int EU_AGI
1987 { 4, -1, A_right, PIDS_AUTOGRP_NICE }, // s_int EU_AGN
1988 { 7, -1, A_right, PIDS_TICS_BEGAN }, // ull_int EU_TM3
1989 { 7, -1, A_right, PIDS_TIME_ELAPSED }, // real EU_TM4
1990 { 6, -1, A_right, PIDS_UTILIZATION }, // real EU_CUU
1991 { 7, -1, A_right, PIDS_UTILIZATION_C }, // real EU_CUC
1992 { 10, -1, A_right, PIDS_NS_CGROUP }, // ul_int EU_NS7
1993 { 10, -1, A_right, PIDS_NS_TIME } // ul_int EU_NS8
1994 #define eu_LAST EU_NS8
1995 // xtra Fieldstab 'pseudo pflag' entries for the newlib interface . . . . . . .
1996 #define eu_CMDLINE eu_LAST +1
1997 #define eu_TICS_ALL_C eu_LAST +2
1998 #define eu_ID_FUID eu_LAST +3
1999 #define eu_TREE_HID eu_LAST +4
2000 #define eu_TREE_LVL eu_LAST +5
2001 #define eu_TREE_ADD eu_LAST +6
2002 #define eu_CMDLINE_V eu_LAST +7
2003 #define eu_ENVIRON_V eu_LAST +8
2004 , { -1, -1, -1, PIDS_CMDLINE } // str ( if Show_CMDLIN, eu_CMDLINE )
2005 , { -1, -1, -1, PIDS_TICS_ALL_C } // ull_int ( if Show_CTIMES, eu_TICS_ALL_C )
2006 , { -1, -1, -1, PIDS_ID_FUID } // u_int ( if a usrseltyp, eu_ID_FUID )
2007 , { -1, -1, -1, PIDS_extra } // s_ch ( if Show_FOREST, eu_TREE_HID )
2008 , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_LVL )
2009 , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_ADD )
2010 , { -1, -1, -1, PIDS_CMDLINE_V } // strv ( if Ctrlk, eu_CMDLINE_V )
2011 , { -1, -1, -1, PIDS_ENVIRON_V } // strv ( if CtrlN, eu_ENVIRON_V )
2018 * A calibrate_fields() *Helper* function which refreshes
2019 * all that cached screen geometry plus related variables */
2020 static void adj_geometry (void) {
2021 static size_t pseudo_max = 0;
2022 static int w_set = 0, w_cols = 0, w_rows = 0;
2025 Screen_cols = columns; // <term.h>
2026 Screen_rows = lines; // <term.h>
2028 if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz)
2029 && 0 < wz.ws_col && 0 < wz.ws_row) {
2030 Screen_cols = wz.ws_col;
2031 Screen_rows = wz.ws_row;
2034 #ifndef RMAN_IGNORED
2035 // be crudely tolerant of crude tty emulators
2036 if (Cap_avoid_eol) Screen_cols--;
2039 // we might disappoint some folks (but they'll deserve it)
2040 if (Screen_cols > SCREENMAX) Screen_cols = SCREENMAX;
2041 if (Screen_cols < W_MIN_COL) Screen_cols = W_MIN_COL;
2044 if (Width_mode > 0) // -w with arg, we'll try to honor
2045 w_cols = Width_mode;
2047 if (Width_mode < 0) { // -w without arg, try environment
2048 char *env_columns = getenv("COLUMNS"),
2049 *env_lines = getenv("LINES"),
2051 if (env_columns && *env_columns) {
2053 t = strtol(env_columns, &ep, 0);
2054 if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tc = t;
2055 if (0 < tc) w_cols = (int)tc;
2057 if (env_lines && *env_lines) {
2059 t = strtol(env_lines, &ep, 0);
2060 if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tr = t;
2061 if (0 < tr) w_rows = (int)tr;
2063 if (!w_cols) w_cols = SCREENMAX;
2064 if (w_cols && w_cols < W_MIN_COL) w_cols = W_MIN_COL;
2065 if (w_rows && w_rows < W_MIN_ROW) w_rows = W_MIN_ROW;
2067 if (w_cols > SCREENMAX) w_cols = SCREENMAX;
2071 /* keep our support for output optimization in sync with current reality
2072 note: when we're in Batch mode, we don't really need a Pseudo_screen
2073 and when not Batch, our buffer will contain 1 extra 'line' since
2074 Msg_row is never represented -- but it's nice to have some space
2075 between us and the great-beyond... */
2077 if (w_cols) Screen_cols = w_cols;
2078 Screen_rows = w_rows ? w_rows : INT_MAX;
2079 Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2081 const int max_rows = INT_MAX / (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2082 if (w_cols && w_cols < Screen_cols) Screen_cols = w_cols;
2083 if (w_rows && w_rows < Screen_rows) Screen_rows = w_rows;
2084 if (Screen_rows < 0 || Screen_rows > max_rows) Screen_rows = max_rows;
2085 Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ) * Screen_rows;
2087 // we'll only grow our Pseudo_screen, never shrink it
2088 if (pseudo_max < Pseudo_size) {
2089 pseudo_max = Pseudo_size;
2090 Pseudo_screen = alloc_r(Pseudo_screen, pseudo_max);
2092 // ensure each row is repainted (just in case)
2095 // prepare to customize potential cpu/memory graphs
2096 if (Curwin->rc.double_up) {
2097 int num = (Curwin->rc.double_up + 1);
2098 int pfx = (Curwin->rc.double_up < 2) ? GRAPH_prefix_std : GRAPH_prefix_abv;
2100 Graph_cpus->length = (Screen_cols - (ADJOIN_space * Curwin->rc.double_up) - (num * (pfx + GRAPH_suffix))) / num;
2101 if (Graph_cpus->length > GRAPH_length_max) Graph_cpus->length = GRAPH_length_max;
2102 if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min;
2105 Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix));
2107 Graph_mems->length = (Screen_cols - ADJOIN_space - (2 * (pfx + GRAPH_suffix))) / 2;
2109 if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max;
2110 if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min;
2112 #if !defined(TOG4_MEM_FIX) && !defined(TOG4_MEM_1UP)
2114 #define cpuGRAPH ( GRAPH_prefix_abv + Graph_cpus->length + GRAPH_suffix )
2115 #define nxtGRAPH ( cpuGRAPH + ADJOIN_space )
2118 if (len + nxtGRAPH > GRAPH_length_max) break;
2121 len -= (GRAPH_prefix_abv + ADJOIN_space);
2122 Graph_mems->length = len;
2128 Graph_cpus->length = Screen_cols - (GRAPH_prefix_std + GRAPH_length_max + GRAPH_suffix);
2129 if (Graph_cpus->length >= 0) Graph_cpus->length = GRAPH_length_max;
2130 else Graph_cpus->length = Screen_cols - GRAPH_prefix_std - GRAPH_suffix;
2131 if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min;
2133 Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix));
2134 if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max;
2135 if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min;
2137 Graph_mems->length = Graph_cpus->length;
2140 Graph_cpus->adjust = (float)Graph_cpus->length / 100.0;
2141 Graph_cpus->style = Curwin->rc.graph_cpus;
2143 Graph_mems->adjust = (float)Graph_mems->length / 100.0;
2144 Graph_mems->style = Curwin->rc.graph_mems;
2147 Frames_signal = BREAK_off;
2148 } // end: adj_geometry
2152 * A calibrate_fields() *Helper* function to build the actual
2153 * column headers & ensure necessary item enumerators support */
2154 static void build_headers (void) {
2155 #define ckITEM(f) do { Pids_itms[f] = Fieldstab[f].item; } while (0)
2156 #define ckCMDS(w) do { if (CHKw(w, Show_CMDLIN)) ckITEM(eu_CMDLINE); } while (0)
2165 // ensure fields not visible incur no significant library costs
2166 for (i = 0; i < MAXTBL(Fieldstab); i++)
2167 Pids_itms[i] = PIDS_extra;
2169 ckITEM(EU_PID); // these 2 fields may not display,
2170 ckITEM(EU_STA); // yet we'll always need them both
2171 ckITEM(EU_CMD); // this is used with 'Y' (inspect)
2175 memset((s = w->columnhdr), 0, sizeof(w->columnhdr));
2176 if (Rc.mode_altscr) s = scat(s, fmtmk("%d", w->winnum));
2178 for (i = 0; i < w->maxpflgs; i++) {
2181 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2182 s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_msg));
2183 w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_msg);
2186 if (EU_MAXPFLGS <= f) continue;
2194 // cpu calculations depend on number of threads
2199 // for 'cumulative' times, we'll need equivalent of cutime & cstime
2200 if (CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C);
2205 s = scat(s, utf8_justify(N_col(f)
2206 , VARcol(f) ? w->varcolsz : Fieldstab[f].width
2207 , CHKw(w, Fieldstab[f].align)));
2209 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2210 s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_hdr));
2211 w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_hdr);
2216 // prepare to even out column header lengths...
2217 if (hdrmax + w->hdrcaplen < (x = strlen(w->columnhdr))) hdrmax = x - w->hdrcaplen;
2219 // for 'busy' only processes, we'll need elapsed tics
2220 if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU);
2221 // with forest view mode, we'll need pid, tgid, ppid & start_time...
2222 #ifndef TREE_VCPUOFF
2223 if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); ckITEM(eu_TREE_ADD); }
2225 if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); }
2227 // for 'u/U' filtering we need these too (old top forgot that, oops)
2228 if (w->usrseltyp) { ckITEM(EU_UED); ckITEM(EU_URD); ckITEM(EU_USD); ckITEM(eu_ID_FUID); }
2230 // we must also accommodate an out of view sort field...
2232 if (EU_CMD == f) ckCMDS(w);
2235 // lastly, accommodate any special non-display 'tagged' needs...
2237 while (Bot_item[i] > BOT_DELIMIT) {
2238 ckITEM(Bot_item[i]);
2243 if (Rc.mode_altscr) w = w->next;
2244 } while (w != Curwin);
2247 /* now we can finally even out column header lengths
2248 (we're assuming entire columnhdr was memset to '\0') */
2249 if (Rc.mode_altscr && SCREENMAX > Screen_cols)
2250 for (i = 0; i < GROUPSMAX; i++) {
2252 if (CHKw(w, Show_TASKON))
2253 if (hdrmax + w->hdrcaplen > (x = strlen(w->columnhdr)))
2254 memset(&w->columnhdr[x], ' ', hdrmax + w->hdrcaplen - x);
2260 } // end: build_headers
2264 * This guy coordinates the activities surrounding the maintenance of
2265 * each visible window's columns headers plus item enumerators needed */
2266 static void calibrate_fields (void) {
2271 int i, varcolcnt, len, rc;
2277 w->hdrcaplen = 0; // really only used with USE_X_COLHDR
2278 // build window's pflgsall array, establish upper bounds for maxpflgs
2279 for (i = 0, w->totpflgs = 0; i < EU_MAXPFLGS; i++) {
2283 w->pflgsall[w->totpflgs++] = f;
2285 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2286 w->pflgsall[w->totpflgs++] = EU_XON;
2287 w->pflgsall[w->totpflgs++] = f;
2288 w->pflgsall[w->totpflgs++] = EU_XOF;
2290 w->pflgsall[w->totpflgs++] = f;
2294 if (!w->totpflgs) w->pflgsall[w->totpflgs++] = EU_PID;
2296 /* build a preliminary columns header not to exceed screen width
2297 while accounting for a possible leading window number */
2298 w->varcolsz = varcolcnt = 0;
2299 *(s = w->columnhdr) = '\0';
2300 if (Rc.mode_altscr) s = scat(s, " ");
2301 for (i = 0; i + w->begpflg < w->totpflgs; i++) {
2302 f = w->pflgsall[i + w->begpflg];
2304 #ifndef USE_X_COLHDR
2305 if (EU_MAXPFLGS <= f) continue;
2308 len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2309 // oops, won't fit -- we're outta here...
2310 if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2311 if (VARcol(f)) { ++varcolcnt; w->varcolsz += strlen(h); }
2312 s = scat(s, fmtmk("%*.*s", len, len, h));
2314 #ifndef USE_X_COLHDR
2315 if (i >= 1 && EU_XON == w->procflgs[i - 1]) --i;
2318 /* establish the final maxpflgs and prepare to grow the variable column
2319 heading(s) via varcolsz - it may be a fib if their pflags weren't
2320 encountered, but that's ok because they won't be displayed anyway */
2322 w->varcolsz += Screen_cols - strlen(w->columnhdr);
2323 if (varcolcnt) w->varcolsz /= varcolcnt;
2325 /* establish the field where all remaining fields would still
2326 fit within screen width, including a leading window number */
2327 *(s = w->columnhdr) = '\0';
2328 if (Rc.mode_altscr) s = scat(s, " ");
2330 for (i = w->totpflgs - 1; -1 < i; i--) {
2332 #ifndef USE_X_COLHDR
2333 if (EU_MAXPFLGS <= f) { w->endpflg = i; continue; }
2336 len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2337 if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2338 s = scat(s, fmtmk("%*.*s", len, len, h));
2341 #ifndef USE_X_COLHDR
2342 if (EU_XOF == w->pflgsall[w->endpflg]) ++w->endpflg;
2344 } // end: if (VIZISw(w))
2346 if (Rc.mode_altscr) w = w->next;
2347 } while (w != Curwin);
2351 if ((rc = procps_pids_reset(Pids_ctx, Pids_itms, Pids_itms_tot)))
2352 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
2353 } // end: calibrate_fields
2357 * Display each field represented in the current window's fieldscur
2358 * array along with its description. Mark with bold and a leading
2359 * asterisk those fields associated with the "on" or "active" state.
2361 * Special highlighting will be accorded the "focus" field with such
2362 * highlighting potentially extended to include the description.
2364 * Below is the current Fieldstab space requirement and how
2365 * we apportion it. The xSUFX is considered sacrificial,
2366 * something we can reduce or do without.
2368 * 12345678901234567890123456789012
2369 * * HEADING = Longest Description!
2370 * xPRFX ----------______________________ xSUFX
2371 * ( xPRFX has pos 2 & 10 for 'extending' when at minimums )
2373 * The first 4 screen rows are reserved for explanatory text, and
2374 * the maximum number of columns is Screen_cols / xPRFX + 1 space
2375 * between columns. Thus, for example, with 42 fields a tty will
2376 * still remain useable under these extremes:
2377 * rows columns what's
2378 * tty top tty top displayed
2379 * --- --- --- --- ------------------
2380 * 46 42 10 1 xPRFX only
2381 * 46 42 32 1 full xPRFX + xSUFX
2382 * 6 2 231 21 xPRFX only
2383 * 10 6 231 7 full xPRFX + xSUFX
2385 static void display_fields (int focus, int extend) {
2386 #define mkERR { putp("\n"); putp(N_txt(XTRA_winsize_txt)); return; }
2387 #define mxCOL ( (Screen_cols / 11) > 0 ? (Screen_cols / 11) : 1 )
2389 #define xEQUS 2 // length of suffix beginning '= '
2390 #define xSUFX 22 // total suffix length, incl xEQUS
2391 #define xPRFX (10 + xadd)
2392 #define xTOTL (xPRFX + xSUFX)
2393 static int col_sav, row_sav;
2394 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
2395 int i; // utility int (a row, tot cols, ix)
2396 int smax; // printable width of xSUFX
2397 int xadd = 0; // spacing between data columns
2398 int cmax = Screen_cols; // total data column width
2399 int rmax = Screen_rows - yRSVD; // total useable rows
2401 i = (EU_MAXPFLGS % mxCOL) ? 1 : 0;
2402 if (rmax < i + (EU_MAXPFLGS / mxCOL)) mkERR;
2403 i = EU_MAXPFLGS / rmax;
2404 if (EU_MAXPFLGS % rmax) ++i;
2405 if (i > 1) { cmax /= i; xadd = 1; }
2406 if (cmax > xTOTL) cmax = xTOTL;
2407 smax = cmax - xPRFX;
2408 if (smax < 0) mkERR;
2410 /* we'll go the extra distance to avoid any potential screen flicker
2411 which occurs under some terminal emulators (but it was our fault) */
2412 if (col_sav != Screen_cols || row_sav != Screen_rows) {
2413 col_sav = Screen_cols;
2414 row_sav = Screen_rows;
2419 for (i = 0; i < EU_MAXPFLGS; ++i) {
2420 int b = FLDviz(w, i), x = (i / rmax) * cmax, y = (i % rmax) + yRSVD;
2421 const char *e = (i == focus && extend) ? w->capclr_hdr : "";
2422 FLG_t f = FLDget(w, i);
2423 char sbuf[xSUFX*4]; // 4 = max multi-byte
2426 /* prep sacrificial suffix (allowing for beginning '= ')
2427 note: width passed to 'utf8_embody' may go negative, but he'll be just fine */
2428 snprintf(sbuf, sizeof(sbuf), "= %.*s", utf8_embody(N_fld(f), smax - xEQUS), N_fld(f));
2429 // obtain translated deltas (if any) ...
2430 xcol = utf8_delta(fmtmk("%.*s", utf8_embody(N_col(f), 8), N_col(f)));
2431 xfld = utf8_delta(sbuf + xEQUS); // ignore beginning '= '
2433 PUTT("%s%c%s%s %s%-*.*s%s%s%s %-*.*s%s"
2436 , b ? w->cap_bold : Cap_norm
2438 , i == focus ? w->capclr_hdr : ""
2439 , 8 + xcol, 8 + xcol
2442 , b ? w->cap_bold : ""
2444 , smax + xfld, smax + xfld
2457 } // end: display_fields
2461 * Manage all fields aspects (order/toggle/sort), for all windows. */
2462 static void fields_utility (void) {
2463 #ifndef SCROLLVAR_NO
2464 #define unSCRL { w->begpflg = w->varcolbeg = 0; OFFw(w, Show_HICOLS); }
2466 #define unSCRL { w->begpflg = 0; OFFw(w, Show_HICOLS); }
2468 #define swapEM { int c; unSCRL; c = w->rc.fieldscur[i]; \
2469 w->rc.fieldscur[i] = *p; *p = c; p = &w->rc.fieldscur[i]; }
2470 #define spewFI { int *t; f = w->rc.sortindx; t = msch(w->rc.fieldscur, ENUcvt(f, FLDon), EU_MAXPFLGS); \
2471 if (!t) t = msch(w->rc.fieldscur, ENUcvt(f, FLDoff), EU_MAXPFLGS); \
2472 i = (t) ? (int)(t - w->rc.fieldscur) : 0; }
2473 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
2474 const char *h = NULL;
2485 if (!h) h = N_col(f);
2487 show_special(1, fmtmk(N_unq(FIELD_header_fmt)
2488 , w->grpname, CHKw(w, Show_FOREST) ? N_txt(FOREST_views_txt) : h));
2489 display_fields(i, (p != NULL));
2492 if (Frames_signal) goto signify_that;
2493 key = iokey(IOKEY_ONCE);
2494 if (key < 1) goto signify_that;
2498 if (i > 0) { --i; if (p) swapEM }
2501 if (i + 1 < EU_MAXPFLGS) { ++i; if (p) swapEM }
2508 p = &w->rc.fieldscur[i];
2516 if (!p) i = EU_MAXPFLGS - 1;
2520 if (!p) { FLDtog(w, i); unSCRL }
2524 if (!p && !CHKw(w, Show_FOREST)) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL }
2526 if (!p) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL; OFFw(w, Show_FOREST); }
2531 Curwin = w = ('a' == key) ? w->next : w->prev;
2536 default: // keep gcc happy
2539 } while (key != 'q' && key != kbd_ESC);
2541 // signal that we just corrupted entire screen
2542 Frames_signal = BREAK_screen;
2546 } // end: fields_utility
2550 * This routine takes care of auto sizing field widths
2551 * if/when the user sets Rc.fixed_widest to -1. Along the
2552 * way he reinitializes some things for the next frame. */
2553 static inline void widths_resize (void) {
2556 // next var may also be set by the guys that actually truncate stuff
2558 for (i = 0; i < EU_MAXPFLGS; i++) {
2559 if (Autox_array[i]) {
2560 Fieldstab[i].width++;
2565 // trigger a call to calibrate_fields (via zap_fieldstab)
2566 if (Autox_found) Frames_signal = BREAK_autox;
2567 } // end: widths_resize
2571 * This routine exists just to consolidate most of the messin'
2572 * around with the Fieldstab array and some related stuff. */
2573 static void zap_fieldstab (void) {
2575 #define maX(E) ( (wtab[E].wnls > wtab[E].wmin) \
2576 ? wtab[E].wnls : wtab[E].wmin )
2578 int wmin; // minimum field width (-1 == variable width)
2579 int wnls; // translated header column requirements
2580 int watx; // +1 == non-scalable auto sized columns
2581 } wtab[EU_MAXPFLGS];
2588 Fieldstab[EU_CPN].width = 1;
2589 Fieldstab[EU_NMA].width = 2;
2590 Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2591 = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2592 = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = 5;
2593 if (5 < (digits = (int)procps_pid_length())) {
2594 if (10 < digits) error_exit(N_txt(FAIL_widepid_txt));
2595 Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2596 = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2597 = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = digits;
2600 // identify our non-scalable auto sized columns
2601 wtab[EU_UED].watx = wtab[EU_UEN].watx = wtab[EU_URD].watx
2602 = wtab[EU_URN].watx = wtab[EU_USD].watx = wtab[EU_USN].watx
2603 = wtab[EU_GID].watx = wtab[EU_GRP].watx = wtab[EU_TTY].watx
2604 = wtab[EU_WCH].watx = wtab[EU_NS1].watx = wtab[EU_NS2].watx
2605 = wtab[EU_NS3].watx = wtab[EU_NS4].watx = wtab[EU_NS5].watx
2606 = wtab[EU_NS6].watx = wtab[EU_NS7].watx = wtab[EU_NS8].watx
2607 = wtab[EU_LXC].watx = wtab[EU_LID].watx
2609 /* establish translatable header 'column' requirements
2610 and ensure .width reflects the widest value */
2611 for (i = 0; i < EU_MAXPFLGS; i++) {
2612 wtab[i].wmin = Fieldstab[i].width;
2613 wtab[i].wnls = (int)strlen(N_col(i)) - utf8_delta(N_col(i));
2614 if (wtab[i].wmin != -1)
2615 Fieldstab[i].width = maX(i);
2622 if (Rc.mode_irixps && Cpu_cnt > 1 && !Thread_mode) {
2623 Cpu_pmax = 100.0 * Cpu_cnt;
2624 if (Cpu_cnt > 1000) {
2625 if (Cpu_pmax > 9999999.0) Cpu_pmax = 9999999.0;
2626 } else if (Cpu_cnt > 100) {
2627 if (Cpu_cnt > 999999.0) Cpu_pmax = 999999.0;
2628 } else if (Cpu_cnt > 10) {
2629 if (Cpu_pmax > 99999.0) Cpu_pmax = 99999.0;
2631 if (Cpu_pmax > 999.9) Cpu_pmax = 999.9;
2636 digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2637 if (wtab[EU_CPN].wmin < digits) {
2638 if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2639 wtab[EU_CPN].wmin = digits;
2640 Fieldstab[EU_CPN].width = maX(EU_CPN);
2642 digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2643 if (wtab[EU_NMA].wmin < digits) {
2644 wtab[EU_NMA].wmin = digits;
2645 Fieldstab[EU_NMA].width = maX(EU_NMA);
2648 // and accommodate optional wider non-scalable columns (maybe)
2650 for (i = 0; i < EU_MAXPFLGS; i++) {
2652 Fieldstab[i].width = Rc.fixed_widest ? Rc.fixed_widest + maX(i) : maX(i);
2656 digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2658 if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2659 Fieldstab[EU_CPN].width = digits;
2661 digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2663 Fieldstab[EU_NMA].width = digits;
2665 // and accommodate optional wider non-scalable columns (maybe)
2667 Fieldstab[EU_UED].width = Fieldstab[EU_URD].width
2668 = Fieldstab[EU_USD].width = Fieldstab[EU_GID].width
2669 = Rc.fixed_widest ? 5 + Rc.fixed_widest : 5;
2670 Fieldstab[EU_UEN].width = Fieldstab[EU_URN].width
2671 = Fieldstab[EU_USN].width = Fieldstab[EU_GRP].width
2672 = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2673 Fieldstab[EU_TTY].width = Fieldstab[EU_LXC].width
2674 = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2675 Fieldstab[EU_WCH].width
2676 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2677 // the initial namespace fields
2678 for (i = EU_NS1; i <= EU_NS6; i++)
2680 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2681 // the later namespace additions
2682 for (i = EU_NS7; i <= EU_NS8; i++)
2684 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2688 /* plus user selectable scaling */
2689 Fieldstab[EU_VRT].scale = Fieldstab[EU_SWP].scale
2690 = Fieldstab[EU_RES].scale = Fieldstab[EU_COD].scale
2691 = Fieldstab[EU_DAT].scale = Fieldstab[EU_SHR].scale
2692 = Fieldstab[EU_USE].scale = Fieldstab[EU_RZA].scale
2693 = Fieldstab[EU_RZF].scale = Fieldstab[EU_RZL].scale
2694 = Fieldstab[EU_RZS].scale = Fieldstab[EU_RSS].scale
2695 = Fieldstab[EU_PSS].scale = Fieldstab[EU_PZA].scale
2696 = Fieldstab[EU_PZF].scale = Fieldstab[EU_PZS].scale
2697 = Fieldstab[EU_USS].scale = Rc.task_mscale;
2699 // lastly, ensure we've got proper column headers...
2702 } // end: zap_fieldstab
2704 /*###### Library Interface (as separate threads) #######################*/
2707 * This guy's responsible for interfacing with the library <stat> API
2708 * and reaping all cpu or numa node tics.
2709 * ( his task is now embarassingly small under the new api ) */
2710 static void *cpus_refresh (void *unused) {
2711 enum stat_reap_type which;
2715 sem_wait(&Semaphore_cpus_beg);
2717 which = STAT_REAP_CPUS_ONLY;
2718 if (CHKw(Curwin, View_CPUNOD))
2719 which = STAT_REAP_NUMA_NODES_TOO;
2721 Stat_reap = procps_stat_reap(Stat_ctx, which, Stat_items, MAXTBL(Stat_items));
2723 error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
2724 #ifndef PRETEND0NUMA
2725 // adapt to changes in total numa nodes (assuming it's even possible)
2726 if (Stat_reap->numa->total && Stat_reap->numa->total != Numa_node_tot) {
2727 Numa_node_tot = Stat_reap->numa->total;
2731 if (Stat_reap->cpus->total && Stat_reap->cpus->total != Cpu_cnt) {
2732 Cpu_cnt = Stat_reap->cpus->total;
2739 x = Cpu_cnt - (Cpu_cnt / 4);
2740 for (i = 0; i < Cpu_cnt; i++)
2741 Stat_reap->cpus->stacks[i]->head[stat_COR_TYP].result.s_int = (i < x) ? P_CORE : E_CORE;
2745 sem_post(&Semaphore_cpus_end);
2752 } // end: cpus_refresh
2756 * This serves as our interface to the memory portion of libprocps.
2757 * The sampling frequency is reduced in order to minimize overhead. */
2758 static void *memory_refresh (void *unused) {
2759 static time_t sav_secs;
2764 sem_wait(&Semaphore_memory_beg);
2768 cur_secs = time(NULL);
2770 if (3 <= cur_secs - sav_secs) {
2771 if (!(Mem_stack = procps_meminfo_select(Mem_ctx, Mem_items, MAXTBL(Mem_items))))
2772 error_exit(fmtmk(N_fmt(LIB_errormem_fmt), __LINE__, strerror(errno)));
2773 sav_secs = cur_secs;
2776 sem_post(&Semaphore_memory_end);
2783 } // end: memory_refresh
2787 * This guy's responsible for interfacing with the library <pids> API
2788 * then refreshing the WIN_t ptr arrays, growing them as appropirate. */
2789 static void *tasks_refresh (void *unused) {
2790 #define nALIGN(n,m) (((n + m - 1) / m) * m) // unconditionally align
2791 #define nALGN2(n,m) ((n + m - 1) & ~(m - 1)) // with power of 2 align
2792 #define n_reap Pids_reap->counts->total
2793 static double uptime_sav;
2794 static int n_alloc = -1; // size of windows stacks arrays
2801 sem_wait(&Semaphore_tasks_beg);
2803 procps_uptime(&uptime_cur, NULL);
2804 et = uptime_cur - uptime_sav;
2805 if (et < 0.01) et = 0.005;
2806 uptime_sav = uptime_cur;
2807 // if in Solaris mode, adjust our scaling for all cpus
2808 Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : Cpu_cnt));
2810 what = Thread_mode ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;
2812 what |= PIDS_SELECT_PID;
2813 Pids_reap = procps_pids_select(Pids_ctx, (unsigned *)Monpids, Monpidsidx, what);
2815 Pids_reap = procps_pids_reap(Pids_ctx, what);
2817 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
2819 // now refresh each window's stacks pointer array...
2820 if (n_alloc < n_reap) {
2821 // n_alloc = nALIGN(n_reap, 100);
2822 n_alloc = nALGN2(n_reap, 128);
2823 for (i = 0; i < GROUPSMAX; i++) {
2824 Winstk[i].ppt = alloc_r(Winstk[i].ppt, sizeof(void *) * n_alloc);
2825 memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2828 for (i = 0; i < GROUPSMAX; i++)
2829 memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2832 sem_post(&Semaphore_tasks_end);
2842 } // end: tasks_refresh
2844 /*###### Inspect Other Output ##########################################*/
2847 * HOWTO Extend the top 'inspect' functionality:
2849 * To exploit the 'Y' interactive command, one must add entries to
2850 * the top personal configuration file. Such entries simply reflect
2851 * a file to be read or command/pipeline to be executed whose results
2852 * will then be displayed in a separate scrollable window.
2854 * Entries beginning with a '#' character are ignored, regardless of
2855 * content. Otherwise they consist of the following 3 elements, each
2856 * of which must be separated by a tab character (thus 2 '\t' total):
2857 * type: literal 'file' or 'pipe'
2858 * name: selection shown on the Inspect screen
2859 * fmts: string representing a path or command
2861 * The two types of Inspect entries are not interchangeable.
2862 * Those designated 'file' will be accessed using fopen/fread and must
2863 * reference a single file in the 'fmts' element. Entries specifying
2864 * 'pipe' will employ popen/fread, their 'fmts' element could contain
2865 * many pipelined commands and, none can be interactive.
2867 * Here are some examples of both types of inspection entries.
2868 * The first entry will be ignored due to the initial '#' character.
2869 * For clarity, the pseudo tab depictions (^I) are surrounded by an
2870 * extra space but the actual tabs would not be.
2872 * # pipe ^I Sockets ^I lsof -n -P -i 2>&1
2873 * pipe ^I Open Files ^I lsof -P -p %d 2>&1
2874 * file ^I NUMA Info ^I /proc/%d/numa_maps
2875 * pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr
2877 * Caution: If the output contains unprintable characters they will
2878 * be displayed in either the ^I notation or hexidecimal <FF> form.
2879 * This applies to tab characters as well. So if one wants a more
2880 * accurate display, any tabs should be expanded within the 'fmts'.
2882 * The following example takes what could have been a 'file' entry
2883 * but employs a 'pipe' instead so as to expand the tabs.
2885 * # next would have contained '\t' ...
2886 * # file ^I <your_name> ^I /proc/%d/status
2887 * # but this will eliminate embedded '\t' ...
2888 * pipe ^I <your_name> ^I cat /proc/%d/status | expand -
2890 * Note: If a pipe such as the following was established, one must
2891 * use Ctrl-C to terminate that pipe in order to review the results.
2892 * This is the single occasion where a '^C' will not terminate top.
2894 * pipe ^I Trace ^I /usr/bin/strace -p %d 2>&1
2898 * Our driving table support, the basis for generalized inspection,
2899 * built at startup (if at all) from rcfile or demo entries. */
2901 void (*func)(char *, int); // a pointer to file/pipe/demo function
2902 char *type; // the type of entry ('file' or 'pipe')
2903 char *name; // the selection label for display
2904 char *fmts; // format string to build path or command
2905 int farg; // 1 = '%d' in fmts, 0 = not (future use)
2906 const char *caps; // not really caps, show_special() delim's
2907 char *fstr; // entry's current/active search string
2908 int flen; // above's strlen, without call overhead
2911 int demo; // do NOT save table entries in rcfile
2912 int total; // total I_ent table entries
2913 char *raw; // all entries for 'W', incl '#' & blank
2916 static struct I_struc Inspect;
2918 static char **Insp_p; // pointers to each line start
2919 static int Insp_nl; // total lines, total Insp_p entries
2920 static int Insp_utf8; // treat Insp_buf as translatable, else raw
2921 static char *Insp_buf; // the results from insp_do_file/pipe
2922 static size_t Insp_bufsz; // allocated size of Insp_buf
2923 static size_t Insp_bufrd; // bytes actually in Insp_buf
2924 static struct I_ent *Insp_sel; // currently selected Inspect entry
2926 // Our 'make status line' macro
2927 #define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \
2929 _sz += utf8_delta(txt); \
2930 _p = fmtmk("%-*.*s", _sz, _sz, txt); \
2931 PUTT("%s%s%.*s%s", tg2(0, (Msg_row = 3)), Curwin->capclr_hdr \
2932 , utf8_embody(_p, Screen_cols), _p, Cap_clr_eol); \
2933 putp(Caps_off); fflush(stdout); }
2935 // Our 'row length' macro, equivalent to a strlen() call
2936 #define INSP_RLEN(idx) (int)(Insp_p[idx +1] - Insp_p[idx] -1)
2938 // Our 'busy/working' macro
2939 #define INSP_BUSY(enu) { INSP_MKSL(0, N_txt(enu)) }
2943 * Establish the number of lines present in the Insp_buf glob plus
2944 * build the all important row start array. It is that array that
2945 * others will rely on since we dare not try to use strlen() on what
2946 * is potentially raw binary data. Who knows what some user might
2947 * name as a file or include in a pipeline (scary, ain't it?). */
2948 static void insp_cnt_nl (void) {
2949 char *beg = Insp_buf;
2950 char *cur = Insp_buf;
2951 char *end = Insp_buf + Insp_bufrd + 1;
2958 snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++);
2959 fd = fopen(fn, "w");
2961 fwrite(Insp_buf, 1, Insp_bufrd, fd);
2966 Insp_p = alloc_c(sizeof(char *) * 2);
2968 for (Insp_nl = 0; beg < end; beg++) {
2970 Insp_p[Insp_nl++] = cur;
2971 // keep our array ahead of next potential need (plus the 2 above)
2972 Insp_p = alloc_r(Insp_p, (sizeof(char *) * (Insp_nl +3)));
2976 Insp_p[0] = Insp_buf;
2977 Insp_p[Insp_nl++] = cur;
2978 Insp_p[Insp_nl] = end;
2979 if ((end - cur) == 1) // if there's an eof null delimiter,
2980 --Insp_nl; // don't count it as a new line
2981 } // end: insp_cnt_nl
2984 #ifndef INSP_OFFDEMO
2986 * The pseudo output DEMO utility. */
2987 static void insp_do_demo (char *fmts, int pid) {
2988 (void)fmts; (void)pid;
2989 /* next will put us on a par with the real file/pipe read buffers
2990 ( and also avoid a harmless, but evil sounding, valgrind warning ) */
2991 Insp_bufsz = READMINSZ + strlen(N_txt(YINSP_dstory_txt));
2992 Insp_buf = alloc_c(Insp_bufsz);
2993 Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_dstory_txt));
2995 } // end: insp_do_demo
3000 * The generalized FILE utility. */
3001 static void insp_do_file (char *fmts, int pid) {
3002 char buf[LRGBUFSIZ];
3006 snprintf(buf, sizeof(buf), fmts, pid);
3007 fp = fopen(buf, "r");
3008 rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
3010 if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
3011 , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
3013 } // end: insp_do_file
3017 * The generalized PIPE utility. */
3018 static void insp_do_pipe (char *fmts, int pid) {
3019 char buf[LRGBUFSIZ];
3020 struct sigaction sa;
3024 memset(&sa, 0, sizeof(sa));
3025 sigemptyset(&sa.sa_mask);
3026 sa.sa_handler = SIG_IGN;
3027 sigaction(SIGINT, &sa, NULL);
3029 snprintf(buf, sizeof(buf), fmts, pid);
3030 fp = popen(buf, "r");
3031 rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
3033 if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
3034 , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
3037 sa.sa_handler = sig_endpgm;
3038 sigaction(SIGINT, &sa, NULL);
3039 } // end: insp_do_pipe
3043 * This guy is a *Helper* function serving the following two masters:
3044 * insp_find_str() - find the next Insp_sel->fstr match
3045 * insp_mkrow_... - highlight any Insp_sel->fstr matches in-view
3046 * If Insp_sel->fstr is found in the designated row, he returns the
3047 * offset from the start of the row, otherwise he returns a huge
3048 * integer so traditional fencepost usage can be employed. */
3049 static inline int insp_find_ofs (int col, int row) {
3050 #define begFS (int)(fnd - Insp_p[row])
3051 char *p, *fnd = NULL;
3053 if (Insp_sel->fstr[0]) {
3054 // skip this row, if there's no chance of a match
3055 if (memchr(Insp_p[row], Insp_sel->fstr[0], INSP_RLEN(row))) {
3056 for ( ; col < INSP_RLEN(row); col++) {
3057 if (!*(p = Insp_p[row] + col)) // skip any empty strings
3059 fnd = STRSTR(p, Insp_sel->fstr); // with binary data, each
3060 if (fnd) // row may have '\0'. so
3061 break; // our scans must be done
3062 col += strlen(p); // as individual strings.
3064 if (fnd && fnd < Insp_p[row + 1]) // and, we must watch out
3065 return begFS; // for potential overrun!
3070 } // end: insp_find_ofs
3074 * This guy supports the inspect 'L' and '&' search provisions
3075 * and returns the row and *optimal* column for viewing any match
3076 * ( we'll always opt for left column justification since any )
3077 * ( preceding ctrl chars appropriate an unpredictable amount ) */
3078 static void insp_find_str (int ch, int *col, int *row) {
3079 #define reDUX (found) ? N_txt(WORD_another_txt) : ""
3082 if ((ch == '&' || ch == 'n') && !Insp_sel->fstr[0]) {
3083 show_msg(N_txt(FIND_no_next_txt));
3086 if (ch == 'L' || ch == '/') {
3087 char *str = ioline(N_txt(GET_find_str_txt));
3088 if (*str == kbd_ESC) return;
3089 snprintf(Insp_sel->fstr, FNDBUFSIZ, "%s", str);
3090 Insp_sel->flen = strlen(Insp_sel->fstr);
3093 if (Insp_sel->fstr[0]) {
3096 INSP_BUSY(YINSP_waitin_txt);
3097 for (xx = *col, yy = *row; yy < Insp_nl; ) {
3098 xx = insp_find_ofs(xx, yy);
3099 if (xx < INSP_RLEN(yy)) {
3101 if (xx == *col && yy == *row) { // matched where we were!
3102 ++xx; // ( was the user maybe )
3103 continue; // ( trying to fool us? )
3112 show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Insp_sel->fstr));
3115 } // end: insp_find_str
3119 * This guy is a *Helper* function responsible for positioning a
3120 * single row in the current 'X axis', then displaying the results.
3121 * Along the way, he makes sure control characters and/or unprintable
3122 * characters display in a less-like fashion:
3123 * '^A' for control chars
3124 * '<BC>' for other unprintable stuff
3125 * Those will be highlighted with the current windows's capclr_msg,
3126 * while visible search matches display with capclr_hdr for emphasis.
3127 * ( we hide ugly plumbing in macros to concentrate on the algorithm ) */
3128 static void insp_mkrow_raw (int col, int row) {
3129 #define maxSZ ( Screen_cols - to )
3130 #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } }
3131 #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3132 fr += Insp_sel->flen -1; to += Insp_sel->flen; hicap = 0; }
3133 #ifndef INSP_JUSTNOT
3134 #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3135 PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 2; hicap = 1; }
3136 #define mkUNP { const char *p = fmtmk("<%02X>", uch); \
3137 PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 4; hicap = 1; }
3139 #define mkCTL { if ((to += 2) <= Screen_cols) \
3140 PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; }
3141 #define mkUNP { if ((to += 4) <= Screen_cols) \
3142 PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; }
3144 #define mkSTD { capNO; if (++to <= Screen_cols) { static char _str[2]; \
3145 _str[0] = uch; putp(_str); } }
3146 unsigned char tline[SCREENMAX];
3150 if (col < INSP_RLEN(row))
3151 memcpy(tline, Insp_p[row] + col, sizeof(tline));
3152 else tline[0] = '\n';
3154 for (fr = 0, to = 0, ofs = 0; to < Screen_cols; fr++) {
3156 ofs = insp_find_ofs(col + fr, row);
3157 if (col + fr < ofs) {
3158 unsigned char uch = tline[fr];
3159 if (uch == '\n') break; // a no show (he,he)
3160 if (uch > 126) mkUNP // show as: '<AB>'
3161 else if (uch < 32) mkCTL // show as: '^C'
3162 else mkSTD // a show off (he,he)
3163 } else { mkFND // a big show (he,he)
3166 if (col + fr >= INSP_RLEN(row)) break;
3177 } // end: insp_mkrow_raw
3181 * This guy is a *Helper* function responsible for positioning a
3182 * single row in the current 'X axis' within a multi-byte string
3183 * then displaying the results. Along the way he ensures control
3184 * characters will then be displayed in two positions like '^A'.
3185 * ( assuming they can even get past those 'gettext' utilities ) */
3186 static void insp_mkrow_utf8 (int col, int row) {
3187 #define maxSZ ( Screen_cols - to )
3188 #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3189 fr += Insp_sel->flen; to += Insp_sel->flen; }
3190 #ifndef INSP_JUSTNOT
3191 #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3192 PUTT("%s%.*s%s", Curwin->capclr_msg, maxSZ, p, Caps_off); to += 2; }
3194 #define mkCTL { if ((to += 2) <= Screen_cols) \
3195 PUTT("%s^%c%s", Curwin->capclr_msg, uch + '@', Caps_off); }
3197 #define mkNUL { buf1[0] = ' '; doPUT(buf1) }
3198 #define doPUT(buf) if ((to += cno) <= Screen_cols) putp(buf);
3199 static char buf1[2], buf2[3], buf3[4], buf4[5];
3200 unsigned char tline[BIGBUFSIZ];
3203 col = utf8_proper_col(Insp_p[row], col, 1);
3204 if (col < INSP_RLEN(row))
3205 memcpy(tline, Insp_p[row] + col, sizeof(tline));
3206 else tline[0] = '\n';
3208 for (fr = 0, to = 0, ofs = 0; to < Screen_cols; ) {
3210 ofs = insp_find_ofs(col + fr, row);
3211 if (col + fr < ofs) {
3212 unsigned char uch = tline[fr];
3213 int bno = UTF8_tab[uch];
3214 int cno = utf8_cols(&tline[fr++], bno);
3217 if (uch == '\n') break;
3219 else if (uch == 127) mkNUL
3220 else { buf1[0] = uch; doPUT(buf1) }
3223 buf2[0] = uch; buf2[1] = tline[fr++];
3227 buf3[0] = uch; buf3[1] = tline[fr++]; buf3[2] = tline[fr++];
3231 buf4[0] = uch; buf4[1] = tline[fr++]; buf4[2] = tline[fr++]; buf4[3] = tline[fr++];
3242 if (col + fr >= INSP_RLEN(row)) break;
3251 } // end: insp_mkrow_utf8
3255 * This guy is an insp_view_choice() *Helper* function who displays
3256 * a page worth of of the user's damages. He also creates a status
3257 * line based on maximum digits for the current selection's lines and
3258 * hozizontal position (so it serves to inform, not distract, by
3259 * otherwise being jumpy). */
3260 static inline void insp_show_pgs (int col, int row, int max) {
3261 char buf[SMLBUFSIZ];
3262 void (*mkrow_func)(int, int);
3263 int r = snprintf(buf, sizeof(buf), "%d", Insp_nl);
3264 int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols);
3265 int l = row +1, ls = Insp_nl;
3269 snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt)
3272 , c, col + 1, c, col + Screen_cols
3273 , (unsigned long)Insp_bufrd);
3276 mkrow_func = Insp_utf8 ? insp_mkrow_utf8 : insp_mkrow_raw;
3278 for ( ; max && row < Insp_nl; row++) {
3280 mkrow_func(col, row);
3285 putp(Cap_nl_clreos);
3286 } // end: insp_show_pgs
3290 * This guy is responsible for displaying the Insp_buf contents and
3291 * managing all scrolling/locate requests until the user gives up. */
3292 static int insp_view_choice (struct pids_stack *p) {
3298 #define maxLN (Screen_rows - (Msg_row +1))
3299 #define makHD(b1,b2) { \
3300 snprintf(b1, sizeof(b1), "%d", PID_VAL(EU_PID, s_int, p)); \
3301 snprintf(b2, sizeof(b2), "%s", PID_VAL(EU_CMD, str, p)); }
3302 #define makFS(dst) { if (Insp_sel->flen < 22) \
3303 snprintf(dst, sizeof(dst), "%s", Insp_sel->fstr); \
3304 else snprintf(dst, sizeof(dst), "%.19s...", Insp_sel->fstr); }
3305 char buf[LRGBUFSIZ];
3306 int key, curlin = 0, curcol = 0;
3313 char pid[6], cmd[64];
3315 if (curcol < 0) curcol = 0;
3316 if (curlin >= Insp_nl) curlin = Insp_nl -1;
3317 if (curlin < 0) curlin = 0;
3322 show_special(1, fmtmk(N_unq(YINSP_hdview_fmt)
3323 , pid, cmd, (Insp_sel->fstr[0]) ? buf : " N/A ")); // nls_maybe
3324 insp_show_pgs(curcol, curlin, maxLN);
3326 /* fflush(stdin) didn't do the trick, so we'll just dip a little deeper
3327 lest repeated <Enter> keys produce immediate re-selection in caller */
3328 tcflush(STDIN_FILENO, TCIFLUSH);
3330 if (Frames_signal) goto signify_that;
3331 key = iokey(IOKEY_ONCE);
3332 if (key < 1) goto signify_that;
3335 case kbd_ENTER: // must force new iokey()
3336 key = INT_MAX; // fall through !
3355 curlin -= maxLN -1; // keep 1 line for reference
3359 curlin += maxLN -1; // ditto
3363 curcol = curlin = 0;
3368 curlin = Insp_nl - maxLN;
3375 insp_find_str(key, &curcol, &curlin);
3377 int tmpcol = utf8_proper_col(Insp_p[curlin], curcol, 1);
3378 insp_find_str(key, &tmpcol, &curlin);
3379 curcol = utf8_proper_col(Insp_p[curlin], tmpcol, 0);
3381 // must re-hide cursor in case a prompt for a string makes it huge
3382 putp((Cursor_state = Cap_curs_hide));
3385 snprintf(buf, sizeof(buf), "%s: %s", Insp_sel->type, Insp_sel->fmts);
3386 INSP_MKSL(1, buf); // show an extended SL
3387 if (iokey(IOKEY_ONCE) < 1)
3390 default: // keep gcc happy
3398 } // end: insp_view_choice
3402 * This is the main Inspect routine, responsible for:
3403 * 1) validating the passed pid (required, but not always used)
3404 * 2) presenting/establishing the target selection
3405 * 3) arranging to fill Insp_buf (via the Inspect.tab[?].func)
3406 * 4) invoking insp_view_choice for viewing/scrolling/searching
3407 * 5) cleaning up the dynamically acquired memory afterwards */
3408 static void inspection_utility (int pid) {
3409 #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \
3410 Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \
3411 for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \
3412 snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \
3413 strncat(dst, _s, (sizeof(dst) - 1) - strlen(dst)); } }
3414 char sels[SCREENMAX];
3417 struct pids_stack *p;
3419 for (i = 0, p = NULL; i < PIDSmaxt; i++)
3420 if (pid == PID_VAL(EU_PID, s_int, Curwin->ppt[i])) {
3425 show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid));
3428 // must re-hide cursor since the prompt for a pid made it huge
3429 putp((Cursor_state = Cap_curs_hide));
3438 show_special(1, fmtmk(N_unq(YINSP_hdsels_fmt)
3439 , pid, PID_VAL(EU_CMD, str, Curwin->ppt[i]), sels));
3442 if (Frames_signal) goto signify_that;
3443 if (key == INT_MAX) key = iokey(IOKEY_ONCE);
3444 if (key < 1) goto signify_that;
3451 sel = 0; // fall through !
3453 if (--sel < 0) sel = Inspect.total -1;
3457 sel = Inspect.total; // fall through !
3459 if (++sel >= Inspect.total) sel = 0;
3463 INSP_BUSY(!strcmp("file", Inspect.tab[sel].type)
3464 ? YINSP_waitin_txt : YINSP_workin_txt);
3465 Insp_sel = &Inspect.tab[sel];
3466 Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid);
3467 Insp_utf8 = utf8_delta(Insp_buf);
3468 key = insp_view_choice(p);
3475 } while (key != 'q' && key != kbd_ESC);
3477 // signal that we just corrupted entire screen
3478 Frames_signal = BREAK_screen;
3480 } // end: inspection_utility
3486 /*###### Other Filtering ###############################################*/
3489 * This sructure is hung from a WIN_t when other filtering is active */
3491 struct osel_s *nxt; // the next criteria or NULL.
3492 int (*rel)(const char *, const char *); // relational strings compare
3493 char *(*sel)(const char *, const char *); // for selection str compares
3494 char *raw; // raw user input (dup check)
3495 char *val; // value included or excluded
3496 int ops; // filter delimiter/operation
3497 int inc; // include == 1, exclude == 0
3498 int enu; // field (procflag) to filter
3499 int typ; // typ used to set: rel & sel
3503 * A function to parse, validate and build a single 'other filter' */
3504 static const char *osel_add (WIN_t *q, int ch, char *glob, int push) {
3505 int (*rel)(const char *, const char *);
3506 char *(*sel)(const char *, const char *);
3507 char raw[MEDBUFSIZ], ops, *pval;
3508 struct osel_s *osel;
3519 if (!snprintf(raw, sizeof(raw), "%s", glob))
3521 for (osel = q->osel_1st; osel; ) {
3522 if (!strcmp(osel->raw, raw)) // #1: is criteria duplicate?
3523 return N_txt(OSEL_errdups_txt);
3526 if (*glob != '!') inc = 1; // #2: is it include/exclude?
3527 else { ++glob; inc = 0; }
3529 if (!(pval = strpbrk(glob, "<=>"))) // #3: do we see a delimiter?
3530 return fmtmk(N_fmt(OSEL_errdelm_fmt)
3531 , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3535 for (enu = 0; enu < EU_MAXPFLGS; enu++) // #4: is this a valid field?
3536 if (!STRCMP(N_col(enu), glob)) break;
3537 if (enu == EU_MAXPFLGS)
3538 return fmtmk(N_fmt(XTRA_badflds_fmt), glob);
3540 if (!(*pval)) // #5: did we get some value?
3541 return fmtmk(N_fmt(OSEL_errvalu_fmt)
3542 , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3544 osel = alloc_c(sizeof(struct osel_s));
3549 if (ops == '=') osel->val = alloc_s(pval);
3550 else osel->val = alloc_s(justify_pad(pval, Fieldstab[enu].width, Fieldstab[enu].align));
3553 osel->raw = alloc_s(raw);
3556 // a LIFO queue was used when we're interactive
3557 osel->nxt = q->osel_1st;
3560 // a FIFO queue must be employed for the rcfile
3564 struct osel_s *prev, *walk = q->osel_1st;
3579 * A function to turn off entire other filtering in the given window */
3580 static void osel_clear (WIN_t *q) {
3581 struct osel_s *osel = q->osel_1st;
3584 struct osel_s *nxt = osel->nxt;
3592 } // end: osel_clear
3596 * Determine if there are matching values or relationships among the
3597 * other criteria in this passed window -- it's called from only one
3598 * place, and likely inlined even without the directive */
3599 static inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str) {
3600 struct osel_s *osel = q->osel_1st;
3603 if (osel->enu == enu) {
3605 switch (osel->ops) {
3606 case '<': // '<' needs the r < 0 unless
3607 r = osel->rel(str, osel->val); // '!' which needs an inverse
3608 if ((r >= 0 && osel->inc) || (r < 0 && !osel->inc)) return 0;
3610 case '>': // '>' needs the r > 0 unless
3611 r = osel->rel(str, osel->val); // '!' which needs an inverse
3612 if ((r <= 0 && osel->inc) || (r > 0 && !osel->inc)) return 0;
3615 { char *p = osel->sel(str, osel->val);
3616 if ((!p && osel->inc) || (p && !osel->inc)) return 0;
3624 } // end: osel_matched
3626 /*###### Startup routines ##############################################*/
3629 * No matter what *they* say, we handle the really really BIG and
3630 * IMPORTANT stuff upon which all those lessor functions depend! */
3631 static void before (char *me) {
3632 #define doALL STAT_REAP_NUMA_NODES_TOO
3634 int linux_version_code = procps_linux_version();
3636 atexit(close_stdout);
3638 // setup our program name
3639 Myname = strrchr(me, '/');
3640 if (Myname) ++Myname; else Myname = me;
3642 // accommodate nls/gettext potential translations
3643 // ( must 'setlocale' before our libproc called )
3646 // is /proc mounted?
3647 fatal_proc_unmounted(NULL, 0);
3649 #ifndef OFF_STDERROR
3650 /* there's a chance that damn libnuma may spew to stderr so we gotta
3651 make sure he does not corrupt poor ol' top's first output screen!
3652 Yes, he provides some overridable 'weak' functions to change such
3653 behavior but we can't exploit that since we don't follow a normal
3654 ld route to symbol resolution (we use that dlopen() guy instead)! */
3655 Stderr_save = dup(fileno(stderr));
3656 if (-1 < Stderr_save && freopen("/dev/null", "w", stderr))
3657 ; // avoid -Wunused-result
3660 // establish some cpu particulars
3661 Hertz = procps_hertz_get();
3662 Cpu_States_fmts = N_unq(STATE_lin2x6_fmt);
3663 if (linux_version_code >= LINUX_VERSION(2, 6, 11))
3664 Cpu_States_fmts = N_unq(STATE_lin2x7_fmt);
3666 // get the total cpus (and, if possible, numa node total)
3667 if ((rc = procps_stat_new(&Stat_ctx)))
3668 Restrict_some = Cpu_cnt = 1;
3670 if (!(Stat_reap = procps_stat_reap(Stat_ctx, doALL, Stat_items, MAXTBL(Stat_items))))
3671 error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
3672 #ifndef PRETEND0NUMA
3673 Numa_node_tot = Stat_reap->numa->total;
3675 Cpu_cnt = Stat_reap->cpus->total;
3681 // prepare for memory stats from new library API ...
3682 if ((rc = procps_meminfo_new(&Mem_ctx)))
3685 // establish max depth for newlib pids stack (# of result structs)
3686 Pids_itms = alloc_c(sizeof(enum pids_item) * MAXTBL(Fieldstab));
3688 for (i = 0; i < MAXTBL(Fieldstab); i++)
3689 Pids_itms[i] = PIDS_noop;
3690 Pids_itms_tot = MAXTBL(Fieldstab);
3691 // we will identify specific items in the build_headers() function
3692 if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot)))
3693 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
3695 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
3696 { struct sigaction sa;
3697 Thread_id_main = pthread_self();
3698 /* in case any of our threads have been enabled, they'll inherit this mask
3699 with everything blocked. therefore, signals go to the main thread (us). */
3700 sigfillset(&sa.sa_mask);
3701 pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
3706 if (0 != sem_init(&Semaphore_cpus_beg, 0, 0)
3707 || (0 != sem_init(&Semaphore_cpus_end, 0, 0)))
3708 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3709 if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL))
3710 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3711 pthread_setname_np(Thread_id_cpus, "update cpus");
3714 if (0 != sem_init(&Semaphore_memory_beg, 0, 0)
3715 || (0 != sem_init(&Semaphore_memory_end, 0, 0)))
3716 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3717 if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL))
3718 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3719 pthread_setname_np(Thread_id_memory, "update memory");
3722 if (0 != sem_init(&Semaphore_tasks_beg, 0, 0)
3723 || (0 != sem_init(&Semaphore_tasks_end, 0, 0)))
3724 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3725 if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL))
3726 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3727 pthread_setname_np(Thread_id_tasks, "update tasks");
3729 // lastly, establish support for graphing cpus & memory
3730 Graph_cpus = alloc_c(sizeof(struct graph_parms));
3731 Graph_mems = alloc_c(sizeof(struct graph_parms));
3737 * A configs_file *Helper* function responsible for transorming
3738 * a 3.2.8 - 3.3.17 format 'fieldscur' into our integer based format */
3739 static int cfg_xform (WIN_t *q, char *flds, const char *defs) {
3740 #define CVTon(c) ((c) |= 0x80)
3744 #define old_View_NOBOLD 0x000001
3745 #define old_VISIBLE_tsk 0x000008
3746 #define old_Qsrt_NORMAL 0x000010
3747 #define old_Show_HICOLS 0x000200
3748 #define old_Show_THREAD 0x010000
3749 { old_View_NOBOLD, View_NOBOLD },
3750 { old_VISIBLE_tsk, Show_TASKON },
3751 { old_Qsrt_NORMAL, Qsrt_NORMAL },
3752 { old_Show_HICOLS, Show_HICOLS },
3753 { old_Show_THREAD, 0 }
3754 #undef old_View_NOBOLD
3755 #undef old_VISIBLE_tsk
3756 #undef old_Qsrt_NORMAL
3757 #undef old_Show_HICOLS
3758 #undef old_Show_THREAD
3760 static char null_flds[] = "abcdefghijklmnopqrstuvwxyz";
3761 static const char fields_src[] = CVT_FORMER;
3762 char fields_dst[PFLAGSSIZ], *p1, *p2;
3763 int c, f, i, x, *pn;
3766 // first we'll touch up this window's winflags ...
3769 for (i = 0; i < MAXTBL(flags_tab); i++) {
3770 if (x & flags_tab[i].old) {
3771 x &= ~flags_tab[i].old;
3772 q->rc.winflags |= flags_tab[i].new;
3775 q->rc.winflags |= x;
3777 // now let's convert old top's more limited fields ...
3779 if (f >= CVT_FLDMAX)
3781 strcpy(fields_dst, fields_src);
3782 /* all other fields represent the 'on' state with a capitalized version
3783 of a particular qwerty key. for the 2 additional suse out-of-memory
3784 fields it makes perfect sense to do the exact opposite, doesn't it?
3785 in any case, we must turn them 'off' temporarily ... */
3786 if ((p1 = strchr(flds, '['))) *p1 = '{';
3787 if ((p2 = strchr(flds, '\\'))) *p2 = '|';
3788 for (i = 0; i < f; i++) {
3790 x = tolower(c) - 'a';
3791 if (x < 0 || x >= CVT_FLDMAX)
3793 fields_dst[i] = fields_src[x];
3795 CVTon(fields_dst[i]);
3797 // if we turned any suse only fields off, turn 'em back on OUR way ...
3798 if (p1) CVTon(fields_dst[p1 - flds]);
3799 if (p2) CVTon(fields_dst[p2 - flds]);
3801 // next, we must adjust the old sort field enum ...
3805 if ((p1 = memchr(flds, c, CVT_FLDMAX))
3806 || ((p1 = memchr(flds, toupper(c), CVT_FLDMAX)))) {
3808 q->rc.sortindx = (fields_dst[x] & 0x7f) - FLD_OFFSET;
3810 // now we're in a 3.3.0 format (soon to be transformed) ...
3811 strcpy(flds, fields_dst);
3814 // lastly, let's attend to the 3.3.0 - 3.3.17 fieldcurs format ...
3815 pn = &q->rc.fieldscur[0];
3817 for (i = 0; i < x; i++) {
3818 f = ((unsigned char)flds[i] & 0x7f);
3820 if ((unsigned char)flds[i] & 0x80) f |= FLDon;
3830 * A configs_file *Helper* function responsible for reading
3831 * and validating a configuration file's 'Inspection' entries */
3832 static int config_insp (FILE *fp, char *buf, size_t size) {
3835 // we'll start off with a 'potential' blank or empty line
3836 // ( only realized if we end up with Inspect.total > 0 )
3837 if (!buf[0] || buf[0] != '\n') Inspect.raw = alloc_s("\n");
3838 else Inspect.raw = alloc_c(1);
3841 #define iT(element) Inspect.tab[i].element
3842 #define nxtLINE { buf[0] = '\0'; continue; }
3843 size_t lraw = strlen(Inspect.raw) +1;
3847 if (i < 0 || (size_t)i >= INT_MAX / sizeof(struct I_ent)) break;
3848 if (lraw >= INT_MAX - size) break;
3850 if (!buf[0] && !fgets(buf, size, fp)) break;
3851 lraw += strlen(buf) +1;
3852 Inspect.raw = alloc_r(Inspect.raw, lraw);
3853 strcat(Inspect.raw, buf);
3855 if (buf[0] == '#' || buf[0] == '\n') nxtLINE;
3856 Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_ent) * (i + 1));
3858 // part of this is used in a show_special() call, so let's sanitize it
3859 for (n = 0, x = strlen(buf); n < x; n++) {
3860 if ((buf[n] != '\t' && buf[n] != '\n')
3861 && (buf[n] < ' ')) {
3866 if (!(s1 = strtok(buf, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3867 if (!(s2 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3868 if (!(s3 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3870 switch (toupper(buf[0])) {
3872 iT(func) = insp_do_file;
3875 iT(func) = insp_do_pipe;
3881 iT(type) = alloc_s(s1);
3882 iT(name) = alloc_s(s2);
3883 iT(fmts) = alloc_s(s3);
3884 iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0;
3885 iT(fstr) = alloc_c(FNDBUFSIZ);
3892 } // end: for ('inspect' entries)
3895 #ifndef INSP_OFFDEMO
3896 if (!Inspect.total) {
3897 #define mkS(n) N_txt(YINSP_demo ## n ## _txt)
3898 const char *sels[] = { mkS(01), mkS(02), mkS(03) };
3899 Inspect.total = Inspect.demo = MAXTBL(sels);
3900 Inspect.tab = alloc_c(sizeof(struct I_ent) * Inspect.total);
3901 for (i = 0; i < Inspect.total; i++) {
3902 Inspect.tab[i].type = alloc_s(N_txt(YINSP_deqtyp_txt));
3903 Inspect.tab[i].name = alloc_s(sels[i]);
3904 Inspect.tab[i].func = insp_do_demo;
3905 Inspect.tab[i].fmts = alloc_s(N_txt(YINSP_deqfmt_txt));
3906 Inspect.tab[i].fstr = alloc_c(FNDBUFSIZ);
3912 } // end: config_insp
3916 * A configs_file *Helper* function responsible for reading
3917 * and validating a configuration file's 'Other Filter' entries */
3918 static int config_osel (FILE *fp, char *buf, size_t size) {
3919 int i, ch, tot, wno, begun;
3923 if (!fgets(buf, size, fp)) return 0;
3924 if (buf[0] == '\n') continue;
3925 // whoa, must be an 'inspect' entry
3926 if (!begun && !strstr(buf, Osel_delim_1_txt))
3928 // ok, we're now beginning
3929 if (!begun && strstr(buf, Osel_delim_1_txt)) {
3933 // this marks the end of our stuff
3934 if (begun && strstr(buf, Osel_delim_2_txt))
3937 if (2 != sscanf(buf, Osel_window_fmts, &wno, &tot))
3939 if (wno < 0 || wno >= GROUPSMAX) goto end_oops;
3940 if (tot < 0) goto end_oops;
3942 for (i = 0; i < tot; i++) {
3943 if (!fgets(buf, size, fp)) return 1;
3944 if (1 > sscanf(buf, Osel_filterI_fmt, &ch)) goto end_oops;
3945 if ((p = strchr(buf, '\n'))) *p = '\0';
3946 if (!(p = strstr(buf, OSEL_FILTER))) goto end_oops;
3947 p += sizeof(OSEL_FILTER) - 1;
3948 if (osel_add(&Winstk[wno], ch, p, 0)) goto end_oops;
3951 // let's prime that buf for the next guy...
3952 fgets(buf, size, fp);
3958 } // end: config_osel
3962 * A configs_reads *Helper* function responsible for processing
3963 * a configuration file (personal or system-wide default) */
3964 static const char *configs_file (FILE *fp, const char *name, float *delay) {
3965 char fbuf[LRGBUFSIZ];
3966 int i, n, tmp_whole, tmp_fract;
3967 const char *p = NULL;
3969 p = fmtmk(N_fmt(RC_bad_files_fmt), name);
3970 (void)fgets(fbuf, sizeof(fbuf), fp); // ignore eyecatcher
3972 , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
3973 , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_whole, &tmp_fract, &i)) {
3976 if (Rc.id < 'a' || Rc.id > RCF_VERSION_ID)
3978 if (strchr("bcde", Rc.id))
3980 if (Rc.mode_altscr < 0 || Rc.mode_altscr > 1)
3982 if (Rc.mode_irixps < 0 || Rc.mode_irixps > 1)
3986 // you saw that, right? (fscanf stickin' it to 'i')
3987 if (i < 0 || i >= GROUPSMAX)
3989 Curwin = &Winstk[i];
3990 // this may be ugly, but it keeps us locale independent...
3991 *delay = (float)tmp_whole + (float)tmp_fract / 1000;
3993 for (i = 0 ; i < GROUPSMAX; i++) {
3994 static const char *def_flds[] = { DEF_FORMER, JOB_FORMER, MEM_FORMER, USR_FORMER };
3996 WIN_t *w = &Winstk[i];
3997 p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, name);
3999 if (1 != fscanf(fp, "%3s\tfieldscur=", w->rc.winname))
4001 if (Rc.id < RCF_XFORMED_ID)
4002 fscanf(fp, "%s\n", fbuf);
4005 if (1 != fscanf(fp, "%d", &w->rc.fieldscur[j]))
4009 // be tolerant of missing release 3.3.10 graph modes additions
4010 if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
4011 ", double_up=%d, combine_cpus=%d, core_types=%d\n"
4012 , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems
4013 , &w->rc.double_up, &w->rc.combine_cpus, &w->rc.core_types))
4015 if (w->rc.sortindx < 0 || w->rc.sortindx >= EU_MAXPFLGS)
4017 if (w->rc.maxtasks < 0)
4019 if (w->rc.graph_cpus < 0 || w->rc.graph_cpus > 2)
4021 if (w->rc.graph_mems < 0 || w->rc.graph_mems > 2)
4023 if (w->rc.double_up < 0 || w->rc.double_up >= ADJOIN_limit)
4025 // can't check upper bounds until Cpu_cnt is known
4026 if (w->rc.combine_cpus < 0)
4028 if (w->rc.core_types < 0 || w->rc.core_types > E_CORES_ONLY)
4031 if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
4032 , &w->rc.summclr, &w->rc.msgsclr, &w->rc.headclr, &w->rc.taskclr))
4034 // would prefer to use 'max_colors', but it isn't available yet...
4035 if (w->rc.summclr < 0 || w->rc.summclr > 255) return p;
4036 if (w->rc.msgsclr < 0 || w->rc.msgsclr > 255) return p;
4037 if (w->rc.headclr < 0 || w->rc.headclr > 255) return p;
4038 if (w->rc.taskclr < 0 || w->rc.taskclr > 255) return p;
4041 case 'a': // 3.2.8 (former procps)
4043 case 'f': // 3.3.0 thru 3.3.3 (ng)
4044 SETw(w, Show_JRNUMS);
4046 case 'g': // from 3.3.4 thru 3.3.8
4047 if (Rc.id > 'a') scat(fbuf, RCF_PLUS_H);
4049 case 'h': // this is release 3.3.9
4050 w->rc.graph_cpus = w->rc.graph_mems = 0;
4051 // these next 2 are really global, but best documented here
4052 Rc.summ_mscale = Rc.task_mscale = SK_Kb;
4054 case 'i': // from 3.3.10 thru 3.3.16
4055 if (Rc.id > 'a') scat(fbuf, RCF_PLUS_J);
4056 w->rc.double_up = w->rc.combine_cpus = 0;
4058 case 'j': // this is release 3.3.17
4059 if (cfg_xform(w, fbuf, def_flds[i]))
4063 case 'k': // current RCF_VERSION_ID
4066 if (mlen(w->rc.fieldscur) < EU_MAXPFLGS)
4068 for (x = 0; x < EU_MAXPFLGS; x++) {
4069 FLG_t f = FLDget(w, x);
4070 if (f >= EU_MAXPFLGS || f < 0)
4075 // ensure there's been no manual alteration of fieldscur
4076 for (n = 0 ; n < EU_MAXPFLGS; n++) {
4077 if (&w->rc.fieldscur[n] != msch(w->rc.fieldscur, w->rc.fieldscur[n], EU_MAXPFLGS))
4080 } // end: for (GROUPSMAX)
4082 // any new addition(s) last, for older rcfiles compatibility...
4083 (void)fscanf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
4084 , &Rc.fixed_widest, &Rc.summ_mscale, &Rc.task_mscale, &Rc.zero_suppress, &Rc.tics_scaled);
4085 if (Rc.fixed_widest < -1 || Rc.fixed_widest > SCREENMAX)
4086 Rc.fixed_widest = 0;
4087 if (Rc.summ_mscale < 0 || Rc.summ_mscale > SK_Eb)
4089 if (Rc.task_mscale < 0 || Rc.task_mscale > SK_Pb)
4091 if (Rc.zero_suppress < 0 || Rc.zero_suppress > 1)
4092 Rc.zero_suppress = 0;
4093 if (Rc.tics_scaled < 0 || Rc.tics_scaled > TICS_AS_LAST)
4096 // prepare to warn that older top can no longer read rcfile ...
4097 if (Rc.id != RCF_VERSION_ID)
4098 Rc_compatibilty = 1;
4100 // lastly, let's process any optional glob(s) ...
4101 // (darn, must do osel 1st even though alphabetically 2nd)
4103 config_osel(fp, fbuf, sizeof(fbuf));
4104 config_insp(fp, fbuf, sizeof(fbuf));
4107 } // end: configs_file
4111 * A configs_reads *Helper* function responsible for ensuring the
4112 * complete path was established, otherwise force the 'W' to fail */
4113 static int configs_path (const char *const fmts, ...) __attribute__((format(printf,1,2)));
4114 static int configs_path (const char *const fmts, ...) {
4119 len = vsnprintf(Rc_name, sizeof(Rc_name), fmts, ap);
4121 if (len <= 0 || (size_t)len >= sizeof(Rc_name)) {
4126 } // end: configs_path
4130 * Try reading up to 3 rcfiles
4131 * 1. 'SYS_RCRESTRICT' contains two lines consisting of the secure
4132 * mode switch and an update interval. Its presence limits what
4133 * ordinary users are allowed to do.
4134 * 2. 'Rc_name' contains multiple lines - both global & per window.
4135 * line 1 : an eyecatcher and creating program/alias name
4136 * line 2 : an id, Mode_altcsr, Mode_irixps, Delay_time, Curwin.
4137 * For each of the 4 windows:
4138 * lines a: contains w->winname, fieldscur
4139 * line b: contains w->winflags, sortindx, maxtasks, etc
4140 * line c: contains w->summclr, msgsclr, headclr, taskclr
4141 * global : miscellaneous additional settings
4142 * Any remaining lines are devoted to the optional entries
4143 * supporting the 'Other Filter' and 'Inspect' provisions.
4144 * 3. 'SYS_RCDEFAULTS' system-wide defaults if 'Rc_name' absent
4145 * format is identical to #2 above */
4146 static void configs_reads (void) {
4147 float tmp_delay = DEF_DELAY;
4148 const char *p, *p_home;
4151 fp = fopen(SYS_RCRESTRICT, "r");
4153 char fbuf[SMLBUFSIZ];
4154 if (fgets(fbuf, sizeof(fbuf), fp)) { // sys rc file, line 1
4156 if (fgets(fbuf, sizeof(fbuf), fp)) // sys rc file, line 2
4157 sscanf(fbuf, "%f", &Rc.delay_time);
4162 Rc_name[0] = '\0'; // "fopen() shall fail if pathname is an empty string."
4163 // attempt to use the legacy file first, if we cannot access that file, use
4164 // the new XDG basedir locations (XDG_CONFIG_HOME or HOME/.config) instead.
4165 p_home = getenv("HOME");
4166 if (!p_home || p_home[0] != '/') {
4167 const struct passwd *const pwd = getpwuid(getuid());
4168 if (!pwd || !(p_home = pwd->pw_dir) || p_home[0] != '/') {
4173 configs_path("%s/.%src", p_home, Myname);
4175 if (!(fp = fopen(Rc_name, "r"))) {
4176 p = getenv("XDG_CONFIG_HOME");
4177 // ensure the path we get is absolute, fallback otherwise.
4178 if (!p || p[0] != '/') {
4179 if (!p_home) goto system_default;
4180 p = fmtmk("%s/.config", p_home);
4181 (void)mkdir(p, 0700);
4183 if (!configs_path("%s/procps", p)) goto system_default;
4184 (void)mkdir(Rc_name, 0700);
4185 if (!configs_path("%s/procps/%src", p, Myname)) goto system_default;
4186 fp = fopen(Rc_name, "r");
4190 p = configs_file(fp, Rc_name, &tmp_delay);
4192 if (p) goto default_or_error;
4195 fp = fopen(SYS_RCDEFAULTS, "r");
4197 p = configs_file(fp, SYS_RCDEFAULTS, &tmp_delay);
4199 if (p) goto default_or_error;
4203 // lastly, establish the true runtime secure mode and delay time
4204 if (!getuid()) Secure_mode = 0;
4205 if (!Secure_mode) Rc.delay_time = tmp_delay;
4210 { RCF_t rcdef = DEF_RCFILE;
4213 for (i = 0 ; i < GROUPSMAX; i++)
4214 Winstk[i].rc = Rc.win[i];
4219 } // end: configs_reads
4223 * Parse command line arguments.
4224 * Note: it's assumed that the rc file(s) have already been read
4225 * and our job is to see if any of those options are to be
4226 * overridden -- we'll force some on and negate others in our
4227 * best effort to honor the loser's (oops, user's) wishes... */
4228 static void parse_args (int argc, char **argv) {
4229 static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1";
4230 static const struct option lopts[] = {
4231 { "batch-mode", no_argument, NULL, 'b' },
4232 { "cmdline-toggle", no_argument, NULL, 'c' },
4233 { "delay", required_argument, NULL, 'd' },
4234 { "scale-summary-mem", required_argument, NULL, 'E' },
4235 { "scale-task-mem", required_argument, NULL, 'e' },
4236 { "threads-show", no_argument, NULL, 'H' },
4237 { "help", no_argument, NULL, 'h' },
4238 { "idle-toggle", no_argument, NULL, 'i' },
4239 { "iterations", required_argument, NULL, 'n' },
4240 { "list-fields", no_argument, NULL, 'O' },
4241 { "sort-override", required_argument, NULL, 'o' },
4242 { "pid", required_argument, NULL, 'p' },
4243 { "accum-time-toggle", no_argument, NULL, 'S' },
4244 { "secure-mode", no_argument, NULL, 's' },
4245 { "filter-any-user", required_argument, NULL, 'U' },
4246 { "filter-only-euser", required_argument, NULL, 'u' },
4247 { "version", no_argument, NULL, 'V' },
4248 { "width", optional_argument, NULL, 'w' },
4249 { "single-cpu-toggle", no_argument, NULL, '1' },
4250 { NULL, 0, NULL, 0 }
4252 float tmp_delay = FLT_MAX;
4255 while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) {
4260 #ifndef GETOPTFIX_NO
4261 /* first, let's plug some awful gaps in the getopt implementation,
4262 especially relating to short options with (optional) arguments! */
4263 if (!cp && optind < argc && argv[optind][0] != '-')
4264 cp = argv[optind++];
4266 if (*cp == '=') ++cp;
4267 /* here, if we're actually accessing argv[argc], we'll rely on
4268 the required NULL delimiter which yields an error_exit next */
4269 if (*cp == '\0') cp = argv[optind++];
4270 if (!cp) error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
4274 case '1': // ensure behavior identical to run-time toggle
4275 if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM);
4276 else TOGw(Curwin, View_CPUSUM);
4277 OFFw(Curwin, View_CPUNOD);
4278 SETw(Curwin, View_STATES);
4284 TOGw(Curwin, Show_CMDLIN);
4287 if (!mkfloat(cp, &tmp_delay, 0))
4288 error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp));
4290 error_exit(N_txt(DELAY_badarg_txt));
4293 { const char *get = "kmgtpe", *got;
4294 if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4295 error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4296 Rc.summ_mscale = (int)(got - get);
4299 { const char *get = "kmgtp", *got;
4300 if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4301 error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4302 Rc.task_mscale = (int)(got - get);
4308 puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname));
4311 TOGw(Curwin, Show_IDLEPS);
4312 Curwin->rc.maxtasks = 0;
4315 if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp)
4316 error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp));
4320 for (i = 0; i < EU_MAXPFLGS; i++)
4324 if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; }
4325 else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; }
4326 for (i = 0; i < EU_MAXPFLGS; i++)
4327 if (!STRCMP(cp, N_col(i))) break;
4328 if (i == EU_MAXPFLGS)
4329 error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp));
4330 OFFw(Curwin, Show_FOREST);
4331 Curwin->rc.sortindx = i;
4335 if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4337 if (Monpidsidx >= MONPIDMAX)
4338 error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX));
4339 if (1 != sscanf(cp, "%d", &pid)
4340 || strpbrk(cp, "+-."))
4341 error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp));
4342 if (!pid) pid = getpid();
4343 for (i = 0; i < Monpidsidx; i++)
4344 if (Monpids[i] == pid) goto next_pid;
4345 Monpids[Monpidsidx++] = pid;
4347 if (!(p = strchr(cp, ','))) break;
4352 TOGw(Curwin, Show_CTIMES);
4359 { const char *errmsg;
4360 if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4361 if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg);
4364 puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING));
4368 if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX))
4369 error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp));
4370 Width_mode = (int)tmp;
4373 /* we'll rely on getopt for any error message while
4374 forcing an EXIT_FAILURE with an empty string ... */
4376 } // end: switch (ch)
4377 #ifndef GETOPTFIX_NO
4378 if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp));
4380 } // end: while getopt_long
4383 error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind]));
4385 // fixup delay time, maybe...
4386 if (FLT_MAX > tmp_delay) {
4388 error_exit(N_txt(DELAY_secure_txt));
4389 Rc.delay_time = tmp_delay;
4391 } // end: parse_args
4395 * Establish a robust signals environment */
4396 static void signals_set (void) {
4397 #ifndef SIGRTMAX // not available on hurd, maybe others too
4401 struct sigaction sa;
4403 memset(&sa, 0, sizeof(sa));
4404 sigemptyset(&sa.sa_mask);
4405 // with user position preserved through SIGWINCH, we must avoid SA_RESTART
4407 for (i = SIGRTMAX; i; i--) {
4411 sa.sa_handler = SIG_IGN;
4413 sa.sa_handler = sig_endpgm;
4415 case SIGALRM: case SIGINT: case SIGPIPE:
4416 case SIGQUIT: case SIGTERM: case SIGUSR1:
4418 sa.sa_handler = sig_endpgm;
4420 case SIGTSTP: case SIGTTIN: case SIGTTOU:
4421 sa.sa_handler = sig_paused;
4423 case SIGCONT: case SIGWINCH:
4424 sa.sa_handler = sig_resize;
4427 sa.sa_handler = sig_abexit;
4429 case SIGKILL: case SIGSTOP:
4430 // because uncatchable, fall through
4431 case SIGCHLD: // we can't catch this
4432 continue; // when opening a pipe
4434 sigaction(i, &sa, NULL);
4436 } // end: signals_set
4440 * Set up the terminal attributes */
4441 static void whack_terminal (void) {
4442 static char dummy[] = "dumb";
4443 struct termios tmptty;
4445 // the curses part...
4447 setupterm(dummy, STDOUT_FILENO, NULL);
4451 setupterm(dummy, STDOUT_FILENO, NULL);
4453 setupterm(NULL, STDOUT_FILENO, NULL);
4456 if (-1 == tcgetattr(STDIN_FILENO, &Tty_original))
4457 error_exit(N_txt(FAIL_tty_get_txt));
4458 // ok, haven't really changed anything but we do have our snapshot
4461 // first, a consistent canonical mode for interactive line input
4462 tmptty = Tty_original;
4463 tmptty.c_lflag |= (ECHO | ECHOCTL | ECHOE | ICANON | ISIG);
4464 tmptty.c_lflag &= ~NOFLSH;
4465 tmptty.c_oflag &= ~TAB3;
4466 tmptty.c_iflag |= BRKINT;
4467 tmptty.c_iflag &= ~IGNBRK;
4468 if (key_backspace && 1 == strlen(key_backspace))
4469 tmptty.c_cc[VERASE] = *key_backspace;
4471 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4472 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4473 tcgetattr(STDIN_FILENO, &Tty_tweaked);
4475 // lastly, a nearly raw mode for unsolicited single keystrokes
4476 tmptty.c_lflag &= ~(ECHO | ECHOCTL | ECHOE | ICANON);
4477 tmptty.c_cc[VMIN] = 1;
4478 tmptty.c_cc[VTIME] = 0;
4479 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4480 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4481 tcgetattr(STDIN_FILENO, &Tty_raw);
4483 #ifndef OFF_STDIOLBF
4484 // thanks anyway stdio, but we'll manage buffering at the frame level...
4485 setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf));
4488 // this has the effect of disabling any troublesome scrollback buffer...
4489 if (enter_ca_mode) putp(enter_ca_mode);
4491 // and don't forget to ask iokey to initialize his tinfo_tab
4493 } // end: whack_terminal
4495 /*###### Windows/Field Groups support #################################*/
4498 * Value a window's name and make the associated group name. */
4499 static void win_names (WIN_t *q, const char *name) {
4500 /* note: sprintf/snprintf results are "undefined" when src==dst,
4501 according to C99 & POSIX.1-2001 (thanks adc) */
4502 if (q->rc.winname != name)
4503 snprintf(q->rc.winname, sizeof(q->rc.winname), "%s", name);
4504 snprintf(q->grpname, sizeof(q->grpname), "%d:%s", q->winnum, name);
4509 * This guy just resets (normalizes) a single window
4510 * and he ensures pid monitoring is no longer active. */
4511 static void win_reset (WIN_t *q) {
4512 SETw(q, Show_IDLEPS | Show_TASKON);
4513 #ifndef SCROLLVAR_NO
4514 q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->varcolbeg = q->focus_pid = 0;
4516 q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->focus_pid = 0;
4520 q->findstr[0] = '\0';
4521 q->rc.combine_cpus = 0;
4522 q->rc.core_types = 0;
4524 // these next guys are global, not really windows based
4532 * Display a window/field group (ie. make it "current"). */
4533 static WIN_t *win_select (int ch) {
4534 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4536 /* if there's no ch, it means we're supporting the external interface,
4537 so we must try to get our own darn ch by begging the user... */
4539 show_pmt(N_txt(CHOOSE_group_txt));
4540 if (1 > (ch = iokey(IOKEY_ONCE))) return w;
4543 case 'a': // we don't carry 'a' / 'w' in our
4544 w = w->next; // pmt - they're here for a good
4545 break; // friend of ours -- wins_colors.
4546 case 'w': // (however those letters work via
4547 w = w->prev; // the pmt too but gee, end-loser
4548 break; // should just press the darn key)
4549 case '1': case '2' : case '3': case '4':
4550 w = &Winstk[ch - '1'];
4552 default: // keep gcc happy
4557 } // end: win_select
4561 * Just warn the user when a command can't be honored. */
4562 static int win_warn (int what) {
4565 show_msg(N_txt(DISABLED_cmd_txt));
4568 show_msg(fmtmk(N_fmt(DISABLED_win_fmt), Curwin->grpname));
4570 default: // keep gcc happy
4573 /* we gotta' return false 'cause we're somewhat well known within
4574 macro society, by way of that sassy little tertiary operator... */
4580 * Change colors *Helper* function to save/restore settings;
4581 * ensure colors will show; and rebuild the terminfo strings. */
4582 static void wins_clrhlp (WIN_t *q, int save) {
4583 static int flgssav, summsav, msgssav, headsav, tasksav;
4586 flgssav = q->rc.winflags; summsav = q->rc.summclr;
4587 msgssav = q->rc.msgsclr; headsav = q->rc.headclr; tasksav = q->rc.taskclr;
4588 SETw(q, Show_COLORS);
4590 q->rc.winflags = flgssav; q->rc.summclr = summsav;
4591 q->rc.msgsclr = msgssav; q->rc.headclr = headsav; q->rc.taskclr = tasksav;
4594 } // end: wins_clrhlp
4598 * Change colors used in display */
4599 static void wins_colors (void) {
4600 #define kbdABORT 'q'
4601 #define kbdAPPLY kbd_ENTER
4602 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4603 int clr = w->rc.taskclr, *pclr = &w->rc.taskclr;
4607 if (0 >= max_colors) {
4608 show_msg(N_txt(COLORS_nomap_txt));
4612 putp((Cursor_state = Cap_curs_huge));
4619 // this string is well above ISO C89's minimum requirements!
4620 show_special(1, fmtmk(N_unq(COLOR_custom_fmt)
4622 , CHKw(w, View_NOBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4623 , CHKw(w, Show_COLORS) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4624 , CHKw(w, Show_HIBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4625 , tgt, max_colors, clr, w->grpname));
4629 if (Frames_signal) goto signify_that;
4630 key = iokey(IOKEY_ONCE);
4631 if (key < 1) goto signify_that;
4632 if (key == kbd_ESC) break;
4636 pclr = &w->rc.summclr;
4641 pclr = &w->rc.msgsclr;
4646 pclr = &w->rc.headclr;
4651 pclr = &w->rc.taskclr;
4655 case '0': case '1': case '2': case '3':
4656 case '4': case '5': case '6': case '7':
4662 if (clr >= max_colors) clr = 0;
4667 if (clr < 0) clr = max_colors - 1;
4671 TOGw(w, View_NOBOLD);
4674 TOGw(w, Show_HIBOLD);
4677 TOGw(w, Show_COLORS);
4681 wins_clrhlp((w = win_select(key)), 1);
4682 clr = w->rc.taskclr, pclr = &w->rc.taskclr;
4686 break; // keep gcc happy
4689 } while (key != kbdAPPLY && key != kbdABORT);
4691 if (key == kbdABORT || key == kbd_ESC) wins_clrhlp(w, 0);
4692 // signal that we just corrupted entire screen
4693 Frames_signal = BREAK_screen;
4696 } // end: wins_colors
4700 * Manipulate flag(s) for all our windows. */
4701 static void wins_reflag (int what, int flg) {
4702 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4709 case Flags_SET: // Ummmm, i can't find anybody
4710 SETw(w, flg); // who uses Flags_set ...
4715 default: // keep gcc happy
4718 /* a flag with special significance -- user wants to rebalance
4719 display so we gotta' off some stuff then force on two flags... */
4720 if (EQUWINS_xxx == flg)
4724 } while (w != Curwin);
4725 } // end: wins_reflag
4729 * Set up the raw/incomplete field group windows --
4730 * they'll be finished off after startup completes.
4731 * [ and very likely that will override most/all of our efforts ]
4732 * [ --- life-is-NOT-fair --- ] */
4733 static void wins_stage_1 (void) {
4737 for (i = 0; i < GROUPSMAX; i++) {
4741 w->captab[0] = Cap_norm;
4742 w->captab[1] = Cap_norm;
4743 w->captab[2] = w->cap_bold;
4744 w->captab[3] = w->capclr_sum;
4745 w->captab[4] = w->capclr_msg;
4746 w->captab[5] = w->capclr_pmt;
4747 w->captab[6] = w->capclr_hdr;
4748 w->captab[7] = w->capclr_rowhigh;
4749 w->captab[8] = w->capclr_rownorm;
4754 // fixup the circular chains...
4755 Winstk[GROUPSMAX - 1].next = &Winstk[0];
4756 Winstk[0].prev = &Winstk[GROUPSMAX - 1];
4759 for (i = 1; i < BOT_MSGSMAX; i++)
4760 Msg_tab[i].prev = &Msg_tab[i - 1];
4761 Msg_tab[0].prev = &Msg_tab[BOT_MSGSMAX -1];
4762 } // end: wins_stage_1
4766 * This guy just completes the field group windows after the
4767 * rcfiles have been read and command line arguments parsed.
4768 * And since he's the cabose of startup, he'll also tidy up
4769 * a few final things... */
4770 static void wins_stage_2 (void) {
4773 for (i = 0; i < GROUPSMAX; i++) {
4774 win_names(&Winstk[i], Winstk[i].rc.winname);
4776 Winstk[i].findstr = alloc_c(FNDBUFSIZ);
4777 Winstk[i].findlen = 0;
4778 if (Winstk[i].rc.combine_cpus >= Cpu_cnt)
4779 Winstk[i].rc.combine_cpus = 0;
4780 if (CHKw(&Winstk[i], (View_CPUSUM | View_CPUNOD)))
4781 Winstk[i].rc.double_up = 0;
4784 putp((Cursor_state = Cap_curs_hide));
4786 OFFw(Curwin, View_SCROLL);
4787 signal(SIGHUP, SIG_IGN); // allow running under nohup
4789 // fill in missing Fieldstab members and build each window's columnhdr
4792 // with preserved 'other filters' & command line 'user filters',
4793 // we must ensure that we always have a visible task on row one.
4796 // lastly, initialize a signal set used to throttle one troublesome signal
4797 sigemptyset(&Sigwinch_set);
4799 sigaddset(&Sigwinch_set, SIGWINCH);
4801 } // end: wins_stage_2
4805 * Determine if this task matches the 'u/U' selection
4806 * criteria for a given window */
4807 static inline int wins_usrselect (const WIN_t *q, int idx) {
4808 // a tailored 'results stack value' extractor macro
4809 #define rSv(E) PID_VAL(E, u_int, p)
4810 struct pids_stack *p = q->ppt[idx];
4812 switch (q->usrseltyp) {
4813 case 0: // uid selection inactive
4815 case 'U': // match any uid
4816 if (rSv(EU_URD) == (unsigned)q->usrseluid) return q->usrselflg;
4817 if (rSv(EU_USD) == (unsigned)q->usrseluid) return q->usrselflg;
4818 if (rSv(eu_ID_FUID) == (unsigned)q->usrseluid) return q->usrselflg;
4820 case 'u': // match effective uid
4821 if (rSv(EU_UED) == (unsigned)q->usrseluid) return q->usrselflg;
4823 default: // no match...
4826 return !q->usrselflg;
4828 } // end: wins_usrselect
4830 /*###### Forest View support ###########################################*/
4833 * We try keeping most existing code unaware of these activities |
4834 * ( plus, maintain alphabetical order within carefully chosen ) |
4835 * ( names beginning forest_a, forest_b, forest_c and forest_d ) |
4836 * ( with each name exactly 1 letter more than its predecessor ) | */
4837 static struct pids_stack **Seed_ppt; // temporary win ppt pointer |
4838 static struct pids_stack **Tree_ppt; // forest_begin resizes this |
4839 static int Tree_idx; // frame_make resets to zero |
4840 /* those next two support collapse/expand children. the Hide_pid |
4841 array holds parent pids whose children have been manipulated. |
4842 positive pid values represent parents with collapsed children |
4843 while a negative pid value means children have been expanded. |
4844 ( both of these are managed under the 'keys_task()' routine ) | */
4845 static int *Hide_pid; // collapsible process array |
4846 static int Hide_tot; // total used in above array |
4849 * This little recursive guy was the real forest view workhorse. |
4850 * He fills in the Tree_ppt array and also sets the child indent |
4851 * level which is stored in an 'extra' result struct as a u_int. | */
4852 static void forest_adds (const int self, int level) {
4853 // tailored 'results stack value' extractor macros
4854 #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X])
4855 // if xtra-procps-debug.h active, can't use PID_VAL with assignment
4856 #define rSv_Lvl Tree_ppt[Tree_idx]->head[eu_TREE_LVL].result.s_int
4859 if (Tree_idx < PIDSmaxt) { // immunize against insanity |
4860 if (level > 100) level = 101; // our arbitrary nests limit |
4861 Tree_ppt[Tree_idx] = Seed_ppt[self]; // add this as root or child |
4862 rSv_Lvl = level; // while recording its level |
4865 for (i = 0; i < PIDSmaxt; i++) {
4866 if (i == self) continue;
4868 for (i = self + 1; i < PIDSmaxt; i++) {
4870 if (rSv(EU_PID, self) == rSv(EU_TGD, i)
4871 || (rSv(EU_PID, self) == rSv(EU_PPD, i) && rSv(EU_PID, i) == rSv(EU_TGD, i)))
4872 forest_adds(i, level + 1); // got one child any others?
4877 } // end: forest_adds
4881 * This function is responsible for making that stacks ptr array |
4882 * a forest display in that designated window. After completion, |
4883 * he'll replace that original window ppt array with a specially |
4884 * ordered forest view version. He'll also mark hidden children! | */
4885 static void forest_begin (WIN_t *q) {
4889 Seed_ppt = q->ppt; // avoid passing pointers |
4890 if (!Tree_idx) { // do just once per frame |
4891 if (hwmsav < PIDSmaxt) { // grow, but never shrink |
4893 Tree_ppt = alloc_r(Tree_ppt, sizeof(void *) * hwmsav);
4896 #ifndef TREE_SCANALL
4897 if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt
4898 , PIDS_TICS_BEGAN, PIDS_SORT_ASCEND)))
4899 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
4901 for (i = 0; i < PIDSmaxt; i++) { // avoid hidepid distorts |
4902 if (!PID_VAL(eu_TREE_LVL, s_int, Seed_ppt[i])) // parents lvl 0 |
4903 forest_adds(i, 0); // add parents + children |
4906 /* we use up to three additional 'PIDS_extra' results in our stack |
4907 eu_TREE_HID (s_ch) : where 'x' == collapsed & 'z' == unseen |
4908 eu_TREE_LVL (s_int): where level number is stored (0 - 100) |
4909 eu_TREE_ADD (u_int): where a children's tics stored (maybe) | */
4910 for (i = 0; i < Hide_tot; i++) {
4912 // if have xtra-procps-debug.h, cannot use PID_VAL w/ assignment |
4913 #define rSv(E,T,X) Tree_ppt[X]->head[E].result.T
4914 #define rSv_Pid(X) rSv(EU_PID, s_int, X)
4915 #define rSv_Lvl(X) rSv(eu_TREE_LVL, s_int, X)
4916 #define rSv_Hid(X) rSv(eu_TREE_HID, s_ch, X)
4917 /* next 2 aren't needed if TREE_VCPUOFF but they cost us nothing |
4918 & the EU_CPU slot will now always be present (even if it's 0) | */
4919 #define rSv_Add(X) rSv(eu_TREE_ADD, u_int, X)
4920 #define rSv_Cpu(X) rSv(EU_CPU, u_int, X)
4922 if (Hide_pid[i] > 0) {
4923 for (j = 0; j < PIDSmaxt; j++) {
4924 if (rSv_Pid(j) == Hide_pid[i]) {
4927 int level = rSv_Lvl(parent);
4928 while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > level) {
4931 #ifndef TREE_VCPUOFF
4932 rSv_Add(parent) += rSv_Cpu(j);
4936 /* if any children found (& collapsed) mark the parent |
4937 ( when children aren't found don't negate the pid ) |
4938 ( to prevent future scans since who's to say such ) |
4939 ( tasks will not fork more children in the future ) | */
4940 if (children) rSv_Hid(parent) = 'x';
4941 // this will force a check of next Hide_pid[i], if any |
4945 // if a target task disappeared prevent any further scanning |
4946 if (j == PIDSmaxt) Hide_pid[i] = -Hide_pid[i];
4956 memcpy(Seed_ppt, Tree_ppt, sizeof(void *) * PIDSmaxt);
4957 } // end: forest_begin
4961 * When there's a 'focus_pid' established for a window, this guy |
4962 * determines that window's 'focus_beg' plus 'focus_end' values. |
4963 * But, if the pid can no longer be found, he'll turn off focus! | */
4964 static void forest_config (WIN_t *q) {
4965 // tailored 'results stack value' extractor macro
4966 #define rSv(x) PID_VAL(eu_TREE_LVL, s_int, q->ppt[(x)])
4969 for (i = 0; i < PIDSmaxt; i++) {
4970 if (q->focus_pid == PID_VAL(EU_PID, s_int, q->ppt[i])) {
4977 q->focus_pid = q->begtask = 0;
4980 q->focus_lvl = rSv(i);
4982 while (i+1 < PIDSmaxt && rSv(i+1) > level)
4984 q->focus_end = i + 1; // make 'focus_end' a proper fencpost
4985 // watch out for newly forked/cloned tasks 'above' us ...
4986 if (q->begtask < q->focus_beg) {
4987 q->begtask = q->focus_beg;
4991 // if some task 'above' us ended, try to maintain focus
4992 // ( but allow scrolling when there are many children )
4993 if (q->begtask > q->focus_beg
4994 && (SCREEN_ROWS > (q->focus_end - q->focus_beg))) {
4995 q->begtask = q->focus_beg;
5001 } // end: forest_config
5005 * This guy adds the artwork to either 'cmd' or 'cmdline' values |
5006 * if we are in forest view mode otherwise he just returns them. | */
5007 static inline const char *forest_display (const WIN_t *q, int idx) {
5008 // tailored 'results stack value' extractor macros
5009 #define rSv(E) PID_VAL(E, str, p)
5010 #define rSv_Lvl PID_VAL(eu_TREE_LVL, s_int, p)
5011 #define rSv_Hid PID_VAL(eu_TREE_HID, s_ch, p)
5012 #ifndef SCROLLVAR_NO
5013 static char buf[MAXBUFSIZ];
5015 static char buf[ROWMINSIZ];
5017 struct pids_stack *p = q->ppt[idx];
5018 const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD);
5019 int level = rSv_Lvl;
5023 if (idx >= q->focus_beg && idx < q->focus_end)
5024 level -= q->focus_lvl;
5027 if (!CHKw(q, Show_FOREST) || level == 0) return which;
5028 #ifndef TREE_VWINALL
5029 if (q == Curwin) // note: the following is NOT indented
5031 if (rSv_Hid == 'x') {
5033 snprintf(buf, sizeof(buf), "%*s%s", (4 * level), "`+ ", which);
5035 snprintf(buf, sizeof(buf), "+%*s%s", ((4 * level) - 1), "`- ", which);
5040 snprintf(buf, sizeof(buf), "%400s%s", " + ", which);
5043 #ifndef FOCUS_VIZOFF
5044 if (q->focus_pid) snprintf(buf, sizeof(buf), "|%*s%s", ((4 * level) - 1), "`- ", which);
5045 else snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
5047 snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
5053 } // end: forest_display
5055 /*###### Special Separate Bottom Window support ########################*/
5058 * This guy actually draws the parsed strings |
5059 * including adding a highlight if necessary. | */
5060 static void bot_do (const char *str, int focus) {
5061 char *cap = Cap_norm;
5063 while (*str == ' ') putchar(*(str++));
5068 cap = strchr(str, Bot_sep) ? Curwin->capclr_msg : Cap_reverse;
5070 putp(fmtmk("%s%s%s", cap, str, Cap_norm));
5075 * This guy draws that bottom window's header |
5076 * then parses/arranges to show the contents. |
5077 * ( returns relative # of elements printed ) | */
5078 static int bot_focus_str (const char *hdr, const char *str) {
5079 #define maxRSVD ( Screen_rows - 1 )
5081 char tmp[BIGBUFSIZ];
5085 // we're a little careless with overhead here (it's a one time cost)
5086 memset(Bot_buf, '\0', sizeof(Bot_buf));
5088 if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5089 if (!*str || !strcmp(str, "-")) strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5090 else memccpy(Bot_buf, str, '\0', n);
5091 Bot_rsvd = 1 + BOT_RSVD + ((strlen(Bot_buf) - utf8_delta(Bot_buf)) / Screen_cols);
5092 if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5093 // somewhere down call chain fmtmk() may be used, so we'll old school it
5094 snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5095 , tg2(0, SCREEN_ROWS)
5096 , Curwin->capclr_hdr
5097 , Screen_cols + utf8_delta(hdr)
5101 // now fmtmk is safe to use ...
5102 putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5108 if (!(end = strchr(beg, Bot_sep)))
5109 end = beg + strlen(beg);
5110 if ((n = end - beg) >= sizeof(tmp))
5111 n = sizeof(tmp) - 1;
5112 memccpy(tmp, beg, '\0', n);
5114 bot_do(tmp, (++x == Bot_indx));
5117 while (*beg == ' ') putchar(*(beg++));
5121 } // end: bot_focus_str
5125 * This guy draws that bottom window's header |
5126 * & parses/arranges to show vector contents. |
5127 * ( returns relative # of elements printed ) | */
5128 static int bot_focus_strv (const char *hdr, const char **strv) {
5129 #define maxRSVD ( Screen_rows - 1 )
5131 char tmp[SCREENMAX], *p;
5135 // we're a little careless with overhead here (it's a one time cost)
5136 memset(Bot_buf, '\0', sizeof(Bot_buf));
5137 n = (char *)&strv[0] - strv[0];
5138 if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5139 memcpy(Bot_buf, strv[0], n);
5140 if ((!Bot_buf[0] || !strcmp(Bot_buf, "-")) && n <= sizeof(char *))
5141 strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5142 for (nsav= 0, p = Bot_buf, x = 0; strv[nsav] != NULL; nsav++) {
5143 p += strlen(strv[nsav]) + 1;
5144 if ((p - Bot_buf) >= sizeof(Bot_buf))
5146 x += utf8_delta(strv[nsav]);
5148 n = (p - Bot_buf) - (x + 1);
5149 Bot_rsvd = 1 + BOT_RSVD + (n / Screen_cols);
5150 if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5151 // somewhere down call chain fmtmk() may be used, so we'll old school it
5152 snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5153 , tg2(0, SCREEN_ROWS)
5154 , Curwin->capclr_hdr
5155 , Screen_cols + utf8_delta(hdr)
5159 // now fmtmk is safe to use ...
5160 putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5165 for (i = 0; i < nsav; i++) {
5166 bot_do(p, (++x == Bot_indx));
5172 } // end: bot_focus_strv
5177 enum namespace_type that;
5179 // careful, cgroup & time were late additions ...
5180 { EU_NS7, PROCPS_NS_CGROUP }, { EU_NS1, PROCPS_NS_IPC },
5181 { EU_NS2, PROCPS_NS_MNT }, { EU_NS3, PROCPS_NS_NET },
5182 { EU_NS4, PROCPS_NS_PID }, { EU_NS8, PROCPS_NS_TIME },
5183 { EU_NS5, PROCPS_NS_USER }, { EU_NS6, PROCPS_NS_UTS }
5188 * A helper function that will gather various |
5189 * stuff for dislay by the bot_item_show guy. | */
5190 static void *bot_item_hlp (struct pids_stack *p) {
5191 static char buf[BIGBUFSIZ];
5192 char tmp[SMLBUFSIZ], *b;
5198 *(b = &buf[0]) = '\0';
5202 b = scat(b, m->msg);
5203 if (m != Msg_this && m->prev->msg[0]) {
5204 // caller itself may have used fmtmk, so we'll old school it ...
5205 snprintf(tmp, sizeof(tmp), "%c ", BOT_SEP_SMI);
5210 } while (m != Msg_this->prev);
5213 *(b = &buf[0]) = '\0';
5214 for (i = 0; i < MAXTBL(ns_tab); i++) {
5215 // caller itself may have used fmtmk, so we'll old school it ...
5216 snprintf(tmp, sizeof(tmp), "%s: %-10lu"
5217 , procps_ns_get_name(ns_tab[i].that)
5218 , PID_VAL(ns_tab[i].this, ul_int, p));
5220 if (i < (MAXTBL(ns_tab) - 1)) b = scat(b, ", ");
5225 return p->head[Bot_item[0]].result.strv;
5227 return p->head[Bot_item[0]].result.str;
5229 } // end: bot_item_hlp
5233 * This guy manages that bottom margin window |
5234 * which shows various process related stuff. | */
5235 static void bot_item_show (void) {
5236 #define mkHDR fmtmk(Bot_head, Bot_task, PID_VAL(EU_CMD, str, p))
5237 struct pids_stack *p;
5240 for (i = 0; i < PIDSmaxt; i++) {
5242 if (Bot_task == PID_VAL(EU_PID, s_int, p))
5246 Bot_focus_func(mkHDR, bot_item_hlp(p));
5255 } // end: bot_item_show
5259 * This guy can toggle between displaying the |
5260 * bottom window or arranging to turn it off. | */
5261 static void bot_item_toggle (int what, const char *head, char sep) {
5264 // if already targeted, assume user wants to turn it off ...
5265 if (Bot_what == what) {
5268 // accommodate transition from larger to smaller window
5272 for (i = 0; i < MAXTBL(ns_tab); i++)
5273 Bot_item[i] = ns_tab[i].this;
5274 Bot_item[i] = BOT_DELIMIT;
5275 Bot_focus_func = (BOT_f)bot_focus_str;
5280 Bot_item[1] = BOT_DELIMIT;
5281 Bot_focus_func = (BOT_f)bot_focus_strv;
5285 Bot_item[1] = BOT_DELIMIT;
5286 Bot_focus_func = (BOT_f)bot_focus_str;
5291 Bot_indx = BOT_UNFOCUS;
5292 Bot_head = (char *)head;
5293 Bot_show_func = bot_item_show;
5294 Bot_task = PID_VAL(EU_PID, s_int, Curwin->ppt[Curwin->begtask]);
5296 } // end: bot_item_toggle
5298 /*###### Interactive Input Tertiary support ############################*/
5301 * This section exists so as to offer some function naming freedom
5302 * while also maintaining the strict alphabetical order protocol
5303 * within each section. */
5306 * This guy is a *Helper* function serving the following two masters:
5307 * find_string() - find the next match in a given window
5308 * task_show() - highlight all matches currently in-view
5309 * If q->findstr is found in the designated buffer, he returns the
5310 * offset from the start of the buffer, otherwise he returns -1. */
5311 static inline int find_ofs (const WIN_t *q, const char *buf) {
5314 if (q->findstr[0] && (fnd = STRSTR(buf, q->findstr)))
5315 return (int)(fnd - buf);
5321 /* This is currently the only true prototype required by top.
5322 It is placed here, instead of top.h, to avoid one compiler
5323 warning when the top_nls.c source was compiled separately. */
5324 static const char *task_show (const WIN_t *q, int idx);
5326 static void find_string (int ch) {
5327 #define reDUX (found) ? N_txt(WORD_another_txt) : ""
5331 if ('&' == ch && !Curwin->findstr[0]) {
5332 show_msg(N_txt(FIND_no_next_txt));
5336 char *str = ioline(N_txt(GET_find_str_txt));
5337 if (*str == kbd_ESC) return;
5338 snprintf(Curwin->findstr, FNDBUFSIZ, "%s", str);
5339 Curwin->findlen = strlen(Curwin->findstr);
5342 if (Curwin->findstr[0]) {
5343 SETw(Curwin, NOPRINT_xxx);
5344 for (i = Curwin->begtask; i < PIDSmaxt; i++) {
5345 const char *row = task_show(Curwin, i);
5346 if (*row && -1 < find_ofs(Curwin, row)) {
5348 if (i == Curwin->begtask) continue;
5349 Curwin->begtask = i;
5353 show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Curwin->findstr));
5356 } // end: find_string
5359 static void help_view (void) {
5360 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5363 putp((Cursor_state = Cap_curs_huge));
5368 show_special(1, fmtmk(N_unq(KEYS_helpbas_fmt)
5371 , CHKw(w, Show_CTIMES) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5373 , Secure_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5374 , Secure_mode ? "" : N_unq(KEYS_helpext_fmt)));
5378 if (Frames_signal) goto signify_that;
5379 key = iokey(IOKEY_ONCE);
5380 if (key < 1) goto signify_that;
5383 // these keys serve the primary help screen
5384 case kbd_ESC: case 'q':
5386 case '?': case 'h': case 'H':
5391 show_special(1, fmtmk(N_unq(WINDOWS_help_fmt)
5393 , Winstk[0].rc.winname, Winstk[1].rc.winname
5394 , Winstk[2].rc.winname, Winstk[3].rc.winname));
5397 if (Frames_signal || (key = iokey(IOKEY_ONCE)) < 1)
5399 else w = win_select(key);
5400 // these keys serve the secondary help screen
5401 } while (key != kbd_ENTER && key != kbd_ESC && key != 'q');
5406 // signal that we just corrupted entire screen
5407 Frames_signal = BREAK_screen;
5411 static void other_filters (int ch) {
5412 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5413 const char *txt, *p;
5419 if (ch == 'o') txt = N_txt(OSEL_casenot_txt);
5420 else txt = N_txt(OSEL_caseyes_txt);
5421 glob = ioline(fmtmk(N_fmt(OSEL_prompts_fmt), w->osel_tot + 1, txt));
5422 if (*glob == kbd_ESC || *glob == '\0')
5424 if ((p = osel_add(w, ch, glob, 1))) {
5431 char buf[SCREENMAX], **pp;
5432 struct osel_s *osel;
5437 pp = alloc_c((w->osel_tot + 1) * sizeof(char *));
5438 while (osel && i < w->osel_tot) {
5439 pp[i++] = osel->raw;
5444 strncat(buf, fmtmk("%s'%s'", " + " , pp[--i]), sizeof(buf) - (strlen(buf) + 1));
5445 if (buf[0]) p = buf + strspn(buf, " + ");
5446 else p = N_txt(WORD_noneone_txt);
5447 ioline(fmtmk(N_fmt(OSEL_statlin_fmt), p));
5451 default: // keep gcc happy
5454 } // end: other_filters
5457 static void write_rcfile (void) {
5462 show_pmt(N_txt(XTRA_warncfg_txt));
5463 if ('y' != tolower(iokey(IOKEY_ONCE)))
5467 if (Rc_compatibilty) {
5468 show_pmt(N_txt(XTRA_warnold_txt));
5469 if ('y' != tolower(iokey(IOKEY_ONCE)))
5471 Rc_compatibilty = 0;
5473 if (!(fp = fopen(Rc_name, "w"))) {
5474 show_msg(fmtmk(N_fmt(FAIL_rc_open_fmt), Rc_name, strerror(errno)));
5477 fprintf(fp, "%s's " RCF_EYECATCHER, Myname);
5478 fprintf(fp, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
5480 , Rc.mode_altscr, Rc.mode_irixps
5481 // this may be ugly, but it keeps us locale independent...
5482 , (int)Rc.delay_time, (int)((Rc.delay_time - (int)Rc.delay_time) * 1000)
5483 , (int)(Curwin - Winstk));
5485 for (i = 0 ; i < GROUPSMAX; i++) {
5486 n = mlen(Winstk[i].rc.fieldscur);
5487 fprintf(fp, "%s\tfieldscur=", Winstk[i].rc.winname);
5488 for (j = 0; j < n; j++) {
5489 if (j && !(j % FLD_ROWMAX) && j < n)
5490 fprintf(fp, "\n\t\t ");
5491 fprintf(fp, "%4d ", (int)Winstk[i].rc.fieldscur[j]);
5494 fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
5495 ", double_up=%d, combine_cpus=%d, core_types=%d\n"
5496 , Winstk[i].rc.winflags, Winstk[i].rc.sortindx, Winstk[i].rc.maxtasks
5497 , Winstk[i].rc.graph_cpus, Winstk[i].rc.graph_mems, Winstk[i].rc.double_up
5498 , Winstk[i].rc.combine_cpus, Winstk[i].rc.core_types);
5499 fprintf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
5500 , Winstk[i].rc.summclr, Winstk[i].rc.msgsclr
5501 , Winstk[i].rc.headclr, Winstk[i].rc.taskclr);
5504 // any new addition(s) last, for older rcfiles compatibility...
5505 fprintf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
5506 , Rc.fixed_widest, Rc.summ_mscale, Rc.task_mscale, Rc.zero_suppress, Rc.tics_scaled);
5508 if (Winstk[0].osel_tot + Winstk[1].osel_tot
5509 + Winstk[2].osel_tot + Winstk[3].osel_tot) {
5511 fprintf(fp, Osel_delim_1_txt);
5512 for (i = 0 ; i < GROUPSMAX; i++) {
5513 struct osel_s *osel = Winstk[i].osel_1st;
5515 fprintf(fp, Osel_window_fmts, i, Winstk[i].osel_tot);
5517 fprintf(fp, Osel_filterO_fmt, osel->typ, osel->raw);
5522 fprintf(fp, Osel_delim_2_txt);
5525 if (Inspect.raw && strcmp(Inspect.raw, "\n"))
5526 fputs(Inspect.raw, fp);
5529 show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name));
5530 } // end: write_rcfile
5532 /*###### Interactive Input Secondary support (do_key helpers) ##########*/
5535 * These routines exist just to keep the do_key() function
5536 * a reasonably modest size. */
5538 static void keys_global (int ch) {
5539 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5540 int i, num, def, pid;
5548 TOGw(w, View_NOBOLD);
5554 show_msg(N_txt(NOT_onsecure_txt));
5557 get_float(fmtmk(N_fmt(DELAY_change_fmt), Rc.delay_time));
5558 if (tmp > -1) Rc.delay_time = tmp;
5562 if (++Rc.summ_mscale > SK_Eb) Rc.summ_mscale = SK_Kb;
5565 if (++Rc.task_mscale > SK_Pb) Rc.task_mscale = SK_Kb;
5574 Thread_mode = !Thread_mode;
5575 if (!CHKw(w, View_STATES))
5576 show_msg(fmtmk(N_fmt(THREADS_show_fmt)
5577 , Thread_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5578 for (i = 0 ; i < GROUPSMAX; i++)
5579 Winstk[i].begtask = Winstk[i].focus_pid = 0;
5580 // force an extra procs refresh to avoid %cpu distortions...
5581 Pseudo_row = PROC_XTRA;
5585 Rc.mode_irixps = !Rc.mode_irixps;
5586 show_msg(fmtmk(N_fmt(IRIX_curmode_fmt)
5587 , Rc.mode_irixps ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5589 show_msg(N_txt(NOT_smp_cpus_txt));
5593 show_msg(N_txt(NOT_onsecure_txt));
5596 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5597 pid = get_int(fmtmk(N_txt(GET_pid2kill_fmt), def));
5598 if (pid > GET_NUM_ESC) {
5600 if (pid == GET_NUM_NOT) pid = def;
5601 str = ioline(fmtmk(N_fmt(GET_sigs_num_fmt), pid, SIGTERM));
5602 if (*str != kbd_ESC) {
5603 if (*str) num = signal_name_to_number(str);
5604 if (Frames_signal) break;
5605 if (0 < num && kill(pid, num))
5606 show_msg(fmtmk(N_fmt(FAIL_signals_fmt)
5607 , pid, num, strerror(errno)));
5608 else if (0 > num) show_msg(N_txt(BAD_signalid_txt));
5615 show_msg(N_txt(NOT_onsecure_txt));
5617 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5618 pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5619 if (pid > GET_NUM_ESC) {
5620 if (pid == GET_NUM_NOT) pid = def;
5621 num = get_int(fmtmk(N_fmt(GET_nice_num_fmt), pid));
5622 if (num > GET_NUM_NOT
5623 && setpriority(PRIO_PROCESS, (unsigned)pid, num))
5624 show_msg(fmtmk(N_fmt(FAIL_re_nice_fmt)
5625 , pid, num, strerror(errno)));
5630 num = get_int(fmtmk(N_fmt(XTRA_fixwide_fmt), Rc.fixed_widest));
5631 if (num > GET_NUM_NOT) {
5632 if (num >= 0 && num <= SCREENMAX) Rc.fixed_widest = num;
5633 else Rc.fixed_widest = -1;
5638 #ifndef INSP_OFFDEMO
5639 ioline(N_txt(YINSP_noent1_txt));
5641 ioline(N_txt(YINSP_noent2_txt));
5644 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5645 pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def));
5646 if (pid > GET_NUM_ESC) {
5647 if (pid == GET_NUM_NOT) pid = def;
5648 if (pid) inspection_utility(pid);
5656 Rc.zero_suppress = !Rc.zero_suppress;
5659 #ifndef SCALE_FORMER
5661 if (Rc.tics_scaled > TICS_AS_LAST)
5666 bot_item_toggle(EU_CGR, N_fmt(X_BOT_ctlgrp_fmt), BOT_SEP_SLS);
5671 if (Bot_indx > Bot_focus_func(NULL, NULL))
5672 Bot_indx = BOT_UNFOCUS;
5676 // with string vectors, the 'separator' may serve a different purpose
5677 bot_item_toggle(eu_CMDLINE_V, N_fmt(X_BOT_cmdlin_fmt), BOT_SEP_SPC);
5680 bot_item_toggle(BOT_MSG_LOG, N_txt(X_BOT_msglog_txt), BOT_SEP_SMI);
5683 // with string vectors, the 'separator' may serve a different purpose
5684 bot_item_toggle(eu_ENVIRON_V, N_fmt(X_BOT_envirn_fmt), BOT_SEP_SPC);
5687 bot_item_toggle(BOT_ITEM_NS, N_fmt(X_BOT_namesp_fmt), BOT_SEP_CMA);
5691 show_msg(N_txt(NOT_onsecure_txt));
5693 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5694 pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5695 if (pid > GET_NUM_ESC) {
5697 if (pid == GET_NUM_NOT) pid = def;
5698 num = get_int(fmtmk(N_fmt(AGNI_valueof_fmt), pid));
5699 if (num > GET_NUM_NOT) {
5700 if (num < -20 || num > +19)
5701 show_msg(N_txt(AGNI_invalid_txt));
5702 else if (0 > (fd = open(fmtmk("/proc/%d/autogroup", pid), O_WRONLY)))
5703 show_msg(fmtmk(N_fmt(AGNI_notopen_fmt), strerror(errno)));
5705 char buf[TNYBUFSIZ];
5706 snprintf(buf, sizeof(buf), "%d", num);
5707 if (0 >= write(fd, buf, strlen(buf)))
5708 show_msg(fmtmk(N_fmt(AGNI_nowrite_fmt), strerror(errno)));
5716 bot_item_toggle(EU_SGN, N_fmt(X_BOT_supgrp_fmt), BOT_SEP_CMA);
5721 num = Bot_focus_func(NULL, NULL);
5722 if (Bot_indx <= BOT_UNFOCUS)
5726 case kbd_ENTER: // these two have the effect of waking us
5727 case kbd_SPACE: // from 'pselect', refreshing the display
5728 break; // and updating any hot-plugged resources
5729 default: // keep gcc happy
5732 } // end: keys_global
5735 static void keys_summary (int ch) {
5736 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5738 if (Restrict_some && ch != 'C') {
5739 show_msg(N_txt(X_RESTRICTED_txt));
5744 if (CHKw(w, View_CPUSUM) || CHKw(w, View_CPUNOD))
5745 show_msg(N_txt(XTRA_modebad_txt));
5747 if (!w->rc.combine_cpus) w->rc.combine_cpus = 1;
5748 else w->rc.combine_cpus *= 2;
5749 if (w->rc.combine_cpus >= Cpu_cnt) w->rc.combine_cpus = 0;
5750 w->rc.core_types = 0;
5754 if (CHKw(w, View_CPUNOD)) OFFw(w, View_CPUSUM);
5755 else TOGw(w, View_CPUSUM);
5756 OFFw(w, View_CPUNOD);
5757 SETw(w, View_STATES);
5758 w->rc.double_up = 0;
5759 w->rc.core_types = 0;
5763 show_msg(N_txt(NUMA_nodenot_txt));
5765 if (Numa_node_sel < 0) TOGw(w, View_CPUNOD);
5766 if (!CHKw(w, View_CPUNOD)) SETw(w, View_CPUSUM);
5767 SETw(w, View_STATES);
5769 w->rc.double_up = 0;
5770 w->rc.core_types = 0;
5775 show_msg(N_txt(NUMA_nodenot_txt));
5777 int num = get_int(fmtmk(N_fmt(NUMA_nodeget_fmt), Numa_node_tot -1));
5778 if (num > GET_NUM_NOT) {
5779 if (num >= 0 && num < Numa_node_tot) {
5780 Numa_node_sel = num;
5781 SETw(w, View_CPUNOD | View_STATES);
5782 OFFw(w, View_CPUSUM);
5783 w->rc.double_up = 0;
5784 w->rc.core_types = 0;
5786 show_msg(N_txt(NUMA_nodebad_txt));
5791 w->rc.double_up += 1;
5792 if ((w->rc.double_up >= ADJOIN_limit)
5793 || ((w->rc.double_up >= Cpu_cnt)))
5794 w->rc.double_up = 0;
5795 if ((w->rc.double_up > 1)
5796 && (!w->rc.graph_cpus))
5797 w->rc.double_up = 0;
5798 OFFw(w, (View_CPUSUM | View_CPUNOD));
5800 #ifndef CORE_TYPE_NO
5802 if (!CHKw(w, View_STATES)
5803 || ((CHKw(w, View_CPUSUM | View_CPUNOD))
5804 || ((w->rc.combine_cpus))))
5805 show_msg(N_txt(XTRA_modebad_txt));
5808 for (scanned = 0; scanned < Cpu_cnt; scanned++)
5809 if (CPU_VAL(stat_COR_TYP, scanned) == E_CORE)
5811 if (scanned < Cpu_cnt) {
5812 w->rc.core_types += 1;
5813 if (w->rc.core_types > E_CORES_ONLY)
5814 w->rc.core_types = 0;
5815 } else w->rc.core_types = 0;
5820 VIZTOGw(w, View_SCROLL);
5823 TOGw(w, View_LOADAV);
5826 if (!CHKw(w, View_MEMORY))
5827 SETw(w, View_MEMORY);
5828 else if (++w->rc.graph_mems > 2) {
5829 w->rc.graph_mems = 0;
5830 OFFw(w, View_MEMORY);
5834 if (!CHKw(w, View_STATES))
5835 SETw(w, View_STATES);
5836 else if (++w->rc.graph_cpus > 2) {
5837 w->rc.graph_cpus = 0;
5838 OFFw(w, View_STATES);
5840 if ((w->rc.double_up > 1)
5841 && (!w->rc.graph_cpus))
5842 w->rc.double_up = 0;
5844 default: // keep gcc happy
5847 } // end: keys_summary
5850 static void keys_task (int ch) {
5851 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5857 int num = get_int(fmtmk(N_fmt(GET_max_task_fmt), w->rc.maxtasks));
5858 if (num > GET_NUM_NOT) {
5859 if (-1 < num ) w->rc.maxtasks = num;
5860 else show_msg(N_txt(BAD_max_task_txt));
5866 if (CHKw(w, Show_FOREST)) break;
5869 FLG_t *p = w->procflgs + w->maxpflgs - 1;
5870 while (p > w->procflgs && *p != w->rc.sortindx) --p;
5871 if (*p == w->rc.sortindx) {
5873 #ifndef USE_X_COLHDR
5874 if (EU_MAXPFLGS < *p) --p;
5876 if (p >= w->procflgs) {
5877 w->rc.sortindx = *p;
5878 #ifndef TREE_NORESET
5879 OFFw(w, Show_FOREST);
5887 if (CHKw(w, Show_FOREST)) break;
5890 FLG_t *p = w->procflgs + w->maxpflgs - 1;
5891 while (p > w->procflgs && *p != w->rc.sortindx) --p;
5892 if (*p == w->rc.sortindx) {
5894 #ifndef USE_X_COLHDR
5895 if (EU_MAXPFLGS < *p) ++p;
5897 if (p < w->procflgs + w->maxpflgs) {
5898 w->rc.sortindx = *p;
5899 #ifndef TREE_NORESET
5900 OFFw(w, Show_FOREST);
5907 TOGw(w, Show_HIBOLD);
5911 VIZTOGw(w, Show_CMDLIN);
5915 if (CHKw(w, Show_FOREST)) {
5916 int n = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5917 if (w->focus_pid == n) w->focus_pid = 0;
5918 else w->focus_pid = n;
5923 { static WIN_t *w_sav;
5925 if (w_sav != w) { beg_sav = 0; w_sav = w; }
5926 if (CHKw(w, Show_IDLEPS)) { beg_sav = w->begtask; w->begtask = 0; }
5927 else { w->begtask = beg_sav; beg_sav = 0; }
5929 VIZTOGw(w, Show_IDLEPS);
5932 VIZTOGw(w, Show_JRNUMS);
5935 VIZTOGw(w, Show_JRSTRS);
5939 if (!CHKw(w, Show_FOREST)) VIZTOGw(w, Qsrt_NORMAL);
5942 TOGw(w, Qsrt_NORMAL);
5943 OFFw(w, Show_FOREST);
5949 TOGw(w, Show_CTIMES);
5950 show_msg(fmtmk(N_fmt(TIME_accumed_fmt) , CHKw(w, Show_CTIMES)
5951 ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5957 if (VIZCHKw(w)) other_filters(ch);
5962 const char *errmsg, *str = ioline(N_txt(GET_user_ids_txt));
5964 && (errmsg = user_certify(w, str, ch)))
5970 TOGw(w, Show_FOREST);
5971 if (!ENUviz(w, EU_CMD))
5972 show_msg(fmtmk(N_fmt(FOREST_modes_fmt) , CHKw(w, Show_FOREST)
5973 ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5974 if (!CHKw(w, Show_FOREST)) w->focus_pid = 0;
5979 if (CHKw(w, Show_FOREST)) {
5980 int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5982 int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid));
5983 if (got < GET_NUM_NOT) break;
5984 if (got > GET_NUM_NOT) pid = got;
5986 for (i = 0; i < Hide_tot; i++) {
5987 if (Hide_pid[i] == pid || Hide_pid[i] == -pid) {
5988 Hide_pid[i] = -Hide_pid[i];
5992 if (i == Hide_tot) {
5994 if (Hide_tot >= totsav) {
5996 Hide_pid = alloc_r(Hide_pid, sizeof(int) * totsav);
5998 Hide_pid[Hide_tot++] = pid;
6000 // if everything's expanded, let's empty the array ...
6001 for (i = 0; i < Hide_tot; i++)
6002 if (Hide_pid[i] > 0) break;
6003 if (i == Hide_tot) Hide_tot = 0;
6011 TOGw(w, Show_HICOLS);
6014 if (ENUviz(w, w->rc.sortindx)) {
6015 TOGw(w, Show_HICOLS);
6016 if (ENUpos(w, w->rc.sortindx) < w->begpflg) {
6017 if (CHKw(w, Show_HICOLS)) w->begpflg += 2;
6018 else w->begpflg -= 2;
6019 if (0 > w->begpflg) w->begpflg = 0;
6028 TOGw(w, Show_HIROWS);
6034 TOGw(w, Show_COLORS);
6038 default: // keep gcc happy
6044 static void keys_window (int ch) {
6045 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
6049 if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx);
6053 if (ALTCHKw) TOGw(w, Show_TASKON);
6060 if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON);
6064 if (VIZCHKw(w)) find_string(ch);
6067 Rc.mode_altscr = !Rc.mode_altscr;
6071 if (ALTCHKw) win_select(ch);
6075 char tmp[SMLBUFSIZ];
6076 STRLCPY(tmp, ioline(fmtmk(N_fmt(NAME_windows_fmt), w->rc.winname)));
6077 if (tmp[0] && tmp[0] != kbd_ESC) win_names(w, tmp);
6081 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(-1)
6084 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(+1)
6086 #ifdef USE_X_COLHDR // ------------------------------------
6088 #ifndef SCROLLVAR_NO
6091 w->varcolbeg -= SCROLLAMT;
6092 else if (0 < w->begpflg)
6096 if (VIZCHKw(w)) if (0 < w->begpflg) w->begpflg -= 1;
6100 #ifndef SCROLLVAR_NO
6103 w->varcolbeg += SCROLLAMT;
6104 if (0 > w->varcolbeg) w->varcolbeg = 0;
6105 } else if (w->begpflg + 1 < w->totpflgs)
6109 if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) w->begpflg += 1;
6112 #else // USE_X_COLHDR ------------------------------------
6114 #ifndef SCROLLVAR_NO
6117 w->varcolbeg -= SCROLLAMT;
6118 else if (0 < w->begpflg) {
6120 if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6124 if (VIZCHKw(w)) if (0 < w->begpflg) {
6126 if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6131 #ifndef SCROLLVAR_NO
6134 w->varcolbeg += SCROLLAMT;
6135 if (0 > w->varcolbeg) w->varcolbeg = 0;
6136 } else if (w->begpflg + 1 < w->totpflgs) {
6137 if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6138 w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6139 else w->begpflg += 1;
6143 if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) {
6144 if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6145 w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6146 else w->begpflg += 1;
6150 #endif // USE_X_COLHDR ------------------------------------
6153 if (CHKw(w, Show_IDLEPS) && 0 < w->begtask) {
6154 mkVIZrowX(-(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6160 if (CHKw(w, Show_IDLEPS) && w->begtask < PIDSmaxt - 1) {
6161 mkVIZrowX(+(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6166 #ifndef SCROLLVAR_NO
6167 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = w->varcolbeg = 0;
6169 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = 0;
6174 if (CHKw(w, Show_IDLEPS)) {
6175 mkVIZrowX((PIDSmaxt - w->winlines) + 1)
6176 w->begpflg = w->endpflg;
6177 #ifndef SCROLLVAR_NO
6183 default: // keep gcc happy
6186 } // end: keys_window
6189 static void keys_xtra (int ch) {
6190 // const char *xmsg;
6191 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
6194 if (CHKw(w, Show_FOREST)) return;
6196 OFFw(w, Show_FOREST);
6198 /* these keys represent old-top compatibility --
6199 they're grouped here so that if users could ever be weaned,
6200 we would just whack do_key's key_tab entry and this function... */
6203 w->rc.sortindx = EU_MEM;
6207 w->rc.sortindx = EU_PID;
6208 // xmsg = "Numerical";
6211 w->rc.sortindx = EU_CPU;
6215 w->rc.sortindx = EU_TM2;
6218 default: // keep gcc happy
6221 // some have objected to this message, so we'll just keep silent...
6222 // show_msg(fmtmk("%s sort compatibility key honored", xmsg));
6225 /*###### Tertiary summary display support (summary_show helpers) #######*/
6228 * note how alphabetical order is maintained within carefully chosen |
6229 * function names such as: (s)sum_see, (t)sum_tics, and (u)sum_unify |
6230 * with every name exactly 1 letter more than the preceding function |
6231 * ( surely, this must make us run much more efficiently. amirite? ) | */
6234 float pcnt_one, pcnt_two, pcnt_tot;
6235 char graph[MEDBUFSIZ];
6239 * A *Helper* function to produce the actual cpu & memory graphs for |
6240 * these functions -- sum_tics (tertiary) and do_memory (secondary). |
6241 * (sorry about the name, but it keeps the above comment commitment) | */
6242 static struct rx_st *sum_rx (struct graph_parms *these) {
6244 const char *part1, *part2, *style;
6246 { "%-.*s~7", "%-.*s~8", Graph_bars },
6247 { "%-.*s~4", "%-.*s~6", Graph_blks }
6249 static __thread struct rx_st rx;
6250 char buf1[SMLBUFSIZ], buf2[SMLBUFSIZ], buf3[MEDBUFSIZ];
6251 int ix, num1, num2, width;
6254 if (these->total > 0)
6255 scale = 100.0 / these->total;
6256 rx.pcnt_one = scale * these->part1;
6257 rx.pcnt_two = scale * these->part2;
6258 if (rx.pcnt_one + rx.pcnt_two > 100.0 || rx.pcnt_two < 0)
6260 rx.pcnt_tot = rx.pcnt_one + rx.pcnt_two;
6262 num1 = (int)((rx.pcnt_one * these->adjust) + .5);
6263 num2 = (int)((rx.pcnt_two * these->adjust) + .5);
6264 if (num1 + num2 > these->length) {
6265 if (num1 > these->length) num1 = these->length;
6266 num2 = these->length - num1;
6269 width = these->length;
6270 buf1[0] = buf2[0] = buf3[0] = '\0';
6271 ix = these->style - 1; // now relative to zero
6273 snprintf(buf1, sizeof(buf1), gtab[ix].part1, num1, gtab[ix].style);
6277 snprintf(buf2, sizeof(buf2), gtab[ix].part2, num2, gtab[ix].style);
6280 snprintf(buf3, sizeof(buf3), "%s%s", buf1, buf2);
6281 // 'width' has accounted for any show_special directives embedded above
6282 snprintf(rx.graph, sizeof(rx.graph), "[~1%-*.*s] ~1", width, width, buf3);
6289 * A *Helper* function to show multiple lines of summary information |
6290 * as a single line. We return the number of lines actually printed. | */
6291 static inline int sum_see (const char *str, int nobuf) {
6292 static char row[ROWMAXSIZ];
6297 if (!str[0]) goto flush_it;
6298 if (Curwin->rc.double_up
6300 if (++tog <= Curwin->rc.double_up) {
6306 if (!row[0]) return 0;
6308 show_special(0, row);
6316 * State display *Helper* function to calculate plus display (maybe) |
6317 * the percentages for a single cpu. In this way, we'll support the |
6318 * following environments without (hopefully) that usual code bloat: |
6319 * 1) single cpu platforms (no matter the paucity of these types) |
6320 * 2) modest smp boxes with ample room for each cpu's percentages |
6321 * 3) massive smp guys leaving little or no room for that process |
6322 * display and thus requiring the '1', '4', or '!' cpu toggles |
6323 * ( we return the number of lines printed, as reported by sum_see ) | */
6324 static int sum_tics (struct stat_stack *this, const char *pfx, int nobuf) {
6325 // tailored 'results stack value' extractor macros
6326 #define qSv(E) STAT_VAL(E, s_int, this, Stat_ctx)
6327 #define rSv(E) TIC_VAL(E, this)
6328 SIC_t idl_frme, tot_frme;
6332 #ifndef CORE_TYPE_NO
6333 if (Curwin->rc.core_types == P_CORES_ONLY && qSv(stat_COR_TYP) != P_CORE) return 0;
6334 if (Curwin->rc.core_types == E_CORES_ONLY && qSv(stat_COR_TYP) != E_CORE) return 0;
6336 idl_frme = rSv(stat_IL);
6337 tot_frme = rSv(stat_SUM_TOT);
6338 if (1 > tot_frme) idl_frme = tot_frme = 1;
6339 scale = 100.0 / (float)tot_frme;
6341 /* display some kinda' cpu state percentages
6342 (who or what is explained by the passed prefix) */
6343 if (Curwin->rc.graph_cpus) {
6344 Graph_cpus->total = tot_frme;
6345 Graph_cpus->part1 = rSv(stat_SUM_USR);
6346 Graph_cpus->part2 = rSv(stat_SUM_SYS);
6347 rx = sum_rx(Graph_cpus);
6348 if (Curwin->rc.double_up > 1)
6349 return sum_see(fmtmk("%s~3%3.0f%s", pfx, rx->pcnt_tot, rx->graph), nobuf);
6351 return sum_see(fmtmk("%s ~3%#5.1f~2/%-#5.1f~3 %3.0f%s"
6352 , pfx, rx->pcnt_one, rx->pcnt_two, rx->pcnt_tot
6357 return sum_see(fmtmk(Cpu_States_fmts, pfx
6358 , (float)rSv(stat_US) * scale, (float)rSv(stat_SY) * scale
6359 , (float)rSv(stat_NI) * scale, (float)idl_frme * scale
6360 , (float)rSv(stat_IO) * scale, (float)rSv(stat_IR) * scale
6361 , (float)rSv(stat_SI) * scale, (float)rSv(stat_ST) * scale), nobuf);
6369 * Cpu *Helper* function to combine additional cpu statistics in our |
6370 * efforts to reduce the total number of processors that'll be shown |
6371 * ( we return the number of lines printed, as reported by sum_see ) | */
6372 static int sum_unify (struct stat_stack *this, int nobuf) {
6373 // a tailored 'results stack value' extractor macro
6374 #define rSv(E,T) STAT_VAL(E, T, this, Stat_ctx)
6375 static struct stat_result stack[MAXTBL(Stat_items)];
6376 static struct stat_stack accum = { &stack[0] };
6381 // entries for stat_ID & stat_NU are unused
6382 stack[stat_US].result.sl_int += rSv(stat_US, sl_int);
6383 stack[stat_SY].result.sl_int += rSv(stat_SY, sl_int);
6384 stack[stat_NI].result.sl_int += rSv(stat_NI, sl_int);
6385 stack[stat_IL].result.sl_int += rSv(stat_IL, sl_int);
6386 stack[stat_IO].result.sl_int += rSv(stat_IO, sl_int);
6387 stack[stat_IR].result.sl_int += rSv(stat_IR, sl_int);
6388 stack[stat_SI].result.sl_int += rSv(stat_SI, sl_int);
6389 stack[stat_ST].result.sl_int += rSv(stat_ST, sl_int);
6390 stack[stat_SUM_USR].result.sl_int += rSv(stat_SUM_USR, sl_int);
6391 stack[stat_SUM_SYS].result.sl_int += rSv(stat_SUM_SYS, sl_int);
6392 stack[stat_SUM_TOT].result.sl_int += rSv(stat_SUM_TOT, sl_int);
6394 if (!ix) beg = rSv(stat_ID, s_int);
6395 if (nobuf || ix >= Curwin->rc.combine_cpus) {
6396 snprintf(pfx, sizeof(pfx), "%-7.7s:", fmtmk("%d-%d", beg, rSv(stat_ID, s_int)));
6397 n = sum_tics(&accum, pfx, nobuf);
6398 memset(&stack, 0, sizeof(stack));
6407 /*###### Secondary summary display support (summary_show helpers) ######*/
6410 * A helper function that displays cpu and/or numa node stuff |
6411 * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6412 static void do_cpus (void) {
6413 #define noMAS (Msg_row + 1 >= SCREEN_ROWS - 1)
6414 #define eachCPU(x) N_fmt(WORD_eachcpu_fmt), 'u', x
6415 char tmp[MEDBUFSIZ];
6418 if (CHKw(Curwin, View_CPUNOD)) {
6419 if (Numa_node_sel < 0) {
6422 * display the 1st /proc/stat line, then the nodes (if room) ... */
6423 Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6424 // display each cpu node's states
6425 for (i = 0; i < Numa_node_tot; i++) {
6426 struct stat_stack *nod_ptr = Stat_reap->numa->stacks[i];
6427 if (NOD_VAL(stat_NU, i) == STAT_NODE_INVALID) continue;
6429 snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), NOD_VAL(stat_ID, i));
6430 Msg_row += sum_tics(nod_ptr, tmp, 1);
6434 * display the node summary, then the associated cpus (if room) ... */
6435 for (i = 0; i < Numa_node_tot; i++) {
6436 if (Numa_node_sel == NOD_VAL(stat_ID, i)
6437 && (NOD_VAL(stat_NU, i) != STAT_NODE_INVALID)) break;
6439 if (i == Numa_node_tot) {
6443 snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), Numa_node_sel);
6444 Msg_row += sum_tics(Stat_reap->numa->stacks[Numa_node_sel], tmp, 1);
6446 #define deLIMIT Stat_reap->cpus->total
6448 #define deLIMIT Cpu_cnt
6450 for (i = 0; i < deLIMIT; i++) {
6451 if (Numa_node_sel == CPU_VAL(stat_NU, i)) {
6453 snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i)));
6454 Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, 1);
6460 } else if (!CHKw(Curwin, View_CPUSUM)) {
6462 * display each cpu's states separately, screen height permitting ... */
6465 if (Curwin->rc.combine_cpus) {
6466 for (i = 0, j = 0; i < Cpu_cnt; i++) {
6467 Stat_reap->cpus->stacks[j]->head[stat_ID].result.s_int = i;
6468 Msg_row += sum_unify(Stat_reap->cpus->stacks[j], (i+1 >= Cpu_cnt));
6469 if (++j >= Stat_reap->cpus->total) j = 0;
6473 for (i = 0, j = 0; i < Cpu_cnt; i++) {
6474 snprintf(tmp, sizeof(tmp), eachCPU(i));
6475 Msg_row += sum_tics(Stat_reap->cpus->stacks[j], tmp, (i+1 >= Cpu_cnt));
6476 if (++j >= Stat_reap->cpus->total) j = 0;
6481 if (Curwin->rc.combine_cpus) {
6482 for (i = 0; i < Cpu_cnt; i++) {
6483 Msg_row += sum_unify(Stat_reap->cpus->stacks[i], (i+1 >= Cpu_cnt));
6487 for (i = 0; i < Cpu_cnt; i++) {
6488 #ifndef CORE_TYPE_NO
6490 char ctab[] = { 'u', 'e', 'p' };
6492 char ctab[] = { 'u', 'E', 'P' };
6494 int cid = CPU_VAL(stat_ID, i), typ = CPU_VAL(stat_COR_TYP, i);
6495 char chr = Curwin->rc.core_types ? ctab[typ] : 'u' ;
6496 snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), chr, cid);
6498 snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i)));
6500 Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, (i+1 >= Cpu_cnt));
6508 * display just the 1st /proc/stat line ... */
6509 Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6512 // coax this guy into flushing any pending cpu stuff ...
6513 Msg_row += sum_see("", 1);
6520 * A helper function which will display the memory/swap stuff |
6521 * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6522 static void do_memory (void) {
6523 #define bfT(n) buftab[n].buf
6524 #define scT(e) scaletab[Rc.summ_mscale]. e
6525 #define mkM(x) (float) x / scT(div)
6526 #define prT(b,z) { if (9 < snprintf(b, 10, scT(fmts), z)) b[8] = '+'; }
6537 { 1, "%.0f ", NULL }, // kibibytes
6539 { 1024.0, "%#.3f ", NULL }, // mebibytes
6540 { 1024.0*1024, "%#.3f ", NULL }, // gibibytes
6541 { 1024.0*1024*1024, "%#.3f ", NULL }, // tebibytes
6542 { 1024.0*1024*1024*1024, "%#.3f ", NULL }, // pebibytes
6543 { 1024.0*1024*1024*1024*1024, "%#.3f ", NULL } // exbibytes
6545 { 1024.0, "%#.1f ", NULL }, // mebibytes
6546 { 1024.0*1024, "%#.1f ", NULL }, // gibibytes
6547 { 1024.0*1024*1024, "%#.1f ", NULL }, // tebibytes
6548 { 1024.0*1024*1024*1024, "%#.1f ", NULL }, // pebibytes
6549 { 1024.0*1024*1024*1024*1024, "%#.1f ", NULL } // exbibytes
6552 struct { // 0123456789
6553 // snprintf contents of each buf (after SK_Kb): 'nnnn.nnn 0'
6554 // & prT macro might replace space at buf[8] with: -------> +
6555 char buf[10]; // MEMORY_lines_fmt provides for 8+1 bytes
6557 char row[ROWMINSIZ];
6558 long my_qued, my_misc, my_used;
6561 if (!scaletab[0].label) {
6562 scaletab[0].label = N_txt(AMT_kilobyte_txt);
6563 scaletab[1].label = N_txt(AMT_megabyte_txt);
6564 scaletab[2].label = N_txt(AMT_gigabyte_txt);
6565 scaletab[3].label = N_txt(AMT_terabyte_txt);
6566 scaletab[4].label = N_txt(AMT_petabyte_txt);
6567 scaletab[5].label = N_txt(AMT_exxabyte_txt);
6569 my_qued = MEM_VAL(mem_BUF) + MEM_VAL(mem_QUE);
6571 if (Curwin->rc.graph_mems) {
6572 my_used = MEM_VAL(mem_TOT) - MEM_VAL(mem_FRE) - my_qued;
6576 my_misc = MEM_VAL(mem_TOT) - MEM_VAL(mem_AVL) - my_used;
6578 Graph_mems->total = MEM_VAL(mem_TOT);
6579 Graph_mems->part1 = my_used;
6580 Graph_mems->part2 = my_misc;
6581 rx = sum_rx(Graph_mems);
6583 prT(bfT(0), mkM(MEM_VAL(mem_TOT)));
6584 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6585 , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph);
6587 if (Curwin->rc.double_up > 1)
6588 snprintf(row, sizeof(row), "%s %s~3%3.0f%s"
6589 , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, rx->graph);
6591 prT(bfT(0), mkM(MEM_VAL(mem_TOT)));
6592 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6593 , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph);
6596 Msg_row += sum_see(row, mem2UP);
6598 Graph_mems->total = MEM_VAL(swp_TOT);
6599 Graph_mems->part1 = 0;
6600 Graph_mems->part2 = MEM_VAL(swp_USE);
6601 rx = sum_rx(Graph_mems);
6603 prT(bfT(1), mkM(MEM_VAL(swp_TOT)));
6604 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6605 , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph);
6607 if (Curwin->rc.double_up > 1)
6608 snprintf(row, sizeof(row), "%s %s~3%3.0f%s"
6609 , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, rx->graph);
6611 prT(bfT(1), mkM(MEM_VAL(swp_TOT)));
6612 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6613 , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph);
6616 Msg_row += sum_see(row, 1);
6619 prT(bfT(0), mkM(MEM_VAL(mem_TOT))); prT(bfT(1), mkM(MEM_VAL(mem_FRE)));
6620 prT(bfT(2), mkM(MEM_VAL(mem_USE))); prT(bfT(3), mkM(my_qued));
6621 prT(bfT(4), mkM(MEM_VAL(swp_TOT))); prT(bfT(5), mkM(MEM_VAL(swp_FRE)));
6622 prT(bfT(6), mkM(MEM_VAL(swp_USE))); prT(bfT(7), mkM(MEM_VAL(mem_AVL)));
6624 snprintf(row, sizeof(row), N_unq(MEMORY_line1_fmt)
6625 , scT(label), N_txt(WORD_abv_mem_txt), bfT(0), bfT(1), bfT(2), bfT(3));
6626 Msg_row += sum_see(row, mem2UP);
6628 snprintf(row, sizeof(row), N_unq(MEMORY_line2_fmt)
6629 , scT(label), N_txt(WORD_abv_swp_txt), bfT(4), bfT(5), bfT(6), bfT(7)
6630 , N_txt(WORD_abv_mem_txt));
6631 Msg_row += sum_see(row, 1);
6640 /*###### Main Screen routines ##########################################*/
6643 * Process keyboard input during the main loop */
6644 static void do_key (int ch) {
6646 void (*func)(int ch);
6647 char keys[SMLBUFSIZ];
6650 { '?', 'B', 'd', 'E', 'e', 'f', 'g', 'H', 'h'
6651 , 'I', 'k', 'r', 's', 'X', 'Y', 'Z', '0'
6652 , kbd_CtrlE, kbd_CtrlG, kbd_CtrlI, kbd_CtrlK, kbd_CtrlL
6653 , kbd_CtrlN, kbd_CtrlP, kbd_CtrlR, kbd_CtrlU
6654 , kbd_ENTER, kbd_SPACE, kbd_BTAB, '\0' } },
6657 { '!', '1', '2', '3', '4', 'C', 'l', 'm', 't', '\0' } },
6659 { '!', '1', '2', '3', '4', '5', 'C', 'l', 'm', 't', '\0' } },
6662 { '#', '<', '>', 'b', 'c', 'F', 'i', 'J', 'j', 'n', 'O', 'o'
6663 , 'R', 'S', 'U', 'u', 'V', 'v', 'x', 'y', 'z'
6664 , kbd_CtrlO, '\0' } },
6666 { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w'
6667 , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN
6668 , kbd_HOME, kbd_END, '\0' } },
6670 { 'M', 'N', 'P', 'T', '\0'} }
6674 Frames_signal = BREAK_off;
6676 case 0: // ignored (always)
6677 case kbd_ESC: // ignored (sometimes)
6679 case 'q': // no return from this guy
6681 case 'W': // no need for rebuilds
6684 default: // and now, the real work...
6685 // and just in case 'Monpids' is active but matched no processes ...
6686 if (!PIDSmaxt && ch != '=') goto all_done;
6687 for (i = 0; i < MAXTBL(key_tab); ++i)
6688 if (strchr(key_tab[i].keys, ch)) {
6689 key_tab[i].func(ch);
6690 if (Frames_signal == BREAK_off)
6691 Frames_signal = BREAK_kbd;
6692 /* due to the proliferation of the need for 'mkVIZrow1', |
6693 aside from 'wins_stage_2' use, we'll now issue it one |
6694 time here. there will remain several places where the |
6695 companion 'mkVIZrowX' macro is issued, thus the check |
6696 for a value already in 'begnext' in this conditional. | */
6697 if (CHKw(Curwin, Show_TASKON) && !mkVIZyes)
6702 /* The Frames_signal above will force a rebuild of column headers.
6703 It's NOT simply lazy programming. Below are some keys that may
6704 require new column headers and/or new library item enumerators:
6706 'c' - likely when !Mode_altscr, maybe when Mode_altscr
6714 'Z' - likely, if 'Curwin' changed when !Mode_altscr
6715 '-' - likely (restricted to Mode_altscr)
6716 '_' - likely (restricted to Mode_altscr)
6717 '=' - maybe, but only when Mode_altscr
6718 '+' - likely (restricted to Mode_altscr)
6719 PLUS, likely for FOUR of the EIGHT cursor motion keys (scrolled)
6720 ( At this point we have a human being involved and so have all the time )
6721 ( in the world. We can afford a few extra cpu cycles every now & then! )
6724 show_msg(N_txt(UNKNOWN_cmds_txt));
6726 putp((Cursor_state = Cap_curs_hide));
6731 * In support of a new frame:
6732 * 1) Display uptime and load average (maybe)
6733 * 2) Display task/cpu states (maybe)
6734 * 3) Display memory & swap usage (maybe) */
6735 static void summary_show (void) {
6736 #define isROOM(f,n) (CHKw(Curwin, f) && Msg_row + (n) < SCREEN_ROWS - 1)
6738 if (Restrict_some) {
6740 sem_wait(&Semaphore_tasks_end);
6742 // Display Task States only
6743 if (isROOM(View_STATES, 1)) {
6744 show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6745 , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6746 , PIDSmaxt, Pids_reap->counts->running
6747 , Pids_reap->counts->sleeping + Pids_reap->counts->other
6748 , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6754 // Display Uptime and Loadavg
6755 if (isROOM(View_LOADAV, 1)) {
6756 if (!Rc.mode_altscr)
6757 show_special(0, fmtmk(LOADAV_line, Myname, procps_uptime_sprint()));
6759 show_special(0, fmtmk(CHKw(Curwin, Show_TASKON)? LOADAV_line_alt : LOADAV_line
6760 , Curwin->grpname, procps_uptime_sprint()));
6762 } // end: View_LOADAV
6765 sem_wait(&Semaphore_cpus_end);
6768 sem_wait(&Semaphore_tasks_end);
6770 // Display Task and Cpu(s) States
6771 if (isROOM(View_STATES, 2)) {
6772 show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6773 , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6774 , PIDSmaxt, Pids_reap->counts->running
6775 , Pids_reap->counts->sleeping + Pids_reap->counts->other
6776 , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6783 sem_wait(&Semaphore_memory_end);
6785 // Display Memory and Swap stats
6786 if (isROOM(View_MEMORY, 2)) {
6791 } // end: summary_show
6795 * Build the information for a single task row and
6796 * display the results or return them to the caller. */
6797 static const char *task_show (const WIN_t *q, int idx) {
6798 // a tailored 'results stack value' extractor macro
6799 #define rSv(E,T) PID_VAL(E, T, p)
6800 #ifndef SCROLLVAR_NO
6801 #define makeVAR(S) { const char *pv = S; \
6802 if (!q->varcolbeg) cp = make_str(pv, q->varcolsz, Js, AUTOX_NO); \
6803 else cp = make_str(q->varcolbeg < (int)strlen(pv) ? pv + q->varcolbeg : "", q->varcolsz, Js, AUTOX_NO); }
6804 #define varUTF8(S) { const char *pv = S; \
6805 if (!q->varcolbeg) cp = make_str_utf8(pv, q->varcolsz, Js, AUTOX_NO); \
6806 else cp = make_str_utf8((q->varcolbeg < ((int)strlen(pv) - utf8_delta(pv))) \
6807 ? pv + utf8_embody(pv, q->varcolbeg) : "", q->varcolsz, Js, AUTOX_NO); }
6809 #define makeVAR(S) { cp = make_str(S, q->varcolsz, Js, AUTOX_NO); }
6810 #define varUTF8(S) { cp = make_str_utf8(S, q->varcolsz, Js, AUTOX_NO); }
6812 struct pids_stack *p = q->ppt[idx];
6813 static char rbuf[ROWMINSIZ];
6817 /* we use up to three additional 'PIDS_extra' results in our stacks
6818 eu_TREE_HID (s_ch) : where 'x' == collapsed and 'z' == unseen
6819 eu_TREE_LVL (s_int): where a level number is stored (0 - 100)
6820 eu_TREE_ADD (u_int): where children's tics are stored (maybe) */
6821 #ifndef TREE_VWINALL
6822 if (q == Curwin) // note: the following is NOT indented
6824 if (CHKw(q, Show_FOREST) && rSv(eu_TREE_HID, s_ch) == 'z')
6827 // we must begin a row with a possible window number in mind...
6828 *(rp = rbuf) = '\0';
6829 if (Rc.mode_altscr) rp = scat(rp, " ");
6831 for (x = 0; x < q->maxpflgs; x++) {
6832 const char *cp = NULL;
6833 FLG_t i = q->procflgs[x];
6834 #define S Fieldstab[i].scale // these used to be variables
6835 #define W Fieldstab[i].width // but it's much better if we
6836 #define Js CHKw(q, Show_JRSTRS) // represent them as #defines
6837 #define Jn CHKw(q, Show_JRNUMS) // and only exec code if used
6839 /* except for the XOF/XON pseudo flags the following case labels are grouped
6840 by result type according to capacity (small -> large) and then ordered by
6841 additional processing requirements (as in plain, scaled, decorated, etc.) */
6844 #ifndef USE_X_COLHDR
6845 // these 2 aren't real procflgs, they're used in column highlighting!
6849 if (!CHKw(q, NOPRINT_xxx)) {
6850 /* treat running tasks specially - entire row may get highlighted
6851 so we needn't turn it on and we MUST NOT turn it off */
6852 if (!('R' == rSv(EU_STA, s_ch) && CHKw(q, Show_HIROWS)))
6853 cp = (EU_XON == i ? q->capclr_rowhigh : q->capclr_rownorm);
6857 /* s_ch, make_chr */
6858 case EU_STA: // PIDS_STATE
6859 cp = make_chr(rSv(EU_STA, s_ch), W, Js);
6861 /* s_int, make_num with auto width */
6862 case EU_LID: // PIDS_ID_LOGIN
6863 cp = make_num(rSv(EU_LID, s_int), W, Jn, EU_LID, 0);
6865 /* s_int, make_num without auto width */
6866 case EU_AGI: // PIDS_AUTOGRP_ID
6867 case EU_CPN: // PIDS_PROCESSOR
6868 case EU_NMA: // PIDS_PROCESSOR_NODE
6869 case EU_PGD: // PIDS_ID_PGRP
6870 case EU_PID: // PIDS_ID_PID
6871 case EU_PPD: // PIDS_ID_PPID
6872 case EU_SID: // PIDS_ID_SESSION
6873 case EU_TGD: // PIDS_ID_TGID
6874 case EU_THD: // PIDS_NLWP
6875 case EU_TPG: // PIDS_ID_TPGID
6876 cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 0);
6878 /* s_int, make_num without auto width, but with zero supression */
6879 case EU_AGN: // PIDS_AUTOGRP_NICE
6880 case EU_NCE: // PIDS_NICE
6881 case EU_OOA: // PIDS_OOM_ADJ
6882 case EU_OOM: // PIDS_OOM_SCORE
6883 cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 1);
6885 /* s_int, scale_num */
6886 case EU_FV1: // PIDS_FLT_MAJ_DELTA
6887 case EU_FV2: // PIDS_FLT_MIN_DELTA
6888 cp = scale_num(rSv(i, s_int), W, Jn);
6890 /* s_int, make_num or make_str */
6891 case EU_PRI: // PIDS_PRIORITY
6892 if (-99 > rSv(EU_PRI, s_int) || 999 < rSv(EU_PRI, s_int))
6893 cp = make_str("rt", W, Jn, AUTOX_NO);
6895 cp = make_num(rSv(EU_PRI, s_int), W, Jn, AUTOX_NO, 0);
6897 /* s_int, scale_pcnt with special handling */
6898 case EU_CPU: // PIDS_TICS_ALL_DELTA
6899 { float u = (float)rSv(EU_CPU, u_int);
6900 int n = rSv(EU_THD, s_int);
6901 if (Restrict_some) {
6902 cp = justify_pad("?", W, Jn);
6905 #ifndef TREE_VCPUOFF
6906 #ifndef TREE_VWINALL
6907 if (q == Curwin) // note: the following is NOT indented
6909 if (CHKw(q, Show_FOREST)) u += rSv(eu_TREE_ADD, u_int);
6911 /* technically, eu_TREE_HID is only valid if Show_FOREST is active
6912 but its zeroed out slot will always be present now */
6913 if (rSv(eu_TREE_HID, s_ch) != 'x' && u > 100.0 * n) u = 100.0 * n;
6916 /* process can't use more %cpu than number of threads it has
6917 ( thanks Jaromir Capik <jcapik@redhat.com> ) */
6918 if (u > 100.0 * n) u = 100.0 * n;
6920 if (u > Cpu_pmax) u = Cpu_pmax;
6921 cp = scale_pcnt(u, W, Jn, 0);
6924 /* ull_int, scale_pcnt for 'utilization' */
6925 case EU_CUU: // PIDS_UTILIZATION
6926 case EU_CUC: // PIDS_UTILIZATION_C
6927 if (Restrict_some) {
6928 cp = justify_pad("?", W, Jn);
6931 cp = scale_pcnt(rSv(i, real), W, Jn, 1);
6933 /* u_int, make_num with auto width */
6934 case EU_GID: // PIDS_ID_EGID
6935 case EU_UED: // PIDS_ID_EUID
6936 case EU_URD: // PIDS_ID_RUID
6937 case EU_USD: // PIDS_ID_SUID
6938 cp = make_num(rSv(i, u_int), W, Jn, i, 0);
6940 /* ul_int, make_num with auto width and zero supression */
6941 case EU_NS1: // PIDS_NS_IPC
6942 case EU_NS2: // PIDS_NS_MNT
6943 case EU_NS3: // PIDS_NS_NET
6944 case EU_NS4: // PIDS_NS_PID
6945 case EU_NS5: // PIDS_NS_USER
6946 case EU_NS6: // PIDS_NS_UTS
6947 case EU_NS7: // PIDS_NS_CGROUP
6948 case EU_NS8: // PIDS_NS_TIME
6949 cp = make_num(rSv(i, ul_int), W, Jn, i, 1);
6951 /* ul_int, scale_mem */
6952 case EU_COD: // PIDS_MEM_CODE
6953 case EU_DAT: // PIDS_MEM_DATA
6954 case EU_DRT: // PIDS_noop, really # pgs, but always 0 since 2.6
6955 case EU_PZA: // PIDS_SMAP_PSS_ANON
6956 case EU_PZF: // PIDS_SMAP_PSS_FILE
6957 case EU_PZS: // PIDS_SMAP_PSS_SHMEM
6958 case EU_PSS: // PIDS_SMAP_PSS
6959 case EU_RES: // PIDS_MEM_RES
6960 case EU_RSS: // PIDS_SMAP_RSS
6961 case EU_RZA: // PIDS_VM_RSS_ANON
6962 case EU_RZF: // PIDS_VM_RSS_FILE
6963 case EU_RZL: // PIDS_VM_RSS_LOCKED
6964 case EU_RZS: // PIDS_VM_RSS_SHARED
6965 case EU_SHR: // PIDS_MEM_SHR
6966 case EU_SWP: // PIDS_VM_SWAP
6967 case EU_USE: // PIDS_VM_USED
6968 case EU_USS: // PIDS_SMAP_PRV_TOTAL
6969 case EU_VRT: // PIDS_MEM_VIRT
6970 cp = scale_mem(S, rSv(i, ul_int), W, Jn);
6972 /* ul_int, scale_num */
6973 case EU_FL1: // PIDS_FLT_MAJ
6974 case EU_FL2: // PIDS_FLT_MIN
6975 case EU_IRB: // PIDS_IO_READ_BYTES
6976 case EU_IRO: // PIDS_IO_READ_OPS
6977 case EU_IWB: // PIDS_IO_WRITE_BYTES
6978 case EU_IWO: // PIDS_IO_WRITE_OPS
6979 cp = scale_num(rSv(i, ul_int), W, Jn);
6981 /* ul_int, scale_pcnt */
6982 case EU_MEM: // derive from PIDS_MEM_RES
6983 if (Restrict_some) {
6984 cp = justify_pad("?", W, Jn);
6987 cp = scale_pcnt((float)rSv(EU_MEM, ul_int) * 100 / MEM_VAL(mem_TOT), W, Jn, 0);
6989 /* ul_int, make_str with special handling */
6990 case EU_FLG: // PIDS_FLAGS
6991 cp = make_str(hex_make(rSv(EU_FLG, ul_int), 1), W, Js, AUTOX_NO);
6993 /* ull_int, scale_tics (try 'minutes:seconds.hundredths') */
6994 case EU_TM2: // PIDS_TICS_ALL
6995 case EU_TME: // PIDS_TICS_ALL
6997 if (CHKw(q, Show_CTIMES)) t = rSv(eu_TICS_ALL_C, ull_int);
6998 else t = rSv(i, ull_int);
6999 cp = scale_tics(t, W, Jn, TICS_AS_SECS);
7002 /* ull_int, scale_tics (try 'minutes:seconds') */
7003 case EU_TM3: // PIDS_TICS_BEGAN
7004 cp = scale_tics(rSv(EU_TM3, ull_int), W, Jn, TICS_AS_MINS);
7006 /* real, scale_tics (try 'hour,minutes') */
7007 case EU_TM4: // PIDS_TIME_ELAPSED
7008 cp = scale_tics(rSv(EU_TM4, real) * Hertz, W, Jn, TICS_AS_HOUR);
7010 /* str, make_str (all AUTOX yes) */
7011 case EU_LXC: // PIDS_LXCNAME
7012 case EU_TTY: // PIDS_TTY_NAME
7013 case EU_WCH: // PIDS_WCHAN_NAME
7014 cp = make_str(rSv(i, str), W, Js, i);
7016 /* str, make_str_utf8 (all AUTOX yes) */
7017 case EU_GRP: // PIDS_ID_EGROUP
7018 case EU_UEN: // PIDS_ID_EUSER
7019 case EU_URN: // PIDS_ID_RUSER
7020 case EU_USN: // PIDS_ID_SUSER
7021 cp = make_str_utf8(rSv(i, str), W, Js, i);
7023 /* str, make_str_utf8 with varialbe width */
7024 case EU_CGN: // PIDS_CGNAME
7025 case EU_CGR: // PIDS_CGROUP
7026 case EU_ENV: // PIDS_ENVIRON
7027 case EU_EXE: // PIDS_EXE
7028 case EU_SGN: // PIDS_SUPGROUPS
7029 varUTF8(rSv(i, str))
7031 /* str, make_str with varialbe width */
7032 case EU_SGD: // PIDS_SUPGIDS
7033 makeVAR(rSv(EU_SGD, str))
7035 /* str, make_str with varialbe width + additional decoration */
7036 case EU_CMD: // PIDS_CMD or PIDS_CMDLINE
7037 varUTF8(forest_display(q, idx))
7039 default: // keep gcc happy
7041 } // end: switch 'procflag'
7044 if (q->osel_tot && !osel_matched(q, i, cp)) return "";
7051 } // end: for 'maxpflgs'
7053 if (!CHKw(q, NOPRINT_xxx)) {
7054 const char *cap = ((CHKw(q, Show_HIROWS) && 'R' == rSv(EU_STA, s_ch)))
7055 ? q->capclr_rowhigh : q->capclr_rownorm;
7058 /* since we can't predict what the search string will be and,
7059 considering what a single space search request would do to
7060 potential buffer needs, when any matches are found we skip
7061 normal output routing and send all of the results directly
7062 to the terminal (and we sound asthmatic: poof, putt, puff) */
7063 if (-1 < (ofs = find_ofs(q, row))) {
7067 PUTT("%s%s%s%s", row, q->capclr_hdr, q->findstr, cap);
7068 row += (ofs + q->findlen);
7069 ofs = find_ofs(q, row);
7071 PUTT("%s%s", row, Caps_endline);
7072 // with a corrupted rbuf, ensure row is 'counted' by window_show
7075 PUFF("\n%s%s%s", cap, row, Caps_endline);
7085 * A window_show *Helper* function ensuring that a window 'begtask' |
7086 * represents a visible process (not any hidden/filtered-out task). |
7087 * In reality this function is called exclusively for the 'current' |
7088 * window and only after available user keystroke(s) are processed. |
7089 * Note: it's entirely possible there are NO visible tasks to show! | */
7090 static void window_hlp (void) {
7091 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
7093 int beg = w->focus_pid ? w->focus_beg : 0;
7094 int end = w->focus_pid ? w->focus_end : PIDSmaxt;
7096 SETw(w, NOPRINT_xxx);
7097 w->begtask += w->begnext;
7098 // next 'if' will force a forward scan ...
7099 if (w->begtask <= beg) { w->begtask = beg; w->begnext = +1; }
7100 else if (w->begtask >= end) w->begtask = end - 1;
7103 // potentially scroll forward ...
7104 if (w->begnext > 0) {
7106 for (i = w->begtask; i < end; i++) {
7107 if (wins_usrselect(w, i)
7108 && (*task_show(w, i)))
7115 // no luck forward, so let's try backward
7116 w->begtask = end - 1;
7119 // potentially scroll backward ...
7120 for (i = w->begtask; i > beg; i--) {
7121 if (wins_usrselect(w, i)
7122 && (*task_show(w, i)))
7127 // reached the top, but maybe this guy ain't visible
7128 if (w->begtask == beg && !reversed) {
7129 if (!(wins_usrselect(w, beg))
7130 || (!(*task_show(w, beg)))) {
7138 OFFw(w, NOPRINT_xxx);
7139 } // end: window_hlp
7143 * Squeeze as many tasks as we can into a single window,
7144 * after sorting the passed proc table. */
7145 static int window_show (WIN_t *q, int wmax) {
7146 #define sORDER CHKw(q, Qsrt_NORMAL) ? PIDS_SORT_DESCEND : PIDS_SORT_ASCEND
7147 /* the isBUSY macro determines if a task is 'active' --
7148 it returns true if some cpu was used since the last sample.
7149 ( actual 'running' tasks will be a subset of those selected ) */
7150 #define isBUSY(x) (0 < PID_VAL(EU_CPU, u_int, (x)))
7151 #define winMIN(a,b) (((a) < (b)) ? (a) : (b))
7152 int i, lwin, numtasks;
7154 // Display Column Headings -- and distract 'em while we sort (maybe)
7155 PUFF("\n%s%s%s", q->capclr_hdr, q->columnhdr, Caps_endline);
7156 // and just in case 'Monpids' is active but matched no processes ...
7157 if (!PIDSmaxt) return 1; // 1 for the column header
7159 if (CHKw(q, Show_FOREST)) {
7161 if (q->focus_pid) forest_config(q);
7163 enum pids_item item = Fieldstab[q->rc.sortindx].item;
7164 if (item == PIDS_CMD && CHKw(q, Show_CMDLIN))
7165 item = PIDS_CMDLINE;
7166 else if (item == PIDS_TICS_ALL && CHKw(q, Show_CTIMES))
7167 item = PIDS_TICS_ALL_C;
7168 if (!(procps_pids_sort(Pids_ctx, q->ppt , PIDSmaxt, item, sORDER)))
7169 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
7172 if (mkVIZyes) window_hlp();
7173 else OFFw(q, NOPRINT_xxx);
7176 lwin = 1; // 1 for the column header
7177 wmax = winMIN(wmax, q->winlines + 1); // ditto for winlines, too
7178 numtasks = q->focus_pid ? winMIN(q->focus_end, PIDSmaxt) : PIDSmaxt;
7180 /* the least likely scenario is also the most costly, so we'll try to avoid
7181 checking some stuff with each iteration and check it just once... */
7182 if (CHKw(q, Show_IDLEPS) && !q->usrseltyp)
7183 while (i < numtasks && lwin < wmax) {
7184 if (*task_show(q, i++))
7188 while (i < numtasks && lwin < wmax) {
7189 if ((CHKw(q, Show_IDLEPS) || isBUSY(q->ppt[i]))
7190 && wins_usrselect(q, i)
7191 && *task_show(q, i))
7200 } // end: window_show
7202 /*###### Entry point plus two ##########################################*/
7205 * This guy's just a *Helper* function who apportions the
7206 * remaining amount of screen real estate under multiple windows */
7207 static void frame_hlp (int wix, int max) {
7210 // calc remaining number of visible windows
7211 for (i = wix, wins = 0; i < GROUPSMAX; i++)
7212 if (CHKw(&Winstk[i], Show_TASKON))
7215 if (!wins) wins = 1;
7216 // deduct 1 line/window for the columns heading
7217 size = (max - wins) / wins;
7219 /* for subject window, set WIN_t winlines to either the user's
7220 maxtask (1st choice) or our 'foxized' size calculation
7221 (foxized adj. - 'fair and balanced') */
7222 Winstk[wix].winlines =
7223 Winstk[wix].rc.maxtasks ? Winstk[wix].rc.maxtasks : size;
7228 * Initiate the Frame Display Update cycle at someone's whim!
7229 * This routine doesn't do much, mostly he just calls others.
7231 * (Whoa, wait a minute, we DO caretake those row guys, plus)
7232 * (we CALCULATE that IMPORTANT Max_lines thingy so that the)
7233 * (*subordinate* functions invoked know WHEN the user's had)
7234 * (ENOUGH already. And at Frame End, it SHOULD be apparent)
7235 * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring)
7236 * (that those auto-sized columns are addressed, know what I)
7237 * (mean? Huh, "doesn't DO MUCH"! Never, EVER think or say)
7238 * (THAT about THIS function again, Ok? Good that's better.)
7240 * (ps. we ARE the UNEQUALED justification KING of COMMENTS!)
7241 * (No, I don't mean significance/relevance, only alignment.)
7243 static void frame_make (void) {
7244 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
7247 // check auto-sized width increases from the last iteration...
7248 if (AUTOX_MODE && Autox_found)
7251 /* deal with potential signal(s) since the last time around
7252 plus any input which may change 'tasks_refresh' needs... */
7253 if (Frames_signal) {
7254 if (Frames_signal == BREAK_sig
7255 || (Frames_signal == BREAK_screen))
7261 sem_post(&Semaphore_tasks_beg);
7263 tasks_refresh(NULL);
7266 if (!Restrict_some) {
7268 sem_post(&Semaphore_cpus_beg);
7273 sem_post(&Semaphore_memory_beg);
7275 memory_refresh(NULL);
7279 // whoa either first time or thread/task mode change, (re)prime the pump...
7280 if (Pseudo_row == PROC_XTRA) {
7283 sem_wait(&Semaphore_tasks_end);
7284 sem_post(&Semaphore_tasks_beg);
7286 tasks_refresh(NULL);
7290 putp(Batch ? "\n\n" : Cap_home);
7292 Tree_idx = Pseudo_row = Msg_row = scrlins = 0;
7294 Max_lines = (SCREEN_ROWS - Msg_row) - 1;
7296 // we're now on Msg_row so clear out any residual messages ...
7299 if (!Rc.mode_altscr) {
7300 // only 1 window to show so, piece o' cake
7301 w->winlines = w->rc.maxtasks ? w->rc.maxtasks : Max_lines;
7302 scrlins = window_show(w, Max_lines);
7304 // maybe NO window is visible but assume, pieces o' cakes
7305 for (i = 0 ; i < GROUPSMAX; i++) {
7306 if (CHKw(&Winstk[i], Show_TASKON)) {
7307 frame_hlp(i, Max_lines - scrlins);
7308 scrlins += window_show(&Winstk[i], Max_lines - scrlins);
7310 if (Max_lines <= scrlins) break;
7314 /* clear to end-of-screen - critical if last window is 'idleps off'
7315 (main loop must iterate such that we're always called before sleep) */
7316 if (!Batch && scrlins < Max_lines) {
7318 putp(Cap_nl_clreos);
7320 for (i = scrlins + Msg_row + 1; i < SCREEN_ROWS; i++) {
7325 PSU_CLREOS(Pseudo_row);
7328 if (CHKw(w, View_SCROLL) && VIZISw(Curwin)) show_scroll();
7329 if (Bot_show_func) Bot_show_func();
7332 /* we'll deem any terminal not supporting tgoto as dumb and disable
7333 the normal non-interactive output optimization... */
7334 if (!Cap_can_goto) PSU_CLREOS(0);
7335 } // end: frame_make
7340 int main (int argc, char *argv[]) {
7343 wins_stage_1(); // top (sic) slice
7344 configs_reads(); // > spread etc, <
7345 parse_args(argc, argv); // > onions etc, <
7346 signals_set(); // > lean stuff, <
7347 whack_terminal(); // > more stuff. <
7348 wins_stage_2(); // as bottom slice
7356 if (0 < Loops) --Loops;
7357 if (!Loops) bye_bye(NULL);
7359 ts.tv_sec = Rc.delay_time;
7360 ts.tv_nsec = (Rc.delay_time - (int)Rc.delay_time) * 1000000000;
7363 pselect(0, NULL, NULL, NULL, &ts, NULL);
7366 do_key(iokey(IOKEY_ONCE));
7368 /* note: that above ioa routine exists to consolidate all logic
7369 which is susceptible to signal interrupt and must then
7370 produce a screen refresh. in this main loop frame_make
7371 assumes responsibility for such refreshes. other logic
7372 in contact with users must deal more obliquely with an
7373 interrupt/refresh (hint: Frames_signal + return code)!
7375 (everything is perfectly justified plus right margins)
7376 (are completely filled, but of course it must be luck)