1 /* top.c - Source file: show Linux processes */
3 * Copyright (c) 2002-2022, by: 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_ITEMMAX 10 // Bot_item array's max size
109 #define BOT_MSGSMAX 10 // total entries for Msg_tab
110 #define BOT_UNFOCUS -1 // tab focus not established
111 // negative 'item' values won't be seen by build_headers() ...
112 #define BOT_DELIMIT -1 // fencepost with item array
113 #define BOT_ITEM_NS -2 // data for namespaces req'd
114 #define BOT_MSG_LOG -3 // show the most recent msgs
115 // next 4 are used when toggling window contents
116 #define BOT_SEP_CMA ','
117 #define BOT_SEP_SLS '/'
118 #define BOT_SEP_SMI ';'
119 #define BOT_SEP_SPC ' '
120 // 1 for horizontal separator
122 #define BOT_KEEP Bot_show_func = NULL
123 #define BOT_TOSS do { Bot_show_func = NULL; Bot_item[0] = BOT_DELIMIT; \
124 Bot_task = Bot_rsvd = Bot_what = 0; \
125 Bot_indx = BOT_UNFOCUS; \
130 Bot_indx = BOT_UNFOCUS,
131 Bot_item[BOT_ITEMMAX] = { BOT_DELIMIT };
134 Bot_buf[BOTBUFSIZ]; // the 'environ' can be huge
135 typedef int(*BOT_f)(const void *, const void *);
136 static BOT_f Bot_focus_func;
137 static void(*Bot_show_func)(void);
139 /* This is really the number of lines needed to display the summary
140 information (0 - nn), but is used as the relative row where we
141 stick the cursor between frames. */
144 /* Global/Non-windows mode stuff that is NOT persistent */
145 static int Batch = 0, // batch mode, collect no input, dumb output
146 Loops = -1, // number of iterations, -1 loops forever
147 Secure_mode = 0, // set if some functionality restricted
148 Width_mode = 0, // set w/ 'w' - potential output override
149 Thread_mode = 0; // set w/ 'H' - show threads vs. tasks
151 /* Unchangeable cap's stuff built just once (if at all) and
152 thus NOT saved in a WIN_t's RCW_t. To accommodate 'Batch'
153 mode, they begin life as empty strings so the overlying
154 logic need not change ! */
155 static char Cap_clr_eol [CAPBUFSIZ] = "", // global and/or static vars
156 Cap_nl_clreos [CAPBUFSIZ] = "", // are initialized to zeros!
157 Cap_clr_scr [CAPBUFSIZ] = "", // the assignments used here
158 Cap_curs_norm [CAPBUFSIZ] = "", // cost nothing but DO serve
159 Cap_curs_huge [CAPBUFSIZ] = "", // to remind people of those
160 Cap_curs_hide [CAPBUFSIZ] = "", // batch requirements!
161 Cap_clr_eos [CAPBUFSIZ] = "",
162 Cap_home [CAPBUFSIZ] = "",
163 Cap_norm [CAPBUFSIZ] = "",
164 Cap_reverse [CAPBUFSIZ] = "",
165 Caps_off [CAPBUFSIZ] = "",
166 Caps_endline [SMLBUFSIZ] = "";
168 static char Cap_rmam [CAPBUFSIZ] = "",
169 Cap_smam [CAPBUFSIZ] = "";
170 /* set to 1 if writing to the last column would be troublesome
171 (we don't distinguish the lowermost row from the other rows) */
172 static int Cap_avoid_eol = 0;
174 static int Cap_can_goto = 0;
176 /* Some optimization stuff, to reduce output demands...
177 The Pseudo_ guys are managed by adj_geometry and frame_make. They
178 are exploited in a macro and represent 90% of our optimization.
179 The Stdout_buf is transparent to our code and regardless of whose
180 buffer is used, stdout is flushed at frame end or if interactive. */
181 static char *Pseudo_screen;
182 static int Pseudo_row = PROC_XTRA;
183 static size_t Pseudo_size;
185 // less than stdout's normal buffer but with luck mostly '\n' anyway
186 static char Stdout_buf[2048];
189 /* Our four WIN_t's, and which of those is considered the 'current'
190 window (ie. which window is associated with any summ info displayed
191 and to which window commands are directed) */
192 static WIN_t Winstk [GROUPSMAX];
193 static WIN_t *Curwin;
195 /* Frame oriented stuff that can't remain local to any 1 function
196 and/or that would be too cumbersome managed as parms,
197 and/or that are simply more efficiently handled as globals
198 [ 'Frames_...' (plural) stuff persists beyond 1 frame ]
199 [ or are used in response to async signals received ! ] */
200 static volatile int Frames_signal; // time to rebuild all column headers
201 static float Frame_etscale; // so we can '*' vs. '/' WHEN 'pcpu'
203 /* Support for automatically sized fixed-width column expansions.
204 * (hopefully, the macros help clarify/document our new 'feature') */
205 static int Autox_array [EU_MAXPFLGS],
207 #define AUTOX_NO EU_MAXPFLGS
208 #define AUTOX_COL(f) if (EU_MAXPFLGS > f && f >= 0) Autox_array[f] = Autox_found = 1
209 #define AUTOX_MODE (0 > Rc.fixed_widest)
211 /* Support for scale_mem and scale_num (to avoid duplication. */
212 #ifdef CASEUP_SUFIX // nls_maybe
213 static char Scaled_sfxtab[] = { 'K', 'M', 'G', 'T', 'P', 'E', 0 };
215 static char Scaled_sfxtab[] = { 'k', 'm', 'g', 't', 'p', 'e', 0 };
218 /* Support for NUMA Node display plus node expansion and targeting */
220 static int Stderr_save = -1;
222 static int Numa_node_tot;
223 static int Numa_node_sel = -1;
225 /* Support for Graphing of the View_STATES ('t') and View_MEMORY ('m')
226 commands -- which are now both 4-way toggles */
227 #define GRAPH_length_max 100 // the actual bars or blocks
228 #define GRAPH_length_min 10 // the actual bars or blocks
229 #define GRAPH_prefix_std 25 // '%Cpunnn: 100.0/100.0 100[' or 'nnn-nnn: 100.0/100.0 100['
230 #define GRAPH_prefix_abv 12 // '%Cpunnn:100[' or 'nnn-nnn:100[' or 'GiB Mem 100[' or 'GiB Swap 99['
231 #define GRAPH_suffix 2 // '] ' (bracket + trailing space)
232 // first 3 more static (adj_geometry), last 3 volatile (sum_tics/do_memory)
234 float adjust; // bars/blocks scaling factor
235 int length; // scaled length (<= GRAPH_length_max)
236 int style; // rc.graph_cpus or rc.graph_mems
237 long total, part1, part2; // elements to be graphed
239 static struct graph_parms *Graph_cpus, *Graph_mems;
240 static const char Graph_blks[] = " ";
241 static const char Graph_bars[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
243 /* Support for 'Other Filters' in the configuration file */
244 static const char Osel_delim_1_txt[] = "begin: saved other filter data -------------------\n";
245 static const char Osel_delim_2_txt[] = "end : saved other filter data -------------------\n";
246 static const char Osel_window_fmts[] = "window #%d, osel_tot=%d\n";
247 #define OSEL_FILTER "filter="
248 static const char Osel_filterO_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%s\n";
249 static const char Osel_filterI_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%*s\n";
251 /* Support for adjoining display (if terminal is wide enough) */
253 static char Adjoin_sp[] = " ";
254 #define ADJOIN_space (sizeof(Adjoin_sp) - 1) // 1 for null
256 static char Adjoin_sp[] = " ~6 ~1";
257 #define ADJOIN_space (sizeof(Adjoin_sp) - 5) // 1 for null + 4 unprintable
259 #define ADJOIN_limit 8
261 /* Support for the new library API -- acquired (if necessary)
262 at program startup and referenced throughout our lifetime. */
264 * --- <proc/pids.h> -------------------------------------------------- */
265 static struct pids_info *Pids_ctx;
266 static int Pids_itms_tot; // same as MAXTBL(Fieldstab)
267 static enum pids_item *Pids_itms; // allocated as MAXTBL(Fieldstab)
268 static struct pids_fetch *Pids_reap; // for reap or select
269 #define PIDSmaxt Pids_reap->counts->total // just a little less wordy
270 // pid stack results extractor macro, where e=our EU enum, t=type, s=stack
271 // ( we'll exploit that <proc/pids.h> provided macro as much as possible )
272 // ( but many functions use their own unique tailored version for access )
273 #define PID_VAL(e,t,s) PIDS_VAL(e, t, s, Pids_ctx)
275 * --- <proc/stat.h> -------------------------------------------------- */
276 static struct stat_info *Stat_ctx;
277 static struct stat_reaped *Stat_reap;
278 static enum stat_item Stat_items[] = {
279 STAT_TIC_ID, STAT_TIC_NUMA_NODE,
280 STAT_TIC_DELTA_USER, STAT_TIC_DELTA_SYSTEM,
281 STAT_TIC_DELTA_NICE, STAT_TIC_DELTA_IDLE,
282 STAT_TIC_DELTA_IOWAIT, STAT_TIC_DELTA_IRQ,
283 STAT_TIC_DELTA_SOFTIRQ, STAT_TIC_DELTA_STOLEN,
284 STAT_TIC_SUM_DELTA_USER, STAT_TIC_SUM_DELTA_SYSTEM,
285 STAT_TIC_SUM_DELTA_TOTAL };
292 stat_SUM_USR, stat_SUM_SYS,
294 // cpu/node stack results extractor macros, where e=rel enum, x=index
295 #define CPU_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->cpus->stacks[x], Stat_ctx)
296 #define NOD_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->numa->stacks[x], Stat_ctx)
297 #define TIC_VAL(e,s) STAT_VAL(e, sl_int, s, Stat_ctx)
299 * --- <proc/meminfo.h> ----------------------------------------------- */
300 static struct meminfo_info *Mem_ctx;
301 static struct meminfo_stack *Mem_stack;
302 static enum meminfo_item Mem_items[] = {
303 MEMINFO_MEM_FREE, MEMINFO_MEM_USED, MEMINFO_MEM_TOTAL,
304 MEMINFO_MEM_CACHED_ALL, MEMINFO_MEM_BUFFERS, MEMINFO_MEM_AVAILABLE,
305 MEMINFO_SWAP_TOTAL, MEMINFO_SWAP_FREE, MEMINFO_SWAP_USED };
307 mem_FRE, mem_USE, mem_TOT,
308 mem_QUE, mem_BUF, mem_AVL,
309 swp_TOT, swp_FRE, swp_USE };
310 // mem stack results extractor macro, where e=rel enum
311 #define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx)
313 /* Support for concurrent library updates via
314 multithreaded background processes */
316 static pthread_t Thread_id_cpus;
317 static sem_t Semaphore_cpus_beg, Semaphore_cpus_end;
320 static pthread_t Thread_id_memory;
321 static sem_t Semaphore_memory_beg, Semaphore_memory_end;
324 static pthread_t Thread_id_tasks;
325 static sem_t Semaphore_tasks_beg, Semaphore_tasks_end;
327 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
328 static pthread_t Thread_id_main;
331 /* Support for a namespace with proc mounted subset=pid,
332 ( we'll limit our display to task information only ). */
333 static int Restrict_some = 0;
335 /*###### Tiny useful routine(s) ########################################*/
338 * This routine simply formats whatever the caller wants and
339 * returns a pointer to the resulting 'const char' string... */
340 static const char *fmtmk (const char *fmts, ...) __attribute__((format(printf,1,2)));
341 static const char *fmtmk (const char *fmts, ...) {
342 static char buf[BIGBUFSIZ]; // with help stuff, our buffer
343 va_list va; // requirements now exceed 1k
346 vsnprintf(buf, sizeof(buf), fmts, va);
348 return (const char *)buf;
353 * Interger based fieldscur version of 'strlen' */
354 static inline int mlen (const int *mem) {
357 for (i = 0; mem[i]; i++)
364 * Interger based fieldscur version of 'strchr' */
365 static inline int *msch (const int *mem, int obj, int max) {
368 for (i = 0; i < max; i++)
369 if (*(mem + i) == obj) return (int *)mem + i;
375 * This guy is just our way of avoiding the overhead of the standard
376 * strcat function (should the caller choose to participate) */
377 static inline char *scat (char *dst, const char *src) {
379 while ((*(dst++) = *(src++)));
385 * This guy just facilitates Batch and protects against dumb ttys
386 * -- we'd 'inline' him but he's only called twice per frame,
387 * yet used in many other locations. */
388 static const char *tg2 (int x, int y) {
389 // it's entirely possible we're trying for an invalid row...
390 return Cap_can_goto ? tgoto(cursor_address, x, y) : "";
393 /*###### Exit/Interrput routines #######################################*/
396 * Reset the tty, if necessary */
397 static void at_eoj (void) {
399 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original);
400 if (keypad_local) putp(keypad_local);
401 putp(tg2(0, Screen_rows));
405 // this next will also replace top's most recent screen with the
406 // original display contents that were visible at our invocation
418 /* we gotta reverse the stderr redirect which was employed during start up
419 and needed because the two libnuma 'weak' functions were useless to us! */
420 if (-1 < Stderr_save) {
421 dup2(Stderr_save, fileno(stderr));
423 Stderr_save = -1; // we'll be ending soon anyway but what the heck
430 * The real program end */
431 static void bye_bye (const char *str) __attribute__((__noreturn__));
432 static void bye_bye (const char *str) {
435 // POSIX.1 async-signal-safe: sigfillset, sigprocmask, pthread_sigmask
437 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
438 pthread_sigmask(SIG_BLOCK, &ss, NULL);
440 sigprocmask(SIG_BLOCK, &ss, NULL);
442 at_eoj(); // restore tty in preparation for exit
445 if (!str && !Frames_signal && Ttychanged) { fprintf(stderr,
446 "\n%s's Summary report:"
449 "\n\t Hertz = %u (%u bytes, %u-bit time)"
450 "\n\t Stat_reap->cpus->total = %d, Stat_reap->numa->total = %d"
451 "\n\t Pids_itms_tot = %d, sizeof(struct pids_result) = %d, pids stack size = %d"
452 "\n\t SCREENMAX = %d, ROWMINSIZ = %d, ROWMAXSIZ = %d"
453 "\n\t PACKAGE = '%s', LOCALEDIR = '%s'"
455 "\n\t device = %s, ncurses = v%s"
456 "\n\t max_colors = %d, max_pairs = %d"
457 "\n\t Cap_can_goto = %s"
458 "\n\t Screen_cols = %d, Screen_rows = %d"
459 "\n\t Max_lines = %d, most recent Pseudo_size = %u"
461 "\n\t Stdout_buf = %u, BUFSIZ = %u"
463 "\n\tWindows and Curwin->"
464 "\n\t sizeof(WIN_t) = %u, GROUPSMAX = %d"
465 "\n\t winname = %s, grpname = %s"
467 "\n\t winflags = %08X, maxpflgs = %d"
469 "\n\t winflags = x%08x, maxpflgs = %d"
471 "\n\t sortindx = %d, maxtasks = %d"
472 "\n\t varcolsz = %d, winlines = %d"
473 "\n\t strlen(columnhdr) = %d"
474 "\n\t current fieldscur = %d, maximum fieldscur = %d"
478 , (unsigned)Hertz, (unsigned)sizeof(Hertz), (unsigned)sizeof(Hertz) * 8
479 , Stat_reap->cpus->total, Stat_reap->numa->total
480 , Pids_itms_tot, (int)sizeof(struct pids_result), (int)(sizeof(struct pids_result) * Pids_itms_tot)
481 , (int)SCREENMAX, (int)ROWMINSIZ, (int)ROWMAXSIZ
488 , ttyname(STDOUT_FILENO), NCURSES_VERSION
489 , max_colors, max_pairs
490 , Cap_can_goto ? "yes" : "No!"
491 , Screen_cols, Screen_rows
492 , Max_lines, (unsigned)Pseudo_size
494 , (unsigned)sizeof(Stdout_buf), (unsigned)BUFSIZ
496 , (unsigned)sizeof(WIN_t), GROUPSMAX
497 , Curwin->rc.winname, Curwin->grpname
498 , Curwin->rc.winflags, Curwin->maxpflgs
499 , Curwin->rc.sortindx, Curwin->rc.maxtasks
500 , Curwin->varcolsz, Curwin->winlines
501 , (int)strlen(Curwin->columnhdr)
502 , EU_MAXPFLGS, mlen(Curwin->rc.fieldscur)
506 #endif // end: ATEOJ_RPTSTD
508 // there's lots of signal-unsafe stuff in the following ...
509 if (Frames_signal != BREAK_sig) {
510 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
511 /* can not execute any cleanup from a sibling thread and
512 we will violate proper indentation to minimize impact */
513 if (pthread_equal(Thread_id_main, pthread_self())) {
516 pthread_cancel(Thread_id_cpus);
517 pthread_join(Thread_id_cpus, NULL);
518 sem_destroy(&Semaphore_cpus_end);
519 sem_destroy(&Semaphore_cpus_beg);
522 pthread_cancel(Thread_id_memory);
523 pthread_join(Thread_id_memory, NULL);
524 sem_destroy(&Semaphore_memory_end);
525 sem_destroy(&Semaphore_memory_beg);
528 pthread_cancel(Thread_id_tasks);
529 pthread_join(Thread_id_tasks, NULL);
530 sem_destroy(&Semaphore_tasks_end);
531 sem_destroy(&Semaphore_tasks_beg);
533 procps_pids_unref(&Pids_ctx);
534 procps_stat_unref(&Stat_ctx);
535 procps_meminfo_unref(&Mem_ctx);
536 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
541 /* we'll only have a 'str' if called by error_exit() |
542 not ever from the sig_endpgm() signal handler ... | */
547 /* this could happen when called from several places |
548 including that sig_endpgm(). thus we must use an |
549 async-signal-safe write function just in case ... |
550 (thanks: Shaohua Zhan shaohua.zhan@windriver.com) | */
552 write(fileno(stdout), "\n", sizeof("\n") - 1);
559 * Standard error handler to normalize the look of all err output */
560 static void error_exit (const char *str) __attribute__((__noreturn__));
561 static void error_exit (const char *str) {
562 static char buf[MEDBUFSIZ];
564 Frames_signal = BREAK_off;
565 /* we'll use our own buffer so callers can still use fmtmk() and, after
566 twelve long years, 2013 was the year we finally eliminated the leading
567 tab character -- now our message can get lost in screen clutter too! */
568 snprintf(buf, sizeof(buf), "%s: %s\n", Myname, str);
574 * Catches all remaining signals not otherwise handled */
575 static void sig_abexit (int sig) __attribute__((__noreturn__));
576 static void sig_abexit (int sig) {
579 // POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, sigprocmask, pthread_sigmask, raise
581 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
582 pthread_sigmask(SIG_BLOCK, &ss, NULL);
584 sigprocmask(SIG_BLOCK, &ss, NULL);
586 at_eoj(); // restore tty in preparation for exit
587 fprintf(stderr, N_fmt(EXIT_signals_fmt)
588 , sig, signal_number_to_name(sig), Myname);
589 signal(sig, SIG_DFL); // allow core dumps, if applicable
592 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
593 pthread_sigmask(SIG_UNBLOCK, &ss, NULL);
595 sigprocmask(SIG_UNBLOCK, &ss, NULL);
597 raise(sig); // ( plus set proper return code )
598 _exit(EXIT_FAILURE); // if default sig action is ignore
604 * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM,
605 * SIGUSR1 and SIGUSR2 */
606 static void sig_endpgm (int dont_care_sig) __attribute__((__noreturn__));
607 static void sig_endpgm (int dont_care_sig) {
608 Frames_signal = BREAK_sig;
616 * SIGTSTP, SIGTTIN and SIGTTOU */
617 static void sig_paused (int dont_care_sig) {
618 // POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise
619 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original))
620 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
621 if (keypad_local) putp(keypad_local);
622 putp(tg2(0, Screen_rows));
627 // tcdrain(STDOUT_FILENO) was not reliable prior to ncurses-5.9.20121017,
628 // so we'll risk POSIX's wrath with good ol' fflush, lest 'Stopped' gets
629 // co-mingled with our most recent output...
632 // later, after SIGCONT...
633 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw))
634 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
638 if (keypad_xmit) putp(keypad_xmit);
640 Frames_signal = BREAK_sig;
647 * SIGCONT and SIGWINCH */
648 static void sig_resize (int dont_care_sig) {
649 // POSIX.1 async-signal-safe: tcdrain
650 tcdrain(STDOUT_FILENO);
651 Frames_signal = BREAK_sig;
655 /*###### Special UTF-8 Multi-Byte support ##############################*/
657 /* Support for NLS translated multi-byte strings */
658 static char UTF8_tab[] = {
659 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F
660 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F
661 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
662 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F
663 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
664 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F
665 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
666 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F
667 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F
668 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F
669 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF
670 -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF
671 -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF, 0xC2 = begins 2
672 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF
673 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF, 0xE0 = begins 3
674 4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF, 0xF0 = begins 4
675 }; // ( 0xF5 & beyond invalid )
679 * Accommodate any potential differences between some multibyte
680 * character sequence and the screen columns needed to print it */
681 static inline int utf8_cols (const unsigned char *p, int n) {
686 (void)mbtowc(&wc, (const char *)p, n);
687 // allow a zero as valid, as with a 'combining acute accent'
688 if ((n = wcwidth(wc)) < 0) n = 1;
699 * Determine difference between total bytes versus printable
700 * characters in that passed, potentially multi-byte, string */
701 static int utf8_delta (const char *str) {
702 const unsigned char *p = (const unsigned char *)str;
706 // -1 represents a decoding error, pretend it's untranslated ...
707 if (0 > (clen = UTF8_tab[*p])) return 0;
708 cnum += utf8_cols(p, clen);
711 return (int)((const char *)p - str) - cnum;
716 * Determine a physical end within a potential multi-byte string
717 * where maximum printable chars could be accommodated in width */
718 static int utf8_embody (const char *str, int width) {
719 const unsigned char *p = (const unsigned char *)str;
724 // -1 represents a decoding error, pretend it's untranslated ...
725 if (0 > (clen = UTF8_tab[*p])) return width;
726 if (width < (cnum += utf8_cols(p, clen))) break;
730 return (int)((const char *)p - str);
731 } // end: utf8_embody
735 * Like the regular justify_pad routine but this guy
736 * can accommodate the multi-byte translated strings */
737 static const char *utf8_justify (const char *str, int width, int justr) {
738 static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s";
739 static char buf[SCREENMAX];
742 snprintf(tmp, sizeof(tmp), "%.*s", utf8_embody(str, width), str);
743 width += utf8_delta(tmp);
744 snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, tmp, COLPADSTR);
746 } // end: utf8_justify
750 * Returns a physical or logical column number given a
751 * multi-byte string and a target column value */
752 static int utf8_proper_col (const char *str, int col, int tophysical) {
753 const unsigned char *p = (const unsigned char *)str;
754 int clen, tlen = 0, cnum = 0;
757 // -1 represents a decoding error, don't encourage repositioning ...
758 if (0 > (clen = UTF8_tab[*p])) return col;
759 if (cnum + 1 > col && tophysical) break;
762 if (tlen > col && !tophysical) break;
765 return tophysical ? tlen : cnum;
766 } // end: utf8_proper_col
768 /*###### Misc Color/Display support ####################################*/
771 * Make the appropriate caps/color strings for a window/field group.
772 * note: we avoid the use of background color so as to maximize
773 * compatibility with the user's xterm settings */
774 static void capsmk (WIN_t *q) {
775 /* macro to test if a basic (non-color) capability is valid
776 thanks: Floyd Davidson <floyd@ptialaska.net> */
777 #define tIF(s) s ? s : ""
778 /* macro to make compatible with netbsd-curses too
779 thanks: rofl0r <retnyg@gmx.net> */
780 #define tPM(a,b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0)
781 static int capsdone = 0;
783 // we must NOT disturb our 'empty' terminfo strings!
786 // these are the unchangeable puppies, so we only do 'em once
788 STRLCPY(Cap_clr_eol, tIF(clr_eol))
789 STRLCPY(Cap_clr_eos, tIF(clr_eos))
790 STRLCPY(Cap_clr_scr, tIF(clear_screen))
791 // due to the leading newline, the following must be used with care
792 snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos));
793 STRLCPY(Cap_curs_huge, tIF(cursor_visible))
794 STRLCPY(Cap_curs_norm, tIF(cursor_normal))
795 STRLCPY(Cap_curs_hide, tIF(cursor_invisible))
796 STRLCPY(Cap_home, tIF(cursor_home))
797 STRLCPY(Cap_norm, tIF(exit_attribute_mode))
798 STRLCPY(Cap_reverse, tIF(enter_reverse_mode))
800 if (!eat_newline_glitch) {
801 STRLCPY(Cap_rmam, tIF(exit_am_mode))
802 STRLCPY(Cap_smam, tIF(enter_am_mode))
803 if (!*Cap_rmam || !*Cap_smam) {
806 if (auto_right_margin)
812 snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm, tIF(orig_pair));
813 snprintf(Caps_endline, sizeof(Caps_endline), "%s%s", Caps_off, Cap_clr_eol);
814 if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1;
818 /* the key to NO run-time costs for configurable colors -- we spend a
819 little time with the user now setting up our terminfo strings, and
820 the job's done until he/she/it has a change-of-heart */
821 STRLCPY(q->cap_bold, CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode))
822 if (CHKw(q, Show_COLORS) && max_colors > 0) {
823 STRLCPY(q->capclr_sum, tPM(set_a_foreground, q->rc.summclr))
824 snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
825 , tPM(set_a_foreground, q->rc.msgsclr), Cap_reverse);
826 snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s"
827 , tPM(set_a_foreground, q->rc.msgsclr), q->cap_bold);
828 snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s"
829 , tPM(set_a_foreground, q->rc.headclr), Cap_reverse);
830 snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s"
831 , Caps_off, tPM(set_a_foreground, q->rc.taskclr));
833 q->capclr_sum[0] = '\0';
835 snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s"
836 , Cap_reverse, q->cap_bold);
838 STRLCPY(q->capclr_msg, Cap_reverse)
840 STRLCPY(q->capclr_pmt, q->cap_bold)
841 STRLCPY(q->capclr_hdr, Cap_reverse)
842 STRLCPY(q->capclr_rownorm, Cap_norm)
845 // composite(s), so we do 'em outside and after the if
846 snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s"
847 , q->capclr_rownorm, CHKw(q, Show_HIBOLD) ? q->cap_bold : Cap_reverse);
853 static struct msg_node {
855 struct msg_node *prev;
856 } Msg_tab[BOT_MSGSMAX];
858 static struct msg_node *Msg_this = Msg_tab;
861 * Show an error message (caller may include '\a' for sound) */
862 static void show_msg (const char *str) {
863 STRLCPY(Msg_this->msg, str);
864 if (++Msg_this > &Msg_tab[BOT_MSGSMAX - 1]) Msg_this = Msg_tab;
866 PUTT("%s%s %.*s %s%s%s"
869 , utf8_embody(str, Screen_cols - 2)
880 * Show an input prompt + larger cursor (if possible) */
881 static int show_pmt (const char *str) {
885 snprintf(buf, sizeof(buf), "%.*s", utf8_embody(str, Screen_cols - 2), str);
886 len = utf8_delta(buf);
888 PUTT("\n%s%s%.*s %s%s%s"
890 PUTT("%s%s%.*s %s%s%s"
894 , (Screen_cols - 2) + len
900 len = strlen(buf) - len;
901 // +1 for the space we added or -1 for the cursor...
902 return (len + 1 < Screen_cols) ? len + 1 : Screen_cols - 1;
907 * Create and print the optional scroll coordinates message */
908 static void show_scroll (void) {
909 char tmp1[SMLBUFSIZ];
911 char tmp2[SMLBUFSIZ];
913 int totpflgs = Curwin->totpflgs;
914 int begpflgs = Curwin->begpflg + 1;
917 if (CHKw(Curwin, Show_HICOLS)) {
919 if (ENUpos(Curwin, Curwin->rc.sortindx) < Curwin->begpflg) begpflgs -= 2;
922 if (1 > totpflgs) totpflgs = 1;
923 if (1 > begpflgs) begpflgs = 1;
924 snprintf(tmp1, sizeof(tmp1), N_fmt(SCROLL_coord_fmt), Curwin->begtask + 1, PIDSmaxt, begpflgs, totpflgs);
926 if (Curwin->varcolbeg) {
927 snprintf(tmp2, sizeof(tmp2), " + %d", Curwin->varcolbeg);
931 PUTT("%s%s %.*s%s", tg2(0, Msg_row), Caps_off, Screen_cols - 3, tmp1, Cap_clr_eol);
932 } // end: show_scroll
936 * Show lines with specially formatted elements, but only output
937 * what will fit within the current screen width.
938 * Our special formatting consists of:
939 * "some text <_delimiter_> some more text <_delimiter_>...\n"
940 * Where <_delimiter_> is a two byte combination consisting of a
941 * tilde followed by an ascii digit in the range of 1 - 8.
942 * examples: ~1, ~5, ~8, etc.
943 * The tilde is effectively stripped and the next digit
944 * converted to an index which is then used to select an
945 * 'attribute' from a capabilities table. That attribute
946 * is then applied to the *preceding* substring.
947 * Once recognized, the delimiter is replaced with a null character
948 * and viola, we've got a substring ready to output! Strings or
949 * substrings without delimiters will receive the Cap_norm attribute.
952 * This routine treats all non-delimiter bytes as displayable
953 * data subject to our screen width marching orders. If callers
954 * embed non-display data like tabs or terminfo strings in our
955 * glob, a line will truncate incorrectly at best. Worse case
956 * would be truncation of an embedded tty escape sequence.
958 * Tabs must always be avoided or our efforts are wasted and
959 * lines will wrap. To lessen but not eliminate the risk of
960 * terminfo string truncation, such non-display stuff should
961 * be placed at the beginning of a "short" line. */
962 static void show_special (int interact, const char *glob) {
963 /* note: the following is for documentation only,
964 the real captab is now found in a group's WIN_t !
965 +------------------------------------------------------+
966 | char *captab[] = { : Cap's = Index |
967 | Cap_norm, Cap_norm, = \000, \001, |
968 | cap_bold, capclr_sum, = \002, \003, |
969 | capclr_msg, capclr_pmt, = \004, \005, |
970 | capclr_hdr, = \006, |
971 | capclr_rowhigh, = \007, |
972 | capclr_rownorm }; = \010 [octal!] |
973 +------------------------------------------------------+ */
974 /* ( Pssst, after adding the termcap transitions, row may )
975 ( exceed 300+ bytes, even in an 80x24 terminal window! )
976 ( Shown here are the former buffer size specifications )
977 ( char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]. )
978 ( So now we use larger buffers and a little protection )
979 ( against overrunning them with this 'lin_end - glob'. )
981 ( That was uncovered during 'Inspect' development when )
982 ( this guy was being considered for a supporting role. )
983 ( However, such an approach was abandoned. As a result )
984 ( this function is called only with a glob under top's )
985 ( control and never containing any 'raw/binary' chars! ) */
986 char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMINSIZ];
987 char *rp, *lin_end, *sub_beg, *sub_end;
990 // handle multiple lines passed in a bunch
991 while ((lin_end = strchr(glob, '\n'))) {
992 #define myMIN(a,b) (((a) < (b)) ? (a) : (b))
993 size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -3);
995 // create a local copy we can extend and otherwise abuse
996 memcpy(lin, glob, lessor);
997 // zero terminate this part and prepare to parse substrings
1000 sub_beg = sub_end = lin;
1005 if ('~' == ch) ch = *(sub_end + 1) - '0';
1007 case 0: // no end delim, captab makes normal
1008 // only possible when '\n' was NOT preceded with a '~#' sequence
1009 // ( '~1' thru '~8' is valid range, '~0' is never actually used )
1010 *(sub_end + 1) = '\0'; // extend str end, then fall through
1011 *(sub_end + 2) = '\0'; // ( +1 optimization for usual path )
1013 case 1: case 2: case 3: case 4:
1014 case 5: case 6: case 7: case 8:
1016 snprintf(tmp, sizeof(tmp), "%s%.*s%s"
1017 , Curwin->captab[ch], utf8_embody(sub_beg, room), sub_beg, Caps_off);
1019 room -= (sub_end - sub_beg);
1020 room += utf8_delta(sub_beg);
1021 sub_beg = (sub_end += 2);
1023 default: // nothin' special, just text
1026 if (0 >= room) break; // skip substrings that won't fit
1029 if (interact) PUTT("%s%s\n", row, Cap_clr_eol);
1030 else PUFF("%s%s\n", row, Caps_endline);
1031 glob = ++lin_end; // point to next line (maybe)
1034 } // end: while 'lines'
1036 /* If there's anything left in the glob (by virtue of no trailing '\n'),
1037 it probably means caller wants to retain cursor position on this final
1038 line. That, in turn, means we're interactive and so we'll just do our
1039 'fit-to-screen' thingy while also leaving room for the cursor... */
1040 if (*glob) PUTT("%.*s", utf8_embody(glob, Screen_cols - 1), glob);
1041 } // end: show_special
1043 /*###### Low Level Memory/Keyboard/File I/O support ####################*/
1046 * Handle our own memory stuff without the risk of leaving the
1047 * user's terminal in an ugly state should things go sour. */
1049 static void *alloc_c (size_t num) {
1053 if (!(pv = calloc(1, num)))
1054 error_exit(N_txt(FAIL_alloc_c_txt));
1059 static void *alloc_r (void *ptr, size_t num) {
1063 if (!(pv = realloc(ptr, num)))
1064 error_exit(N_txt(FAIL_alloc_r_txt));
1069 static char *alloc_s (const char *str) {
1070 return strcpy(alloc_c(strlen(str) +1), str);
1075 * An 'I/O available' routine which will detect raw single byte |
1076 * unsolicited keyboard input which was susceptible to SIGWINCH |
1077 * interrupts (or any other signal). He'll also support timout |
1078 * in the absence of any user keystrokes or a signal interrupt. | */
1079 static inline int ioa (struct timespec *ts) {
1084 FD_SET(STDIN_FILENO, &fs);
1086 #ifdef SIGNALS_LESS // conditional comments are silly, but help in documenting
1087 // hold here until we've got keyboard input, any signal except SIGWINCH
1088 // or (optionally) we timeout with nanosecond granularity
1090 // hold here until we've got keyboard input, any signal (including SIGWINCH)
1091 // or (optionally) we timeout with nanosecond granularity
1093 rc = pselect(STDIN_FILENO + 1, &fs, NULL, NULL, ts, &Sigwinch_set);
1101 * This routine isolates ALL user INPUT and ensures that we
1102 * wont be mixing I/O from stdio and low-level read() requests */
1103 static int ioch (int ech, char *buf, unsigned cnt) {
1108 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_tweaked);
1109 rc = read(STDIN_FILENO, buf, cnt);
1110 tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw);
1113 rc = read(STDIN_FILENO, buf, cnt);
1118 rc = read(STDIN_FILENO, buf, cnt);
1121 // zero means EOF, might happen if we erroneously get detached from terminal
1122 if (0 == rc) bye_bye(NULL);
1124 // it may have been the beginning of a lengthy escape sequence
1125 tcflush(STDIN_FILENO, TCIFLUSH);
1127 // note: we do NOT produce a valid 'string'
1132 #define IOKEY_INIT 0
1133 #define IOKEY_ONCE 1
1134 #define IOKEY_NEXT 2
1137 * Support for single or multiple keystroke input AND
1138 * escaped cursor motion keys.
1139 * note: we support more keys than we currently need, in case
1140 * we attract new consumers in the future */
1141 static int iokey (int action) {
1146 { NULL, kbd_BKSP }, { NULL, kbd_INS }, { NULL, kbd_DEL }, { NULL, kbd_LEFT },
1147 { NULL, kbd_DOWN }, { NULL, kbd_UP }, { NULL, kbd_RIGHT }, { NULL, kbd_HOME },
1148 { NULL, kbd_PGDN }, { NULL, kbd_PGUP }, { NULL, kbd_END }, { NULL, kbd_BTAB },
1149 // remainder are alternatives for above, just in case...
1150 // ( the h,j,k,l entries are the vim cursor motion keys )
1151 { "\033h", kbd_LEFT }, { "\033j", kbd_DOWN }, /* meta+ h,j */
1152 { "\033k", kbd_UP }, { "\033l", kbd_RIGHT }, /* meta+ k,l */
1153 { "\033\010", kbd_HOME }, { "\033\012", kbd_PGDN }, /* ctrl+meta+ h,j */
1154 { "\033\013", kbd_PGUP }, { "\033\014", kbd_END }, /* ctrl+meta+ k,l */
1155 { "\xC3\xA8", kbd_LEFT }, { "\xC3\xAA", kbd_DOWN }, /* meta+ h,j (some xterms) */
1156 { "\xC3\xAB", kbd_UP }, { "\xC3\xAC", kbd_RIGHT }, /* meta+ k,l (some xterms) */
1157 { "\xC2\x88", kbd_HOME }, { "\xC2\x8A", kbd_PGDN }, /* ctrl+meta+ h,j (some xterms) */
1158 { "\xC2\x8B", kbd_PGUP }, { "\xC2\x8C", kbd_END }, /* ctrl+meta+ k,l (some xterms) */
1159 { "\033\011", kbd_BTAB }
1162 char buf[SMLBUFSIZ], *pb;
1164 static char buf[MEDBUFSIZ];
1165 static int pos, len;
1170 if (action == IOKEY_INIT) {
1171 #define tOk(s) s ? s : ""
1172 tinfo_tab[0].str = tOk(key_backspace);
1173 tinfo_tab[1].str = tOk(key_ic);
1174 tinfo_tab[2].str = tOk(key_dc);
1175 tinfo_tab[3].str = tOk(key_left);
1176 tinfo_tab[4].str = tOk(key_down);
1177 tinfo_tab[5].str = tOk(key_up);
1178 tinfo_tab[6].str = tOk(key_right);
1179 tinfo_tab[7].str = tOk(key_home);
1180 tinfo_tab[8].str = tOk(key_npage);
1181 tinfo_tab[9].str = tOk(key_ppage);
1182 tinfo_tab[10].str = tOk(key_end);
1183 tinfo_tab[11].str = tOk(back_tab);
1184 // next is critical so returned results match bound terminfo keys
1185 putp(tOk(keypad_xmit));
1186 // ( converse keypad_local issued at pause/pgm end, just in case )
1191 if (action == IOKEY_ONCE) {
1192 memset(buf, '\0', sizeof(buf));
1193 if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1196 #ifndef TERMIOS_ONLY
1197 if (action == IOKEY_NEXT) {
1199 return buf[pos++]; // exhaust prior keystrokes
1201 memset(buf, '\0', sizeof(buf));
1202 if (1 > ioch(0, buf, sizeof(buf)-1)) return 0;
1203 if (!iscntrl(buf[0])) { // no need for translation
1211 /* some emulators implement 'key repeat' too well and we get duplicate
1212 key sequences -- so we'll focus on the last escaped sequence, while
1213 also allowing use of the meta key... */
1214 if (!(pb = strrchr(buf, '\033'))) pb = buf;
1215 else if (pb > buf && '\033' == *(pb - 1)) --pb;
1217 for (i = 0; i < MAXTBL(tinfo_tab); i++)
1218 if (!strcmp(tinfo_tab[i].str, pb))
1219 return tinfo_tab[i].key;
1221 // no match, so we'll return single non-escaped keystrokes only
1222 if (buf[0] == '\033' && buf[1]) return -1;
1229 * Get line oriented interactive input from the user,
1230 * using native tty support */
1231 static char *ioline (const char *prompt) {
1232 static const char ws[] = "\b\f\n\r\t\v\x1b\x9b"; // 0x1b + 0x9b are escape
1233 static char buf[MEDBUFSIZ];
1237 memset(buf, '\0', sizeof(buf));
1238 ioch(1, buf, sizeof(buf)-1);
1240 if ((p = strpbrk(buf, ws))) *p = '\0';
1241 // note: we DO produce a vaid 'string'
1247 * Get some line oriented interactive input from the ol' user,
1248 * going way, way beyond that native tty support by providing:
1249 * . true input line editing, not just a destructive backspace
1250 * . an input limit sensitive to the current screen dimensions
1251 * . an ability to recall prior strings for editing & re-input */
1252 static char *ioline (const char *prompt) {
1253 #define setLEN ( len = strlen(buf) - utf8_delta(buf) )
1254 #define setPOS(X) ( pos = utf8_embody(buf, X) )
1255 #define utfCHR(X) ( (unsigned char *)&buf[X] )
1256 #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] )
1257 #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) )
1258 #define movBKW { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; }
1259 #define chkCUR { if (cur < 0) cur = 0; if (cur > len) cur = len; }
1260 // thank goodness ol' memmove will safely allow strings to overlap
1261 #define sqzSTR { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \
1262 if (!utfCOL(pos + i)) i += utfTOT(pos + i); \
1263 memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \
1264 memset(&buf[bufMAX - i], '\0', i); }
1265 #define expSTR(X) if (bufNXT < bufMAX && scrNXT < Screen_cols) { \
1266 memmove(&buf[pos + X], &buf[pos], bufMAX - pos); }
1268 #define bufNXT ( pos + 4 ) // four equals longest utf8 str
1269 #define scrNXT ( beg + len + 2 ) // two due to multi-column char
1270 #define bufMAX ((int)sizeof(buf)-2) // -1 for '\0' string delimeter
1271 static char buf[MEDBUFSIZ+1]; // +1 for '\0' string delimeter
1273 int beg, // the physical column where input began, buf[0]
1274 cur, // the logical current column/insertion position
1275 len, // the logical input length, thus the end column
1276 pos, // the physical position in the buffer currently
1279 struct lin_s *bkw; // pointer for older saved strs
1280 struct lin_s *fwd; // pointer for newer saved strs
1281 char *str; // an actual saved input string
1283 static struct lin_s *anchor, *plin;
1286 anchor = alloc_c(sizeof(struct lin_s));
1287 anchor->str = alloc_s(""); // the top-of-stack (empty str)
1290 cur = len = pos = 0;
1291 beg = show_pmt(prompt);
1292 memset(buf, '\0', sizeof(buf));
1293 // this may not work under a gui emulator (but linux console is ok)
1294 putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1298 key = iokey(IOKEY_NEXT);
1307 case kbd_BTAB: case kbd_PGUP: case kbd_PGDN:
1311 putp(ovt ? Cap_curs_huge : Cap_curs_norm);
1317 if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; }
1320 if (0 < cur) { movBKW; cur -= utfCOL(pos); }
1323 if (cur < len) cur += utfCOL(pos);
1335 memset(buf, '\0', sizeof(buf));
1336 memccpy(buf, plin->str, '\0', bufMAX);
1342 if (plin->fwd) plin = plin->fwd;
1343 memset(buf, '\0', sizeof(buf));
1344 memccpy(buf, plin->str, '\0', bufMAX);
1348 default: // what we REALLY wanted (maybe)
1349 if (bufNXT < bufMAX && scrNXT < Screen_cols && strlen(buf) < bufMAX) {
1350 int tot = UTF8_tab[(unsigned char)key],
1352 if (tot < 1) tot = 1;
1353 if (!ovt) { expSTR(tot); }
1354 else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); }
1357 key = iokey(IOKEY_NEXT);
1368 putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf));
1370 putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' '));
1372 putp(tg2(beg + cur, Msg_row));
1373 } while (key != kbd_ENTER);
1375 // weed out duplicates, including empty strings (top-of-stack)...
1376 for (i = 0, plin = anchor; ; i++) {
1378 if (!STRCMP(plin->str, buf)) // if matched, retain original order
1381 if (!STRCMP(plin->str, buf)) { // if matched, rearrange stack order
1382 if (i > 1) { // but not null str or if already #2
1383 if (plin->bkw) // splice around this matched string
1384 plin->bkw->fwd = plin->fwd; // if older exists link to newer
1385 plin->fwd->bkw = plin->bkw; // newer linked to older or NULL
1386 anchor->bkw->fwd = plin; // stick matched on top of former #2
1387 plin->bkw = anchor->bkw; // keep empty string at top-of-stack
1388 plin->fwd = anchor; // then prepare to be the 2nd banana
1389 anchor->bkw = plin; // by sliding us in below the anchor
1394 if (!plin->bkw) break; // let i equal total stacked strings
1395 plin = plin->bkw; // ( with plin representing bottom )
1398 plin = alloc_c(sizeof(struct lin_s));
1399 else { // when a new string causes overflow
1400 plin->fwd->bkw = NULL; // make next-to-last string new last
1401 free(plin->str); // and toss copy but keep the struct
1403 plin->str = alloc_s(buf); // copy user's new unique input line
1404 plin->bkw = anchor->bkw; // keep empty string as top-of-stack
1405 if (plin->bkw) // did we have some already stacked?
1406 plin->bkw->fwd = plin; // yep, so point prior to new string
1407 plin->fwd = anchor; // and prepare to be a second banana
1408 anchor->bkw = plin; // by sliding it in as new number 2!
1410 return buf; // protect our copy, return original
1429 * Make locale unaware float (but maybe restrict to whole numbers). */
1430 static int mkfloat (const char *str, float *num, int whole) {
1431 char tmp[SMLBUFSIZ], *ep;
1434 *num = (float)strtol(str, &ep, 0);
1435 if (ep != str && *ep == '\0' && *num < INT_MAX)
1439 snprintf(tmp, sizeof(tmp), "%s", str);
1440 *num = strtof(tmp, &ep);
1442 // fallback - try to swap the floating point separator
1443 if (*ep == '.') *ep = ',';
1444 else if (*ep == ',') *ep = '.';
1445 *num = strtof(tmp, &ep);
1447 if (ep != tmp && *ep == '\0' && *num < INT_MAX)
1454 * This routine provides the i/o in support of files whose size
1455 * cannot be determined in advance. Given a stream pointer, he'll
1456 * try to slurp in the whole thing and return a dynamically acquired
1457 * buffer supporting that single string glob.
1459 * He always creates a buffer at least READMINSZ big, possibly
1460 * all zeros (an empty string), even if the file wasn't read. */
1461 static int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread) {
1462 char chunk[4096*16];
1467 *baddr = alloc_c(READMINSZ);
1469 while (0 < (num = fread(chunk, 1, sizeof(chunk), fp))) {
1470 *baddr = alloc_r(*baddr, num + *bsize);
1471 memcpy(*baddr + *bread, chunk, num);
1475 *(*baddr + *bread) = '\0';
1481 /*###### Small Utility routines ########################################*/
1483 #define GET_NUM_BAD INT_MIN
1484 #define GET_NUM_ESC (INT_MIN + 1)
1485 #define GET_NUM_NOT (INT_MIN + 2)
1488 * Get a float from the user */
1489 static float get_float (const char *prompt) {
1493 line = ioline(prompt);
1494 if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1495 if (!line[0]) return GET_NUM_NOT;
1496 // note: we're not allowing negative floats
1497 if (!mkfloat(line, &f, 0) || f < 0) {
1498 show_msg(N_txt(BAD_numfloat_txt));
1506 * Get an integer from the user, returning INT_MIN for error */
1507 static int get_int (const char *prompt) {
1511 line = ioline(prompt);
1512 if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC;
1513 if (!line[0]) return GET_NUM_NOT;
1514 // note: we've got to allow negative ints (renice)
1515 if (!mkfloat(line, &f, 1)) {
1516 show_msg(N_txt(BAD_integers_txt));
1524 * Make a hex value, and maybe suppress zeroes. */
1525 static inline const char *hex_make (long num, int noz) {
1526 static char buf[SMLBUFSIZ];
1530 snprintf(buf, sizeof(buf), "%08lX", num);
1532 snprintf(buf, sizeof(buf), "%08lx", num);
1535 for (i = 0; buf[i]; i++)
1543 * Validate the passed string as a user name or number,
1544 * and/or update the window's 'u/U' selection stuff. */
1545 static const char *user_certify (WIN_t *q, const char *str, char typ) {
1554 if ('!' == *str) { ++str; q->usrselflg = 0; }
1555 num = (uid_t)strtoul(str, &endp, 0);
1556 if ('\0' == *endp) {
1557 pwd = getpwuid(num);
1559 /* allow foreign users, from e.g within chroot
1560 ( thanks Dr. Werner Fink <werner@suse.de> ) */
1566 pwd = getpwnam(str);
1567 if (!pwd) return N_txt(BAD_username_txt);
1568 q->usrseluid = pwd->pw_uid;
1572 } // end: user_certify
1574 /*###### Basic Formatting support ######################################*/
1577 * Just do some justify stuff, then add post column padding. */
1578 static inline const char *justify_pad (const char *str, int width, int justr) {
1579 static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s";
1580 static char buf[SCREENMAX];
1582 snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, str, COLPADSTR);
1584 } // end: justify_pad
1588 * Make and then justify a single character. */
1589 static inline const char *make_chr (const char ch, int width, int justr) {
1590 static char buf[SMLBUFSIZ];
1592 snprintf(buf, sizeof(buf), "%c", ch);
1593 return justify_pad(buf, width, justr);
1598 * Make and then justify an integer NOT subject to scaling,
1599 * and include a visual clue should tuncation be necessary. */
1600 static inline const char *make_num (long num, int width, int justr, int col, int noz) {
1601 static char buf[SMLBUFSIZ];
1604 if (noz && Rc.zero_suppress && 0 == num)
1607 if (width < snprintf(buf, sizeof(buf), "%ld", num)) {
1608 if (width <= 0 || (size_t)width >= sizeof(buf))
1609 width = sizeof(buf)-1;
1610 buf[width-1] = COLPLUSCH;
1615 return justify_pad(buf, width, justr);
1620 * Make and then justify a character string,
1621 * and include a visual clue should tuncation be necessary. */
1622 static inline const char *make_str (const char *str, int width, int justr, int col) {
1623 static char buf[SCREENMAX];
1625 if (width < snprintf(buf, sizeof(buf), "%s", str)) {
1626 if (width <= 0 || (size_t)width >= sizeof(buf))
1627 width = sizeof(buf)-1;
1628 buf[width-1] = COLPLUSCH;
1632 return justify_pad(buf, width, justr);
1637 * Make and then justify a potentially multi-byte character string,
1638 * and include a visual clue should tuncation be necessary. */
1639 static inline const char *make_str_utf8 (const char *str, int width, int justr, int col) {
1640 static char buf[SCREENMAX];
1641 int delta = utf8_delta(str);
1643 if (width + delta < snprintf(buf, sizeof(buf), "%s", str)) {
1644 snprintf(buf, sizeof(buf), "%.*s%c", utf8_embody(str, width-1), str, COLPLUSCH);
1645 delta = utf8_delta(buf);
1648 return justify_pad(buf, width + delta, justr);
1649 } // end: make_str_utf8
1653 * Do some scaling then justify stuff.
1654 * We'll interpret 'num' as a kibibytes quantity and try to
1655 * format it to reach 'target' while also fitting 'width'. */
1656 static const char *scale_mem (int target, float num, int width, int justr) {
1657 // SK_Kb SK_Mb SK_Gb SK_Tb SK_Pb SK_Eb
1659 static const char *fmttab[] = { "%.0f", "%#.1f%c", "%#.3f%c", "%#.3f%c", "%#.3f%c", NULL };
1661 static const char *fmttab[] = { "%.0f", "%.1f%c", "%.1f%c", "%.1f%c", "%.1f%c", NULL };
1663 static char buf[SMLBUFSIZ];
1668 if (Rc.zero_suppress && 0 >= num)
1671 for (i = SK_Kb, psfx = Scaled_sfxtab; i < SK_Eb; psfx++, i++) {
1673 && (width >= snprintf(buf, sizeof(buf), fmttab[i], num, *psfx)))
1678 // well shoot, this outta' fit...
1679 snprintf(buf, sizeof(buf), "?");
1681 return justify_pad(buf, width, justr);
1686 * Do some scaling then justify stuff. */
1687 static const char *scale_num (float num, int width, int justr) {
1688 static char buf[SMLBUFSIZ];
1692 if (Rc.zero_suppress && 0 >= num)
1694 if (width >= snprintf(buf, sizeof(buf), "%.0f", num))
1697 for (psfx = Scaled_sfxtab; 0 < *psfx; psfx++) {
1699 if (width >= snprintf(buf, sizeof(buf), "%.1f%c", num, *psfx))
1701 if (width >= snprintf(buf, sizeof(buf), "%.0f%c", num, *psfx))
1705 // well shoot, this outta' fit...
1706 snprintf(buf, sizeof(buf), "?");
1708 return justify_pad(buf, width, justr);
1713 * Make and then justify a percentage, with decreasing precision. */
1714 static const char *scale_pcnt (float num, int width, int justr, int xtra) {
1715 static char buf[SMLBUFSIZ];
1718 if (Rc.zero_suppress && 0 >= num)
1721 if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1723 if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1728 if (width >= snprintf(buf, sizeof(buf), "%#.3f", num))
1730 if (width >= snprintf(buf, sizeof(buf), "%#.2f", num))
1735 if (width >= snprintf(buf, sizeof(buf), "%#.1f", num))
1737 if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, num))
1740 // well shoot, this outta' fit...
1741 snprintf(buf, sizeof(buf), "?");
1743 return justify_pad(buf, width, justr);
1744 } // end: scale_pcnt
1747 #define TICS_AS_SECS 0
1748 #define TICS_AS_MINS 1
1749 #define TICS_AS_HOUR 2
1750 #define TICS_AS_DAY1 3
1751 #define TICS_AS_DAY2 4
1752 #define TICS_AS_WEEK 5
1753 #define TICS_AS_LAST 6
1756 * Do some scaling stuff.
1757 * Try to format 'tics' to reach 'target' while also
1758 * fitting in 'width', then justify it. */
1759 static const char *scale_tics (TIC_t tics, int width, int justr, int target) {
1761 #define HH "%luH" // nls_maybe
1765 #define HH "%luh" // nls_maybe
1769 static char buf[SMLBUFSIZ];
1770 TIC_t nt; // for speed on 64-bit
1772 unsigned long cc; // centiseconds
1773 unsigned long nn; // multi-purpose whatever
1775 unsigned long cent, secs, mins, hour, days, week;
1779 nt = (tics * 100ull) / Hertz; // lots of room for any time
1780 if (Rc.zero_suppress && 0 >= nt)
1784 cc = nt % 100; // centiseconds past second
1785 nt /= 100; // total seconds
1786 nn = nt % 60; // seconds past the minute
1787 nt /= 60; // total minutes
1788 if (target < TICS_AS_MINS
1789 && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu.%02lu", nt, nn, cc)))
1791 if (target < TICS_AS_HOUR
1792 && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu", nt, nn)))
1794 nn = nt % 60; // minutes past the hour
1795 nt /= 60; // total hours
1796 if (width >= snprintf(buf, sizeof(buf), "%llu,%02lu", nt, nn))
1798 nn = nt; // now also hours
1799 if (width >= snprintf(buf, sizeof(buf), HH, nn))
1801 nn /= 24; // now days
1802 if (width >= snprintf(buf, sizeof(buf), DD, nn))
1804 nn /= 7; // now weeks
1805 if (width >= snprintf(buf, sizeof(buf), WW, nn))
1808 #define mmLIMIT 360 // arbitrary 6 hours
1809 #define hhLIMIT 96 // arbitrary 4 days
1810 #define ddLIMIT 14 // arbitrary 2 weeks
1812 cent = (nt % 100); // cent past secs
1813 secs = (nt /= 100); // total secs
1814 mins = (nt /= 60); // total mins
1815 hour = (nt /= 60); // total hour
1816 days = (nt /= 24); // totat days
1817 week = (nt / 7); // total week
1819 if (Rc.tics_scaled > target)
1820 target += Rc.tics_scaled - target;
1824 if (mins < mmLIMIT + 1) {
1825 if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu.%02lu", mins, secs % 60, cent))
1828 case TICS_AS_MINS: // fall through
1829 if (mins < mmLIMIT + 1) {
1830 if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu", mins, secs % 60))
1833 case TICS_AS_HOUR: // fall through
1834 if (hour < hhLIMIT + 1) {
1835 if (width >= snprintf(buf, sizeof(buf), "%lu,%02lu", hour, mins % 60))
1838 case TICS_AS_DAY1: // fall through
1839 if (days < ddLIMIT + 1) {
1840 if (width >= snprintf(buf, sizeof(buf), DD "+" HH, days, hour % 24))
1843 if (width >= snprintf(buf, sizeof(buf), DD "+%lu", days, hour % 24))
1846 case TICS_AS_DAY2: // fall through
1847 if (width >= snprintf(buf, sizeof(buf), DD, days))
1850 case TICS_AS_WEEK: // fall through
1851 if (width >= snprintf(buf, sizeof(buf), WW "+" DD, week, days % 7))
1854 if (width >= snprintf(buf, sizeof(buf), WW "+%lu", week, days % 7))
1857 case TICS_AS_LAST: // fall through
1858 if (width >= snprintf(buf, sizeof(buf), WW, week))
1860 default: // fall through
1868 // well shoot, this outta' fit...
1869 snprintf(buf, sizeof(buf), "?");
1872 return justify_pad(buf, width, justr);
1876 } // end: scale_tics
1878 /*###### Fields Management support #####################################*/
1880 /* These are our gosh darn 'Fields' !
1881 They MUST be kept in sync with pflags !! */
1883 int width; // field width, if applicable
1884 int scale; // scaled target, if applicable
1885 const int align; // the default column alignment flag
1886 enum pids_item item; // the new libproc item enum identifier
1888 // these identifiers reflect the default column alignment but they really
1889 // contain the WIN_t flag used to check/change justification at run-time!
1890 #define A_left Show_JRSTRS /* toggled with lower case 'j' */
1891 #define A_right Show_JRNUMS /* toggled with upper case 'J' */
1893 /* .width anomalies:
1894 a -1 width represents variable width columns
1895 a 0 width represents columns set once at startup (see zap_fieldstab)
1897 .width .scale .align .item
1898 ------ ------ -------- ------------------- */
1899 { 0, -1, A_right, PIDS_ID_PID }, // s_int EU_PID
1900 { 0, -1, A_right, PIDS_ID_PPID }, // s_int EU_PPD
1901 { 5, -1, A_right, PIDS_ID_EUID }, // u_int EU_UED
1902 { 8, -1, A_left, PIDS_ID_EUSER }, // str EU_UEN
1903 { 5, -1, A_right, PIDS_ID_RUID }, // u_int EU_URD
1904 { 8, -1, A_left, PIDS_ID_RUSER }, // str EU_URN
1905 { 5, -1, A_right, PIDS_ID_SUID }, // u_int EU_USD
1906 { 8, -1, A_left, PIDS_ID_SUSER }, // str EU_USN
1907 { 5, -1, A_right, PIDS_ID_EGID }, // u_int EU_GID
1908 { 8, -1, A_left, PIDS_ID_EGROUP }, // str EU_GRP
1909 { 0, -1, A_right, PIDS_ID_PGRP }, // s_int EU_PGD
1910 { 8, -1, A_left, PIDS_TTY_NAME }, // str EU_TTY
1911 { 0, -1, A_right, PIDS_ID_TPGID }, // s_int EU_TPG
1912 { 0, -1, A_right, PIDS_ID_SESSION }, // s_int EU_SID
1913 { 3, -1, A_right, PIDS_PRIORITY }, // s_int EU_PRI
1914 { 3, -1, A_right, PIDS_NICE }, // s_int EU_NCE
1915 { 3, -1, A_right, PIDS_NLWP }, // s_int EU_THD
1916 { 0, -1, A_right, PIDS_PROCESSOR }, // s_int EU_CPN
1917 { 5, -1, A_right, PIDS_TICS_ALL_DELTA }, // u_int EU_CPU
1918 { 6, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TME
1919 { 9, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TM2
1920 { 5, -1, A_right, PIDS_MEM_RES }, // ul_int EU_MEM
1921 { 7, SK_Kb, A_right, PIDS_MEM_VIRT }, // ul_int EU_VRT
1922 { 6, SK_Kb, A_right, PIDS_VM_SWAP }, // ul_int EU_SWP
1923 { 6, SK_Kb, A_right, PIDS_MEM_RES }, // ul_int EU_RES
1924 { 6, SK_Kb, A_right, PIDS_MEM_CODE }, // ul_int EU_COD
1925 { 7, SK_Kb, A_right, PIDS_MEM_DATA }, // ul_int EU_DAT
1926 { 6, SK_Kb, A_right, PIDS_MEM_SHR }, // ul_int EU_SHR
1927 { 4, -1, A_right, PIDS_FLT_MAJ }, // ul_int EU_FL1
1928 { 4, -1, A_right, PIDS_FLT_MIN }, // ul_int EU_FL2
1929 { 4, -1, A_right, PIDS_noop }, // ul_int EU_DRT ( always 0 w/ since 2.6 )
1930 { 1, -1, A_right, PIDS_STATE }, // s_ch EU_STA
1931 { -1, -1, A_left, PIDS_CMD }, // str EU_CMD
1932 { 10, -1, A_left, PIDS_WCHAN_NAME }, // str EU_WCH
1933 { 8, -1, A_left, PIDS_FLAGS }, // ul_int EU_FLG
1934 { -1, -1, A_left, PIDS_CGROUP }, // str EU_CGR
1935 { -1, -1, A_left, PIDS_SUPGIDS }, // str EU_SGD
1936 { -1, -1, A_left, PIDS_SUPGROUPS }, // str EU_SGN
1937 { 0, -1, A_right, PIDS_ID_TGID }, // s_int EU_TGD
1938 { 5, -1, A_right, PIDS_OOM_ADJ }, // s_int EU_OOA
1939 { 4, -1, A_right, PIDS_OOM_SCORE }, // s_int EU_OOM
1940 { -1, -1, A_left, PIDS_ENVIRON }, // str EU_ENV
1941 { 3, -1, A_right, PIDS_FLT_MAJ_DELTA }, // s_int EU_FV1
1942 { 3, -1, A_right, PIDS_FLT_MIN_DELTA }, // s_int EU_FV2
1943 { 6, SK_Kb, A_right, PIDS_VM_USED }, // ul_int EU_USE
1944 { 10, -1, A_right, PIDS_NS_IPC }, // ul_int EU_NS1
1945 { 10, -1, A_right, PIDS_NS_MNT }, // ul_int EU_NS2
1946 { 10, -1, A_right, PIDS_NS_NET }, // ul_int EU_NS3
1947 { 10, -1, A_right, PIDS_NS_PID }, // ul_int EU_NS4
1948 { 10, -1, A_right, PIDS_NS_USER }, // ul_int EU_NS5
1949 { 10, -1, A_right, PIDS_NS_UTS }, // ul_int EU_NS6
1950 { 8, -1, A_left, PIDS_LXCNAME }, // str EU_LXC
1951 { 6, SK_Kb, A_right, PIDS_VM_RSS_ANON }, // ul_int EU_RZA
1952 { 6, SK_Kb, A_right, PIDS_VM_RSS_FILE }, // ul_int EU_RZF
1953 { 6, SK_Kb, A_right, PIDS_VM_RSS_LOCKED }, // ul_int EU_RZL
1954 { 6, SK_Kb, A_right, PIDS_VM_RSS_SHARED }, // ul_int EU_RZS
1955 { -1, -1, A_left, PIDS_CGNAME }, // str EU_CGN
1956 { 0, -1, A_right, PIDS_PROCESSOR_NODE }, // s_int EU_NMA
1957 { 5, -1, A_right, PIDS_ID_LOGIN }, // s_int EU_LID
1958 { -1, -1, A_left, PIDS_EXE }, // str EU_EXE
1959 { 6, SK_Kb, A_right, PIDS_SMAP_RSS }, // ul_int EU_RSS
1960 { 6, SK_Kb, A_right, PIDS_SMAP_PSS }, // ul_int EU_PSS
1961 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_ANON }, // ul_int EU_PZA
1962 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_FILE }, // ul_int EU_PZF
1963 { 6, SK_Kb, A_right, PIDS_SMAP_PSS_SHMEM }, // ul_int EU_PZS
1964 { 6, SK_Kb, A_right, PIDS_SMAP_PRV_TOTAL }, // ul_int EU_USS
1965 { 6, -1, A_right, PIDS_IO_READ_BYTES }, // ul_int EU_IRB
1966 { 5, -1, A_right, PIDS_IO_READ_OPS }, // ul_int EU_IRO
1967 { 6, -1, A_right, PIDS_IO_WRITE_BYTES }, // ul_int EU_IWB
1968 { 5, -1, A_right, PIDS_IO_WRITE_OPS }, // ul_int EU_IWO
1969 { 5, -1, A_right, PIDS_AUTOGRP_ID }, // s_int EU_AGI
1970 { 4, -1, A_right, PIDS_AUTOGRP_NICE }, // s_int EU_AGN
1971 { 7, -1, A_right, PIDS_TICS_BEGAN }, // ull_int EU_TM3
1972 { 7, -1, A_right, PIDS_TIME_ELAPSED }, // real EU_TM4
1973 { 6, -1, A_right, PIDS_UTILIZATION }, // real EU_CUU
1974 { 7, -1, A_right, PIDS_UTILIZATION_C }, // real EU_CUC
1975 { 10, -1, A_right, PIDS_NS_CGROUP }, // ul_int EU_NS7
1976 { 10, -1, A_right, PIDS_NS_TIME } // ul_int EU_NS8
1977 #define eu_LAST EU_NS8
1978 // xtra Fieldstab 'pseudo pflag' entries for the newlib interface . . . . . . .
1979 #define eu_CMDLINE eu_LAST +1
1980 #define eu_TICS_ALL_C eu_LAST +2
1981 #define eu_ID_FUID eu_LAST +3
1982 #define eu_TREE_HID eu_LAST +4
1983 #define eu_TREE_LVL eu_LAST +5
1984 #define eu_TREE_ADD eu_LAST +6
1985 #define eu_CMDLINE_V eu_LAST +7
1986 #define eu_ENVIRON_V eu_LAST +8
1987 , { -1, -1, -1, PIDS_CMDLINE } // str ( if Show_CMDLIN, eu_CMDLINE )
1988 , { -1, -1, -1, PIDS_TICS_ALL_C } // ull_int ( if Show_CTIMES, eu_TICS_ALL_C )
1989 , { -1, -1, -1, PIDS_ID_FUID } // u_int ( if a usrseltyp, eu_ID_FUID )
1990 , { -1, -1, -1, PIDS_extra } // s_ch ( if Show_FOREST, eu_TREE_HID )
1991 , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_LVL )
1992 , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_ADD )
1993 , { -1, -1, -1, PIDS_CMDLINE_V } // strv ( if Ctrlk, eu_CMDLINE_V )
1994 , { -1, -1, -1, PIDS_ENVIRON_V } // strv ( if CtrlN, eu_ENVIRON_V )
2001 * A calibrate_fields() *Helper* function which refreshes
2002 * all that cached screen geometry plus related variables */
2003 static void adj_geometry (void) {
2004 static size_t pseudo_max = 0;
2005 static int w_set = 0, w_cols = 0, w_rows = 0;
2008 Screen_cols = columns; // <term.h>
2009 Screen_rows = lines; // <term.h>
2011 if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz)
2012 && 0 < wz.ws_col && 0 < wz.ws_row) {
2013 Screen_cols = wz.ws_col;
2014 Screen_rows = wz.ws_row;
2017 #ifndef RMAN_IGNORED
2018 // be crudely tolerant of crude tty emulators
2019 if (Cap_avoid_eol) Screen_cols--;
2022 // we might disappoint some folks (but they'll deserve it)
2023 if (Screen_cols > SCREENMAX) Screen_cols = SCREENMAX;
2024 if (Screen_cols < W_MIN_COL) Screen_cols = W_MIN_COL;
2027 if (Width_mode > 0) // -w with arg, we'll try to honor
2028 w_cols = Width_mode;
2030 if (Width_mode < 0) { // -w without arg, try environment
2031 char *env_columns = getenv("COLUMNS"),
2032 *env_lines = getenv("LINES"),
2034 if (env_columns && *env_columns) {
2036 t = strtol(env_columns, &ep, 0);
2037 if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tc = t;
2038 if (0 < tc) w_cols = (int)tc;
2040 if (env_lines && *env_lines) {
2042 t = strtol(env_lines, &ep, 0);
2043 if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tr = t;
2044 if (0 < tr) w_rows = (int)tr;
2046 if (!w_cols) w_cols = SCREENMAX;
2047 if (w_cols && w_cols < W_MIN_COL) w_cols = W_MIN_COL;
2048 if (w_rows && w_rows < W_MIN_ROW) w_rows = W_MIN_ROW;
2050 if (w_cols > SCREENMAX) w_cols = SCREENMAX;
2054 /* keep our support for output optimization in sync with current reality
2055 note: when we're in Batch mode, we don't really need a Pseudo_screen
2056 and when not Batch, our buffer will contain 1 extra 'line' since
2057 Msg_row is never represented -- but it's nice to have some space
2058 between us and the great-beyond... */
2060 if (w_cols) Screen_cols = w_cols;
2061 Screen_rows = w_rows ? w_rows : INT_MAX;
2062 Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2064 const int max_rows = INT_MAX / (sizeof(*Pseudo_screen) * ROWMAXSIZ);
2065 if (w_cols && w_cols < Screen_cols) Screen_cols = w_cols;
2066 if (w_rows && w_rows < Screen_rows) Screen_rows = w_rows;
2067 if (Screen_rows < 0 || Screen_rows > max_rows) Screen_rows = max_rows;
2068 Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ) * Screen_rows;
2070 // we'll only grow our Pseudo_screen, never shrink it
2071 if (pseudo_max < Pseudo_size) {
2072 pseudo_max = Pseudo_size;
2073 Pseudo_screen = alloc_r(Pseudo_screen, pseudo_max);
2075 // ensure each row is repainted (just in case)
2078 // prepare to customize potential cpu/memory graphs
2079 if (Curwin->rc.double_up) {
2080 int num = (Curwin->rc.double_up + 1);
2081 int pfx = (Curwin->rc.double_up < 2) ? GRAPH_prefix_std : GRAPH_prefix_abv;
2082 Graph_cpus->length = (Screen_cols - (ADJOIN_space * Curwin->rc.double_up) - (num * (pfx + GRAPH_suffix))) / num;
2083 Graph_mems->length = (Screen_cols - ADJOIN_space - (2 * (GRAPH_prefix_std + GRAPH_suffix))) / 2;
2085 Graph_cpus->length = Screen_cols - (GRAPH_prefix_std + GRAPH_length_max + GRAPH_suffix);
2086 if (Graph_cpus->length >= 0) Graph_cpus->length = GRAPH_length_max;
2087 else Graph_cpus->length = Screen_cols - GRAPH_prefix_std - GRAPH_suffix;
2088 Graph_mems->length = Graph_cpus->length;
2090 if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min;
2091 if (Graph_cpus->length > GRAPH_length_max) Graph_cpus->length = GRAPH_length_max;
2092 Graph_cpus->adjust = (float)Graph_cpus->length / 100.0;
2093 Graph_cpus->style = Curwin->rc.graph_cpus;
2095 if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min;
2096 if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max;
2097 Graph_mems->adjust = (float)Graph_mems->length / 100.0;
2098 Graph_mems->style = Curwin->rc.graph_mems;
2101 } // end: adj_geometry
2105 * A calibrate_fields() *Helper* function to build the actual
2106 * column headers & ensure necessary item enumerators support */
2107 static void build_headers (void) {
2108 #define ckITEM(f) do { Pids_itms[f] = Fieldstab[f].item; } while (0)
2109 #define ckCMDS(w) do { if (CHKw(w, Show_CMDLIN)) ckITEM(eu_CMDLINE); } while (0)
2118 // ensure fields not visible incur no significant library costs
2119 for (i = 0; i < MAXTBL(Fieldstab); i++)
2120 Pids_itms[i] = PIDS_extra;
2122 ckITEM(EU_PID); // these 2 fields may not display,
2123 ckITEM(EU_STA); // yet we'll always need them both
2124 ckITEM(EU_CMD); // this is used with 'Y' (inspect)
2128 memset((s = w->columnhdr), 0, sizeof(w->columnhdr));
2129 if (Rc.mode_altscr) s = scat(s, fmtmk("%d", w->winnum));
2131 for (i = 0; i < w->maxpflgs; i++) {
2134 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2135 s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_msg));
2136 w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_msg);
2139 if (EU_MAXPFLGS <= f) continue;
2147 // cpu calculations depend on number of threads
2152 // for 'cumulative' times, we'll need equivalent of cutime & cstime
2153 if (CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C);
2158 s = scat(s, utf8_justify(N_col(f)
2159 , VARcol(f) ? w->varcolsz : Fieldstab[f].width
2160 , CHKw(w, Fieldstab[f].align)));
2162 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2163 s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_hdr));
2164 w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_hdr);
2169 // prepare to even out column header lengths...
2170 if (hdrmax + w->hdrcaplen < (x = strlen(w->columnhdr))) hdrmax = x - w->hdrcaplen;
2172 // for 'busy' only processes, we'll need elapsed tics
2173 if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU);
2174 // with forest view mode, we'll need pid, tgid, ppid & start_time...
2175 #ifndef TREE_VCPUOFF
2176 if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); ckITEM(eu_TREE_ADD); }
2178 if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); }
2180 // for 'u/U' filtering we need these too (old top forgot that, oops)
2181 if (w->usrseltyp) { ckITEM(EU_UED); ckITEM(EU_URD); ckITEM(EU_USD); ckITEM(eu_ID_FUID); }
2183 // we must also accommodate an out of view sort field...
2185 if (EU_CMD == f) ckCMDS(w);
2188 // lastly, accommodate any special non-display 'tagged' needs...
2190 while (Bot_item[i] > BOT_DELIMIT) {
2191 ckITEM(Bot_item[i]);
2196 if (Rc.mode_altscr) w = w->next;
2197 } while (w != Curwin);
2200 /* now we can finally even out column header lengths
2201 (we're assuming entire columnhdr was memset to '\0') */
2202 if (Rc.mode_altscr && SCREENMAX > Screen_cols)
2203 for (i = 0; i < GROUPSMAX; i++) {
2205 if (CHKw(w, Show_TASKON))
2206 if (hdrmax + w->hdrcaplen > (x = strlen(w->columnhdr)))
2207 memset(&w->columnhdr[x], ' ', hdrmax + w->hdrcaplen - x);
2213 } // end: build_headers
2217 * This guy coordinates the activities surrounding the maintenance of
2218 * each visible window's columns headers plus item enumerators needed */
2219 static void calibrate_fields (void) {
2224 int i, varcolcnt, len, rc;
2230 w->hdrcaplen = 0; // really only used with USE_X_COLHDR
2231 // build window's pflgsall array, establish upper bounds for maxpflgs
2232 for (i = 0, w->totpflgs = 0; i < EU_MAXPFLGS; i++) {
2236 w->pflgsall[w->totpflgs++] = f;
2238 if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) {
2239 w->pflgsall[w->totpflgs++] = EU_XON;
2240 w->pflgsall[w->totpflgs++] = f;
2241 w->pflgsall[w->totpflgs++] = EU_XOF;
2243 w->pflgsall[w->totpflgs++] = f;
2247 if (!w->totpflgs) w->pflgsall[w->totpflgs++] = EU_PID;
2249 /* build a preliminary columns header not to exceed screen width
2250 while accounting for a possible leading window number */
2251 w->varcolsz = varcolcnt = 0;
2252 *(s = w->columnhdr) = '\0';
2253 if (Rc.mode_altscr) s = scat(s, " ");
2254 for (i = 0; i + w->begpflg < w->totpflgs; i++) {
2255 f = w->pflgsall[i + w->begpflg];
2257 #ifndef USE_X_COLHDR
2258 if (EU_MAXPFLGS <= f) continue;
2261 len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2262 // oops, won't fit -- we're outta here...
2263 if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2264 if (VARcol(f)) { ++varcolcnt; w->varcolsz += strlen(h); }
2265 s = scat(s, fmtmk("%*.*s", len, len, h));
2267 #ifndef USE_X_COLHDR
2268 if (i >= 1 && EU_XON == w->procflgs[i - 1]) --i;
2271 /* establish the final maxpflgs and prepare to grow the variable column
2272 heading(s) via varcolsz - it may be a fib if their pflags weren't
2273 encountered, but that's ok because they won't be displayed anyway */
2275 w->varcolsz += Screen_cols - strlen(w->columnhdr);
2276 if (varcolcnt) w->varcolsz /= varcolcnt;
2278 /* establish the field where all remaining fields would still
2279 fit within screen width, including a leading window number */
2280 *(s = w->columnhdr) = '\0';
2281 if (Rc.mode_altscr) s = scat(s, " ");
2283 for (i = w->totpflgs - 1; -1 < i; i--) {
2285 #ifndef USE_X_COLHDR
2286 if (EU_MAXPFLGS <= f) { w->endpflg = i; continue; }
2289 len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ;
2290 if (Screen_cols < ((int)(s - w->columnhdr) + len)) break;
2291 s = scat(s, fmtmk("%*.*s", len, len, h));
2294 #ifndef USE_X_COLHDR
2295 if (EU_XOF == w->pflgsall[w->endpflg]) ++w->endpflg;
2297 } // end: if (VIZISw(w))
2299 if (Rc.mode_altscr) w = w->next;
2300 } while (w != Curwin);
2304 if ((rc = procps_pids_reset(Pids_ctx, Pids_itms, Pids_itms_tot)))
2305 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
2306 } // end: calibrate_fields
2310 * Display each field represented in the current window's fieldscur
2311 * array along with its description. Mark with bold and a leading
2312 * asterisk those fields associated with the "on" or "active" state.
2314 * Special highlighting will be accorded the "focus" field with such
2315 * highlighting potentially extended to include the description.
2317 * Below is the current Fieldstab space requirement and how
2318 * we apportion it. The xSUFX is considered sacrificial,
2319 * something we can reduce or do without.
2321 * 12345678901234567890123456789012
2322 * * HEADING = Longest Description!
2323 * xPRFX ----------______________________ xSUFX
2324 * ( xPRFX has pos 2 & 10 for 'extending' when at minimums )
2326 * The first 4 screen rows are reserved for explanatory text, and
2327 * the maximum number of columns is Screen_cols / xPRFX + 1 space
2328 * between columns. Thus, for example, with 42 fields a tty will
2329 * still remain useable under these extremes:
2330 * rows columns what's
2331 * tty top tty top displayed
2332 * --- --- --- --- ------------------
2333 * 46 42 10 1 xPRFX only
2334 * 46 42 32 1 full xPRFX + xSUFX
2335 * 6 2 231 21 xPRFX only
2336 * 10 6 231 7 full xPRFX + xSUFX
2338 static void display_fields (int focus, int extend) {
2339 #define mkERR { putp("\n"); putp(N_txt(XTRA_winsize_txt)); return; }
2340 #define mxCOL ( (Screen_cols / 11) > 0 ? (Screen_cols / 11) : 1 )
2342 #define xEQUS 2 // length of suffix beginning '= '
2343 #define xSUFX 22 // total suffix length, incl xEQUS
2344 #define xPRFX (10 + xadd)
2345 #define xTOTL (xPRFX + xSUFX)
2346 static int col_sav, row_sav;
2347 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
2348 int i; // utility int (a row, tot cols, ix)
2349 int smax; // printable width of xSUFX
2350 int xadd = 0; // spacing between data columns
2351 int cmax = Screen_cols; // total data column width
2352 int rmax = Screen_rows - yRSVD; // total useable rows
2354 i = (EU_MAXPFLGS % mxCOL) ? 1 : 0;
2355 if (rmax < i + (EU_MAXPFLGS / mxCOL)) mkERR;
2356 i = EU_MAXPFLGS / rmax;
2357 if (EU_MAXPFLGS % rmax) ++i;
2358 if (i > 1) { cmax /= i; xadd = 1; }
2359 if (cmax > xTOTL) cmax = xTOTL;
2360 smax = cmax - xPRFX;
2361 if (smax < 0) mkERR;
2363 /* we'll go the extra distance to avoid any potential screen flicker
2364 which occurs under some terminal emulators (but it was our fault) */
2365 if (col_sav != Screen_cols || row_sav != Screen_rows) {
2366 col_sav = Screen_cols;
2367 row_sav = Screen_rows;
2372 for (i = 0; i < EU_MAXPFLGS; ++i) {
2373 int b = FLDviz(w, i), x = (i / rmax) * cmax, y = (i % rmax) + yRSVD;
2374 const char *e = (i == focus && extend) ? w->capclr_hdr : "";
2375 FLG_t f = FLDget(w, i);
2376 char sbuf[xSUFX*4]; // 4 = max multi-byte
2379 /* prep sacrificial suffix (allowing for beginning '= ')
2380 note: width passed to 'utf8_embody' may go negative, but he'll be just fine */
2381 snprintf(sbuf, sizeof(sbuf), "= %.*s", utf8_embody(N_fld(f), smax - xEQUS), N_fld(f));
2382 // obtain translated deltas (if any) ...
2383 xcol = utf8_delta(fmtmk("%.*s", utf8_embody(N_col(f), 8), N_col(f)));
2384 xfld = utf8_delta(sbuf + xEQUS); // ignore beginning '= '
2386 PUTT("%s%c%s%s %s%-*.*s%s%s%s %-*.*s%s"
2389 , b ? w->cap_bold : Cap_norm
2391 , i == focus ? w->capclr_hdr : ""
2392 , 8 + xcol, 8 + xcol
2395 , b ? w->cap_bold : ""
2397 , smax + xfld, smax + xfld
2410 } // end: display_fields
2414 * Manage all fields aspects (order/toggle/sort), for all windows. */
2415 static void fields_utility (void) {
2416 #ifndef SCROLLVAR_NO
2417 #define unSCRL { w->begpflg = w->varcolbeg = 0; OFFw(w, Show_HICOLS); }
2419 #define unSCRL { w->begpflg = 0; OFFw(w, Show_HICOLS); }
2421 #define swapEM { int c; unSCRL; c = w->rc.fieldscur[i]; \
2422 w->rc.fieldscur[i] = *p; *p = c; p = &w->rc.fieldscur[i]; }
2423 #define spewFI { int *t; f = w->rc.sortindx; t = msch(w->rc.fieldscur, ENUcvt(f, FLDon), EU_MAXPFLGS); \
2424 if (!t) t = msch(w->rc.fieldscur, ENUcvt(f, FLDoff), EU_MAXPFLGS); \
2425 i = (t) ? (int)(t - w->rc.fieldscur) : 0; }
2426 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
2427 const char *h = NULL;
2438 if (!h) h = N_col(f);
2440 show_special(1, fmtmk(N_unq(FIELD_header_fmt)
2441 , w->grpname, CHKw(w, Show_FOREST) ? N_txt(FOREST_views_txt) : h));
2442 display_fields(i, (p != NULL));
2445 if (Frames_signal) goto signify_that;
2446 key = iokey(IOKEY_ONCE);
2447 if (key < 1) goto signify_that;
2451 if (i > 0) { --i; if (p) swapEM }
2454 if (i + 1 < EU_MAXPFLGS) { ++i; if (p) swapEM }
2461 p = &w->rc.fieldscur[i];
2469 if (!p) i = EU_MAXPFLGS - 1;
2473 if (!p) { FLDtog(w, i); unSCRL }
2477 if (!p && !CHKw(w, Show_FOREST)) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL }
2479 if (!p) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL; OFFw(w, Show_FOREST); }
2484 Curwin = w = ('a' == key) ? w->next : w->prev;
2489 default: // keep gcc happy
2492 } while (key != 'q' && key != kbd_ESC);
2494 // signal that we just corrupted entire screen
2495 Frames_signal = BREAK_screen;
2499 } // end: fields_utility
2503 * This routine takes care of auto sizing field widths
2504 * if/when the user sets Rc.fixed_widest to -1. Along the
2505 * way he reinitializes some things for the next frame. */
2506 static inline void widths_resize (void) {
2509 // next var may also be set by the guys that actually truncate stuff
2511 for (i = 0; i < EU_MAXPFLGS; i++) {
2512 if (Autox_array[i]) {
2513 Fieldstab[i].width++;
2518 // trigger a call to calibrate_fields (via zap_fieldstab)
2519 if (Autox_found) Frames_signal = BREAK_autox;
2520 } // end: widths_resize
2524 * This routine exists just to consolidate most of the messin'
2525 * around with the Fieldstab array and some related stuff. */
2526 static void zap_fieldstab (void) {
2528 #define maX(E) ( (wtab[E].wnls > wtab[E].wmin) \
2529 ? wtab[E].wnls : wtab[E].wmin )
2531 int wmin; // minimum field width (-1 == variable width)
2532 int wnls; // translated header column requirements
2533 int watx; // +1 == non-scalable auto sized columns
2534 } wtab[EU_MAXPFLGS];
2541 Fieldstab[EU_CPN].width = 1;
2542 Fieldstab[EU_NMA].width = 2;
2543 Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2544 = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2545 = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = 5;
2546 if (5 < (digits = (int)procps_pid_length())) {
2547 if (10 < digits) error_exit(N_txt(FAIL_widepid_txt));
2548 Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width
2549 = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width
2550 = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = digits;
2553 // identify our non-scalable auto sized columns
2554 wtab[EU_UED].watx = wtab[EU_UEN].watx = wtab[EU_URD].watx
2555 = wtab[EU_URN].watx = wtab[EU_USD].watx = wtab[EU_USN].watx
2556 = wtab[EU_GID].watx = wtab[EU_GRP].watx = wtab[EU_TTY].watx
2557 = wtab[EU_WCH].watx = wtab[EU_NS1].watx = wtab[EU_NS2].watx
2558 = wtab[EU_NS3].watx = wtab[EU_NS4].watx = wtab[EU_NS5].watx
2559 = wtab[EU_NS6].watx = wtab[EU_NS7].watx = wtab[EU_NS8].watx
2560 = wtab[EU_LXC].watx = wtab[EU_LID].watx
2562 /* establish translatable header 'column' requirements
2563 and ensure .width reflects the widest value */
2564 for (i = 0; i < EU_MAXPFLGS; i++) {
2565 wtab[i].wmin = Fieldstab[i].width;
2566 wtab[i].wnls = (int)strlen(N_col(i)) - utf8_delta(N_col(i));
2567 if (wtab[i].wmin != -1)
2568 Fieldstab[i].width = maX(i);
2575 if (Rc.mode_irixps && Cpu_cnt > 1 && !Thread_mode) {
2576 Cpu_pmax = 100.0 * Cpu_cnt;
2577 if (Cpu_cnt > 1000) {
2578 if (Cpu_pmax > 9999999.0) Cpu_pmax = 9999999.0;
2579 } else if (Cpu_cnt > 100) {
2580 if (Cpu_cnt > 999999.0) Cpu_pmax = 999999.0;
2581 } else if (Cpu_cnt > 10) {
2582 if (Cpu_pmax > 99999.0) Cpu_pmax = 99999.0;
2584 if (Cpu_pmax > 999.9) Cpu_pmax = 999.9;
2589 digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2590 if (wtab[EU_CPN].wmin < digits) {
2591 if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2592 wtab[EU_CPN].wmin = digits;
2593 Fieldstab[EU_CPN].width = maX(EU_CPN);
2595 digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2596 if (wtab[EU_NMA].wmin < digits) {
2597 wtab[EU_NMA].wmin = digits;
2598 Fieldstab[EU_NMA].width = maX(EU_NMA);
2601 // and accommodate optional wider non-scalable columns (maybe)
2603 for (i = 0; i < EU_MAXPFLGS; i++) {
2605 Fieldstab[i].width = Rc.fixed_widest ? Rc.fixed_widest + maX(i) : maX(i);
2609 digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt);
2611 if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt));
2612 Fieldstab[EU_CPN].width = digits;
2614 digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot);
2616 Fieldstab[EU_NMA].width = digits;
2618 // and accommodate optional wider non-scalable columns (maybe)
2620 Fieldstab[EU_UED].width = Fieldstab[EU_URD].width
2621 = Fieldstab[EU_USD].width = Fieldstab[EU_GID].width
2622 = Rc.fixed_widest ? 5 + Rc.fixed_widest : 5;
2623 Fieldstab[EU_UEN].width = Fieldstab[EU_URN].width
2624 = Fieldstab[EU_USN].width = Fieldstab[EU_GRP].width
2625 = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2626 Fieldstab[EU_TTY].width = Fieldstab[EU_LXC].width
2627 = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8;
2628 Fieldstab[EU_WCH].width
2629 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2630 // the initial namespace fields
2631 for (i = EU_NS1; i <= EU_NS6; i++)
2633 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2634 // the later namespace additions
2635 for (i = EU_NS7; i <= EU_NS8; i++)
2637 = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10;
2641 /* plus user selectable scaling */
2642 Fieldstab[EU_VRT].scale = Fieldstab[EU_SWP].scale
2643 = Fieldstab[EU_RES].scale = Fieldstab[EU_COD].scale
2644 = Fieldstab[EU_DAT].scale = Fieldstab[EU_SHR].scale
2645 = Fieldstab[EU_USE].scale = Fieldstab[EU_RZA].scale
2646 = Fieldstab[EU_RZF].scale = Fieldstab[EU_RZL].scale
2647 = Fieldstab[EU_RZS].scale = Fieldstab[EU_RSS].scale
2648 = Fieldstab[EU_PSS].scale = Fieldstab[EU_PZA].scale
2649 = Fieldstab[EU_PZF].scale = Fieldstab[EU_PZS].scale
2650 = Fieldstab[EU_USS].scale = Rc.task_mscale;
2652 // lastly, ensure we've got proper column headers...
2655 } // end: zap_fieldstab
2657 /*###### Library Interface (as separate threads) #######################*/
2660 * This guy's responsible for interfacing with the library <stat> API
2661 * and reaping all cpu or numa node tics.
2662 * ( his task is now embarassingly small under the new api ) */
2663 static void *cpus_refresh (void *unused) {
2664 enum stat_reap_type which;
2668 sem_wait(&Semaphore_cpus_beg);
2670 which = STAT_REAP_CPUS_ONLY;
2671 if (CHKw(Curwin, View_CPUNOD))
2672 which = STAT_REAP_NUMA_NODES_TOO;
2674 Stat_reap = procps_stat_reap(Stat_ctx, which, Stat_items, MAXTBL(Stat_items));
2676 error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
2677 #ifndef PRETEND0NUMA
2678 // adapt to changes in total numa nodes (assuming it's even possible)
2679 if (Stat_reap->numa->total && Stat_reap->numa->total != Numa_node_tot) {
2680 Numa_node_tot = Stat_reap->numa->total;
2684 if (Stat_reap->cpus->total && Stat_reap->cpus->total != Cpu_cnt) {
2685 Cpu_cnt = Stat_reap->cpus->total;
2691 sem_post(&Semaphore_cpus_end);
2698 } // end: cpus_refresh
2702 * This serves as our interface to the memory portion of libprocps.
2703 * The sampling frequency is reduced in order to minimize overhead. */
2704 static void *memory_refresh (void *unused) {
2705 static time_t sav_secs;
2710 sem_wait(&Semaphore_memory_beg);
2714 cur_secs = time(NULL);
2716 if (3 <= cur_secs - sav_secs) {
2717 if (!(Mem_stack = procps_meminfo_select(Mem_ctx, Mem_items, MAXTBL(Mem_items))))
2718 error_exit(fmtmk(N_fmt(LIB_errormem_fmt), __LINE__, strerror(errno)));
2719 sav_secs = cur_secs;
2722 sem_post(&Semaphore_memory_end);
2729 } // end: memory_refresh
2733 * This guy's responsible for interfacing with the library <pids> API
2734 * then refreshing the WIN_t ptr arrays, growing them as appropirate. */
2735 static void *tasks_refresh (void *unused) {
2736 #define nALIGN(n,m) (((n + m - 1) / m) * m) // unconditionally align
2737 #define nALGN2(n,m) ((n + m - 1) & ~(m - 1)) // with power of 2 align
2738 #define n_reap Pids_reap->counts->total
2739 static double uptime_sav;
2740 static int n_alloc = -1; // size of windows stacks arrays
2747 sem_wait(&Semaphore_tasks_beg);
2749 procps_uptime(&uptime_cur, NULL);
2750 et = uptime_cur - uptime_sav;
2751 if (et < 0.01) et = 0.005;
2752 uptime_sav = uptime_cur;
2753 // if in Solaris mode, adjust our scaling for all cpus
2754 Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : Cpu_cnt));
2756 what = Thread_mode ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY;
2758 what |= PIDS_SELECT_PID;
2759 Pids_reap = procps_pids_select(Pids_ctx, (unsigned *)Monpids, Monpidsidx, what);
2761 Pids_reap = procps_pids_reap(Pids_ctx, what);
2763 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
2765 // now refresh each window's stacks pointer array...
2766 if (n_alloc < n_reap) {
2767 // n_alloc = nALIGN(n_reap, 100);
2768 n_alloc = nALGN2(n_reap, 128);
2769 for (i = 0; i < GROUPSMAX; i++) {
2770 Winstk[i].ppt = alloc_r(Winstk[i].ppt, sizeof(void *) * n_alloc);
2771 memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2774 for (i = 0; i < GROUPSMAX; i++)
2775 memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
2778 sem_post(&Semaphore_tasks_end);
2788 } // end: tasks_refresh
2790 /*###### Inspect Other Output ##########################################*/
2793 * HOWTO Extend the top 'inspect' functionality:
2795 * To exploit the 'Y' interactive command, one must add entries to
2796 * the top personal configuration file. Such entries simply reflect
2797 * a file to be read or command/pipeline to be executed whose results
2798 * will then be displayed in a separate scrollable window.
2800 * Entries beginning with a '#' character are ignored, regardless of
2801 * content. Otherwise they consist of the following 3 elements, each
2802 * of which must be separated by a tab character (thus 2 '\t' total):
2803 * type: literal 'file' or 'pipe'
2804 * name: selection shown on the Inspect screen
2805 * fmts: string representing a path or command
2807 * The two types of Inspect entries are not interchangeable.
2808 * Those designated 'file' will be accessed using fopen/fread and must
2809 * reference a single file in the 'fmts' element. Entries specifying
2810 * 'pipe' will employ popen/fread, their 'fmts' element could contain
2811 * many pipelined commands and, none can be interactive.
2813 * Here are some examples of both types of inspection entries.
2814 * The first entry will be ignored due to the initial '#' character.
2815 * For clarity, the pseudo tab depictions (^I) are surrounded by an
2816 * extra space but the actual tabs would not be.
2818 * # pipe ^I Sockets ^I lsof -n -P -i 2>&1
2819 * pipe ^I Open Files ^I lsof -P -p %d 2>&1
2820 * file ^I NUMA Info ^I /proc/%d/numa_maps
2821 * pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr
2823 * Caution: If the output contains unprintable characters they will
2824 * be displayed in either the ^I notation or hexidecimal <FF> form.
2825 * This applies to tab characters as well. So if one wants a more
2826 * accurate display, any tabs should be expanded within the 'fmts'.
2828 * The following example takes what could have been a 'file' entry
2829 * but employs a 'pipe' instead so as to expand the tabs.
2831 * # next would have contained '\t' ...
2832 * # file ^I <your_name> ^I /proc/%d/status
2833 * # but this will eliminate embedded '\t' ...
2834 * pipe ^I <your_name> ^I cat /proc/%d/status | expand -
2836 * Note: If a pipe such as the following was established, one must
2837 * use Ctrl-C to terminate that pipe in order to review the results.
2838 * This is the single occasion where a '^C' will not terminate top.
2840 * pipe ^I Trace ^I /usr/bin/strace -p %d 2>&1
2844 * Our driving table support, the basis for generalized inspection,
2845 * built at startup (if at all) from rcfile or demo entries. */
2847 void (*func)(char *, int); // a pointer to file/pipe/demo function
2848 char *type; // the type of entry ('file' or 'pipe')
2849 char *name; // the selection label for display
2850 char *fmts; // format string to build path or command
2851 int farg; // 1 = '%d' in fmts, 0 = not (future use)
2852 const char *caps; // not really caps, show_special() delim's
2853 char *fstr; // entry's current/active search string
2854 int flen; // above's strlen, without call overhead
2857 int demo; // do NOT save table entries in rcfile
2858 int total; // total I_ent table entries
2859 char *raw; // all entries for 'W', incl '#' & blank
2862 static struct I_struc Inspect;
2864 static char **Insp_p; // pointers to each line start
2865 static int Insp_nl; // total lines, total Insp_p entries
2866 static int Insp_utf8; // treat Insp_buf as translatable, else raw
2867 static char *Insp_buf; // the results from insp_do_file/pipe
2868 static size_t Insp_bufsz; // allocated size of Insp_buf
2869 static size_t Insp_bufrd; // bytes actually in Insp_buf
2870 static struct I_ent *Insp_sel; // currently selected Inspect entry
2872 // Our 'make status line' macro
2873 #define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \
2875 _sz += utf8_delta(txt); \
2876 _p = fmtmk("%-*.*s", _sz, _sz, txt); \
2877 PUTT("%s%s%.*s%s", tg2(0, (Msg_row = 3)), Curwin->capclr_hdr \
2878 , utf8_embody(_p, Screen_cols), _p, Cap_clr_eol); \
2879 putp(Caps_off); fflush(stdout); }
2881 // Our 'row length' macro, equivalent to a strlen() call
2882 #define INSP_RLEN(idx) (int)(Insp_p[idx +1] - Insp_p[idx] -1)
2884 // Our 'busy/working' macro
2885 #define INSP_BUSY(enu) { INSP_MKSL(0, N_txt(enu)) }
2889 * Establish the number of lines present in the Insp_buf glob plus
2890 * build the all important row start array. It is that array that
2891 * others will rely on since we dare not try to use strlen() on what
2892 * is potentially raw binary data. Who knows what some user might
2893 * name as a file or include in a pipeline (scary, ain't it?). */
2894 static void insp_cnt_nl (void) {
2895 char *beg = Insp_buf;
2896 char *cur = Insp_buf;
2897 char *end = Insp_buf + Insp_bufrd + 1;
2904 snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++);
2905 fd = fopen(fn, "w");
2907 fwrite(Insp_buf, 1, Insp_bufrd, fd);
2912 Insp_p = alloc_c(sizeof(char *) * 2);
2914 for (Insp_nl = 0; beg < end; beg++) {
2916 Insp_p[Insp_nl++] = cur;
2917 // keep our array ahead of next potential need (plus the 2 above)
2918 Insp_p = alloc_r(Insp_p, (sizeof(char *) * (Insp_nl +3)));
2922 Insp_p[0] = Insp_buf;
2923 Insp_p[Insp_nl++] = cur;
2924 Insp_p[Insp_nl] = end;
2925 if ((end - cur) == 1) // if there's an eof null delimiter,
2926 --Insp_nl; // don't count it as a new line
2927 } // end: insp_cnt_nl
2930 #ifndef INSP_OFFDEMO
2932 * The pseudo output DEMO utility. */
2933 static void insp_do_demo (char *fmts, int pid) {
2934 (void)fmts; (void)pid;
2935 /* next will put us on a par with the real file/pipe read buffers
2936 ( and also avoid a harmless, but evil sounding, valgrind warning ) */
2937 Insp_bufsz = READMINSZ + strlen(N_txt(YINSP_dstory_txt));
2938 Insp_buf = alloc_c(Insp_bufsz);
2939 Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_dstory_txt));
2941 } // end: insp_do_demo
2946 * The generalized FILE utility. */
2947 static void insp_do_file (char *fmts, int pid) {
2948 char buf[LRGBUFSIZ];
2952 snprintf(buf, sizeof(buf), fmts, pid);
2953 fp = fopen(buf, "r");
2954 rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
2956 if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
2957 , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
2959 } // end: insp_do_file
2963 * The generalized PIPE utility. */
2964 static void insp_do_pipe (char *fmts, int pid) {
2965 char buf[LRGBUFSIZ];
2966 struct sigaction sa;
2970 memset(&sa, 0, sizeof(sa));
2971 sigemptyset(&sa.sa_mask);
2972 sa.sa_handler = SIG_IGN;
2973 sigaction(SIGINT, &sa, NULL);
2975 snprintf(buf, sizeof(buf), fmts, pid);
2976 fp = popen(buf, "r");
2977 rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd);
2979 if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s"
2980 , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno)));
2983 sa.sa_handler = sig_endpgm;
2984 sigaction(SIGINT, &sa, NULL);
2985 } // end: insp_do_pipe
2989 * This guy is a *Helper* function serving the following two masters:
2990 * insp_find_str() - find the next Insp_sel->fstr match
2991 * insp_mkrow_... - highlight any Insp_sel->fstr matches in-view
2992 * If Insp_sel->fstr is found in the designated row, he returns the
2993 * offset from the start of the row, otherwise he returns a huge
2994 * integer so traditional fencepost usage can be employed. */
2995 static inline int insp_find_ofs (int col, int row) {
2996 #define begFS (int)(fnd - Insp_p[row])
2997 char *p, *fnd = NULL;
2999 if (Insp_sel->fstr[0]) {
3000 // skip this row, if there's no chance of a match
3001 if (memchr(Insp_p[row], Insp_sel->fstr[0], INSP_RLEN(row))) {
3002 for ( ; col < INSP_RLEN(row); col++) {
3003 if (!*(p = Insp_p[row] + col)) // skip any empty strings
3005 fnd = STRSTR(p, Insp_sel->fstr); // with binary data, each
3006 if (fnd) // row may have '\0'. so
3007 break; // our scans must be done
3008 col += strlen(p); // as individual strings.
3010 if (fnd && fnd < Insp_p[row + 1]) // and, we must watch out
3011 return begFS; // for potential overrun!
3016 } // end: insp_find_ofs
3020 * This guy supports the inspect 'L' and '&' search provisions
3021 * and returns the row and *optimal* column for viewing any match
3022 * ( we'll always opt for left column justification since any )
3023 * ( preceding ctrl chars appropriate an unpredictable amount ) */
3024 static void insp_find_str (int ch, int *col, int *row) {
3025 #define reDUX (found) ? N_txt(WORD_another_txt) : ""
3028 if ((ch == '&' || ch == 'n') && !Insp_sel->fstr[0]) {
3029 show_msg(N_txt(FIND_no_next_txt));
3032 if (ch == 'L' || ch == '/') {
3033 char *str = ioline(N_txt(GET_find_str_txt));
3034 if (*str == kbd_ESC) return;
3035 snprintf(Insp_sel->fstr, FNDBUFSIZ, "%s", str);
3036 Insp_sel->flen = strlen(Insp_sel->fstr);
3039 if (Insp_sel->fstr[0]) {
3042 INSP_BUSY(YINSP_waitin_txt);
3043 for (xx = *col, yy = *row; yy < Insp_nl; ) {
3044 xx = insp_find_ofs(xx, yy);
3045 if (xx < INSP_RLEN(yy)) {
3047 if (xx == *col && yy == *row) { // matched where we were!
3048 ++xx; // ( was the user maybe )
3049 continue; // ( trying to fool us? )
3058 show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Insp_sel->fstr));
3061 } // end: insp_find_str
3065 * This guy is a *Helper* function responsible for positioning a
3066 * single row in the current 'X axis', then displaying the results.
3067 * Along the way, he makes sure control characters and/or unprintable
3068 * characters display in a less-like fashion:
3069 * '^A' for control chars
3070 * '<BC>' for other unprintable stuff
3071 * Those will be highlighted with the current windows's capclr_msg,
3072 * while visible search matches display with capclr_hdr for emphasis.
3073 * ( we hide ugly plumbing in macros to concentrate on the algorithm ) */
3074 static void insp_mkrow_raw (int col, int row) {
3075 #define maxSZ ( Screen_cols - to )
3076 #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } }
3077 #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3078 fr += Insp_sel->flen -1; to += Insp_sel->flen; hicap = 0; }
3079 #ifndef INSP_JUSTNOT
3080 #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3081 PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 2; hicap = 1; }
3082 #define mkUNP { const char *p = fmtmk("<%02X>", uch); \
3083 PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 4; hicap = 1; }
3085 #define mkCTL { if ((to += 2) <= Screen_cols) \
3086 PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; }
3087 #define mkUNP { if ((to += 4) <= Screen_cols) \
3088 PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; }
3090 #define mkSTD { capNO; if (++to <= Screen_cols) { static char _str[2]; \
3091 _str[0] = uch; putp(_str); } }
3092 unsigned char tline[SCREENMAX];
3096 if (col < INSP_RLEN(row))
3097 memcpy(tline, Insp_p[row] + col, sizeof(tline));
3098 else tline[0] = '\n';
3100 for (fr = 0, to = 0, ofs = 0; to < Screen_cols; fr++) {
3102 ofs = insp_find_ofs(col + fr, row);
3103 if (col + fr < ofs) {
3104 unsigned char uch = tline[fr];
3105 if (uch == '\n') break; // a no show (he,he)
3106 if (uch > 126) mkUNP // show as: '<AB>'
3107 else if (uch < 32) mkCTL // show as: '^C'
3108 else mkSTD // a show off (he,he)
3109 } else { mkFND // a big show (he,he)
3112 if (col + fr >= INSP_RLEN(row)) break;
3123 } // end: insp_mkrow_raw
3127 * This guy is a *Helper* function responsible for positioning a
3128 * single row in the current 'X axis' within a multi-byte string
3129 * then displaying the results. Along the way he ensures control
3130 * characters will then be displayed in two positions like '^A'.
3131 * ( assuming they can even get past those 'gettext' utilities ) */
3132 static void insp_mkrow_utf8 (int col, int row) {
3133 #define maxSZ ( Screen_cols - to )
3134 #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \
3135 fr += Insp_sel->flen; to += Insp_sel->flen; }
3136 #ifndef INSP_JUSTNOT
3137 #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \
3138 PUTT("%s%.*s%s", Curwin->capclr_msg, maxSZ, p, Caps_off); to += 2; }
3140 #define mkCTL { if ((to += 2) <= Screen_cols) \
3141 PUTT("%s^%c%s", Curwin->capclr_msg, uch + '@', Caps_off); }
3143 #define mkNUL { buf1[0] = ' '; doPUT(buf1) }
3144 #define doPUT(buf) if ((to += cno) <= Screen_cols) putp(buf);
3145 static char buf1[2], buf2[3], buf3[4], buf4[5];
3146 unsigned char tline[BIGBUFSIZ];
3149 col = utf8_proper_col(Insp_p[row], col, 1);
3150 if (col < INSP_RLEN(row))
3151 memcpy(tline, Insp_p[row] + col, sizeof(tline));
3152 else tline[0] = '\n';
3154 for (fr = 0, to = 0, ofs = 0; to < Screen_cols; ) {
3156 ofs = insp_find_ofs(col + fr, row);
3157 if (col + fr < ofs) {
3158 unsigned char uch = tline[fr];
3159 int bno = UTF8_tab[uch];
3160 int cno = utf8_cols(&tline[fr++], bno);
3163 if (uch == '\n') break;
3165 else if (uch == 127) mkNUL
3166 else { buf1[0] = uch; doPUT(buf1) }
3169 buf2[0] = uch; buf2[1] = tline[fr++];
3173 buf3[0] = uch; buf3[1] = tline[fr++]; buf3[2] = tline[fr++];
3177 buf4[0] = uch; buf4[1] = tline[fr++]; buf4[2] = tline[fr++]; buf4[3] = tline[fr++];
3188 if (col + fr >= INSP_RLEN(row)) break;
3197 } // end: insp_mkrow_utf8
3201 * This guy is an insp_view_choice() *Helper* function who displays
3202 * a page worth of of the user's damages. He also creates a status
3203 * line based on maximum digits for the current selection's lines and
3204 * hozizontal position (so it serves to inform, not distract, by
3205 * otherwise being jumpy). */
3206 static inline void insp_show_pgs (int col, int row, int max) {
3207 char buf[SMLBUFSIZ];
3208 void (*mkrow_func)(int, int);
3209 int r = snprintf(buf, sizeof(buf), "%d", Insp_nl);
3210 int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols);
3211 int l = row +1, ls = Insp_nl;
3215 snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt)
3218 , c, col + 1, c, col + Screen_cols
3219 , (unsigned long)Insp_bufrd);
3222 mkrow_func = Insp_utf8 ? insp_mkrow_utf8 : insp_mkrow_raw;
3224 for ( ; max && row < Insp_nl; row++) {
3226 mkrow_func(col, row);
3231 putp(Cap_nl_clreos);
3232 } // end: insp_show_pgs
3236 * This guy is responsible for displaying the Insp_buf contents and
3237 * managing all scrolling/locate requests until the user gives up. */
3238 static int insp_view_choice (struct pids_stack *p) {
3244 #define maxLN (Screen_rows - (Msg_row +1))
3245 #define makHD(b1,b2) { \
3246 snprintf(b1, sizeof(b1), "%d", PID_VAL(EU_PID, s_int, p)); \
3247 snprintf(b2, sizeof(b2), "%s", PID_VAL(EU_CMD, str, p)); }
3248 #define makFS(dst) { if (Insp_sel->flen < 22) \
3249 snprintf(dst, sizeof(dst), "%s", Insp_sel->fstr); \
3250 else snprintf(dst, sizeof(dst), "%.19s...", Insp_sel->fstr); }
3251 char buf[LRGBUFSIZ];
3252 int key, curlin = 0, curcol = 0;
3259 char pid[6], cmd[64];
3261 if (curcol < 0) curcol = 0;
3262 if (curlin >= Insp_nl) curlin = Insp_nl -1;
3263 if (curlin < 0) curlin = 0;
3268 show_special(1, fmtmk(N_unq(YINSP_hdview_fmt)
3269 , pid, cmd, (Insp_sel->fstr[0]) ? buf : " N/A ")); // nls_maybe
3270 insp_show_pgs(curcol, curlin, maxLN);
3272 /* fflush(stdin) didn't do the trick, so we'll just dip a little deeper
3273 lest repeated <Enter> keys produce immediate re-selection in caller */
3274 tcflush(STDIN_FILENO, TCIFLUSH);
3276 if (Frames_signal) goto signify_that;
3277 key = iokey(IOKEY_ONCE);
3278 if (key < 1) goto signify_that;
3281 case kbd_ENTER: // must force new iokey()
3282 key = INT_MAX; // fall through !
3301 curlin -= maxLN -1; // keep 1 line for reference
3305 curlin += maxLN -1; // ditto
3309 curcol = curlin = 0;
3314 curlin = Insp_nl - maxLN;
3321 insp_find_str(key, &curcol, &curlin);
3323 int tmpcol = utf8_proper_col(Insp_p[curlin], curcol, 1);
3324 insp_find_str(key, &tmpcol, &curlin);
3325 curcol = utf8_proper_col(Insp_p[curlin], tmpcol, 0);
3327 // must re-hide cursor in case a prompt for a string makes it huge
3328 putp((Cursor_state = Cap_curs_hide));
3331 snprintf(buf, sizeof(buf), "%s: %s", Insp_sel->type, Insp_sel->fmts);
3332 INSP_MKSL(1, buf); // show an extended SL
3333 if (iokey(IOKEY_ONCE) < 1)
3336 default: // keep gcc happy
3344 } // end: insp_view_choice
3348 * This is the main Inspect routine, responsible for:
3349 * 1) validating the passed pid (required, but not always used)
3350 * 2) presenting/establishing the target selection
3351 * 3) arranging to fill Insp_buf (via the Inspect.tab[?].func)
3352 * 4) invoking insp_view_choice for viewing/scrolling/searching
3353 * 5) cleaning up the dynamically acquired memory afterwards */
3354 static void inspection_utility (int pid) {
3355 #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \
3356 Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \
3357 for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \
3358 snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \
3359 strncat(dst, _s, (sizeof(dst) - 1) - strlen(dst)); } }
3360 char sels[SCREENMAX];
3363 struct pids_stack *p;
3365 for (i = 0, p = NULL; i < PIDSmaxt; i++)
3366 if (pid == PID_VAL(EU_PID, s_int, Curwin->ppt[i])) {
3371 show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid));
3374 // must re-hide cursor since the prompt for a pid made it huge
3375 putp((Cursor_state = Cap_curs_hide));
3384 show_special(1, fmtmk(N_unq(YINSP_hdsels_fmt)
3385 , pid, PID_VAL(EU_CMD, str, Curwin->ppt[i]), sels));
3388 if (Frames_signal) goto signify_that;
3389 if (key == INT_MAX) key = iokey(IOKEY_ONCE);
3390 if (key < 1) goto signify_that;
3397 sel = 0; // fall through !
3399 if (--sel < 0) sel = Inspect.total -1;
3403 sel = Inspect.total; // fall through !
3405 if (++sel >= Inspect.total) sel = 0;
3409 INSP_BUSY(!strcmp("file", Inspect.tab[sel].type)
3410 ? YINSP_waitin_txt : YINSP_workin_txt);
3411 Insp_sel = &Inspect.tab[sel];
3412 Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid);
3413 Insp_utf8 = utf8_delta(Insp_buf);
3414 key = insp_view_choice(p);
3421 } while (key != 'q' && key != kbd_ESC);
3423 // signal that we just corrupted entire screen
3424 Frames_signal = BREAK_screen;
3426 } // end: inspection_utility
3432 /*###### Other Filtering ###############################################*/
3435 * This sructure is hung from a WIN_t when other filtering is active */
3437 struct osel_s *nxt; // the next criteria or NULL.
3438 int (*rel)(const char *, const char *); // relational strings compare
3439 char *(*sel)(const char *, const char *); // for selection str compares
3440 char *raw; // raw user input (dup check)
3441 char *val; // value included or excluded
3442 int ops; // filter delimiter/operation
3443 int inc; // include == 1, exclude == 0
3444 int enu; // field (procflag) to filter
3445 int typ; // typ used to set: rel & sel
3449 * A function to parse, validate and build a single 'other filter' */
3450 static const char *osel_add (WIN_t *q, int ch, char *glob, int push) {
3451 int (*rel)(const char *, const char *);
3452 char *(*sel)(const char *, const char *);
3453 char raw[MEDBUFSIZ], ops, *pval;
3454 struct osel_s *osel;
3465 if (!snprintf(raw, sizeof(raw), "%s", glob))
3467 for (osel = q->osel_1st; osel; ) {
3468 if (!strcmp(osel->raw, raw)) // #1: is criteria duplicate?
3469 return N_txt(OSEL_errdups_txt);
3472 if (*glob != '!') inc = 1; // #2: is it include/exclude?
3473 else { ++glob; inc = 0; }
3475 if (!(pval = strpbrk(glob, "<=>"))) // #3: do we see a delimiter?
3476 return fmtmk(N_fmt(OSEL_errdelm_fmt)
3477 , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3481 for (enu = 0; enu < EU_MAXPFLGS; enu++) // #4: is this a valid field?
3482 if (!STRCMP(N_col(enu), glob)) break;
3483 if (enu == EU_MAXPFLGS)
3484 return fmtmk(N_fmt(XTRA_badflds_fmt), glob);
3486 if (!(*pval)) // #5: did we get some value?
3487 return fmtmk(N_fmt(OSEL_errvalu_fmt)
3488 , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt));
3490 osel = alloc_c(sizeof(struct osel_s));
3495 if (ops == '=') osel->val = alloc_s(pval);
3496 else osel->val = alloc_s(justify_pad(pval, Fieldstab[enu].width, Fieldstab[enu].align));
3499 osel->raw = alloc_s(raw);
3502 // a LIFO queue was used when we're interactive
3503 osel->nxt = q->osel_1st;
3506 // a FIFO queue must be employed for the rcfile
3510 struct osel_s *prev, *walk = q->osel_1st;
3525 * A function to turn off entire other filtering in the given window */
3526 static void osel_clear (WIN_t *q) {
3527 struct osel_s *osel = q->osel_1st;
3530 struct osel_s *nxt = osel->nxt;
3538 } // end: osel_clear
3542 * Determine if there are matching values or relationships among the
3543 * other criteria in this passed window -- it's called from only one
3544 * place, and likely inlined even without the directive */
3545 static inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str) {
3546 struct osel_s *osel = q->osel_1st;
3549 if (osel->enu == enu) {
3551 switch (osel->ops) {
3552 case '<': // '<' needs the r < 0 unless
3553 r = osel->rel(str, osel->val); // '!' which needs an inverse
3554 if ((r >= 0 && osel->inc) || (r < 0 && !osel->inc)) return 0;
3556 case '>': // '>' needs the r > 0 unless
3557 r = osel->rel(str, osel->val); // '!' which needs an inverse
3558 if ((r <= 0 && osel->inc) || (r > 0 && !osel->inc)) return 0;
3561 { char *p = osel->sel(str, osel->val);
3562 if ((!p && osel->inc) || (p && !osel->inc)) return 0;
3570 } // end: osel_matched
3572 /*###### Startup routines ##############################################*/
3575 * No matter what *they* say, we handle the really really BIG and
3576 * IMPORTANT stuff upon which all those lessor functions depend! */
3577 static void before (char *me) {
3578 #define doALL STAT_REAP_NUMA_NODES_TOO
3580 int linux_version_code = procps_linux_version();
3582 atexit(close_stdout);
3584 // setup our program name
3585 Myname = strrchr(me, '/');
3586 if (Myname) ++Myname; else Myname = me;
3588 // accommodate nls/gettext potential translations
3589 // ( must 'setlocale' before our libproc called )
3592 // is /proc mounted?
3593 fatal_proc_unmounted(NULL, 0);
3595 #ifndef OFF_STDERROR
3596 /* there's a chance that damn libnuma may spew to stderr so we gotta
3597 make sure he does not corrupt poor ol' top's first output screen!
3598 Yes, he provides some overridable 'weak' functions to change such
3599 behavior but we can't exploit that since we don't follow a normal
3600 ld route to symbol resolution (we use that dlopen() guy instead)! */
3601 Stderr_save = dup(fileno(stderr));
3602 if (-1 < Stderr_save && freopen("/dev/null", "w", stderr))
3603 ; // avoid -Wunused-result
3606 // establish some cpu particulars
3607 Hertz = procps_hertz_get();
3608 Cpu_States_fmts = N_unq(STATE_lin2x6_fmt);
3609 if (linux_version_code >= LINUX_VERSION(2, 6, 11))
3610 Cpu_States_fmts = N_unq(STATE_lin2x7_fmt);
3612 // get the total cpus (and, if possible, numa node total)
3613 if ((rc = procps_stat_new(&Stat_ctx)))
3614 Restrict_some = Cpu_cnt = 1;
3616 if (!(Stat_reap = procps_stat_reap(Stat_ctx, doALL, Stat_items, MAXTBL(Stat_items))))
3617 error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno)));
3618 #ifndef PRETEND0NUMA
3619 Numa_node_tot = Stat_reap->numa->total;
3621 Cpu_cnt = Stat_reap->cpus->total;
3627 // prepare for memory stats from new library API ...
3628 if ((rc = procps_meminfo_new(&Mem_ctx)))
3631 // establish max depth for newlib pids stack (# of result structs)
3632 Pids_itms = alloc_c(sizeof(enum pids_item) * MAXTBL(Fieldstab));
3634 for (i = 0; i < MAXTBL(Fieldstab); i++)
3635 Pids_itms[i] = PIDS_noop;
3636 Pids_itms_tot = MAXTBL(Fieldstab);
3637 // we will identify specific items in the build_headers() function
3638 if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot)))
3639 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc)));
3641 #if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK
3642 { struct sigaction sa;
3643 Thread_id_main = pthread_self();
3644 /* in case any of our threads have been enabled, they'll inherit this mask
3645 with everything blocked. therefore, signals go to the main thread (us). */
3646 sigfillset(&sa.sa_mask);
3647 pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
3652 if (0 != sem_init(&Semaphore_cpus_beg, 0, 0)
3653 || (0 != sem_init(&Semaphore_cpus_end, 0, 0)))
3654 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3655 if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL))
3656 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3657 pthread_setname_np(Thread_id_cpus, "update cpus");
3660 if (0 != sem_init(&Semaphore_memory_beg, 0, 0)
3661 || (0 != sem_init(&Semaphore_memory_end, 0, 0)))
3662 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3663 if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL))
3664 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3665 pthread_setname_np(Thread_id_memory, "update memory");
3668 if (0 != sem_init(&Semaphore_tasks_beg, 0, 0)
3669 || (0 != sem_init(&Semaphore_tasks_end, 0, 0)))
3670 error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno)));
3671 if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL))
3672 error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno)));
3673 pthread_setname_np(Thread_id_tasks, "update tasks");
3675 // lastly, establish support for graphing cpus & memory
3676 Graph_cpus = alloc_c(sizeof(struct graph_parms));
3677 Graph_mems = alloc_c(sizeof(struct graph_parms));
3683 * A configs_file *Helper* function responsible for transorming
3684 * a 3.2.8 - 3.3.17 format 'fieldscur' into our integer based format */
3685 static int cfg_xform (WIN_t *q, char *flds, const char *defs) {
3686 #define CVTon(c) ((c) |= 0x80)
3690 #define old_View_NOBOLD 0x000001
3691 #define old_VISIBLE_tsk 0x000008
3692 #define old_Qsrt_NORMAL 0x000010
3693 #define old_Show_HICOLS 0x000200
3694 #define old_Show_THREAD 0x010000
3695 { old_View_NOBOLD, View_NOBOLD },
3696 { old_VISIBLE_tsk, Show_TASKON },
3697 { old_Qsrt_NORMAL, Qsrt_NORMAL },
3698 { old_Show_HICOLS, Show_HICOLS },
3699 { old_Show_THREAD, 0 }
3700 #undef old_View_NOBOLD
3701 #undef old_VISIBLE_tsk
3702 #undef old_Qsrt_NORMAL
3703 #undef old_Show_HICOLS
3704 #undef old_Show_THREAD
3706 static char null_flds[] = "abcdefghijklmnopqrstuvwxyz";
3707 static const char fields_src[] = CVT_FORMER;
3708 char fields_dst[PFLAGSSIZ], *p1, *p2;
3709 int c, f, i, x, *pn;
3712 // first we'll touch up this window's winflags ...
3715 for (i = 0; i < MAXTBL(flags_tab); i++) {
3716 if (x & flags_tab[i].old) {
3717 x &= ~flags_tab[i].old;
3718 q->rc.winflags |= flags_tab[i].new;
3721 q->rc.winflags |= x;
3723 // now let's convert old top's more limited fields ...
3725 if (f >= CVT_FLDMAX)
3727 strcpy(fields_dst, fields_src);
3728 /* all other fields represent the 'on' state with a capitalized version
3729 of a particular qwerty key. for the 2 additional suse out-of-memory
3730 fields it makes perfect sense to do the exact opposite, doesn't it?
3731 in any case, we must turn them 'off' temporarily ... */
3732 if ((p1 = strchr(flds, '['))) *p1 = '{';
3733 if ((p2 = strchr(flds, '\\'))) *p2 = '|';
3734 for (i = 0; i < f; i++) {
3736 x = tolower(c) - 'a';
3737 if (x < 0 || x >= CVT_FLDMAX)
3739 fields_dst[i] = fields_src[x];
3741 CVTon(fields_dst[i]);
3743 // if we turned any suse only fields off, turn 'em back on OUR way ...
3744 if (p1) CVTon(fields_dst[p1 - flds]);
3745 if (p2) CVTon(fields_dst[p2 - flds]);
3747 // next, we must adjust the old sort field enum ...
3751 if ((p1 = memchr(flds, c, CVT_FLDMAX))
3752 || ((p1 = memchr(flds, toupper(c), CVT_FLDMAX)))) {
3754 q->rc.sortindx = (fields_dst[x] & 0x7f) - FLD_OFFSET;
3756 // now we're in a 3.3.0 format (soon to be transformed) ...
3757 strcpy(flds, fields_dst);
3760 // lastly, let's attend to the 3.3.0 - 3.3.17 fieldcurs format ...
3761 pn = &q->rc.fieldscur[0];
3763 for (i = 0; i < x; i++) {
3764 f = ((unsigned char)flds[i] & 0x7f);
3766 if ((unsigned char)flds[i] & 0x80) f |= FLDon;
3776 * A configs_file *Helper* function responsible for reading
3777 * and validating a configuration file's 'Inspection' entries */
3778 static int config_insp (FILE *fp, char *buf, size_t size) {
3781 // we'll start off with a 'potential' blank or empty line
3782 // ( only realized if we end up with Inspect.total > 0 )
3783 if (!buf[0] || buf[0] != '\n') Inspect.raw = alloc_s("\n");
3784 else Inspect.raw = alloc_c(1);
3787 #define iT(element) Inspect.tab[i].element
3788 #define nxtLINE { buf[0] = '\0'; continue; }
3789 size_t lraw = strlen(Inspect.raw) +1;
3793 if (i < 0 || (size_t)i >= INT_MAX / sizeof(struct I_ent)) break;
3794 if (lraw >= INT_MAX - size) break;
3796 if (!buf[0] && !fgets(buf, size, fp)) break;
3797 lraw += strlen(buf) +1;
3798 Inspect.raw = alloc_r(Inspect.raw, lraw);
3799 strcat(Inspect.raw, buf);
3801 if (buf[0] == '#' || buf[0] == '\n') nxtLINE;
3802 Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_ent) * (i + 1));
3804 // part of this is used in a show_special() call, so let's sanitize it
3805 for (n = 0, x = strlen(buf); n < x; n++) {
3806 if ((buf[n] != '\t' && buf[n] != '\n')
3807 && (buf[n] < ' ')) {
3812 if (!(s1 = strtok(buf, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3813 if (!(s2 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3814 if (!(s3 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; }
3816 switch (toupper(buf[0])) {
3818 iT(func) = insp_do_file;
3821 iT(func) = insp_do_pipe;
3827 iT(type) = alloc_s(s1);
3828 iT(name) = alloc_s(s2);
3829 iT(fmts) = alloc_s(s3);
3830 iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0;
3831 iT(fstr) = alloc_c(FNDBUFSIZ);
3838 } // end: for ('inspect' entries)
3841 #ifndef INSP_OFFDEMO
3842 if (!Inspect.total) {
3843 #define mkS(n) N_txt(YINSP_demo ## n ## _txt)
3844 const char *sels[] = { mkS(01), mkS(02), mkS(03) };
3845 Inspect.total = Inspect.demo = MAXTBL(sels);
3846 Inspect.tab = alloc_c(sizeof(struct I_ent) * Inspect.total);
3847 for (i = 0; i < Inspect.total; i++) {
3848 Inspect.tab[i].type = alloc_s(N_txt(YINSP_deqtyp_txt));
3849 Inspect.tab[i].name = alloc_s(sels[i]);
3850 Inspect.tab[i].func = insp_do_demo;
3851 Inspect.tab[i].fmts = alloc_s(N_txt(YINSP_deqfmt_txt));
3852 Inspect.tab[i].fstr = alloc_c(FNDBUFSIZ);
3858 } // end: config_insp
3862 * A configs_file *Helper* function responsible for reading
3863 * and validating a configuration file's 'Other Filter' entries */
3864 static int config_osel (FILE *fp, char *buf, size_t size) {
3865 int i, ch, tot, wno, begun;
3869 if (!fgets(buf, size, fp)) return 0;
3870 if (buf[0] == '\n') continue;
3871 // whoa, must be an 'inspect' entry
3872 if (!begun && !strstr(buf, Osel_delim_1_txt))
3874 // ok, we're now beginning
3875 if (!begun && strstr(buf, Osel_delim_1_txt)) {
3879 // this marks the end of our stuff
3880 if (begun && strstr(buf, Osel_delim_2_txt))
3883 if (2 != sscanf(buf, Osel_window_fmts, &wno, &tot))
3885 if (wno < 0 || wno >= GROUPSMAX) goto end_oops;
3886 if (tot < 0) goto end_oops;
3888 for (i = 0; i < tot; i++) {
3889 if (!fgets(buf, size, fp)) return 1;
3890 if (1 > sscanf(buf, Osel_filterI_fmt, &ch)) goto end_oops;
3891 if ((p = strchr(buf, '\n'))) *p = '\0';
3892 if (!(p = strstr(buf, OSEL_FILTER))) goto end_oops;
3893 p += sizeof(OSEL_FILTER) - 1;
3894 if (osel_add(&Winstk[wno], ch, p, 0)) goto end_oops;
3897 // let's prime that buf for the next guy...
3898 fgets(buf, size, fp);
3904 } // end: config_osel
3908 * A configs_reads *Helper* function responsible for processing
3909 * a configuration file (personal or system-wide default) */
3910 static const char *configs_file (FILE *fp, const char *name, float *delay) {
3911 char fbuf[LRGBUFSIZ];
3912 int i, n, tmp_whole, tmp_fract;
3913 const char *p = NULL;
3915 p = fmtmk(N_fmt(RC_bad_files_fmt), name);
3916 (void)fgets(fbuf, sizeof(fbuf), fp); // ignore eyecatcher
3918 , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
3919 , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_whole, &tmp_fract, &i)) {
3922 if (Rc.id < 'a' || Rc.id > RCF_VERSION_ID)
3924 if (strchr("bcde", Rc.id))
3926 if (Rc.mode_altscr < 0 || Rc.mode_altscr > 1)
3928 if (Rc.mode_irixps < 0 || Rc.mode_irixps > 1)
3932 // you saw that, right? (fscanf stickin' it to 'i')
3933 if (i < 0 || i >= GROUPSMAX)
3935 Curwin = &Winstk[i];
3936 // this may be ugly, but it keeps us locale independent...
3937 *delay = (float)tmp_whole + (float)tmp_fract / 1000;
3939 for (i = 0 ; i < GROUPSMAX; i++) {
3940 static const char *def_flds[] = { DEF_FORMER, JOB_FORMER, MEM_FORMER, USR_FORMER };
3942 WIN_t *w = &Winstk[i];
3943 p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, name);
3945 if (1 != fscanf(fp, "%3s\tfieldscur=", w->rc.winname))
3947 if (Rc.id < RCF_XFORMED_ID)
3948 fscanf(fp, "%s\n", fbuf);
3951 if (1 != fscanf(fp, "%d", &w->rc.fieldscur[j]))
3955 // be tolerant of missing release 3.3.10 graph modes additions
3956 if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
3957 ", double_up=%d, combine_cpus=%d\n"
3958 , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems
3959 , &w->rc.double_up, &w->rc.combine_cpus))
3961 if (w->rc.sortindx < 0 || w->rc.sortindx >= EU_MAXPFLGS)
3963 if (w->rc.maxtasks < 0)
3965 if (w->rc.graph_cpus < 0 || w->rc.graph_cpus > 2)
3967 if (w->rc.graph_mems < 0 || w->rc.graph_mems > 2)
3969 if (w->rc.double_up < 0 || w->rc.double_up >= ADJOIN_limit)
3971 // can't check upper bounds until Cpu_cnt is known
3972 if (w->rc.combine_cpus < 0)
3975 if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
3976 , &w->rc.summclr, &w->rc.msgsclr, &w->rc.headclr, &w->rc.taskclr))
3978 // would prefer to use 'max_colors', but it isn't available yet...
3979 if (w->rc.summclr < 0 || w->rc.summclr > 255) return p;
3980 if (w->rc.msgsclr < 0 || w->rc.msgsclr > 255) return p;
3981 if (w->rc.headclr < 0 || w->rc.headclr > 255) return p;
3982 if (w->rc.taskclr < 0 || w->rc.taskclr > 255) return p;
3985 case 'a': // 3.2.8 (former procps)
3987 case 'f': // 3.3.0 thru 3.3.3 (ng)
3988 SETw(w, Show_JRNUMS);
3990 case 'g': // from 3.3.4 thru 3.3.8
3991 if (Rc.id > 'a') scat(fbuf, RCF_PLUS_H);
3993 case 'h': // this is release 3.3.9
3994 w->rc.graph_cpus = w->rc.graph_mems = 0;
3995 // these next 2 are really global, but best documented here
3996 Rc.summ_mscale = Rc.task_mscale = SK_Kb;
3998 case 'i': // from 3.3.10 thru 3.3.16
3999 if (Rc.id > 'a') scat(fbuf, RCF_PLUS_J);
4000 w->rc.double_up = w->rc.combine_cpus = 0;
4002 case 'j': // this is release 3.3.17
4003 if (cfg_xform(w, fbuf, def_flds[i]))
4007 case 'k': // current RCF_VERSION_ID
4010 if (mlen(w->rc.fieldscur) < EU_MAXPFLGS)
4012 for (x = 0; x < EU_MAXPFLGS; x++) {
4013 FLG_t f = FLDget(w, x);
4014 if (f >= EU_MAXPFLGS || f < 0)
4019 // ensure there's been no manual alteration of fieldscur
4020 for (n = 0 ; n < EU_MAXPFLGS; n++) {
4021 if (&w->rc.fieldscur[n] != msch(w->rc.fieldscur, w->rc.fieldscur[n], EU_MAXPFLGS))
4024 } // end: for (GROUPSMAX)
4026 // any new addition(s) last, for older rcfiles compatibility...
4027 (void)fscanf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
4028 , &Rc.fixed_widest, &Rc.summ_mscale, &Rc.task_mscale, &Rc.zero_suppress, &Rc.tics_scaled);
4029 if (Rc.fixed_widest < -1 || Rc.fixed_widest > SCREENMAX)
4030 Rc.fixed_widest = 0;
4031 if (Rc.summ_mscale < 0 || Rc.summ_mscale > SK_Eb)
4033 if (Rc.task_mscale < 0 || Rc.task_mscale > SK_Pb)
4035 if (Rc.zero_suppress < 0 || Rc.zero_suppress > 1)
4036 Rc.zero_suppress = 0;
4037 if (Rc.tics_scaled < 0 || Rc.tics_scaled > TICS_AS_LAST)
4040 // prepare to warn that older top can no longer read rcfile ...
4041 if (Rc.id != RCF_VERSION_ID)
4042 Rc_compatibilty = 1;
4044 // lastly, let's process any optional glob(s) ...
4045 // (darn, must do osel 1st even though alphabetically 2nd)
4047 config_osel(fp, fbuf, sizeof(fbuf));
4048 config_insp(fp, fbuf, sizeof(fbuf));
4051 } // end: configs_file
4055 * A configs_reads *Helper* function responsible for ensuring the
4056 * complete path was established, otherwise force the 'W' to fail */
4057 static int configs_path (const char *const fmts, ...) __attribute__((format(printf,1,2)));
4058 static int configs_path (const char *const fmts, ...) {
4063 len = vsnprintf(Rc_name, sizeof(Rc_name), fmts, ap);
4065 if (len <= 0 || (size_t)len >= sizeof(Rc_name)) {
4070 } // end: configs_path
4074 * Try reading up to 3 rcfiles
4075 * 1. 'SYS_RCRESTRICT' contains two lines consisting of the secure
4076 * mode switch and an update interval. Its presence limits what
4077 * ordinary users are allowed to do.
4078 * 2. 'Rc_name' contains multiple lines - both global & per window.
4079 * line 1 : an eyecatcher and creating program/alias name
4080 * line 2 : an id, Mode_altcsr, Mode_irixps, Delay_time, Curwin.
4081 * For each of the 4 windows:
4082 * lines a: contains w->winname, fieldscur
4083 * line b: contains w->winflags, sortindx, maxtasks, etc
4084 * line c: contains w->summclr, msgsclr, headclr, taskclr
4085 * global : miscellaneous additional settings
4086 * Any remaining lines are devoted to the optional entries
4087 * supporting the 'Other Filter' and 'Inspect' provisions.
4088 * 3. 'SYS_RCDEFAULTS' system-wide defaults if 'Rc_name' absent
4089 * format is identical to #2 above */
4090 static void configs_reads (void) {
4091 float tmp_delay = DEF_DELAY;
4092 const char *p, *p_home;
4095 fp = fopen(SYS_RCRESTRICT, "r");
4097 char fbuf[SMLBUFSIZ];
4098 if (fgets(fbuf, sizeof(fbuf), fp)) { // sys rc file, line 1
4100 if (fgets(fbuf, sizeof(fbuf), fp)) // sys rc file, line 2
4101 sscanf(fbuf, "%f", &Rc.delay_time);
4106 Rc_name[0] = '\0'; // "fopen() shall fail if pathname is an empty string."
4107 // attempt to use the legacy file first, if we cannot access that file, use
4108 // the new XDG basedir locations (XDG_CONFIG_HOME or HOME/.config) instead.
4109 p_home = getenv("HOME");
4110 if (!p_home || p_home[0] != '/') {
4111 const struct passwd *const pwd = getpwuid(getuid());
4112 if (!pwd || !(p_home = pwd->pw_dir) || p_home[0] != '/') {
4117 configs_path("%s/.%src", p_home, Myname);
4119 if (!(fp = fopen(Rc_name, "r"))) {
4120 p = getenv("XDG_CONFIG_HOME");
4121 // ensure the path we get is absolute, fallback otherwise.
4122 if (!p || p[0] != '/') {
4123 if (!p_home) goto system_default;
4124 p = fmtmk("%s/.config", p_home);
4125 (void)mkdir(p, 0700);
4127 if (!configs_path("%s/procps", p)) goto system_default;
4128 (void)mkdir(Rc_name, 0700);
4129 if (!configs_path("%s/procps/%src", p, Myname)) goto system_default;
4130 fp = fopen(Rc_name, "r");
4134 p = configs_file(fp, Rc_name, &tmp_delay);
4136 if (p) goto default_or_error;
4139 fp = fopen(SYS_RCDEFAULTS, "r");
4141 p = configs_file(fp, SYS_RCDEFAULTS, &tmp_delay);
4143 if (p) goto default_or_error;
4147 // lastly, establish the true runtime secure mode and delay time
4148 if (!getuid()) Secure_mode = 0;
4149 if (!Secure_mode) Rc.delay_time = tmp_delay;
4154 { RCF_t rcdef = DEF_RCFILE;
4157 for (i = 0 ; i < GROUPSMAX; i++)
4158 Winstk[i].rc = Rc.win[i];
4163 } // end: configs_reads
4167 * Parse command line arguments.
4168 * Note: it's assumed that the rc file(s) have already been read
4169 * and our job is to see if any of those options are to be
4170 * overridden -- we'll force some on and negate others in our
4171 * best effort to honor the loser's (oops, user's) wishes... */
4172 static void parse_args (int argc, char **argv) {
4173 static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1";
4174 static const struct option lopts[] = {
4175 { "batch-mode", no_argument, NULL, 'b' },
4176 { "cmdline-toggle", no_argument, NULL, 'c' },
4177 { "delay", required_argument, NULL, 'd' },
4178 { "scale-summary-mem", required_argument, NULL, 'E' },
4179 { "scale-task-mem", required_argument, NULL, 'e' },
4180 { "threads-show", no_argument, NULL, 'H' },
4181 { "help", no_argument, NULL, 'h' },
4182 { "idle-toggle", no_argument, NULL, 'i' },
4183 { "iterations", required_argument, NULL, 'n' },
4184 { "list-fields", no_argument, NULL, 'O' },
4185 { "sort-override", required_argument, NULL, 'o' },
4186 { "pid", required_argument, NULL, 'p' },
4187 { "accum-time-toggle", no_argument, NULL, 'S' },
4188 { "secure-mode", no_argument, NULL, 's' },
4189 { "filter-any-user", required_argument, NULL, 'U' },
4190 { "filter-only-euser", required_argument, NULL, 'u' },
4191 { "version", no_argument, NULL, 'V' },
4192 { "width", optional_argument, NULL, 'w' },
4193 { "single-cpu-toggle", no_argument, NULL, '1' },
4194 { NULL, 0, NULL, 0 }
4196 float tmp_delay = FLT_MAX;
4199 while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) {
4204 #ifndef GETOPTFIX_NO
4205 /* first, let's plug some awful gaps in the getopt implementation,
4206 especially relating to short options with (optional) arguments! */
4207 if (!cp && optind < argc && argv[optind][0] != '-')
4208 cp = argv[optind++];
4210 if (*cp == '=') ++cp;
4211 /* here, if we're actually accessing argv[argc], we'll rely on
4212 the required NULL delimiter which yields an error_exit next */
4213 if (*cp == '\0') cp = argv[optind++];
4214 if (!cp) error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
4218 case '1': // ensure behavior identical to run-time toggle
4219 if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM);
4220 else TOGw(Curwin, View_CPUSUM);
4221 OFFw(Curwin, View_CPUNOD);
4222 SETw(Curwin, View_STATES);
4228 TOGw(Curwin, Show_CMDLIN);
4231 if (!mkfloat(cp, &tmp_delay, 0))
4232 error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp));
4234 error_exit(N_txt(DELAY_badarg_txt));
4237 { const char *get = "kmgtpe", *got;
4238 if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4239 error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4240 Rc.summ_mscale = (int)(got - get);
4243 { const char *get = "kmgtp", *got;
4244 if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
4245 error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
4246 Rc.task_mscale = (int)(got - get);
4252 puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname));
4255 TOGw(Curwin, Show_IDLEPS);
4256 Curwin->rc.maxtasks = 0;
4259 if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp)
4260 error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp));
4264 for (i = 0; i < EU_MAXPFLGS; i++)
4268 if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; }
4269 else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; }
4270 for (i = 0; i < EU_MAXPFLGS; i++)
4271 if (!STRCMP(cp, N_col(i))) break;
4272 if (i == EU_MAXPFLGS)
4273 error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp));
4274 OFFw(Curwin, Show_FOREST);
4275 Curwin->rc.sortindx = i;
4279 if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4281 if (Monpidsidx >= MONPIDMAX)
4282 error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX));
4283 if (1 != sscanf(cp, "%d", &pid)
4284 || strpbrk(cp, "+-."))
4285 error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp));
4286 if (!pid) pid = getpid();
4287 for (i = 0; i < Monpidsidx; i++)
4288 if (Monpids[i] == pid) goto next_pid;
4289 Monpids[Monpidsidx++] = pid;
4291 if (!(p = strchr(cp, ','))) break;
4296 TOGw(Curwin, Show_CTIMES);
4303 { const char *errmsg;
4304 if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
4305 if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg);
4308 puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING));
4312 if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX))
4313 error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp));
4314 Width_mode = (int)tmp;
4317 // we'll rely on getopt for any error message ...
4319 } // end: switch (ch)
4320 #ifndef GETOPTFIX_NO
4321 if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp));
4323 } // end: while getopt_long
4326 error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind]));
4328 // fixup delay time, maybe...
4329 if (FLT_MAX > tmp_delay) {
4331 error_exit(N_txt(DELAY_secure_txt));
4332 Rc.delay_time = tmp_delay;
4334 } // end: parse_args
4338 * Establish a robust signals environment */
4339 static void signals_set (void) {
4340 #ifndef SIGRTMAX // not available on hurd, maybe others too
4344 struct sigaction sa;
4346 memset(&sa, 0, sizeof(sa));
4347 sigemptyset(&sa.sa_mask);
4348 // with user position preserved through SIGWINCH, we must avoid SA_RESTART
4350 for (i = SIGRTMAX; i; i--) {
4354 sa.sa_handler = SIG_IGN;
4356 sa.sa_handler = sig_endpgm;
4358 case SIGALRM: case SIGINT: case SIGPIPE:
4359 case SIGQUIT: case SIGTERM: case SIGUSR1:
4361 sa.sa_handler = sig_endpgm;
4363 case SIGTSTP: case SIGTTIN: case SIGTTOU:
4364 sa.sa_handler = sig_paused;
4366 case SIGCONT: case SIGWINCH:
4367 sa.sa_handler = sig_resize;
4370 sa.sa_handler = sig_abexit;
4372 case SIGKILL: case SIGSTOP:
4373 // because uncatchable, fall through
4374 case SIGCHLD: // we can't catch this
4375 continue; // when opening a pipe
4377 sigaction(i, &sa, NULL);
4379 } // end: signals_set
4383 * Set up the terminal attributes */
4384 static void whack_terminal (void) {
4385 static char dummy[] = "dumb";
4386 struct termios tmptty;
4388 // the curses part...
4390 setupterm(dummy, STDOUT_FILENO, NULL);
4394 setupterm(dummy, STDOUT_FILENO, NULL);
4396 setupterm(NULL, STDOUT_FILENO, NULL);
4399 if (-1 == tcgetattr(STDIN_FILENO, &Tty_original))
4400 error_exit(N_txt(FAIL_tty_get_txt));
4401 // ok, haven't really changed anything but we do have our snapshot
4404 // first, a consistent canonical mode for interactive line input
4405 tmptty = Tty_original;
4406 tmptty.c_lflag |= (ECHO | ECHOCTL | ECHOE | ICANON | ISIG);
4407 tmptty.c_lflag &= ~NOFLSH;
4408 tmptty.c_oflag &= ~TAB3;
4409 tmptty.c_iflag |= BRKINT;
4410 tmptty.c_iflag &= ~IGNBRK;
4411 if (key_backspace && 1 == strlen(key_backspace))
4412 tmptty.c_cc[VERASE] = *key_backspace;
4414 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4415 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4416 tcgetattr(STDIN_FILENO, &Tty_tweaked);
4418 // lastly, a nearly raw mode for unsolicited single keystrokes
4419 tmptty.c_lflag &= ~(ECHO | ECHOCTL | ECHOE | ICANON);
4420 tmptty.c_cc[VMIN] = 1;
4421 tmptty.c_cc[VTIME] = 0;
4422 if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty))
4423 error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
4424 tcgetattr(STDIN_FILENO, &Tty_raw);
4426 #ifndef OFF_STDIOLBF
4427 // thanks anyway stdio, but we'll manage buffering at the frame level...
4428 setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf));
4431 // this has the effect of disabling any troublesome scrollback buffer...
4432 if (enter_ca_mode) putp(enter_ca_mode);
4434 // and don't forget to ask iokey to initialize his tinfo_tab
4436 } // end: whack_terminal
4438 /*###### Windows/Field Groups support #################################*/
4441 * Value a window's name and make the associated group name. */
4442 static void win_names (WIN_t *q, const char *name) {
4443 /* note: sprintf/snprintf results are "undefined" when src==dst,
4444 according to C99 & POSIX.1-2001 (thanks adc) */
4445 if (q->rc.winname != name)
4446 snprintf(q->rc.winname, sizeof(q->rc.winname), "%s", name);
4447 snprintf(q->grpname, sizeof(q->grpname), "%d:%s", q->winnum, name);
4452 * This guy just resets (normalizes) a single window
4453 * and he ensures pid monitoring is no longer active. */
4454 static void win_reset (WIN_t *q) {
4455 SETw(q, Show_IDLEPS | Show_TASKON);
4456 #ifndef SCROLLVAR_NO
4457 q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->varcolbeg = q->focus_pid = 0;
4459 q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->focus_pid = 0;
4463 q->findstr[0] = '\0';
4464 q->rc.combine_cpus = 0;
4466 // these next guys are global, not really windows based
4474 * Display a window/field group (ie. make it "current"). */
4475 static WIN_t *win_select (int ch) {
4476 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4478 /* if there's no ch, it means we're supporting the external interface,
4479 so we must try to get our own darn ch by begging the user... */
4481 show_pmt(N_txt(CHOOSE_group_txt));
4482 if (1 > (ch = iokey(IOKEY_ONCE))) return w;
4485 case 'a': // we don't carry 'a' / 'w' in our
4486 w = w->next; // pmt - they're here for a good
4487 break; // friend of ours -- wins_colors.
4488 case 'w': // (however those letters work via
4489 w = w->prev; // the pmt too but gee, end-loser
4490 break; // should just press the darn key)
4491 case '1': case '2' : case '3': case '4':
4492 w = &Winstk[ch - '1'];
4494 default: // keep gcc happy
4499 } // end: win_select
4503 * Just warn the user when a command can't be honored. */
4504 static int win_warn (int what) {
4507 show_msg(N_txt(DISABLED_cmd_txt));
4510 show_msg(fmtmk(N_fmt(DISABLED_win_fmt), Curwin->grpname));
4512 default: // keep gcc happy
4515 /* we gotta' return false 'cause we're somewhat well known within
4516 macro society, by way of that sassy little tertiary operator... */
4522 * Change colors *Helper* function to save/restore settings;
4523 * ensure colors will show; and rebuild the terminfo strings. */
4524 static void wins_clrhlp (WIN_t *q, int save) {
4525 static int flgssav, summsav, msgssav, headsav, tasksav;
4528 flgssav = q->rc.winflags; summsav = q->rc.summclr;
4529 msgssav = q->rc.msgsclr; headsav = q->rc.headclr; tasksav = q->rc.taskclr;
4530 SETw(q, Show_COLORS);
4532 q->rc.winflags = flgssav; q->rc.summclr = summsav;
4533 q->rc.msgsclr = msgssav; q->rc.headclr = headsav; q->rc.taskclr = tasksav;
4536 } // end: wins_clrhlp
4540 * Change colors used in display */
4541 static void wins_colors (void) {
4542 #define kbdABORT 'q'
4543 #define kbdAPPLY kbd_ENTER
4544 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4545 int clr = w->rc.taskclr, *pclr = &w->rc.taskclr;
4549 if (0 >= max_colors) {
4550 show_msg(N_txt(COLORS_nomap_txt));
4554 putp((Cursor_state = Cap_curs_huge));
4561 // this string is well above ISO C89's minimum requirements!
4562 show_special(1, fmtmk(N_unq(COLOR_custom_fmt)
4564 , CHKw(w, View_NOBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4565 , CHKw(w, Show_COLORS) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4566 , CHKw(w, Show_HIBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
4567 , tgt, max_colors, clr, w->grpname));
4571 if (Frames_signal) goto signify_that;
4572 key = iokey(IOKEY_ONCE);
4573 if (key < 1) goto signify_that;
4574 if (key == kbd_ESC) break;
4578 pclr = &w->rc.summclr;
4583 pclr = &w->rc.msgsclr;
4588 pclr = &w->rc.headclr;
4593 pclr = &w->rc.taskclr;
4597 case '0': case '1': case '2': case '3':
4598 case '4': case '5': case '6': case '7':
4604 if (clr >= max_colors) clr = 0;
4609 if (clr < 0) clr = max_colors - 1;
4613 TOGw(w, View_NOBOLD);
4616 TOGw(w, Show_HIBOLD);
4619 TOGw(w, Show_COLORS);
4623 wins_clrhlp((w = win_select(key)), 1);
4624 clr = w->rc.taskclr, pclr = &w->rc.taskclr;
4628 break; // keep gcc happy
4631 } while (key != kbdAPPLY && key != kbdABORT);
4633 if (key == kbdABORT || key == kbd_ESC) wins_clrhlp(w, 0);
4634 // signal that we just corrupted entire screen
4635 Frames_signal = BREAK_screen;
4638 } // end: wins_colors
4642 * Manipulate flag(s) for all our windows. */
4643 static void wins_reflag (int what, int flg) {
4644 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
4651 case Flags_SET: // Ummmm, i can't find anybody
4652 SETw(w, flg); // who uses Flags_set ...
4657 default: // keep gcc happy
4660 /* a flag with special significance -- user wants to rebalance
4661 display so we gotta' off some stuff then force on two flags... */
4662 if (EQUWINS_xxx == flg)
4666 } while (w != Curwin);
4667 } // end: wins_reflag
4671 * Set up the raw/incomplete field group windows --
4672 * they'll be finished off after startup completes.
4673 * [ and very likely that will override most/all of our efforts ]
4674 * [ --- life-is-NOT-fair --- ] */
4675 static void wins_stage_1 (void) {
4679 for (i = 0; i < GROUPSMAX; i++) {
4683 w->captab[0] = Cap_norm;
4684 w->captab[1] = Cap_norm;
4685 w->captab[2] = w->cap_bold;
4686 w->captab[3] = w->capclr_sum;
4687 w->captab[4] = w->capclr_msg;
4688 w->captab[5] = w->capclr_pmt;
4689 w->captab[6] = w->capclr_hdr;
4690 w->captab[7] = w->capclr_rowhigh;
4691 w->captab[8] = w->capclr_rownorm;
4696 // fixup the circular chains...
4697 Winstk[GROUPSMAX - 1].next = &Winstk[0];
4698 Winstk[0].prev = &Winstk[GROUPSMAX - 1];
4701 for (i = 1; i < BOT_MSGSMAX; i++)
4702 Msg_tab[i].prev = &Msg_tab[i - 1];
4703 Msg_tab[0].prev = &Msg_tab[BOT_MSGSMAX -1];
4704 } // end: wins_stage_1
4708 * This guy just completes the field group windows after the
4709 * rcfiles have been read and command line arguments parsed.
4710 * And since he's the cabose of startup, he'll also tidy up
4711 * a few final things... */
4712 static void wins_stage_2 (void) {
4715 for (i = 0; i < GROUPSMAX; i++) {
4716 win_names(&Winstk[i], Winstk[i].rc.winname);
4718 Winstk[i].findstr = alloc_c(FNDBUFSIZ);
4719 Winstk[i].findlen = 0;
4720 if (Winstk[i].rc.combine_cpus >= Cpu_cnt)
4721 Winstk[i].rc.combine_cpus = 0;
4722 if (CHKw(&Winstk[i], (View_CPUSUM | View_CPUNOD)))
4723 Winstk[i].rc.double_up = 0;
4726 putp((Cursor_state = Cap_curs_hide));
4728 OFFw(Curwin, View_SCROLL);
4729 signal(SIGHUP, SIG_IGN); // allow running under nohup
4731 // fill in missing Fieldstab members and build each window's columnhdr
4734 // with preserved 'other filters' & command line 'user filters',
4735 // we must ensure that we always have a visible task on row one.
4738 // lastly, initialize a signal set used to throttle one troublesome signal
4739 sigemptyset(&Sigwinch_set);
4741 sigaddset(&Sigwinch_set, SIGWINCH);
4743 } // end: wins_stage_2
4747 * Determine if this task matches the 'u/U' selection
4748 * criteria for a given window */
4749 static inline int wins_usrselect (const WIN_t *q, int idx) {
4750 // a tailored 'results stack value' extractor macro
4751 #define rSv(E) PID_VAL(E, u_int, p)
4752 struct pids_stack *p = q->ppt[idx];
4754 switch (q->usrseltyp) {
4755 case 0: // uid selection inactive
4757 case 'U': // match any uid
4758 if (rSv(EU_URD) == (unsigned)q->usrseluid) return q->usrselflg;
4759 if (rSv(EU_USD) == (unsigned)q->usrseluid) return q->usrselflg;
4760 if (rSv(eu_ID_FUID) == (unsigned)q->usrseluid) return q->usrselflg;
4762 case 'u': // match effective uid
4763 if (rSv(EU_UED) == (unsigned)q->usrseluid) return q->usrselflg;
4765 default: // no match...
4768 return !q->usrselflg;
4770 } // end: wins_usrselect
4772 /*###### Forest View support ###########################################*/
4775 * We try keeping most existing code unaware of these activities |
4776 * ( plus, maintain alphabetical order within carefully chosen ) |
4777 * ( names beginning forest_a, forest_b, forest_c and forest_d ) |
4778 * ( with each name exactly 1 letter more than its predecessor ) | */
4779 static struct pids_stack **Seed_ppt; // temporary win ppt pointer |
4780 static struct pids_stack **Tree_ppt; // forest_begin resizes this |
4781 static int Tree_idx; // frame_make resets to zero |
4782 /* those next two support collapse/expand children. the Hide_pid |
4783 array holds parent pids whose children have been manipulated. |
4784 positive pid values represent parents with collapsed children |
4785 while a negative pid value means children have been expanded. |
4786 ( both of these are managed under the 'keys_task()' routine ) | */
4787 static int *Hide_pid; // collapsible process array |
4788 static int Hide_tot; // total used in above array |
4791 * This little recursive guy was the real forest view workhorse. |
4792 * He fills in the Tree_ppt array and also sets the child indent |
4793 * level which is stored in an 'extra' result struct as a u_int. | */
4794 static void forest_adds (const int self, int level) {
4795 // tailored 'results stack value' extractor macros
4796 #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X])
4797 // if xtra-procps-debug.h active, can't use PID_VAL with assignment
4798 #define rSv_Lvl Tree_ppt[Tree_idx]->head[eu_TREE_LVL].result.s_int
4801 if (Tree_idx < PIDSmaxt) { // immunize against insanity |
4802 if (level > 100) level = 101; // our arbitrary nests limit |
4803 Tree_ppt[Tree_idx] = Seed_ppt[self]; // add this as root or child |
4804 rSv_Lvl = level; // while recording its level |
4807 for (i = 0; i < PIDSmaxt; i++) {
4808 if (i == self) continue;
4810 for (i = self + 1; i < PIDSmaxt; i++) {
4812 if (rSv(EU_PID, self) == rSv(EU_TGD, i)
4813 || (rSv(EU_PID, self) == rSv(EU_PPD, i) && rSv(EU_PID, i) == rSv(EU_TGD, i)))
4814 forest_adds(i, level + 1); // got one child any others?
4819 } // end: forest_adds
4823 * This function is responsible for making that stacks ptr array |
4824 * a forest display in that designated window. After completion, |
4825 * he'll replace that original window ppt array with a specially |
4826 * ordered forest view version. He'll also mark hidden children! | */
4827 static void forest_begin (WIN_t *q) {
4831 Seed_ppt = q->ppt; // avoid passing pointers |
4832 if (!Tree_idx) { // do just once per frame |
4833 if (hwmsav < PIDSmaxt) { // grow, but never shrink |
4835 Tree_ppt = alloc_r(Tree_ppt, sizeof(void *) * hwmsav);
4838 #ifndef TREE_SCANALL
4839 if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt
4840 , PIDS_TICS_BEGAN, PIDS_SORT_ASCEND)))
4841 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
4843 for (i = 0; i < PIDSmaxt; i++) { // avoid hidepid distorts |
4844 if (!PID_VAL(eu_TREE_LVL, s_int, Seed_ppt[i])) // parents lvl 0 |
4845 forest_adds(i, 0); // add parents + children |
4848 /* we use up to three additional 'PIDS_extra' results in our stack |
4849 eu_TREE_HID (s_ch) : where 'x' == collapsed & 'z' == unseen |
4850 eu_TREE_LVL (s_int): where level number is stored (0 - 100) |
4851 eu_TREE_ADD (u_int): where a children's tics stored (maybe) | */
4852 for (i = 0; i < Hide_tot; i++) {
4854 // if have xtra-procps-debug.h, cannot use PID_VAL w/ assignment |
4855 #define rSv(E,T,X) Tree_ppt[X]->head[E].result.T
4856 #define rSv_Pid(X) rSv(EU_PID, s_int, X)
4857 #define rSv_Lvl(X) rSv(eu_TREE_LVL, s_int, X)
4858 #define rSv_Hid(X) rSv(eu_TREE_HID, s_ch, X)
4859 /* next 2 aren't needed if TREE_VCPUOFF but they cost us nothing |
4860 & the EU_CPU slot will now always be present (even if it's 0) | */
4861 #define rSv_Add(X) rSv(eu_TREE_ADD, u_int, X)
4862 #define rSv_Cpu(X) rSv(EU_CPU, u_int, X)
4864 if (Hide_pid[i] > 0) {
4865 for (j = 0; j < PIDSmaxt; j++) {
4866 if (rSv_Pid(j) == Hide_pid[i]) {
4869 int level = rSv_Lvl(parent);
4870 while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > level) {
4873 #ifndef TREE_VCPUOFF
4874 rSv_Add(parent) += rSv_Cpu(j);
4878 /* if any children found (& collapsed) mark the parent |
4879 ( when children aren't found don't negate the pid ) |
4880 ( to prevent future scans since who's to say such ) |
4881 ( tasks will not fork more children in the future ) | */
4882 if (children) rSv_Hid(parent) = 'x';
4883 // this will force a check of next Hide_pid[i], if any |
4887 // if a target task disappeared prevent any further scanning |
4888 if (j == PIDSmaxt) Hide_pid[i] = -Hide_pid[i];
4898 memcpy(Seed_ppt, Tree_ppt, sizeof(void *) * PIDSmaxt);
4899 } // end: forest_begin
4903 * When there's a 'focus_pid' established for a window, this guy |
4904 * determines that window's 'focus_beg' plus 'focus_end' values. |
4905 * But, if the pid can no longer be found, he'll turn off focus! | */
4906 static void forest_config (WIN_t *q) {
4907 // tailored 'results stack value' extractor macro
4908 #define rSv(x) PID_VAL(eu_TREE_LVL, s_int, q->ppt[(x)])
4911 for (i = 0; i < PIDSmaxt; i++) {
4912 if (q->focus_pid == PID_VAL(EU_PID, s_int, q->ppt[i])) {
4919 q->focus_pid = q->begtask = 0;
4922 q->focus_lvl = rSv(i);
4924 while (i+1 < PIDSmaxt && rSv(i+1) > level)
4926 q->focus_end = i + 1; // make 'focus_end' a proper fencpost
4927 // watch out for newly forked/cloned tasks 'above' us ...
4928 if (q->begtask < q->focus_beg) {
4929 q->begtask = q->focus_beg;
4933 // if some task 'above' us ended, try to maintain focus
4934 // ( but allow scrolling when there are many children )
4935 if (q->begtask > q->focus_beg
4936 && (SCREEN_ROWS > (q->focus_end - q->focus_beg))) {
4937 q->begtask = q->focus_beg;
4943 } // end: forest_config
4947 * This guy adds the artwork to either 'cmd' or 'cmdline' values |
4948 * if we are in forest view mode otherwise he just returns them. | */
4949 static inline const char *forest_display (const WIN_t *q, int idx) {
4950 // tailored 'results stack value' extractor macros
4951 #define rSv(E) PID_VAL(E, str, p)
4952 #define rSv_Lvl PID_VAL(eu_TREE_LVL, s_int, p)
4953 #define rSv_Hid PID_VAL(eu_TREE_HID, s_ch, p)
4954 #ifndef SCROLLVAR_NO
4955 static char buf[MAXBUFSIZ];
4957 static char buf[ROWMINSIZ];
4959 struct pids_stack *p = q->ppt[idx];
4960 const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD);
4961 int level = rSv_Lvl;
4965 if (idx >= q->focus_beg && idx < q->focus_end)
4966 level -= q->focus_lvl;
4969 if (!CHKw(q, Show_FOREST) || level == 0) return which;
4970 #ifndef TREE_VWINALL
4971 if (q == Curwin) // note: the following is NOT indented
4973 if (rSv_Hid == 'x') {
4975 snprintf(buf, sizeof(buf), "%*s%s", (4 * level), "`+ ", which);
4977 snprintf(buf, sizeof(buf), "+%*s%s", ((4 * level) - 1), "`- ", which);
4982 snprintf(buf, sizeof(buf), "%400s%s", " + ", which);
4985 #ifndef FOCUS_VIZOFF
4986 if (q->focus_pid) snprintf(buf, sizeof(buf), "|%*s%s", ((4 * level) - 1), "`- ", which);
4987 else snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
4989 snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which);
4995 } // end: forest_display
4997 /*###### Special Separate Bottom Window support ########################*/
5000 * This guy actually draws the parsed strings |
5001 * including adding a highlight if necessary. | */
5002 static void bot_do (const char *str, int focus) {
5003 char *cap = Cap_norm;
5005 while (*str == ' ') putchar(*(str++));
5010 cap = strchr(str, Bot_sep) ? Curwin->capclr_msg : Cap_reverse;
5012 putp(fmtmk("%s%s%s", cap, str, Cap_norm));
5017 * This guy draws that bottom window's header |
5018 * then parses/arranges to show the contents. |
5019 * ( returns relative # of elements printed ) | */
5020 static int bot_focus_str (const char *hdr, const char *str) {
5021 #define maxRSVD ( Screen_rows - 1 )
5023 char tmp[BIGBUFSIZ];
5027 // we're a little careless with overhead here (it's a one time cost)
5028 memset(Bot_buf, '\0', sizeof(Bot_buf));
5030 if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5031 if (!*str || !strcmp(str, "-")) strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5032 else memccpy(Bot_buf, str, '\0', n);
5033 Bot_rsvd = 1 + BOT_RSVD + ((strlen(Bot_buf) - utf8_delta(Bot_buf)) / Screen_cols);
5034 if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5035 // somewhere down call chain fmtmk() may be used, so we'll old school it
5036 snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5037 , tg2(0, SCREEN_ROWS)
5038 , Curwin->capclr_hdr
5039 , Screen_cols + utf8_delta(hdr)
5043 // now fmtmk is safe to use ...
5044 putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5050 if (!(end = strchr(beg, Bot_sep)))
5051 end = beg + strlen(beg);
5052 if ((n = end - beg) >= sizeof(tmp))
5053 n = sizeof(tmp) - 1;
5054 memccpy(tmp, beg, '\0', n);
5056 bot_do(tmp, (++x == Bot_indx));
5059 while (*beg == ' ') putchar(*(beg++));
5063 } // end: bot_focus_str
5067 * This guy draws that bottom window's header |
5068 * & parses/arranges to show vector contents. |
5069 * ( returns relative # of elements printed ) | */
5070 static int bot_focus_strv (const char *hdr, const char **strv) {
5071 #define maxRSVD ( Screen_rows - 1 )
5073 char tmp[SCREENMAX], *p;
5077 // we're a little careless with overhead here (it's a one time cost)
5078 memset(Bot_buf, '\0', sizeof(Bot_buf));
5079 n = (char *)&strv[0] - strv[0];
5080 if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1;
5081 memcpy(Bot_buf, strv[0], n);
5082 if ((!Bot_buf[0] || !strcmp(Bot_buf, "-")) && n <= sizeof(char *))
5083 strcpy(Bot_buf, N_txt(X_BOT_nodata_txt));
5084 for (nsav= 0, p = Bot_buf, x = 0; strv[nsav] != NULL; nsav++) {
5085 p += strlen(strv[nsav]) + 1;
5086 if ((p - Bot_buf) >= sizeof(Bot_buf))
5088 x += utf8_delta(strv[nsav]);
5090 n = (p - Bot_buf) - (x + 1);
5091 Bot_rsvd = 1 + BOT_RSVD + (n / Screen_cols);
5092 if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD;
5093 // somewhere down call chain fmtmk() may be used, so we'll old school it
5094 snprintf(tmp, sizeof(tmp), "%s%s%-*s"
5095 , tg2(0, SCREEN_ROWS)
5096 , Curwin->capclr_hdr
5097 , Screen_cols + utf8_delta(hdr)
5101 // now fmtmk is safe to use ...
5102 putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm));
5107 for (i = 0; i < nsav; i++) {
5108 bot_do(p, (++x == Bot_indx));
5114 } // end: bot_focus_strv
5119 enum namespace_type that;
5121 // careful, cgroup & time were late additions ...
5122 { EU_NS7, PROCPS_NS_CGROUP }, { EU_NS1, PROCPS_NS_IPC },
5123 { EU_NS2, PROCPS_NS_MNT }, { EU_NS3, PROCPS_NS_NET },
5124 { EU_NS4, PROCPS_NS_PID }, { EU_NS8, PROCPS_NS_TIME },
5125 { EU_NS5, PROCPS_NS_USER }, { EU_NS6, PROCPS_NS_UTS }
5130 * A helper function that will gather various |
5131 * stuff for dislay by the bot_item_show guy. | */
5132 static void *bot_item_hlp (struct pids_stack *p) {
5133 static char buf[BIGBUFSIZ];
5134 char tmp[SMLBUFSIZ], *b;
5140 *(b = &buf[0]) = '\0';
5144 b = scat(b, m->msg);
5145 if (m != Msg_this && m->prev->msg[0]) {
5146 // caller itself may have used fmtmk, so we'll old school it ...
5147 snprintf(tmp, sizeof(tmp), "%c ", BOT_SEP_SMI);
5152 } while (m != Msg_this->prev);
5155 *(b = &buf[0]) = '\0';
5156 for (i = 0; i < MAXTBL(ns_tab); i++) {
5157 // caller itself may have used fmtmk, so we'll old school it ...
5158 snprintf(tmp, sizeof(tmp), "%s: %-10lu"
5159 , procps_ns_get_name(ns_tab[i].that)
5160 , PID_VAL(ns_tab[i].this, ul_int, p));
5162 if (i < (MAXTBL(ns_tab) - 1)) b = scat(b, ", ");
5167 return p->head[Bot_item[0]].result.strv;
5169 return p->head[Bot_item[0]].result.str;
5171 } // end: bot_item_hlp
5175 * This guy manages that bottom margin window |
5176 * which shows various process related stuff. | */
5177 static void bot_item_show (void) {
5178 #define mkHDR fmtmk(Bot_head, Bot_task, PID_VAL(EU_CMD, str, p))
5179 struct pids_stack *p;
5182 for (i = 0; i < PIDSmaxt; i++) {
5184 if (Bot_task == PID_VAL(EU_PID, s_int, p))
5188 Bot_focus_func(mkHDR, bot_item_hlp(p));
5197 } // end: bot_item_show
5201 * This guy can toggle between displaying the |
5202 * bottom window or arranging to turn it off. | */
5203 static void bot_item_toggle (int what, const char *head, char sep) {
5206 // if already targeted, assume user wants to turn it off ...
5207 if (Bot_what == what) {
5212 for (i = 0; i < MAXTBL(ns_tab); i++)
5213 Bot_item[i] = ns_tab[i].this;
5214 Bot_item[i] = BOT_DELIMIT;
5215 Bot_focus_func = (BOT_f)bot_focus_str;
5220 Bot_item[1] = BOT_DELIMIT;
5221 Bot_focus_func = (BOT_f)bot_focus_strv;
5225 Bot_item[1] = BOT_DELIMIT;
5226 Bot_focus_func = (BOT_f)bot_focus_str;
5231 Bot_indx = BOT_UNFOCUS;
5232 Bot_head = (char *)head;
5233 Bot_show_func = bot_item_show;
5234 Bot_task = PID_VAL(EU_PID, s_int, Curwin->ppt[Curwin->begtask]);
5236 } // end: bot_item_toggle
5238 /*###### Interactive Input Tertiary support ############################*/
5241 * This section exists so as to offer some function naming freedom
5242 * while also maintaining the strict alphabetical order protocol
5243 * within each section. */
5246 * This guy is a *Helper* function serving the following two masters:
5247 * find_string() - find the next match in a given window
5248 * task_show() - highlight all matches currently in-view
5249 * If q->findstr is found in the designated buffer, he returns the
5250 * offset from the start of the buffer, otherwise he returns -1. */
5251 static inline int find_ofs (const WIN_t *q, const char *buf) {
5254 if (q->findstr[0] && (fnd = STRSTR(buf, q->findstr)))
5255 return (int)(fnd - buf);
5261 /* This is currently the only true prototype required by top.
5262 It is placed here, instead of top.h, to avoid one compiler
5263 warning when the top_nls.c source was compiled separately. */
5264 static const char *task_show (const WIN_t *q, int idx);
5266 static void find_string (int ch) {
5267 #define reDUX (found) ? N_txt(WORD_another_txt) : ""
5271 if ('&' == ch && !Curwin->findstr[0]) {
5272 show_msg(N_txt(FIND_no_next_txt));
5276 char *str = ioline(N_txt(GET_find_str_txt));
5277 if (*str == kbd_ESC) return;
5278 snprintf(Curwin->findstr, FNDBUFSIZ, "%s", str);
5279 Curwin->findlen = strlen(Curwin->findstr);
5282 if (Curwin->findstr[0]) {
5283 SETw(Curwin, NOPRINT_xxx);
5284 for (i = Curwin->begtask; i < PIDSmaxt; i++) {
5285 const char *row = task_show(Curwin, i);
5286 if (*row && -1 < find_ofs(Curwin, row)) {
5288 if (i == Curwin->begtask) continue;
5289 Curwin->begtask = i;
5293 show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Curwin->findstr));
5296 } // end: find_string
5299 static void help_view (void) {
5300 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5303 putp((Cursor_state = Cap_curs_huge));
5308 show_special(1, fmtmk(N_unq(KEYS_helpbas_fmt)
5311 , CHKw(w, Show_CTIMES) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5313 , Secure_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)
5314 , Secure_mode ? "" : N_unq(KEYS_helpext_fmt)));
5318 if (Frames_signal) goto signify_that;
5319 key = iokey(IOKEY_ONCE);
5320 if (key < 1) goto signify_that;
5323 case kbd_ESC: case 'q':
5325 case '?': case 'h': case 'H':
5328 show_special(1, fmtmk(N_unq(WINDOWS_help_fmt)
5330 , Winstk[0].rc.winname, Winstk[1].rc.winname
5331 , Winstk[2].rc.winname, Winstk[3].rc.winname));
5334 if (Frames_signal || (key = iokey(IOKEY_ONCE)) < 1) {
5337 } else w = win_select(key);
5338 } while (key != kbd_ENTER && key != kbd_ESC);
5343 // signal that we just corrupted entire screen
5344 Frames_signal = BREAK_screen;
5348 static void other_filters (int ch) {
5349 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5350 const char *txt, *p;
5356 if (ch == 'o') txt = N_txt(OSEL_casenot_txt);
5357 else txt = N_txt(OSEL_caseyes_txt);
5358 glob = ioline(fmtmk(N_fmt(OSEL_prompts_fmt), w->osel_tot + 1, txt));
5359 if (*glob == kbd_ESC || *glob == '\0')
5361 if ((p = osel_add(w, ch, glob, 1))) {
5368 char buf[SCREENMAX], **pp;
5369 struct osel_s *osel;
5374 pp = alloc_c((w->osel_tot + 1) * sizeof(char *));
5375 while (osel && i < w->osel_tot) {
5376 pp[i++] = osel->raw;
5381 strncat(buf, fmtmk("%s'%s'", " + " , pp[--i]), sizeof(buf) - (strlen(buf) + 1));
5382 if (buf[0]) p = buf + strspn(buf, " + ");
5383 else p = N_txt(WORD_noneone_txt);
5384 ioline(fmtmk(N_fmt(OSEL_statlin_fmt), p));
5388 default: // keep gcc happy
5391 } // end: other_filters
5394 static void write_rcfile (void) {
5399 show_pmt(N_txt(XTRA_warncfg_txt));
5400 if ('y' != tolower(iokey(IOKEY_ONCE)))
5404 if (Rc_compatibilty) {
5405 show_pmt(N_txt(XTRA_warnold_txt));
5406 if ('y' != tolower(iokey(IOKEY_ONCE)))
5408 Rc_compatibilty = 0;
5410 if (!(fp = fopen(Rc_name, "w"))) {
5411 show_msg(fmtmk(N_fmt(FAIL_rc_open_fmt), Rc_name, strerror(errno)));
5414 fprintf(fp, "%s's " RCF_EYECATCHER, Myname);
5415 fprintf(fp, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n"
5417 , Rc.mode_altscr, Rc.mode_irixps
5418 // this may be ugly, but it keeps us locale independent...
5419 , (int)Rc.delay_time, (int)((Rc.delay_time - (int)Rc.delay_time) * 1000)
5420 , (int)(Curwin - Winstk));
5422 for (i = 0 ; i < GROUPSMAX; i++) {
5423 n = mlen(Winstk[i].rc.fieldscur);
5424 fprintf(fp, "%s\tfieldscur=", Winstk[i].rc.winname);
5425 for (j = 0; j < n; j++) {
5426 if (j && !(j % FLD_ROWMAX) && j < n)
5427 fprintf(fp, "\n\t\t ");
5428 fprintf(fp, "%4d ", (int)Winstk[i].rc.fieldscur[j]);
5431 fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d"
5432 ", double_up=%d, combine_cpus=%d\n"
5433 , Winstk[i].rc.winflags, Winstk[i].rc.sortindx, Winstk[i].rc.maxtasks
5434 , Winstk[i].rc.graph_cpus, Winstk[i].rc.graph_mems
5435 , Winstk[i].rc.double_up, Winstk[i].rc.combine_cpus);
5436 fprintf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n"
5437 , Winstk[i].rc.summclr, Winstk[i].rc.msgsclr
5438 , Winstk[i].rc.headclr, Winstk[i].rc.taskclr);
5441 // any new addition(s) last, for older rcfiles compatibility...
5442 fprintf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n"
5443 , Rc.fixed_widest, Rc.summ_mscale, Rc.task_mscale, Rc.zero_suppress, Rc.tics_scaled);
5445 if (Winstk[0].osel_tot + Winstk[1].osel_tot
5446 + Winstk[2].osel_tot + Winstk[3].osel_tot) {
5448 fprintf(fp, Osel_delim_1_txt);
5449 for (i = 0 ; i < GROUPSMAX; i++) {
5450 struct osel_s *osel = Winstk[i].osel_1st;
5452 fprintf(fp, Osel_window_fmts, i, Winstk[i].osel_tot);
5454 fprintf(fp, Osel_filterO_fmt, osel->typ, osel->raw);
5459 fprintf(fp, Osel_delim_2_txt);
5462 if (Inspect.raw && strcmp(Inspect.raw, "\n"))
5463 fputs(Inspect.raw, fp);
5466 show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name));
5467 } // end: write_rcfile
5469 /*###### Interactive Input Secondary support (do_key helpers) ##########*/
5472 * These routines exist just to keep the do_key() function
5473 * a reasonably modest size. */
5475 static void keys_global (int ch) {
5476 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5477 int i, num, def, pid;
5485 TOGw(w, View_NOBOLD);
5491 show_msg(N_txt(NOT_onsecure_txt));
5494 get_float(fmtmk(N_fmt(DELAY_change_fmt), Rc.delay_time));
5495 if (tmp > -1) Rc.delay_time = tmp;
5499 if (++Rc.summ_mscale > SK_Eb) Rc.summ_mscale = SK_Kb;
5502 if (++Rc.task_mscale > SK_Pb) Rc.task_mscale = SK_Kb;
5511 Thread_mode = !Thread_mode;
5512 if (!CHKw(w, View_STATES))
5513 show_msg(fmtmk(N_fmt(THREADS_show_fmt)
5514 , Thread_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5515 for (i = 0 ; i < GROUPSMAX; i++)
5516 Winstk[i].begtask = Winstk[i].focus_pid = 0;
5517 // force an extra procs refresh to avoid %cpu distortions...
5518 Pseudo_row = PROC_XTRA;
5519 // signal that we just corrupted entire screen
5520 Frames_signal = BREAK_screen;
5524 Rc.mode_irixps = !Rc.mode_irixps;
5525 show_msg(fmtmk(N_fmt(IRIX_curmode_fmt)
5526 , Rc.mode_irixps ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5528 show_msg(N_txt(NOT_smp_cpus_txt));
5532 show_msg(N_txt(NOT_onsecure_txt));
5535 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5536 pid = get_int(fmtmk(N_txt(GET_pid2kill_fmt), def));
5537 if (pid > GET_NUM_ESC) {
5539 if (pid == GET_NUM_NOT) pid = def;
5540 str = ioline(fmtmk(N_fmt(GET_sigs_num_fmt), pid, SIGTERM));
5541 if (*str != kbd_ESC) {
5542 if (*str) num = signal_name_to_number(str);
5543 if (Frames_signal) break;
5544 if (0 < num && kill(pid, num))
5545 show_msg(fmtmk(N_fmt(FAIL_signals_fmt)
5546 , pid, num, strerror(errno)));
5547 else if (0 > num) show_msg(N_txt(BAD_signalid_txt));
5554 show_msg(N_txt(NOT_onsecure_txt));
5556 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5557 pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5558 if (pid > GET_NUM_ESC) {
5559 if (pid == GET_NUM_NOT) pid = def;
5560 num = get_int(fmtmk(N_fmt(GET_nice_num_fmt), pid));
5561 if (num > GET_NUM_NOT
5562 && setpriority(PRIO_PROCESS, (unsigned)pid, num))
5563 show_msg(fmtmk(N_fmt(FAIL_re_nice_fmt)
5564 , pid, num, strerror(errno)));
5569 num = get_int(fmtmk(N_fmt(XTRA_fixwide_fmt), Rc.fixed_widest));
5570 if (num > GET_NUM_NOT) {
5571 if (num >= 0 && num <= SCREENMAX) Rc.fixed_widest = num;
5572 else Rc.fixed_widest = -1;
5577 #ifndef INSP_OFFDEMO
5578 ioline(N_txt(YINSP_noent1_txt));
5580 ioline(N_txt(YINSP_noent2_txt));
5583 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5584 pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def));
5585 if (pid > GET_NUM_ESC) {
5586 if (pid == GET_NUM_NOT) pid = def;
5587 if (pid) inspection_utility(pid);
5595 Rc.zero_suppress = !Rc.zero_suppress;
5598 #ifndef SCALE_FORMER
5600 if (Rc.tics_scaled > TICS_AS_LAST)
5605 bot_item_toggle(EU_CGR, N_fmt(X_BOT_ctlgrp_fmt), BOT_SEP_SLS);
5610 if (Bot_indx > Bot_focus_func(NULL, NULL))
5611 Bot_indx = BOT_UNFOCUS;
5615 // with string vectors, the 'separator' may serve a different purpose
5616 bot_item_toggle(eu_CMDLINE_V, N_fmt(X_BOT_cmdlin_fmt), BOT_SEP_SPC);
5619 bot_item_toggle(BOT_MSG_LOG, N_txt(X_BOT_msglog_txt), BOT_SEP_SMI);
5622 // with string vectors, the 'separator' may serve a different purpose
5623 bot_item_toggle(eu_ENVIRON_V, N_fmt(X_BOT_envirn_fmt), BOT_SEP_SPC);
5626 bot_item_toggle(BOT_ITEM_NS, N_fmt(X_BOT_namesp_fmt), BOT_SEP_CMA);
5630 show_msg(N_txt(NOT_onsecure_txt));
5632 def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5633 pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def));
5634 if (pid > GET_NUM_ESC) {
5636 if (pid == GET_NUM_NOT) pid = def;
5637 num = get_int(fmtmk(N_fmt(AGNI_valueof_fmt), pid));
5638 if (num > GET_NUM_NOT) {
5639 if (num < -20 || num > +19)
5640 show_msg(N_txt(AGNI_invalid_txt));
5641 else if (0 > (fd = open(fmtmk("/proc/%d/autogroup", pid), O_WRONLY)))
5642 show_msg(fmtmk(N_fmt(AGNI_notopen_fmt), strerror(errno)));
5644 char buf[TNYBUFSIZ];
5645 snprintf(buf, sizeof(buf), "%d", num);
5646 if (0 >= write(fd, buf, strlen(buf)))
5647 show_msg(fmtmk(N_fmt(AGNI_nowrite_fmt), strerror(errno)));
5655 bot_item_toggle(EU_SGN, N_fmt(X_BOT_supgrp_fmt), BOT_SEP_CMA);
5660 num = Bot_focus_func(NULL, NULL);
5661 if (Bot_indx <= BOT_UNFOCUS)
5665 case kbd_ENTER: // these two have the effect of waking us
5666 case kbd_SPACE: // from 'pselect', refreshing the display
5667 break; // and updating any hot-plugged resources
5668 default: // keep gcc happy
5671 } // end: keys_global
5674 static void keys_summary (int ch) {
5675 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5677 if (Restrict_some && ch != 'C') {
5678 show_msg(N_txt(X_RESTRICTED_txt));
5683 if (CHKw(w, View_CPUSUM) || CHKw(w, View_CPUNOD))
5684 show_msg(N_txt(XTRA_modebad_txt));
5686 if (!w->rc.combine_cpus) w->rc.combine_cpus = 1;
5687 else w->rc.combine_cpus *= 2;
5688 if (w->rc.combine_cpus >= Cpu_cnt) w->rc.combine_cpus = 0;
5692 if (CHKw(w, View_CPUNOD)) OFFw(w, View_CPUSUM);
5693 else TOGw(w, View_CPUSUM);
5694 OFFw(w, View_CPUNOD);
5695 SETw(w, View_STATES);
5696 w->rc.double_up = 0;
5700 show_msg(N_txt(NUMA_nodenot_txt));
5702 if (Numa_node_sel < 0) TOGw(w, View_CPUNOD);
5703 if (!CHKw(w, View_CPUNOD)) SETw(w, View_CPUSUM);
5704 SETw(w, View_STATES);
5706 w->rc.double_up = 0;
5711 show_msg(N_txt(NUMA_nodenot_txt));
5713 int num = get_int(fmtmk(N_fmt(NUMA_nodeget_fmt), Numa_node_tot -1));
5714 if (num > GET_NUM_NOT) {
5715 if (num >= 0 && num < Numa_node_tot) {
5716 Numa_node_sel = num;
5717 SETw(w, View_CPUNOD | View_STATES);
5718 OFFw(w, View_CPUSUM);
5719 w->rc.double_up = 0;
5721 show_msg(N_txt(NUMA_nodebad_txt));
5726 w->rc.double_up += 1;
5727 if ((w->rc.double_up >= ADJOIN_limit)
5728 || ((w->rc.double_up >= Cpu_cnt)))
5729 w->rc.double_up = 0;
5730 if ((w->rc.double_up > 1)
5731 && (!w->rc.graph_cpus))
5732 w->rc.double_up = 0;
5733 OFFw(w, (View_CPUSUM | View_CPUNOD));
5736 VIZTOGw(w, View_SCROLL);
5739 TOGw(w, View_LOADAV);
5742 if (!CHKw(w, View_MEMORY))
5743 SETw(w, View_MEMORY);
5744 else if (++w->rc.graph_mems > 2) {
5745 w->rc.graph_mems = 0;
5746 OFFw(w, View_MEMORY);
5750 if (!CHKw(w, View_STATES))
5751 SETw(w, View_STATES);
5752 else if (++w->rc.graph_cpus > 2) {
5753 w->rc.graph_cpus = 0;
5754 OFFw(w, View_STATES);
5756 if ((w->rc.double_up > 1)
5757 && (!w->rc.graph_cpus))
5758 w->rc.double_up = 0;
5760 default: // keep gcc happy
5763 } // end: keys_summary
5766 static void keys_task (int ch) {
5767 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5773 int num = get_int(fmtmk(N_fmt(GET_max_task_fmt), w->rc.maxtasks));
5774 if (num > GET_NUM_NOT) {
5775 if (-1 < num ) w->rc.maxtasks = num;
5776 else show_msg(N_txt(BAD_max_task_txt));
5782 if (CHKw(w, Show_FOREST)) break;
5785 FLG_t *p = w->procflgs + w->maxpflgs - 1;
5786 while (p > w->procflgs && *p != w->rc.sortindx) --p;
5787 if (*p == w->rc.sortindx) {
5789 #ifndef USE_X_COLHDR
5790 if (EU_MAXPFLGS < *p) --p;
5792 if (p >= w->procflgs) {
5793 w->rc.sortindx = *p;
5794 #ifndef TREE_NORESET
5795 OFFw(w, Show_FOREST);
5803 if (CHKw(w, Show_FOREST)) break;
5806 FLG_t *p = w->procflgs + w->maxpflgs - 1;
5807 while (p > w->procflgs && *p != w->rc.sortindx) --p;
5808 if (*p == w->rc.sortindx) {
5810 #ifndef USE_X_COLHDR
5811 if (EU_MAXPFLGS < *p) ++p;
5813 if (p < w->procflgs + w->maxpflgs) {
5814 w->rc.sortindx = *p;
5815 #ifndef TREE_NORESET
5816 OFFw(w, Show_FOREST);
5823 TOGw(w, Show_HIBOLD);
5827 VIZTOGw(w, Show_CMDLIN);
5831 if (CHKw(w, Show_FOREST)) {
5832 int n = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5833 if (w->focus_pid == n) w->focus_pid = 0;
5834 else w->focus_pid = n;
5839 { static WIN_t *w_sav;
5841 if (w_sav != w) { beg_sav = 0; w_sav = w; }
5842 if (CHKw(w, Show_IDLEPS)) { beg_sav = w->begtask; w->begtask = 0; }
5843 else { w->begtask = beg_sav; beg_sav = 0; }
5845 VIZTOGw(w, Show_IDLEPS);
5848 VIZTOGw(w, Show_JRNUMS);
5851 VIZTOGw(w, Show_JRSTRS);
5855 if (!CHKw(w, Show_FOREST)) VIZTOGw(w, Qsrt_NORMAL);
5858 TOGw(w, Qsrt_NORMAL);
5859 OFFw(w, Show_FOREST);
5865 TOGw(w, Show_CTIMES);
5866 show_msg(fmtmk(N_fmt(TIME_accumed_fmt) , CHKw(w, Show_CTIMES)
5867 ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5873 if (VIZCHKw(w)) other_filters(ch);
5878 const char *errmsg, *str = ioline(N_txt(GET_user_ids_txt));
5880 && (errmsg = user_certify(w, str, ch)))
5886 TOGw(w, Show_FOREST);
5887 if (!ENUviz(w, EU_CMD))
5888 show_msg(fmtmk(N_fmt(FOREST_modes_fmt) , CHKw(w, Show_FOREST)
5889 ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt)));
5890 if (!CHKw(w, Show_FOREST)) w->focus_pid = 0;
5895 if (CHKw(w, Show_FOREST)) {
5896 int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]);
5898 int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid));
5899 if (got < GET_NUM_NOT) break;
5900 if (got > GET_NUM_NOT) pid = got;
5902 for (i = 0; i < Hide_tot; i++) {
5903 if (Hide_pid[i] == pid || Hide_pid[i] == -pid) {
5904 Hide_pid[i] = -Hide_pid[i];
5908 if (i == Hide_tot) {
5910 if (Hide_tot >= totsav) {
5912 Hide_pid = alloc_r(Hide_pid, sizeof(int) * totsav);
5914 Hide_pid[Hide_tot++] = pid;
5916 // if everything's expanded, let's empty the array ...
5917 for (i = 0; i < Hide_tot; i++)
5918 if (Hide_pid[i] > 0) break;
5919 if (i == Hide_tot) Hide_tot = 0;
5927 TOGw(w, Show_HICOLS);
5930 if (ENUviz(w, w->rc.sortindx)) {
5931 TOGw(w, Show_HICOLS);
5932 if (ENUpos(w, w->rc.sortindx) < w->begpflg) {
5933 if (CHKw(w, Show_HICOLS)) w->begpflg += 2;
5934 else w->begpflg -= 2;
5935 if (0 > w->begpflg) w->begpflg = 0;
5944 TOGw(w, Show_HIROWS);
5950 TOGw(w, Show_COLORS);
5954 default: // keep gcc happy
5960 static void keys_window (int ch) {
5961 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
5965 if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx);
5969 if (ALTCHKw) TOGw(w, Show_TASKON);
5976 if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON);
5980 if (VIZCHKw(w)) find_string(ch);
5983 Rc.mode_altscr = !Rc.mode_altscr;
5987 if (ALTCHKw) win_select(ch);
5991 char tmp[SMLBUFSIZ];
5992 STRLCPY(tmp, ioline(fmtmk(N_fmt(NAME_windows_fmt), w->rc.winname)));
5993 if (tmp[0] && tmp[0] != kbd_ESC) win_names(w, tmp);
5997 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(-1)
6000 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(+1)
6002 #ifdef USE_X_COLHDR // ------------------------------------
6004 #ifndef SCROLLVAR_NO
6007 w->varcolbeg -= SCROLLAMT;
6008 else if (0 < w->begpflg)
6012 if (VIZCHKw(w)) if (0 < w->begpflg) w->begpflg -= 1;
6016 #ifndef SCROLLVAR_NO
6019 w->varcolbeg += SCROLLAMT;
6020 if (0 > w->varcolbeg) w->varcolbeg = 0;
6021 } else if (w->begpflg + 1 < w->totpflgs)
6025 if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) w->begpflg += 1;
6028 #else // USE_X_COLHDR ------------------------------------
6030 #ifndef SCROLLVAR_NO
6033 w->varcolbeg -= SCROLLAMT;
6034 else if (0 < w->begpflg) {
6036 if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6040 if (VIZCHKw(w)) if (0 < w->begpflg) {
6042 if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2;
6047 #ifndef SCROLLVAR_NO
6050 w->varcolbeg += SCROLLAMT;
6051 if (0 > w->varcolbeg) w->varcolbeg = 0;
6052 } else if (w->begpflg + 1 < w->totpflgs) {
6053 if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6054 w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6055 else w->begpflg += 1;
6059 if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) {
6060 if (EU_MAXPFLGS < w->pflgsall[w->begpflg])
6061 w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0;
6062 else w->begpflg += 1;
6066 #endif // USE_X_COLHDR ------------------------------------
6069 if (CHKw(w, Show_IDLEPS) && 0 < w->begtask) {
6070 mkVIZrowX(-(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6076 if (CHKw(w, Show_IDLEPS) && w->begtask < PIDSmaxt - 1) {
6077 mkVIZrowX(+(w->winlines - (Rc.mode_altscr ? 1 : 2)))
6082 #ifndef SCROLLVAR_NO
6083 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = w->varcolbeg = 0;
6085 if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = 0;
6090 if (CHKw(w, Show_IDLEPS)) {
6091 mkVIZrowX((PIDSmaxt - w->winlines) + 1)
6092 w->begpflg = w->endpflg;
6093 #ifndef SCROLLVAR_NO
6099 default: // keep gcc happy
6102 } // end: keys_window
6105 static void keys_xtra (int ch) {
6106 // const char *xmsg;
6107 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
6110 if (CHKw(w, Show_FOREST)) return;
6112 OFFw(w, Show_FOREST);
6114 /* these keys represent old-top compatibility --
6115 they're grouped here so that if users could ever be weaned,
6116 we would just whack do_key's key_tab entry and this function... */
6119 w->rc.sortindx = EU_MEM;
6123 w->rc.sortindx = EU_PID;
6124 // xmsg = "Numerical";
6127 w->rc.sortindx = EU_CPU;
6131 w->rc.sortindx = EU_TM2;
6134 default: // keep gcc happy
6137 // some have objected to this message, so we'll just keep silent...
6138 // show_msg(fmtmk("%s sort compatibility key honored", xmsg));
6141 /*###### Tertiary summary display support (summary_show helpers) #######*/
6144 * note how alphabetical order is maintained within carefully chosen |
6145 * function names such as: (s)sum_see, (t)sum_tics, and (u)sum_unify |
6146 * with every name exactly 1 letter more than the preceding function |
6147 * ( surely, this must make us run much more efficiently. amirite? ) | */
6150 float pcnt_one, pcnt_two, pcnt_tot;
6151 char graph[MEDBUFSIZ];
6155 * A *Helper* function to produce the actual cpu & memory graphs for |
6156 * these functions -- sum_tics (tertiary) and do_memory (secondary). |
6157 * (sorry about the name, but it keeps the above comment commitment) | */
6158 static struct rx_st *sum_rx (struct graph_parms *these) {
6160 const char *part1, *part2, *style;
6162 { "%-.*s~7", "%-.*s~8", Graph_bars },
6163 { "%-.*s~4", "%-.*s~6", Graph_blks }
6165 static __thread struct rx_st rx;
6166 char buf1[SMLBUFSIZ], buf2[SMLBUFSIZ], buf3[MEDBUFSIZ];
6167 int ix, num1, num2, width;
6170 scale = 100.0 / these->total;
6171 rx.pcnt_one = scale * these->part1;
6172 rx.pcnt_two = scale * these->part2;
6173 if (rx.pcnt_one + rx.pcnt_two > 100.0 || rx.pcnt_two < 0)
6175 rx.pcnt_tot = rx.pcnt_one + rx.pcnt_two;
6177 num1 = (int)((rx.pcnt_one * these->adjust) + .5),
6178 num2 = (int)((rx.pcnt_two * these->adjust) + .5);
6179 if (num1 + num2 > these->length)
6180 num2 = these->length - num1;
6182 width = these->length;
6183 buf1[0] = buf2[0] = buf3[0] = '\0';
6184 ix = these->style - 1; // now relative to zero
6186 snprintf(buf1, sizeof(buf1), gtab[ix].part1, num1, gtab[ix].style);
6190 snprintf(buf2, sizeof(buf2), gtab[ix].part2, num2, gtab[ix].style);
6193 snprintf(buf3, sizeof(buf3), "%s%s", buf1, buf2);
6194 // 'width' has accounted for any show_special directives embedded above
6195 snprintf(rx.graph, sizeof(rx.graph), "[~1%-*.*s] ~1", width, width, buf3);
6202 * A *Helper* function to show multiple lines of summary information |
6203 * as a single line. We return the number of lines actually printed. | */
6204 static inline int sum_see (const char *str, int nobuf) {
6205 static char row[ROWMAXSIZ];
6210 if (Curwin->rc.double_up
6212 if (++tog <= Curwin->rc.double_up) {
6218 show_special(0, row);
6226 * State display *Helper* function to calculate plus display (maybe) |
6227 * the percentages for a single cpu. In this way, we'll support the |
6228 * following environments without (hopefully) that usual code bloat: |
6229 * 1) single cpu platforms (no matter the paucity of these types) |
6230 * 2) modest smp boxes with ample room for each cpu's percentages |
6231 * 3) massive smp guys leaving little or no room for that process |
6232 * display and thus requiring the '1', '4', or '!' cpu toggles |
6233 * ( we return the number of lines printed, as reported by sum_see ) | */
6234 static int sum_tics (struct stat_stack *this, const char *pfx, int nobuf) {
6235 // a tailored 'results stack value' extractor macro
6236 #define rSv(E) TIC_VAL(E, this)
6237 SIC_t idl_frme, tot_frme;
6241 idl_frme = rSv(stat_IL);
6242 tot_frme = rSv(stat_SUM_TOT);
6243 if (1 > tot_frme) idl_frme = tot_frme = 1;
6244 scale = 100.0 / (float)tot_frme;
6246 /* display some kinda' cpu state percentages
6247 (who or what is explained by the passed prefix) */
6248 if (Curwin->rc.graph_cpus) {
6249 Graph_cpus->total = tot_frme;
6250 Graph_cpus->part1 = rSv(stat_SUM_USR);
6251 Graph_cpus->part2 = rSv(stat_SUM_SYS);
6252 rx = sum_rx(Graph_cpus);
6253 if (Curwin->rc.double_up > 1)
6254 return sum_see(fmtmk("%s~3%3.0f%s", pfx, rx->pcnt_tot, rx->graph), nobuf);
6256 return sum_see(fmtmk("%s ~3%#5.1f~2/%-#5.1f~3 %3.0f%s"
6257 , pfx, rx->pcnt_one, rx->pcnt_two, rx->pcnt_tot
6262 return sum_see(fmtmk(Cpu_States_fmts, pfx
6263 , (float)rSv(stat_US) * scale, (float)rSv(stat_SY) * scale
6264 , (float)rSv(stat_NI) * scale, (float)idl_frme * scale
6265 , (float)rSv(stat_IO) * scale, (float)rSv(stat_IR) * scale
6266 , (float)rSv(stat_SI) * scale, (float)rSv(stat_ST) * scale), nobuf);
6273 * Cpu *Helper* function to combine additional cpu statistics in our |
6274 * efforts to reduce the total number of processors that'll be shown |
6275 * ( we return the number of lines printed, as reported by sum_see ) | */
6276 static int sum_unify (struct stat_stack *this, int nobuf) {
6277 // a tailored 'results stack value' extractor macro
6278 #define rSv(E,T) STAT_VAL(E, T, this, Stat_ctx)
6279 static struct stat_result stack[MAXTBL(Stat_items)];
6280 static struct stat_stack accum = { &stack[0] };
6285 // entries for stat_ID & stat_NU are unused
6286 stack[stat_US].result.sl_int += rSv(stat_US, sl_int);
6287 stack[stat_SY].result.sl_int += rSv(stat_SY, sl_int);
6288 stack[stat_NI].result.sl_int += rSv(stat_NI, sl_int);
6289 stack[stat_IL].result.sl_int += rSv(stat_IL, sl_int);
6290 stack[stat_IO].result.sl_int += rSv(stat_IO, sl_int);
6291 stack[stat_IR].result.sl_int += rSv(stat_IR, sl_int);
6292 stack[stat_SI].result.sl_int += rSv(stat_SI, sl_int);
6293 stack[stat_ST].result.sl_int += rSv(stat_ST, sl_int);
6294 stack[stat_SUM_USR].result.sl_int += rSv(stat_SUM_USR, sl_int);
6295 stack[stat_SUM_SYS].result.sl_int += rSv(stat_SUM_SYS, sl_int);
6296 stack[stat_SUM_TOT].result.sl_int += rSv(stat_SUM_TOT, sl_int);
6298 if (!ix) beg = rSv(stat_ID, s_int);
6299 if (nobuf || ix >= Curwin->rc.combine_cpus) {
6300 snprintf(pfx, sizeof(pfx), "%-7.7s:", fmtmk("%d-%d", beg, rSv(stat_ID, s_int)));
6301 n = sum_tics(&accum, pfx, nobuf);
6302 memset(&stack, 0, sizeof(stack));
6311 /*###### Secondary summary display support (summary_show helpers) ######*/
6314 * A helper function that displays cpu and/or numa node stuff |
6315 * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6316 static void do_cpus (void) {
6317 #define noMAS (Msg_row + 1 >= SCREEN_ROWS - 1)
6318 char tmp[MEDBUFSIZ];
6321 if (CHKw(Curwin, View_CPUNOD)) {
6322 if (Numa_node_sel < 0) {
6325 * display the 1st /proc/stat line, then the nodes (if room) ... */
6326 Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6327 // display each cpu node's states
6328 for (i = 0; i < Numa_node_tot; i++) {
6329 struct stat_stack *nod_ptr = Stat_reap->numa->stacks[i];
6330 if (NOD_VAL(stat_NU, i) == STAT_NODE_INVALID) continue;
6332 snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), NOD_VAL(stat_ID, i));
6333 Msg_row += sum_tics(nod_ptr, tmp, 1);
6337 * display the node summary, then the associated cpus (if room) ... */
6338 for (i = 0; i < Numa_node_tot; i++) {
6339 if (Numa_node_sel == NOD_VAL(stat_ID, i)
6340 && (NOD_VAL(stat_NU, i) != STAT_NODE_INVALID)) break;
6342 if (i == Numa_node_tot) {
6346 snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), Numa_node_sel);
6347 Msg_row += sum_tics(Stat_reap->numa->stacks[Numa_node_sel], tmp, 1);
6349 #define deLIMIT Stat_reap->cpus->total
6351 #define deLIMIT Cpu_cnt
6353 for (i = 0; i < deLIMIT; i++) {
6354 if (Numa_node_sel == CPU_VAL(stat_NU, i)) {
6356 snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), CPU_VAL(stat_ID, i));
6357 Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, 1);
6363 } else if (!CHKw(Curwin, View_CPUSUM)) {
6365 * display each cpu's states separately, screen height permitting ... */
6368 if (Curwin->rc.combine_cpus) {
6369 for (i = 0, j = 0; i < Cpu_cnt; i++) {
6370 Stat_reap->cpus->stacks[j]->head[stat_ID].result.s_int = i;
6371 Msg_row += sum_unify(Stat_reap->cpus->stacks[j], (i+1 >= Cpu_cnt));
6372 if (++j >= Stat_reap->cpus->total) j = 0;
6376 for (i = 0, j = 0; i < Cpu_cnt; i++) {
6377 snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), i);
6378 Msg_row += sum_tics(Stat_reap->cpus->stacks[j], tmp, (i+1 >= Cpu_cnt));
6379 if (++j >= Stat_reap->cpus->total) j = 0;
6384 if (Curwin->rc.combine_cpus) {
6385 for (i = 0; i < Cpu_cnt; i++) {
6386 Msg_row += sum_unify(Stat_reap->cpus->stacks[i], (i+1 >= Cpu_cnt));
6390 for (i = 0; i < Cpu_cnt; i++) {
6391 snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), CPU_VAL(stat_ID, i));
6392 Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, (i+1 >= Cpu_cnt));
6400 * display just the 1st /proc/stat line ... */
6401 Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1);
6408 * A helper function which will display the memory/swap stuff |
6409 * ( so as to keep the 'summary_show' guy a reasonable size ) | */
6410 static void do_memory (void) {
6411 #define bfT(n) buftab[n].buf
6412 #define scT(e) scaletab[Rc.summ_mscale]. e
6413 #define mkM(x) (float) x / scT(div)
6414 #define prT(b,z) { if (9 < snprintf(b, 10, scT(fmts), z)) b[8] = '+'; }
6425 { 1, "%.0f ", NULL }, // kibibytes
6427 { 1024.0, "%#.3f ", NULL }, // mebibytes
6428 { 1024.0*1024, "%#.3f ", NULL }, // gibibytes
6429 { 1024.0*1024*1024, "%#.3f ", NULL }, // tebibytes
6430 { 1024.0*1024*1024*1024, "%#.3f ", NULL }, // pebibytes
6431 { 1024.0*1024*1024*1024*1024, "%#.3f ", NULL } // exbibytes
6433 { 1024.0, "%#.1f ", NULL }, // mebibytes
6434 { 1024.0*1024, "%#.1f ", NULL }, // gibibytes
6435 { 1024.0*1024*1024, "%#.1f ", NULL }, // tebibytes
6436 { 1024.0*1024*1024*1024, "%#.1f ", NULL }, // pebibytes
6437 { 1024.0*1024*1024*1024*1024, "%#.1f ", NULL } // exbibytes
6440 struct { // 0123456789
6441 // snprintf contents of each buf (after SK_Kb): 'nnnn.nnn 0'
6442 // & prT macro might replace space at buf[8] with: -------> +
6443 char buf[10]; // MEMORY_lines_fmt provides for 8+1 bytes
6445 char row[ROWMINSIZ];
6446 long my_qued, my_misc, my_used;
6449 if (!scaletab[0].label) {
6450 scaletab[0].label = N_txt(AMT_kilobyte_txt);
6451 scaletab[1].label = N_txt(AMT_megabyte_txt);
6452 scaletab[2].label = N_txt(AMT_gigabyte_txt);
6453 scaletab[3].label = N_txt(AMT_terabyte_txt);
6454 scaletab[4].label = N_txt(AMT_petabyte_txt);
6455 scaletab[5].label = N_txt(AMT_exxabyte_txt);
6457 my_qued = MEM_VAL(mem_BUF) + MEM_VAL(mem_QUE);
6459 if (Curwin->rc.graph_mems) {
6460 my_misc = MEM_VAL(mem_TOT) - MEM_VAL(mem_FRE) - my_qued;
6461 my_used = MEM_VAL(mem_TOT) - MEM_VAL(mem_AVL) - my_misc;
6463 Graph_mems->total = MEM_VAL(mem_TOT);
6464 Graph_mems->part1 = my_misc;
6465 Graph_mems->part2 = my_used;
6466 rx = sum_rx(Graph_mems);
6467 prT(bfT(0), mkM(MEM_VAL(mem_TOT)));
6468 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6469 , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0)
6471 Msg_row += sum_see(row, mem2UP);
6473 Graph_mems->total = MEM_VAL(swp_TOT);
6474 Graph_mems->part1 = 0;
6475 Graph_mems->part2 = MEM_VAL(swp_USE);
6476 rx = sum_rx(Graph_mems);
6477 prT(bfT(1), mkM(MEM_VAL(swp_TOT)));
6478 snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s"
6479 , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph);
6480 Msg_row += sum_see(row, 1);
6483 prT(bfT(0), mkM(MEM_VAL(mem_TOT))); prT(bfT(1), mkM(MEM_VAL(mem_FRE)));
6484 prT(bfT(2), mkM(MEM_VAL(mem_USE))); prT(bfT(3), mkM(my_qued));
6485 prT(bfT(4), mkM(MEM_VAL(swp_TOT))); prT(bfT(5), mkM(MEM_VAL(swp_FRE)));
6486 prT(bfT(6), mkM(MEM_VAL(swp_USE))); prT(bfT(7), mkM(MEM_VAL(mem_AVL)));
6488 snprintf(row, sizeof(row), N_unq(MEMORY_line1_fmt)
6489 , scT(label), N_txt(WORD_abv_mem_txt), bfT(0), bfT(1), bfT(2), bfT(3));
6490 Msg_row += sum_see(row, mem2UP);
6492 snprintf(row, sizeof(row), N_unq(MEMORY_line2_fmt)
6493 , scT(label), N_txt(WORD_abv_swp_txt), bfT(4), bfT(5), bfT(6), bfT(7)
6494 , N_txt(WORD_abv_mem_txt));
6495 Msg_row += sum_see(row, 1);
6504 /*###### Main Screen routines ##########################################*/
6507 * Process keyboard input during the main loop */
6508 static void do_key (int ch) {
6510 void (*func)(int ch);
6511 char keys[SMLBUFSIZ];
6514 { '?', 'B', 'd', 'E', 'e', 'f', 'g', 'H', 'h'
6515 , 'I', 'k', 'r', 's', 'X', 'Y', 'Z', '0'
6516 , kbd_CtrlE, kbd_CtrlG, kbd_CtrlI, kbd_CtrlK, kbd_CtrlL
6517 , kbd_CtrlN, kbd_CtrlP, kbd_CtrlR, kbd_CtrlU
6518 , kbd_ENTER, kbd_SPACE, kbd_BTAB, '\0' } },
6520 { '!', '1', '2', '3', '4', 'C', 'l', 'm', 't', '\0' } },
6522 { '#', '<', '>', 'b', 'c', 'F', 'i', 'J', 'j', 'n', 'O', 'o'
6523 , 'R', 'S', 'U', 'u', 'V', 'v', 'x', 'y', 'z'
6524 , kbd_CtrlO, '\0' } },
6526 { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w'
6527 , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN
6528 , kbd_HOME, kbd_END, '\0' } },
6530 { 'M', 'N', 'P', 'T', '\0'} }
6534 Frames_signal = BREAK_off;
6536 case 0: // ignored (always)
6537 case kbd_ESC: // ignored (sometimes)
6539 case 'q': // no return from this guy
6541 case 'W': // no need for rebuilds
6544 default: // and now, the real work...
6545 // and just in case 'Monpids' is active but matched no processes ...
6546 if (!PIDSmaxt && ch != '=') goto all_done;
6547 for (i = 0; i < MAXTBL(key_tab); ++i)
6548 if (strchr(key_tab[i].keys, ch)) {
6549 key_tab[i].func(ch);
6550 if (Frames_signal == BREAK_off)
6551 Frames_signal = BREAK_kbd;
6552 /* due to the proliferation of the need for 'mkVIZrow1', |
6553 aside from 'wins_stage_2' use, we'll now issue it one |
6554 time here. there will remain several places where the |
6555 companion 'mkVIZrowX' macro is issued, thus the check |
6556 for a value already in 'begnext' in this conditional. | */
6557 if (CHKw(Curwin, Show_TASKON) && !mkVIZyes)
6562 /* The Frames_signal above will force a rebuild of column headers.
6563 It's NOT simply lazy programming. Below are some keys that may
6564 require new column headers and/or new library item enumerators:
6566 'c' - likely when !Mode_altscr, maybe when Mode_altscr
6574 'Z' - likely, if 'Curwin' changed when !Mode_altscr
6575 '-' - likely (restricted to Mode_altscr)
6576 '_' - likely (restricted to Mode_altscr)
6577 '=' - maybe, but only when Mode_altscr
6578 '+' - likely (restricted to Mode_altscr)
6579 PLUS, likely for FOUR of the EIGHT cursor motion keys (scrolled)
6580 ( At this point we have a human being involved and so have all the time )
6581 ( in the world. We can afford a few extra cpu cycles every now & then! )
6584 show_msg(N_txt(UNKNOWN_cmds_txt));
6586 putp((Cursor_state = Cap_curs_hide));
6591 * In support of a new frame:
6592 * 1) Display uptime and load average (maybe)
6593 * 2) Display task/cpu states (maybe)
6594 * 3) Display memory & swap usage (maybe) */
6595 static void summary_show (void) {
6596 #define isROOM(f,n) (CHKw(Curwin, f) && Msg_row + (n) < SCREEN_ROWS - 1)
6598 if (Restrict_some) {
6600 sem_wait(&Semaphore_tasks_end);
6602 // Display Task States only
6603 if (isROOM(View_STATES, 1)) {
6604 show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6605 , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6606 , PIDSmaxt, Pids_reap->counts->running
6607 , Pids_reap->counts->sleeping + Pids_reap->counts->other
6608 , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6614 // Display Uptime and Loadavg
6615 if (isROOM(View_LOADAV, 1)) {
6616 if (!Rc.mode_altscr)
6617 show_special(0, fmtmk(LOADAV_line, Myname, procps_uptime_sprint()));
6619 show_special(0, fmtmk(CHKw(Curwin, Show_TASKON)? LOADAV_line_alt : LOADAV_line
6620 , Curwin->grpname, procps_uptime_sprint()));
6622 } // end: View_LOADAV
6625 sem_wait(&Semaphore_cpus_end);
6628 sem_wait(&Semaphore_tasks_end);
6630 // Display Task and Cpu(s) States
6631 if (isROOM(View_STATES, 2)) {
6632 show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
6633 , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt)
6634 , PIDSmaxt, Pids_reap->counts->running
6635 , Pids_reap->counts->sleeping + Pids_reap->counts->other
6636 , Pids_reap->counts->stopped, Pids_reap->counts->zombied));
6643 sem_wait(&Semaphore_memory_end);
6645 // Display Memory and Swap stats
6646 if (isROOM(View_MEMORY, 2)) {
6651 } // end: summary_show
6655 * Build the information for a single task row and
6656 * display the results or return them to the caller. */
6657 static const char *task_show (const WIN_t *q, int idx) {
6658 // a tailored 'results stack value' extractor macro
6659 #define rSv(E,T) PID_VAL(E, T, p)
6660 #ifndef SCROLLVAR_NO
6661 #define makeVAR(S) { const char *pv = S; \
6662 if (!q->varcolbeg) cp = make_str(pv, q->varcolsz, Js, AUTOX_NO); \
6663 else cp = make_str(q->varcolbeg < (int)strlen(pv) ? pv + q->varcolbeg : "", q->varcolsz, Js, AUTOX_NO); }
6664 #define varUTF8(S) { const char *pv = S; \
6665 if (!q->varcolbeg) cp = make_str_utf8(pv, q->varcolsz, Js, AUTOX_NO); \
6666 else cp = make_str_utf8((q->varcolbeg < ((int)strlen(pv) - utf8_delta(pv))) \
6667 ? pv + utf8_embody(pv, q->varcolbeg) : "", q->varcolsz, Js, AUTOX_NO); }
6669 #define makeVAR(S) { cp = make_str(S, q->varcolsz, Js, AUTOX_NO); }
6670 #define varUTF8(S) { cp = make_str_utf8(S, q->varcolsz, Js, AUTOX_NO); }
6672 struct pids_stack *p = q->ppt[idx];
6673 static char rbuf[ROWMINSIZ];
6677 /* we use up to three additional 'PIDS_extra' results in our stacks
6678 eu_TREE_HID (s_ch) : where 'x' == collapsed and 'z' == unseen
6679 eu_TREE_LVL (s_int): where a level number is stored (0 - 100)
6680 eu_TREE_ADD (u_int): where children's tics are stored (maybe) */
6681 #ifndef TREE_VWINALL
6682 if (q == Curwin) // note: the following is NOT indented
6684 if (CHKw(q, Show_FOREST) && rSv(eu_TREE_HID, s_ch) == 'z')
6687 // we must begin a row with a possible window number in mind...
6688 *(rp = rbuf) = '\0';
6689 if (Rc.mode_altscr) rp = scat(rp, " ");
6691 for (x = 0; x < q->maxpflgs; x++) {
6692 const char *cp = NULL;
6693 FLG_t i = q->procflgs[x];
6694 #define S Fieldstab[i].scale // these used to be variables
6695 #define W Fieldstab[i].width // but it's much better if we
6696 #define Js CHKw(q, Show_JRSTRS) // represent them as #defines
6697 #define Jn CHKw(q, Show_JRNUMS) // and only exec code if used
6699 /* except for the XOF/XON pseudo flags the following case labels are grouped
6700 by result type according to capacity (small -> large) and then ordered by
6701 additional processing requirements (as in plain, scaled, decorated, etc.) */
6704 #ifndef USE_X_COLHDR
6705 // these 2 aren't real procflgs, they're used in column highlighting!
6709 if (!CHKw(q, NOPRINT_xxx)) {
6710 /* treat running tasks specially - entire row may get highlighted
6711 so we needn't turn it on and we MUST NOT turn it off */
6712 if (!('R' == rSv(EU_STA, s_ch) && CHKw(q, Show_HIROWS)))
6713 cp = (EU_XON == i ? q->capclr_rowhigh : q->capclr_rownorm);
6717 /* s_ch, make_chr */
6718 case EU_STA: // PIDS_STATE
6719 cp = make_chr(rSv(EU_STA, s_ch), W, Js);
6721 /* s_int, make_num with auto width */
6722 case EU_LID: // PIDS_ID_LOGIN
6723 cp = make_num(rSv(EU_LID, s_int), W, Jn, EU_LID, 0);
6725 /* s_int, make_num without auto width */
6726 case EU_AGI: // PIDS_AUTOGRP_ID
6727 case EU_CPN: // PIDS_PROCESSOR
6728 case EU_NMA: // PIDS_PROCESSOR_NODE
6729 case EU_PGD: // PIDS_ID_PGRP
6730 case EU_PID: // PIDS_ID_PID
6731 case EU_PPD: // PIDS_ID_PPID
6732 case EU_SID: // PIDS_ID_SESSION
6733 case EU_TGD: // PIDS_ID_TGID
6734 case EU_THD: // PIDS_NLWP
6735 case EU_TPG: // PIDS_ID_TPGID
6736 cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 0);
6738 /* s_int, make_num without auto width, but with zero supression */
6739 case EU_AGN: // PIDS_AUTOGRP_NICE
6740 case EU_NCE: // PIDS_NICE
6741 case EU_OOA: // PIDS_OOM_ADJ
6742 case EU_OOM: // PIDS_OOM_SCORE
6743 cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 1);
6745 /* s_int, scale_num */
6746 case EU_FV1: // PIDS_FLT_MAJ_DELTA
6747 case EU_FV2: // PIDS_FLT_MIN_DELTA
6748 cp = scale_num(rSv(i, s_int), W, Jn);
6750 /* s_int, make_num or make_str */
6751 case EU_PRI: // PIDS_PRIORITY
6752 if (-99 > rSv(EU_PRI, s_int) || 999 < rSv(EU_PRI, s_int))
6753 cp = make_str("rt", W, Jn, AUTOX_NO);
6755 cp = make_num(rSv(EU_PRI, s_int), W, Jn, AUTOX_NO, 0);
6757 /* s_int, scale_pcnt with special handling */
6758 case EU_CPU: // PIDS_TICS_ALL_DELTA
6759 { float u = (float)rSv(EU_CPU, u_int);
6760 int n = rSv(EU_THD, s_int);
6761 if (Restrict_some) {
6762 cp = justify_pad("?", W, Jn);
6765 #ifndef TREE_VCPUOFF
6766 #ifndef TREE_VWINALL
6767 if (q == Curwin) // note: the following is NOT indented
6769 if (CHKw(q, Show_FOREST)) u += rSv(eu_TREE_ADD, u_int);
6771 /* technically, eu_TREE_HID is only valid if Show_FOREST is active
6772 but its zeroed out slot will always be present now */
6773 if (rSv(eu_TREE_HID, s_ch) != 'x' && u > 100.0 * n) u = 100.0 * n;
6776 /* process can't use more %cpu than number of threads it has
6777 ( thanks Jaromir Capik <jcapik@redhat.com> ) */
6778 if (u > 100.0 * n) u = 100.0 * n;
6780 if (u > Cpu_pmax) u = Cpu_pmax;
6781 cp = scale_pcnt(u, W, Jn, 0);
6784 /* ull_int, scale_pcnt for 'utilization' */
6785 case EU_CUU: // PIDS_UTILIZATION
6786 case EU_CUC: // PIDS_UTILIZATION_C
6787 if (Restrict_some) {
6788 cp = justify_pad("?", W, Jn);
6791 cp = scale_pcnt(rSv(i, real), W, Jn, 1);
6793 /* u_int, make_num with auto width */
6794 case EU_GID: // PIDS_ID_EGID
6795 case EU_UED: // PIDS_ID_EUID
6796 case EU_URD: // PIDS_ID_RUID
6797 case EU_USD: // PIDS_ID_SUID
6798 cp = make_num(rSv(i, u_int), W, Jn, i, 0);
6800 /* ul_int, make_num with auto width and zero supression */
6801 case EU_NS1: // PIDS_NS_IPC
6802 case EU_NS2: // PIDS_NS_MNT
6803 case EU_NS3: // PIDS_NS_NET
6804 case EU_NS4: // PIDS_NS_PID
6805 case EU_NS5: // PIDS_NS_USER
6806 case EU_NS6: // PIDS_NS_UTS
6807 case EU_NS7: // PIDS_NS_CGROUP
6808 case EU_NS8: // PIDS_NS_TIME
6809 cp = make_num(rSv(i, ul_int), W, Jn, i, 1);
6811 /* ul_int, scale_mem */
6812 case EU_COD: // PIDS_MEM_CODE
6813 case EU_DAT: // PIDS_MEM_DATA
6814 case EU_DRT: // PIDS_noop, really # pgs, but always 0 since 2.6
6815 case EU_PZA: // PIDS_SMAP_PSS_ANON
6816 case EU_PZF: // PIDS_SMAP_PSS_FILE
6817 case EU_PZS: // PIDS_SMAP_PSS_SHMEM
6818 case EU_PSS: // PIDS_SMAP_PSS
6819 case EU_RES: // PIDS_MEM_RES
6820 case EU_RSS: // PIDS_SMAP_RSS
6821 case EU_RZA: // PIDS_VM_RSS_ANON
6822 case EU_RZF: // PIDS_VM_RSS_FILE
6823 case EU_RZL: // PIDS_VM_RSS_LOCKED
6824 case EU_RZS: // PIDS_VM_RSS_SHARED
6825 case EU_SHR: // PIDS_MEM_SHR
6826 case EU_SWP: // PIDS_VM_SWAP
6827 case EU_USE: // PIDS_VM_USED
6828 case EU_USS: // PIDS_SMAP_PRV_TOTAL
6829 case EU_VRT: // PIDS_MEM_VIRT
6830 cp = scale_mem(S, rSv(i, ul_int), W, Jn);
6832 /* ul_int, scale_num */
6833 case EU_FL1: // PIDS_FLT_MAJ
6834 case EU_FL2: // PIDS_FLT_MIN
6835 case EU_IRB: // PIDS_IO_READ_BYTES
6836 case EU_IRO: // PIDS_IO_READ_OPS
6837 case EU_IWB: // PIDS_IO_WRITE_BYTES
6838 case EU_IWO: // PIDS_IO_WRITE_OPS
6839 cp = scale_num(rSv(i, ul_int), W, Jn);
6841 /* ul_int, scale_pcnt */
6842 case EU_MEM: // derive from PIDS_MEM_RES
6843 if (Restrict_some) {
6844 cp = justify_pad("?", W, Jn);
6847 cp = scale_pcnt((float)rSv(EU_MEM, ul_int) * 100 / MEM_VAL(mem_TOT), W, Jn, 0);
6849 /* ul_int, make_str with special handling */
6850 case EU_FLG: // PIDS_FLAGS
6851 cp = make_str(hex_make(rSv(EU_FLG, ul_int), 1), W, Js, AUTOX_NO);
6853 /* ull_int, scale_tics (try 'minutes:seconds.hundredths') */
6854 case EU_TM2: // PIDS_TICS_ALL
6855 case EU_TME: // PIDS_TICS_ALL
6857 if (CHKw(q, Show_CTIMES)) t = rSv(eu_TICS_ALL_C, ull_int);
6858 else t = rSv(i, ull_int);
6859 cp = scale_tics(t, W, Jn, TICS_AS_SECS);
6862 /* ull_int, scale_tics (try 'minutes:seconds') */
6863 case EU_TM3: // PIDS_TICS_BEGAN
6864 cp = scale_tics(rSv(EU_TM3, ull_int), W, Jn, TICS_AS_MINS);
6866 /* real, scale_tics (try 'hour,minutes') */
6867 case EU_TM4: // PIDS_TIME_ELAPSED
6868 cp = scale_tics(rSv(EU_TM4, real) * Hertz, W, Jn, TICS_AS_HOUR);
6870 /* str, make_str (all AUTOX yes) */
6871 case EU_LXC: // PIDS_LXCNAME
6872 case EU_TTY: // PIDS_TTY_NAME
6873 case EU_WCH: // PIDS_WCHAN_NAME
6874 cp = make_str(rSv(i, str), W, Js, i);
6876 /* str, make_str_utf8 (all AUTOX yes) */
6877 case EU_GRP: // PIDS_ID_EGROUP
6878 case EU_UEN: // PIDS_ID_EUSER
6879 case EU_URN: // PIDS_ID_RUSER
6880 case EU_USN: // PIDS_ID_SUSER
6881 cp = make_str_utf8(rSv(i, str), W, Js, i);
6883 /* str, make_str_utf8 with varialbe width */
6884 case EU_CGN: // PIDS_CGNAME
6885 case EU_CGR: // PIDS_CGROUP
6886 case EU_ENV: // PIDS_ENVIRON
6887 case EU_EXE: // PIDS_EXE
6888 case EU_SGN: // PIDS_SUPGROUPS
6889 varUTF8(rSv(i, str))
6891 /* str, make_str with varialbe width */
6892 case EU_SGD: // PIDS_SUPGIDS
6893 makeVAR(rSv(EU_SGD, str))
6895 /* str, make_str with varialbe width + additional decoration */
6896 case EU_CMD: // PIDS_CMD or PIDS_CMDLINE
6897 varUTF8(forest_display(q, idx))
6899 default: // keep gcc happy
6901 } // end: switch 'procflag'
6904 if (q->osel_tot && !osel_matched(q, i, cp)) return "";
6911 } // end: for 'maxpflgs'
6913 if (!CHKw(q, NOPRINT_xxx)) {
6914 const char *cap = ((CHKw(q, Show_HIROWS) && 'R' == rSv(EU_STA, s_ch)))
6915 ? q->capclr_rowhigh : q->capclr_rownorm;
6918 /* since we can't predict what the search string will be and,
6919 considering what a single space search request would do to
6920 potential buffer needs, when any matches are found we skip
6921 normal output routing and send all of the results directly
6922 to the terminal (and we sound asthmatic: poof, putt, puff) */
6923 if (-1 < (ofs = find_ofs(q, row))) {
6927 PUTT("%s%s%s%s", row, q->capclr_hdr, q->findstr, cap);
6928 row += (ofs + q->findlen);
6929 ofs = find_ofs(q, row);
6931 PUTT("%s%s", row, Caps_endline);
6932 // with a corrupted rbuf, ensure row is 'counted' by window_show
6935 PUFF("\n%s%s%s", cap, row, Caps_endline);
6945 * A window_show *Helper* function ensuring that a window 'begtask' |
6946 * represents a visible process (not any hidden/filtered-out task). |
6947 * In reality this function is called exclusively for the 'current' |
6948 * window and only after available user keystroke(s) are processed. |
6949 * Note: it's entirely possible there are NO visible tasks to show! | */
6950 static void window_hlp (void) {
6951 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
6953 int beg = w->focus_pid ? w->focus_beg : 0;
6954 int end = w->focus_pid ? w->focus_end : PIDSmaxt;
6956 SETw(w, NOPRINT_xxx);
6957 w->begtask += w->begnext;
6958 // next 'if' will force a forward scan ...
6959 if (w->begtask <= beg) { w->begtask = beg; w->begnext = +1; }
6960 else if (w->begtask >= end) w->begtask = end - 1;
6963 // potentially scroll forward ...
6964 if (w->begnext > 0) {
6966 for (i = w->begtask; i < end; i++) {
6967 if (wins_usrselect(w, i)
6968 && (*task_show(w, i)))
6975 // no luck forward, so let's try backward
6976 w->begtask = end - 1;
6979 // potentially scroll backward ...
6980 for (i = w->begtask; i > beg; i--) {
6981 if (wins_usrselect(w, i)
6982 && (*task_show(w, i)))
6987 // reached the top, but maybe this guy ain't visible
6988 if (w->begtask == beg && !reversed) {
6989 if (!(wins_usrselect(w, beg))
6990 || (!(*task_show(w, beg)))) {
6998 OFFw(w, NOPRINT_xxx);
6999 } // end: window_hlp
7003 * Squeeze as many tasks as we can into a single window,
7004 * after sorting the passed proc table. */
7005 static int window_show (WIN_t *q, int wmax) {
7006 #define sORDER CHKw(q, Qsrt_NORMAL) ? PIDS_SORT_DESCEND : PIDS_SORT_ASCEND
7007 /* the isBUSY macro determines if a task is 'active' --
7008 it returns true if some cpu was used since the last sample.
7009 ( actual 'running' tasks will be a subset of those selected ) */
7010 #define isBUSY(x) (0 < PID_VAL(EU_CPU, u_int, (x)))
7011 #define winMIN(a,b) (((a) < (b)) ? (a) : (b))
7012 int i, lwin, numtasks;
7014 // Display Column Headings -- and distract 'em while we sort (maybe)
7015 PUFF("\n%s%s%s", q->capclr_hdr, q->columnhdr, Caps_endline);
7016 // and just in case 'Monpids' is active but matched no processes ...
7017 if (!PIDSmaxt) return 1; // 1 for the column header
7019 if (CHKw(q, Show_FOREST)) {
7021 if (q->focus_pid) forest_config(q);
7023 enum pids_item item = Fieldstab[q->rc.sortindx].item;
7024 if (item == PIDS_CMD && CHKw(q, Show_CMDLIN))
7025 item = PIDS_CMDLINE;
7026 else if (item == PIDS_TICS_ALL && CHKw(q, Show_CTIMES))
7027 item = PIDS_TICS_ALL_C;
7028 if (!(procps_pids_sort(Pids_ctx, q->ppt , PIDSmaxt, item, sORDER)))
7029 error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno)));
7032 if (mkVIZyes) window_hlp();
7033 else OFFw(q, NOPRINT_xxx);
7036 lwin = 1; // 1 for the column header
7037 wmax = winMIN(wmax, q->winlines + 1); // ditto for winlines, too
7038 numtasks = q->focus_pid ? winMIN(q->focus_end, PIDSmaxt) : PIDSmaxt;
7040 /* the least likely scenario is also the most costly, so we'll try to avoid
7041 checking some stuff with each iteration and check it just once... */
7042 if (CHKw(q, Show_IDLEPS) && !q->usrseltyp)
7043 while (i < numtasks && lwin < wmax) {
7044 if (*task_show(q, i++))
7048 while (i < numtasks && lwin < wmax) {
7049 if ((CHKw(q, Show_IDLEPS) || isBUSY(q->ppt[i]))
7050 && wins_usrselect(q, i)
7051 && *task_show(q, i))
7060 } // end: window_show
7062 /*###### Entry point plus two ##########################################*/
7065 * This guy's just a *Helper* function who apportions the
7066 * remaining amount of screen real estate under multiple windows */
7067 static void frame_hlp (int wix, int max) {
7070 // calc remaining number of visible windows
7071 for (i = wix, wins = 0; i < GROUPSMAX; i++)
7072 if (CHKw(&Winstk[i], Show_TASKON))
7075 if (!wins) wins = 1;
7076 // deduct 1 line/window for the columns heading
7077 size = (max - wins) / wins;
7079 /* for subject window, set WIN_t winlines to either the user's
7080 maxtask (1st choice) or our 'foxized' size calculation
7081 (foxized adj. - 'fair and balanced') */
7082 Winstk[wix].winlines =
7083 Winstk[wix].rc.maxtasks ? Winstk[wix].rc.maxtasks : size;
7088 * Initiate the Frame Display Update cycle at someone's whim!
7089 * This routine doesn't do much, mostly he just calls others.
7091 * (Whoa, wait a minute, we DO caretake those row guys, plus)
7092 * (we CALCULATE that IMPORTANT Max_lines thingy so that the)
7093 * (*subordinate* functions invoked know WHEN the user's had)
7094 * (ENOUGH already. And at Frame End, it SHOULD be apparent)
7095 * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring)
7096 * (that those auto-sized columns are addressed, know what I)
7097 * (mean? Huh, "doesn't DO MUCH"! Never, EVER think or say)
7098 * (THAT about THIS function again, Ok? Good that's better.)
7100 * (ps. we ARE the UNEQUALED justification KING of COMMENTS!)
7101 * (No, I don't mean significance/relevance, only alignment.)
7103 static void frame_make (void) {
7104 WIN_t *w = Curwin; // avoid gcc bloat with a local copy
7107 // check auto-sized width increases from the last iteration...
7108 if (AUTOX_MODE && Autox_found)
7111 /* deal with potential signal(s) since the last time around
7112 plus any input which may change 'tasks_refresh' needs... */
7113 if (Frames_signal) {
7114 if (Frames_signal == BREAK_sig
7115 || (Frames_signal == BREAK_screen))
7121 sem_post(&Semaphore_tasks_beg);
7123 tasks_refresh(NULL);
7126 if (!Restrict_some) {
7128 sem_post(&Semaphore_cpus_beg);
7133 sem_post(&Semaphore_memory_beg);
7135 memory_refresh(NULL);
7139 // whoa either first time or thread/task mode change, (re)prime the pump...
7140 if (Pseudo_row == PROC_XTRA) {
7143 sem_wait(&Semaphore_tasks_end);
7144 sem_post(&Semaphore_tasks_beg);
7146 tasks_refresh(NULL);
7150 putp(Batch ? "\n\n" : Cap_home);
7152 Tree_idx = Pseudo_row = Msg_row = scrlins = 0;
7154 Max_lines = (SCREEN_ROWS - Msg_row) - 1;
7156 // we're now on Msg_row so clear out any residual messages ...
7159 if (!Rc.mode_altscr) {
7160 // only 1 window to show so, piece o' cake
7161 w->winlines = w->rc.maxtasks ? w->rc.maxtasks : Max_lines;
7162 scrlins = window_show(w, Max_lines);
7164 // maybe NO window is visible but assume, pieces o' cakes
7165 for (i = 0 ; i < GROUPSMAX; i++) {
7166 if (CHKw(&Winstk[i], Show_TASKON)) {
7167 frame_hlp(i, Max_lines - scrlins);
7168 scrlins += window_show(&Winstk[i], Max_lines - scrlins);
7170 if (Max_lines <= scrlins) break;
7174 /* clear to end-of-screen - critical if last window is 'idleps off'
7175 (main loop must iterate such that we're always called before sleep) */
7176 if (!Batch && scrlins < Max_lines) {
7177 for (i = scrlins + Msg_row + 1; i < SCREEN_ROWS; i++) {
7181 PSU_CLREOS(Pseudo_row);
7184 if (CHKw(w, View_SCROLL) && VIZISw(Curwin)) show_scroll();
7185 if (Bot_show_func) Bot_show_func();
7188 /* we'll deem any terminal not supporting tgoto as dumb and disable
7189 the normal non-interactive output optimization... */
7190 if (!Cap_can_goto) PSU_CLREOS(0);
7191 } // end: frame_make
7196 int main (int argc, char *argv[]) {
7199 wins_stage_1(); // top (sic) slice
7200 configs_reads(); // > spread etc, <
7201 parse_args(argc, argv); // > onions etc, <
7202 signals_set(); // > lean stuff, <
7203 whack_terminal(); // > more stuff. <
7204 wins_stage_2(); // as bottom slice
7212 if (0 < Loops) --Loops;
7213 if (!Loops) bye_bye(NULL);
7214 if (Frames_signal) { Frames_signal = BREAK_off; zap_fieldstab(); continue; }
7216 ts.tv_sec = Rc.delay_time;
7217 ts.tv_nsec = (Rc.delay_time - (int)Rc.delay_time) * 1000000000;
7220 pselect(0, NULL, NULL, NULL, &ts, NULL);
7223 do_key(iokey(IOKEY_ONCE));
7225 /* note: that above ioa routine exists to consolidate all logic
7226 which is susceptible to signal interrupt and must then
7227 produce a screen refresh. in this main loop frame_make
7228 assumes responsibility for such refreshes. other logic
7229 in contact with users must deal more obliquely with an
7230 interrupt/refresh (hint: Frames_signal + return code)!
7232 (everything is perfectly justified plus right margins)
7233 (are completely filled, but of course it must be luck)