diff options
Diffstat (limited to 'src/top/top.c')
-rw-r--r-- | src/top/top.c | 7404 |
1 files changed, 7404 insertions, 0 deletions
diff --git a/src/top/top.c b/src/top/top.c new file mode 100644 index 0000000..969c553 --- /dev/null +++ b/src/top/top.c @@ -0,0 +1,7404 @@ +/* top.c - Source file: show Linux processes */ +/* + * Copyright © 2002-2023 Jim Warner <james.warner@comcast.net + * + * This file may be used subject to the terms and conditions of the + * GNU Library General Public License Version 2, or any later version + * at your option, as published by the Free Software Foundation. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + */ +/* For contributions to this program, the author wishes to thank: + * Craig Small, <csmall@dropbear.xyz> + * Albert D. Cahalan, <albert@users.sf.net> + * Sami Kerola, <kerolasa@iki.fi> + */ + +#include <ctype.h> +#include <curses.h> +#include <errno.h> +#include <fcntl.h> +#include <float.h> +#include <getopt.h> +#include <limits.h> +#include <pwd.h> +#include <pthread.h> +#include <semaphore.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <term.h> // foul sob, defines all sorts of stuff... +#undef raw +#undef tab +#undef TTY +#include <termios.h> +#include <time.h> +#include <unistd.h> +#include <wchar.h> + +#include <sys/ioctl.h> +#include <sys/resource.h> +#include <sys/select.h> // also available via <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> // also available via <stdlib.h> + +#include "fileutils.h" +#include "signals.h" +#include "nls.h" + +#include "meminfo.h" +#include "misc.h" +#include "pids.h" +#include "stat.h" + +#include "top.h" +#include "top_nls.h" + +/*###### Miscellaneous global stuff ####################################*/ + + /* The original and new terminal definitions + (only set when not in 'Batch' mode) */ +static struct termios Tty_original, // our inherited terminal definition +#ifdef TERMIOS_ONLY + Tty_tweaked, // for interactive 'line' input +#endif + Tty_raw; // for unsolicited input +static int Ttychanged = 0; + + /* Last established cursor state/shape */ +static const char *Cursor_state = ""; + + /* Program name used in error messages and local 'rc' file name */ +static char *Myname; + + /* Our constant sigset, so we need initialize it but once */ +static sigset_t Sigwinch_set; + + /* The 'local' config file support */ +static char Rc_name [OURPATHSZ]; +static RCF_t Rc = DEF_RCFILE; +static int Rc_questions; +static int Rc_compatibilty; + + /* SMP, Irix/Solaris mode, Linux 2.5.xx support (and beyond) */ +static long Hertz; +static int Cpu_cnt; +static float Cpu_pmax; +static const char *Cpu_States_fmts; + + /* Specific process id monitoring support */ +static int Monpids [MONPIDMAX+1] = { 0 }; +static int Monpidsidx = 0; + + /* Current screen dimensions. + note: the number of processes displayed is tracked on a per window + basis (see the WIN_t). Max_lines is the total number of + screen rows after deducting summary information overhead. */ + /* Current terminal screen size. */ +static int Screen_cols, Screen_rows, Max_lines; + + /* These 'SCREEN_ROWS', 'BOT_ and 'Bot_' guys are used + in managing the special separate bottom 'window' ... */ +#define SCREEN_ROWS ( Screen_rows - Bot_rsvd ) +#define BOT_PRESENT ( Bot_what != 0 ) +#define BOT_ITEMMAX 10 // Bot_item array's max size +#define BOT_MSGSMAX 10 // total entries for Msg_tab +#define BOT_UNFOCUS -1 // tab focus not established + // negative 'item' values won't be seen by build_headers() ... +#define BOT_DELIMIT -1 // fencepost with item array +#define BOT_ITEM_NS -2 // data for namespaces req'd +#define BOT_MSG_LOG -3 // show the most recent msgs + // next 4 are used when toggling window contents +#define BOT_SEP_CMA ',' +#define BOT_SEP_SLS '/' +#define BOT_SEP_SMI ';' +#define BOT_SEP_SPC ' ' + // 1 for horizontal separator +#define BOT_RSVD 1 +#define BOT_KEEP Bot_show_func = NULL +#define BOT_TOSS do { Bot_show_func = NULL; Bot_item[0] = BOT_DELIMIT; \ + Bot_task = Bot_rsvd = Bot_what = 0; \ + Bot_indx = BOT_UNFOCUS; \ + } while(0) +static int Bot_task, + Bot_what, + Bot_rsvd, + Bot_indx = BOT_UNFOCUS, + Bot_item[BOT_ITEMMAX] = { BOT_DELIMIT }; +static char Bot_sep, + *Bot_head, + Bot_buf[BOTBUFSIZ]; // the 'environ' can be huge +typedef int(*BOT_f)(const void *, const void *); +static BOT_f Bot_focus_func; +static void(*Bot_show_func)(void); + + /* This is really the number of lines needed to display the summary + information (0 - nn), but is used as the relative row where we + stick the cursor between frames. */ +static int Msg_row; + + /* Global/Non-windows mode stuff that is NOT persistent */ +static int Batch = 0, // batch mode, collect no input, dumb output + Loops = -1, // number of iterations, -1 loops forever + Secure_mode = 0, // set if some functionality restricted + Width_mode = 0, // set w/ 'w' - potential output override + Thread_mode = 0; // set w/ 'H' - show threads vs. tasks + + /* Unchangeable cap's stuff built just once (if at all) and + thus NOT saved in a WIN_t's RCW_t. To accommodate 'Batch' + mode, they begin life as empty strings so the overlying + logic need not change ! */ +static char Cap_clr_eol [CAPBUFSIZ] = "", // global and/or static vars + Cap_nl_clreos [CAPBUFSIZ] = "", // are initialized to zeros! + Cap_clr_scr [CAPBUFSIZ] = "", // the assignments used here + Cap_curs_norm [CAPBUFSIZ] = "", // cost nothing but DO serve + Cap_curs_huge [CAPBUFSIZ] = "", // to remind people of those + Cap_curs_hide [CAPBUFSIZ] = "", // batch requirements! + Cap_clr_eos [CAPBUFSIZ] = "", + Cap_home [CAPBUFSIZ] = "", + Cap_norm [CAPBUFSIZ] = "", + Cap_reverse [CAPBUFSIZ] = "", + Caps_off [CAPBUFSIZ] = "", + Caps_endline [SMLBUFSIZ] = ""; +#ifndef RMAN_IGNORED +static char Cap_rmam [CAPBUFSIZ] = "", + Cap_smam [CAPBUFSIZ] = ""; + /* set to 1 if writing to the last column would be troublesome + (we don't distinguish the lowermost row from the other rows) */ +static int Cap_avoid_eol = 0; +#endif +static int Cap_can_goto = 0; + + /* Some optimization stuff, to reduce output demands... + The Pseudo_ guys are managed by adj_geometry and frame_make. They + are exploited in a macro and represent 90% of our optimization. + The Stdout_buf is transparent to our code and regardless of whose + buffer is used, stdout is flushed at frame end or if interactive. */ +static char *Pseudo_screen; +static int Pseudo_row = PROC_XTRA; +static size_t Pseudo_size; +#ifndef OFF_STDIOLBF + // less than stdout's normal buffer but with luck mostly '\n' anyway +static char Stdout_buf[2048]; +#endif + + /* Our four WIN_t's, and which of those is considered the 'current' + window (ie. which window is associated with any summ info displayed + and to which window commands are directed) */ +static WIN_t Winstk [GROUPSMAX]; +static WIN_t *Curwin; + + /* Frame oriented stuff that can't remain local to any 1 function + and/or that would be too cumbersome managed as parms, + and/or that are simply more efficiently handled as globals + [ 'Frames_...' (plural) stuff persists beyond 1 frame ] + [ or are used in response to async signals received ! ] */ +static volatile int Frames_signal; // time to rebuild all column headers +static float Frame_etscale; // so we can '*' vs. '/' WHEN 'pcpu' + + /* Support for automatically sized fixed-width column expansions. + * (hopefully, the macros help clarify/document our new 'feature') */ +static int Autox_array [EU_MAXPFLGS], + Autox_found; +#define AUTOX_NO EU_MAXPFLGS +#define AUTOX_COL(f) if (EU_MAXPFLGS > f && f >= 0) Autox_array[f] = Autox_found = 1 +#define AUTOX_MODE (0 > Rc.fixed_widest) + + /* Support for scale_mem and scale_num (to avoid duplication. */ +#ifdef CASEUP_SUFIX // nls_maybe + static char Scaled_sfxtab[] = { 'K', 'M', 'G', 'T', 'P', 'E', 0 }; +#else // nls_maybe + static char Scaled_sfxtab[] = { 'k', 'm', 'g', 't', 'p', 'e', 0 }; +#endif + + /* Support for NUMA Node display plus node expansion and targeting */ +#ifndef OFF_STDERROR +static int Stderr_save = -1; +#endif +static int Numa_node_tot; +static int Numa_node_sel = -1; + + /* Support for Graphing of the View_STATES ('t') and View_MEMORY ('m') + commands -- which are now both 4-way toggles */ +#define GRAPH_length_max 100 // the actual bars or blocks +#define GRAPH_length_min 10 // the actual bars or blocks +#define GRAPH_prefix_std 25 // '.......: 100.0/100.0 100[' +#define GRAPH_prefix_abv 12 // '.......:100[' +#define GRAPH_suffix 2 // '] ' (bracket + trailing space) + // first 3 more static (adj_geometry), last 3 volatile (sum_tics/do_memory) +struct graph_parms { + float adjust; // bars/blocks scaling factor + int length; // scaled length (<= GRAPH_length_max) + int style; // rc.graph_cpus or rc.graph_mems + long total, part1, part2; // elements to be graphed +}; +static struct graph_parms *Graph_cpus, *Graph_mems; +static const char Graph_blks[] = " "; +static const char Graph_bars[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"; + + /* Support for 'Other Filters' in the configuration file */ +static const char Osel_delim_1_txt[] = "begin: saved other filter data -------------------\n"; +static const char Osel_delim_2_txt[] = "end : saved other filter data -------------------\n"; +static const char Osel_window_fmts[] = "window #%d, osel_tot=%d\n"; +#define OSEL_FILTER "filter=" +static const char Osel_filterO_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%s\n"; +static const char Osel_filterI_fmt[] = "\ttype=%d,\t" OSEL_FILTER "%*s\n"; + + /* Support for adjoining display (if terminal is wide enough) */ +#ifdef TOG4_SEP_OFF +static char Adjoin_sp[] = " "; +#define ADJOIN_space (sizeof(Adjoin_sp) - 1) // 1 for null +#else +#ifdef TOG4_SEP_STD +static char Adjoin_sp[] = "~1 ~6 "; +#else +static char Adjoin_sp[] = " ~6 ~1"; +#endif +#define ADJOIN_space (sizeof(Adjoin_sp) - 5) // 1 for null + 4 unprintable +#endif +#define ADJOIN_limit 8 + + /* Support for the new library API -- acquired (if necessary) + at program startup and referenced throughout our lifetime. */ + /* + * --- <proc/pids.h> -------------------------------------------------- */ +static struct pids_info *Pids_ctx; +static int Pids_itms_tot; // same as MAXTBL(Fieldstab) +static enum pids_item *Pids_itms; // allocated as MAXTBL(Fieldstab) +static struct pids_fetch *Pids_reap; // for reap or select +#define PIDSmaxt Pids_reap->counts->total // just a little less wordy + // pid stack results extractor macro, where e=our EU enum, t=type, s=stack + // ( we'll exploit that <proc/pids.h> provided macro as much as possible ) + // ( but many functions use their own unique tailored version for access ) +#define PID_VAL(e,t,s) PIDS_VAL(e, t, s, Pids_ctx) + /* + * --- <proc/stat.h> -------------------------------------------------- */ +static struct stat_info *Stat_ctx; +static struct stat_reaped *Stat_reap; +static enum stat_item Stat_items[] = { + STAT_TIC_ID, STAT_TIC_NUMA_NODE, + STAT_TIC_DELTA_USER, STAT_TIC_DELTA_SYSTEM, + STAT_TIC_DELTA_NICE, STAT_TIC_DELTA_IDLE, + STAT_TIC_DELTA_IOWAIT, STAT_TIC_DELTA_IRQ, + STAT_TIC_DELTA_SOFTIRQ, STAT_TIC_DELTA_STOLEN, + STAT_TIC_DELTA_GUEST, STAT_TIC_DELTA_GUEST_NICE, + STAT_TIC_SUM_DELTA_USER, STAT_TIC_SUM_DELTA_SYSTEM, +#ifdef CORE_TYPE_NO + STAT_TIC_SUM_DELTA_TOTAL }; +#else + STAT_TIC_SUM_DELTA_TOTAL, STAT_TIC_TYPE_CORE }; +#endif +enum Rel_statitems { + stat_ID, stat_NU, + stat_US, stat_SY, + stat_NI, stat_IL, + stat_IO, stat_IR, + stat_SI, stat_ST, + stat_GU, stat_GN, + stat_SUM_USR, stat_SUM_SYS, +#ifdef CORE_TYPE_NO + stat_SUM_TOT }; +#else + stat_SUM_TOT, stat_COR_TYP }; +#endif + // cpu/node stack results extractor macros, where e=rel enum, x=index +#define CPU_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->cpus->stacks[x], Stat_ctx) +#define NOD_VAL(e,x) STAT_VAL(e, s_int, Stat_reap->numa->stacks[x], Stat_ctx) +#define TIC_VAL(e,s) STAT_VAL(e, sl_int, s, Stat_ctx) +#define E_CORE 1 // values for stat_COR_TYP itself +#define P_CORE 2 // ( 0 = unsure/unknown ) +#define P_CORES_ONLY 2 // values of rc.core_types toggle, for filtering +#define E_CORES_ONLY 3 // ( 0 = Cpu shown, 1 = both CpP & CpE shown ) + /* + * --- <proc/meminfo.h> ----------------------------------------------- */ +static struct meminfo_info *Mem_ctx; +static struct meminfo_stack *Mem_stack; +static enum meminfo_item Mem_items[] = { + MEMINFO_MEM_FREE, MEMINFO_MEM_USED, MEMINFO_MEM_TOTAL, + MEMINFO_MEM_CACHED_ALL, MEMINFO_MEM_BUFFERS, MEMINFO_MEM_AVAILABLE, + MEMINFO_SWAP_TOTAL, MEMINFO_SWAP_FREE, MEMINFO_SWAP_USED }; +enum Rel_memitems { + mem_FRE, mem_USE, mem_TOT, + mem_QUE, mem_BUF, mem_AVL, + swp_TOT, swp_FRE, swp_USE }; + // mem stack results extractor macro, where e=rel enum +#define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx) + + /* Support for concurrent library updates via + multithreaded background processes */ +#ifdef THREADED_CPU +static pthread_t Thread_id_cpus; +static sem_t Semaphore_cpus_beg, Semaphore_cpus_end; +#endif +#ifdef THREADED_MEM +static pthread_t Thread_id_memory; +static sem_t Semaphore_memory_beg, Semaphore_memory_end; +#endif +#ifdef THREADED_TSK +static pthread_t Thread_id_tasks; +static sem_t Semaphore_tasks_beg, Semaphore_tasks_end; +#endif +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK +static pthread_t Thread_id_main; +#endif + + /* Support for a namespace with proc mounted subset=pid, + ( we'll limit our display to task information only ). */ +static int Restrict_some = 0; + +/*###### Tiny useful routine(s) ########################################*/ + + /* + * This routine simply formats whatever the caller wants and + * returns a pointer to the resulting 'const char' string... */ +static const char *fmtmk (const char *fmts, ...) __attribute__((format(printf,1,2))); +static const char *fmtmk (const char *fmts, ...) { + static char buf[BIGBUFSIZ]; // with help stuff, our buffer + va_list va; // requirements now exceed 1k + + va_start(va, fmts); + vsnprintf(buf, sizeof(buf), fmts, va); + va_end(va); + return (const char *)buf; +} // end: fmtmk + + + /* + * Integer based fieldscur version of 'strlen' */ +static inline int mlen (const int *mem) { + int i; + + for (i = 0; mem[i]; i++) + ; + return i; +} // end: mlen + + + /* + * Integer based fieldscur version of 'strchr' */ +static inline int *msch (const int *mem, int obj, int max) { + int i; + + for (i = 0; i < max; i++) + if (*(mem + i) == obj) return (int *)mem + i; + return NULL; +} // end: msch + + + /* + * This guy is just our way of avoiding the overhead of the standard + * strcat function (should the caller choose to participate) */ +static inline char *scat (char *dst, const char *src) { + while (*dst) dst++; + while ((*(dst++) = *(src++))); + return --dst; +} // end: scat + + + /* + * This guy just facilitates Batch and protects against dumb ttys + * -- we'd 'inline' him but he's only called twice per frame, + * yet used in many other locations. */ +static const char *tg2 (int x, int y) { + // it's entirely possible we're trying for an invalid row... + return Cap_can_goto ? tgoto(cursor_address, x, y) : ""; +} // end: tg2 + +/*###### Exit/Interrupt routines #######################################*/ + + /* + * Reset the tty, if necessary */ +static void at_eoj (void) { + if (Ttychanged) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original); + if (keypad_local) putp(keypad_local); + putp(tg2(0, Screen_rows)); + putp("\n"); +#ifdef OFF_SCROLLBK + if (exit_ca_mode) { + // this next will also replace top's most recent screen with the + // original display contents that were visible at our invocation + putp(exit_ca_mode); + } +#endif + putp(Cap_curs_norm); + putp(Cap_clr_eol); +#ifndef RMAN_IGNORED + putp(Cap_smam); +#endif + } + fflush(stdout); +#ifndef OFF_STDERROR + /* we gotta reverse the stderr redirect which was employed during start up + and needed because the two libnuma 'weak' functions were useless to us! */ + if (-1 < Stderr_save) { + dup2(Stderr_save, fileno(stderr)); + close(Stderr_save); + Stderr_save = -1; // we'll be ending soon anyway but what the heck + } +#endif +} // end: at_eoj + + + /* + * The real program end */ +static void bye_bye (const char *str) __attribute__((__noreturn__)); +static void bye_bye (const char *str) { + sigset_t ss; + +// POSIX.1 async-signal-safe: sigfillset, sigprocmask, pthread_sigmask + sigfillset(&ss); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_BLOCK, &ss, NULL); +#else + sigprocmask(SIG_BLOCK, &ss, NULL); +#endif + at_eoj(); // restore tty in preparation for exit +#ifdef ATEOJ_RPTSTD +{ + if (!str && !Frames_signal && Ttychanged) { fprintf(stderr, + "\n%s's Summary report:" + "\n\tProgram" + "\n\t %s" + "\n\t Hertz = %u (%u bytes, %u-bit time)" + "\n\t Stat_reap->cpus->total = %d, Stat_reap->numa->total = %d" + "\n\t Pids_itms_tot = %d, sizeof(struct pids_result) = %d, pids stack size = %d" + "\n\t SCREENMAX = %d, ROWMINSIZ = %d, ROWMAXSIZ = %d" + "\n\t PACKAGE = '%s', LOCALEDIR = '%s'" + "\n\tTerminal: %s" + "\n\t device = %s, ncurses = v%s" + "\n\t max_colors = %d, max_pairs = %d" + "\n\t Cap_can_goto = %s" + "\n\t Screen_cols = %d, Screen_rows = %d" + "\n\t Max_lines = %d, most recent Pseudo_size = %u" +#ifndef OFF_STDIOLBF + "\n\t Stdout_buf = %u, BUFSIZ = %u" +#endif + "\n\tWindows and Curwin->" + "\n\t sizeof(WIN_t) = %u, GROUPSMAX = %d" + "\n\t winname = %s, grpname = %s" +#ifdef CASEUP_HEXES + "\n\t winflags = %08X, maxpflgs = %d" +#else + "\n\t winflags = x%08x, maxpflgs = %d" +#endif + "\n\t sortindx = %d, maxtasks = %d" + "\n\t varcolsz = %d, winlines = %d" + "\n\t strlen(columnhdr) = %d" + "\n\t current fieldscur = %d, maximum fieldscur = %d" + "\n" + , __func__ + , PACKAGE_STRING + , (unsigned)Hertz, (unsigned)sizeof(Hertz), (unsigned)sizeof(Hertz) * 8 + , Stat_reap->cpus->total, Stat_reap->numa->total + , Pids_itms_tot, (int)sizeof(struct pids_result), (int)(sizeof(struct pids_result) * Pids_itms_tot) + , (int)SCREENMAX, (int)ROWMINSIZ, (int)ROWMAXSIZ + , PACKAGE, LOCALEDIR +#ifdef PRETENDNOCAP + , "dumb" +#else + , termname() +#endif + , ttyname(STDOUT_FILENO), NCURSES_VERSION + , max_colors, max_pairs + , Cap_can_goto ? "yes" : "No!" + , Screen_cols, Screen_rows + , Max_lines, (unsigned)Pseudo_size +#ifndef OFF_STDIOLBF + , (unsigned)sizeof(Stdout_buf), (unsigned)BUFSIZ +#endif + , (unsigned)sizeof(WIN_t), GROUPSMAX + , Curwin->rc.winname, Curwin->grpname + , Curwin->rc.winflags, Curwin->maxpflgs + , Curwin->rc.sortindx, Curwin->rc.maxtasks + , Curwin->varcolsz, Curwin->winlines + , (int)strlen(Curwin->columnhdr) + , EU_MAXPFLGS, mlen(Curwin->rc.fieldscur) + ); + } +} +#endif // end: ATEOJ_RPTSTD + + // there's lots of signal-unsafe stuff in the following ... + if (Frames_signal != BREAK_sig) { +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + /* can not execute any cleanup from a sibling thread and + we will violate proper indentation to minimize impact */ + if (pthread_equal(Thread_id_main, pthread_self())) { +#endif +#ifdef THREADED_CPU + pthread_cancel(Thread_id_cpus); + pthread_join(Thread_id_cpus, NULL); + sem_destroy(&Semaphore_cpus_end); + sem_destroy(&Semaphore_cpus_beg); +#endif +#ifdef THREADED_MEM + pthread_cancel(Thread_id_memory); + pthread_join(Thread_id_memory, NULL); + sem_destroy(&Semaphore_memory_end); + sem_destroy(&Semaphore_memory_beg); +#endif +#ifdef THREADED_TSK + pthread_cancel(Thread_id_tasks); + pthread_join(Thread_id_tasks, NULL); + sem_destroy(&Semaphore_tasks_end); + sem_destroy(&Semaphore_tasks_beg); +#endif + procps_pids_unref(&Pids_ctx); + procps_stat_unref(&Stat_ctx); + procps_meminfo_unref(&Mem_ctx); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + } +#endif + } + + /* we'll only have a 'str' if called by error_exit() | + and parse_args(), never from a signal handler ... | */ + if (str) { + fputs(str, stderr); + exit(EXIT_FAILURE); + } + /* this could happen when called from several places | + including that sig_endpgm(). thus we must use an | + async-signal-safe write function just in case ... | + (thanks: Shaohua Zhan shaohua.zhan@windriver.com) | */ + if (Batch) + write(fileno(stdout), "\n", sizeof("\n") - 1); + + exit(EXIT_SUCCESS); +} // end: bye_bye + + + /* + * Standard error handler to normalize the look of all err output */ +static void error_exit (const char *str) __attribute__((__noreturn__)); +static void error_exit (const char *str) { + static char buf[MEDBUFSIZ]; + + Frames_signal = BREAK_off; + /* we'll use our own buffer so callers can still use fmtmk() and, after + twelve long years, 2013 was the year we finally eliminated the leading + tab character -- now our message can get lost in screen clutter too! */ + snprintf(buf, sizeof(buf), "%s: %s\n", Myname, str); + bye_bye(buf); +} // end: error_exit + + + /* + * Catches all remaining signals not otherwise handled */ +static void sig_abexit (int sig) __attribute__((__noreturn__)); +static void sig_abexit (int sig) { + sigset_t ss; + +// POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, sigprocmask, pthread_sigmask, raise + sigfillset(&ss); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_BLOCK, &ss, NULL); +#else + sigprocmask(SIG_BLOCK, &ss, NULL); +#endif + at_eoj(); // restore tty in preparation for exit + fprintf(stderr, N_fmt(EXIT_signals_fmt) + , sig, signal_number_to_name(sig), Myname); + signal(sig, SIG_DFL); // allow core dumps, if applicable + sigemptyset(&ss); + sigaddset(&ss, sig); +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK + pthread_sigmask(SIG_UNBLOCK, &ss, NULL); +#else + sigprocmask(SIG_UNBLOCK, &ss, NULL); +#endif + raise(sig); // ( plus set proper return code ) + _exit(EXIT_FAILURE); // if default sig action is ignore +} // end: sig_abexit + + + /* + * Catches: + * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT, SIGTERM, + * SIGUSR1 and SIGUSR2 */ +static void sig_endpgm (int dont_care_sig) __attribute__((__noreturn__)); +static void sig_endpgm (int dont_care_sig) { + Frames_signal = BREAK_sig; + bye_bye(NULL); + (void)dont_care_sig; +} // end: sig_endpgm + + + /* + * Catches: + * SIGTSTP, SIGTTIN and SIGTTOU */ +static void sig_paused (int dont_care_sig) { +// POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + if (keypad_local) putp(keypad_local); + putp(tg2(0, Screen_rows)); + putp(Cap_curs_norm); +#ifndef RMAN_IGNORED + putp(Cap_smam); +#endif + // tcdrain(STDOUT_FILENO) was not reliable prior to ncurses-5.9.20121017, + // so we'll risk POSIX's wrath with good ol' fflush, lest 'Stopped' gets + // co-mingled with our most recent output... + fflush(stdout); + raise(SIGSTOP); + // later, after SIGCONT... + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); +#ifndef RMAN_IGNORED + putp(Cap_rmam); +#endif + if (keypad_xmit) putp(keypad_xmit); + putp(Cursor_state); + Frames_signal = BREAK_sig; + (void)dont_care_sig; +} // end: sig_paused + + + /* + * Catches: + * SIGCONT and SIGWINCH */ +static void sig_resize (int dont_care_sig) { +// POSIX.1 async-signal-safe: tcdrain + tcdrain(STDOUT_FILENO); + Frames_signal = BREAK_sig; + (void)dont_care_sig; +} // end: sig_resize + +/*###### Special UTF-8 Multi-Byte support ##############################*/ + + /* Support for NLS translated multi-byte strings */ +static char UTF8_tab[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 - 0x0F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 - 0x1F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 - 0x3F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x80 - 0x8F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0x90 - 0x9F + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xA0 - 0xAF + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xB0 - 0xBF + -1,-1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xC0 - 0xCF, 0xC2 = begins 2 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xD0 - 0xDF + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xE0 - 0xEF, 0xE0 = begins 3 + 4, 4, 4, 4, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, // 0xF0 - 0xFF, 0xF0 = begins 4 +}; // ( 0xF5 & beyond invalid ) + + + /* + * Accommodate any potential differences between some multibyte + * character sequence and the screen columns needed to print it */ +static inline int utf8_cols (const unsigned char *p, int n) { +#ifndef OFF_XTRAWIDE + wchar_t wc; + + if (n > 1) { + (void)mbtowc(&wc, (const char *)p, n); + // allow a zero as valid, as with a 'combining acute accent' + if ((n = wcwidth(wc)) < 0) n = 1; + } + return n; +#else + (void)p; (void)n; + return 1; +#endif +} // end: utf8_cols + + + /* + * Determine difference between total bytes versus printable + * characters in that passed, potentially multi-byte, string */ +static int utf8_delta (const char *str) { + const unsigned char *p = (const unsigned char *)str; + int clen, cnum = 0; + + while (*p) { + // -1 represents a decoding error, pretend it's untranslated ... + if (0 > (clen = UTF8_tab[*p])) return 0; + cnum += utf8_cols(p, clen); + p += clen; + } + return (int)((const char *)p - str) - cnum; +} // end: utf8_delta + + + /* + * Determine a physical end within a potential multi-byte string + * where maximum printable chars could be accommodated in width */ +static int utf8_embody (const char *str, int width) { + const unsigned char *p = (const unsigned char *)str; + int clen, cnum = 0; + + if (width > 0) { + while (*p) { + // -1 represents a decoding error, pretend it's untranslated ... + if (0 > (clen = UTF8_tab[*p])) return width; + if (width < (cnum += utf8_cols(p, clen))) break; + p += clen; + } + } + return (int)((const char *)p - str); +} // end: utf8_embody + + + /* + * Like the regular justify_pad routine but this guy + * can accommodate the multi-byte translated strings */ +static const char *utf8_justify (const char *str, int width, int justr) { + static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s"; + static char buf[SCREENMAX]; + char tmp[SCREENMAX]; + + snprintf(tmp, sizeof(tmp), "%.*s", utf8_embody(str, width), str); + width += utf8_delta(tmp); + snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, tmp, COLPADSTR); + return buf; +} // end: utf8_justify + + + /* + * Returns a physical or logical column number given a + * multi-byte string and a target column value */ +static int utf8_proper_col (const char *str, int col, int tophysical) { + const unsigned char *p = (const unsigned char *)str; + int clen, tlen = 0, cnum = 0; + + while (*p) { + // -1 represents a decoding error, don't encourage repositioning ... + if (0 > (clen = UTF8_tab[*p])) return col; + if (cnum + 1 > col && tophysical) break; + p += clen; + tlen += clen; + if (tlen > col && !tophysical) break; + ++cnum; + } + return tophysical ? tlen : cnum; +} // end: utf8_proper_col + +/*###### Misc Color/Display support ####################################*/ + + /* + * Make the appropriate caps/color strings for a window/field group. + * note: we avoid the use of background color so as to maximize + * compatibility with the user's xterm settings */ +static void capsmk (WIN_t *q) { + /* macro to test if a basic (non-color) capability is valid + thanks: Floyd Davidson <floyd@ptialaska.net> */ + #define tIF(s) s ? s : "" + /* macro to make compatible with netbsd-curses too + thanks: rofl0r <retnyg@gmx.net> */ + #define tPM(a,b) tparm(a, b, 0, 0, 0, 0, 0, 0, 0, 0) + static int capsdone = 0; + + // we must NOT disturb our 'empty' terminfo strings! + if (Batch) return; + + // these are the unchangeable puppies, so we only do 'em once + if (!capsdone) { + STRLCPY(Cap_clr_eol, tIF(clr_eol)) + STRLCPY(Cap_clr_eos, tIF(clr_eos)) + STRLCPY(Cap_clr_scr, tIF(clear_screen)) + // due to the leading newline, the following must be used with care + snprintf(Cap_nl_clreos, sizeof(Cap_nl_clreos), "\n%s", tIF(clr_eos)); + STRLCPY(Cap_curs_huge, tIF(cursor_visible)) + STRLCPY(Cap_curs_norm, tIF(cursor_normal)) + STRLCPY(Cap_curs_hide, tIF(cursor_invisible)) + STRLCPY(Cap_home, tIF(cursor_home)) + STRLCPY(Cap_norm, tIF(exit_attribute_mode)) + STRLCPY(Cap_reverse, tIF(enter_reverse_mode)) +#ifndef RMAN_IGNORED + if (!eat_newline_glitch) { + STRLCPY(Cap_rmam, tIF(exit_am_mode)) + STRLCPY(Cap_smam, tIF(enter_am_mode)) + if (!*Cap_rmam || !*Cap_smam) { + *Cap_rmam = '\0'; + *Cap_smam = '\0'; + if (auto_right_margin) + Cap_avoid_eol = 1; + } + putp(Cap_rmam); + } +#endif + snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm, tIF(orig_pair)); + snprintf(Caps_endline, sizeof(Caps_endline), "%s%s", Caps_off, Cap_clr_eol); + if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1; + capsdone = 1; + } + + /* the key to NO run-time costs for configurable colors -- we spend a + little time with the user now setting up our terminfo strings, and + the job's done until he/she/it has a change-of-heart */ + STRLCPY(q->cap_bold, CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode)) + if (CHKw(q, Show_COLORS) && max_colors > 0) { + STRLCPY(q->capclr_sum, tPM(set_a_foreground, q->rc.summclr)) + snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s" + , tPM(set_a_foreground, q->rc.msgsclr), Cap_reverse); + snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s" + , tPM(set_a_foreground, q->rc.msgsclr), q->cap_bold); + snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s" + , tPM(set_a_foreground, q->rc.headclr), Cap_reverse); + snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s" + , Caps_off, tPM(set_a_foreground, q->rc.taskclr)); + } else { + q->capclr_sum[0] = '\0'; +#ifdef USE_X_COLHDR + snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s" + , Cap_reverse, q->cap_bold); +#else + STRLCPY(q->capclr_msg, Cap_reverse) +#endif + STRLCPY(q->capclr_pmt, q->cap_bold) + STRLCPY(q->capclr_hdr, Cap_reverse) + STRLCPY(q->capclr_rownorm, Cap_norm) + } + + // composite(s), so we do 'em outside and after the if + snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s" + , q->capclr_rownorm, CHKw(q, Show_HIBOLD) ? q->cap_bold : Cap_reverse); + #undef tIF + #undef tPM +} // end: capsmk + + +static struct msg_node { + char msg[SMLBUFSIZ]; + struct msg_node *prev; +} Msg_tab[BOT_MSGSMAX]; + +static struct msg_node *Msg_this = Msg_tab; + + /* + * Show an error message (caller may include '\a' for sound) */ +static void show_msg (const char *str) { + STRLCPY(Msg_this->msg, str); + if (++Msg_this > &Msg_tab[BOT_MSGSMAX - 1]) Msg_this = Msg_tab; + + PUTT("%s%s %.*s %s%s%s" + , tg2(0, Msg_row) + , Curwin->capclr_msg + , utf8_embody(str, Screen_cols - 2) + , str + , Cap_curs_hide + , Caps_off + , Cap_clr_eol); + fflush(stdout); + usleep(MSG_USLEEP); +} // end: show_msg + + + /* + * Show an input prompt + larger cursor (if possible) */ +static int show_pmt (const char *str) { + char buf[MEDBUFSIZ]; + int len; + + snprintf(buf, sizeof(buf), "%.*s", utf8_embody(str, Screen_cols - 2), str); + len = utf8_delta(buf); +#ifdef PRETENDNOCAP + PUTT("\n%s%s%.*s %s%s%s" +#else + PUTT("%s%s%.*s %s%s%s" +#endif + , tg2(0, Msg_row) + , Curwin->capclr_pmt + , (Screen_cols - 2) + len + , buf + , Cap_curs_huge + , Caps_off + , Cap_clr_eol); + fflush(stdout); + len = strlen(buf) - len; + // +1 for the space we added or -1 for the cursor... + return (len + 1 < Screen_cols) ? len + 1 : Screen_cols - 1; +} // end: show_pmt + + + /* + * Create and print the optional scroll coordinates message */ +static void show_scroll (void) { + char tmp1[SMLBUFSIZ]; +#ifndef SCROLLVAR_NO + char tmp2[SMLBUFSIZ]; +#endif + int totpflgs = Curwin->totpflgs; + int begpflgs = Curwin->begpflg + 1; + +#ifndef USE_X_COLHDR + if (CHKw(Curwin, Show_HICOLS)) { + totpflgs -= 2; + if (ENUpos(Curwin, Curwin->rc.sortindx) < Curwin->begpflg) begpflgs -= 2; + } +#endif + if (1 > totpflgs) totpflgs = 1; + if (1 > begpflgs) begpflgs = 1; + snprintf(tmp1, sizeof(tmp1), N_fmt(SCROLL_coord_fmt), Curwin->begtask + 1, PIDSmaxt, begpflgs, totpflgs); +#ifndef SCROLLVAR_NO + if (Curwin->varcolbeg) { + snprintf(tmp2, sizeof(tmp2), " + %d", Curwin->varcolbeg); + scat(tmp1, tmp2); + } +#endif + PUTT("%s%s %.*s%s", tg2(0, Msg_row), Caps_off, Screen_cols - 3, tmp1, Cap_clr_eol); +} // end: show_scroll + + + /* + * Show lines with specially formatted elements, but only output + * what will fit within the current screen width. + * Our special formatting consists of: + * "some text <_delimiter_> some more text <_delimiter_>...\n" + * Where <_delimiter_> is a two byte combination consisting of a + * tilde followed by an ascii digit in the range of 1 - 8. + * examples: ~1, ~5, ~8, etc. + * The tilde is effectively stripped and the next digit + * converted to an index which is then used to select an + * 'attribute' from a capabilities table. That attribute + * is then applied to the *preceding* substring. + * Once recognized, the delimiter is replaced with a null character + * and viola, we've got a substring ready to output! Strings or + * substrings without delimiters will receive the Cap_norm attribute. + * + * Caution: + * This routine treats all non-delimiter bytes as displayable + * data subject to our screen width marching orders. If callers + * embed non-display data like tabs or terminfo strings in our + * glob, a line will truncate incorrectly at best. Worse case + * would be truncation of an embedded tty escape sequence. + * + * Tabs must always be avoided or our efforts are wasted and + * lines will wrap. To lessen but not eliminate the risk of + * terminfo string truncation, such non-display stuff should + * be placed at the beginning of a "short" line. */ +static void show_special (int interact, const char *glob) { + /* note: the following is for documentation only, + the real captab is now found in a group's WIN_t ! + +------------------------------------------------------+ + | char *captab[] = { : Cap's = Index | + | Cap_norm, Cap_norm, = \000, \001, | + | cap_bold, capclr_sum, = \002, \003, | + | capclr_msg, capclr_pmt, = \004, \005, | + | capclr_hdr, = \006, | + | capclr_rowhigh, = \007, | + | capclr_rownorm }; = \010 [octal!] | + +------------------------------------------------------+ */ + /* ( Pssst, after adding the termcap transitions, row may ) + ( exceed 300+ bytes, even in an 80x24 terminal window! ) + ( Shown here are the former buffer size specifications ) + ( char tmp[SMLBUFSIZ], lin[MEDBUFSIZ], row[LRGBUFSIZ]. ) + ( So now we use larger buffers and a little protection ) + ( against overrunning them with this 'lin_end - glob'. ) + + ( That was uncovered during 'Inspect' development when ) + ( this guy was being considered for a supporting role. ) + ( However, such an approach was abandoned. As a result ) + ( this function is called only with a glob under top's ) + ( control and never containing any 'raw/binary' chars! ) */ + char tmp[LRGBUFSIZ], lin[LRGBUFSIZ], row[ROWMINSIZ]; + char *rp, *lin_end, *sub_beg, *sub_end; + int room; + + // handle multiple lines passed in a bunch + while ((lin_end = strchr(glob, '\n'))) { + #define myMIN(a,b) (((a) < (b)) ? (a) : (b)) + size_t lessor = myMIN((size_t)(lin_end - glob), sizeof(lin) -3); + + // create a local copy we can extend and otherwise abuse + memcpy(lin, glob, lessor); + // zero terminate this part and prepare to parse substrings + lin[lessor] = '\0'; + room = Screen_cols; + sub_beg = sub_end = lin; + *(rp = row) = '\0'; + + while (*sub_beg) { + int ch = *sub_end; + if ('~' == ch) ch = *(sub_end + 1) - '0'; + switch (ch) { + case 0: // no end delim, captab makes normal + // only possible when '\n' was NOT preceded with a '~#' sequence + // ( '~1' thru '~8' is valid range, '~0' is never actually used ) + *(sub_end + 1) = '\0'; // extend str end, then fall through + *(sub_end + 2) = '\0'; // ( +1 optimization for usual path ) + // fall through + case 1: case 2: case 3: case 4: + case 5: case 6: case 7: case 8: + *sub_end = '\0'; + snprintf(tmp, sizeof(tmp), "%s%.*s%s" + , Curwin->captab[ch], utf8_embody(sub_beg, room), sub_beg, Caps_off); + rp = scat(rp, tmp); + room -= (sub_end - sub_beg); + room += utf8_delta(sub_beg); + sub_beg = (sub_end += 2); + break; + default: // nothin' special, just text + ++sub_end; + } + if (0 >= room) break; // skip substrings that won't fit + } + + if (interact) PUTT("%s%s\n", row, Cap_clr_eol); + else PUFF("%s%s\n", row, Caps_endline); + glob = ++lin_end; // point to next line (maybe) + + #undef myMIN + } // end: while 'lines' + + /* If there's anything left in the glob (by virtue of no trailing '\n'), + it probably means caller wants to retain cursor position on this final + line. That, in turn, means we're interactive and so we'll just do our + 'fit-to-screen' thingy while also leaving room for the cursor... */ + if (*glob) PUTT("%.*s", utf8_embody(glob, Screen_cols - 1), glob); +} // end: show_special + +/*###### Low Level Memory/Keyboard/File I/O support ####################*/ + + /* + * Handle our own memory stuff without the risk of leaving the + * user's terminal in an ugly state should things go sour. */ + +static void *alloc_c (size_t num) { + void *pv; + + if (!num) ++num; + if (!(pv = calloc(1, num))) + error_exit(N_txt(FAIL_alloc_c_txt)); + return pv; +} // end: alloc_c + + +static void *alloc_r (void *ptr, size_t num) { + void *pv; + + if (!num) ++num; + if (!(pv = realloc(ptr, num))) + error_exit(N_txt(FAIL_alloc_r_txt)); + return pv; +} // end: alloc_r + + +static char *alloc_s (const char *str) { + return strcpy(alloc_c(strlen(str) +1), str); +} // end: alloc_s + + + /* + * An 'I/O available' routine which will detect raw single byte | + * unsolicited keyboard input which was susceptible to SIGWINCH | + * interrupt (or any other signal). He'll also support timeout | + * in the absence of any user keystrokes or a signal interrupt. | */ +static inline int ioa (struct timespec *ts) { + fd_set fs; + int rc; + + FD_ZERO(&fs); + FD_SET(STDIN_FILENO, &fs); + +#ifdef SIGNALS_LESS // conditional comments are silly, but help in documenting + // hold here until we've got keyboard input, any signal except SIGWINCH + // or (optionally) we timeout with nanosecond granularity +#else + // hold here until we've got keyboard input, any signal (including SIGWINCH) + // or (optionally) we timeout with nanosecond granularity +#endif + rc = pselect(STDIN_FILENO + 1, &fs, NULL, NULL, ts, &Sigwinch_set); + + if (rc < 0) rc = 0; + return rc; +} // end: ioa + + + /* + * This routine isolates ALL user INPUT and ensures that we + * won't be mixing I/O from stdio and low-level read() requests */ +static int ioch (int ech, char *buf, unsigned cnt) { + int rc = -1; + +#ifdef TERMIOS_ONLY + if (ech) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_tweaked); + rc = read(STDIN_FILENO, buf, cnt); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_raw); + } else { + if (ioa(NULL)) + rc = read(STDIN_FILENO, buf, cnt); + } +#else + (void)ech; + if (ioa(NULL)) + rc = read(STDIN_FILENO, buf, cnt); +#endif + + // zero means EOF, might happen if we erroneously get detached from terminal + if (0 == rc) bye_bye(NULL); + + // it may have been the beginning of a lengthy escape sequence + tcflush(STDIN_FILENO, TCIFLUSH); + + // note: we do NOT produce a valid 'string' + return rc; +} // end: ioch + + +#define IOKEY_INIT 0 +#define IOKEY_ONCE 1 +#define IOKEY_NEXT 2 + + /* + * Support for single or multiple keystroke input AND + * escaped cursor motion keys. + * note: we support more keys than we currently need, in case + * we attract new consumers in the future */ +static int iokey (int action) { + static struct { + const char *str; + int key; + } tinfo_tab[] = { + { NULL, kbd_BKSP }, { NULL, kbd_INS }, { NULL, kbd_DEL }, { NULL, kbd_LEFT }, + { NULL, kbd_DOWN }, { NULL, kbd_UP }, { NULL, kbd_RIGHT }, { NULL, kbd_HOME }, + { NULL, kbd_PGDN }, { NULL, kbd_PGUP }, { NULL, kbd_END }, { NULL, kbd_BTAB }, + // remainder are alternatives for above, just in case... + // ( the h,j,k,l entries are the vim cursor motion keys ) + { "\b", kbd_BKSP }, { "\177", kbd_BKSP }, /* backspace */ + { "\033h", kbd_LEFT }, { "\033j", kbd_DOWN }, /* meta+ h,j */ + { "\033k", kbd_UP }, { "\033l", kbd_RIGHT }, /* meta+ k,l */ + { "\033\010", kbd_HOME }, { "\033\012", kbd_PGDN }, /* ctrl+meta+ h,j */ + { "\033\013", kbd_PGUP }, { "\033\014", kbd_END }, /* ctrl+meta+ k,l */ + { "\xC3\xA8", kbd_LEFT }, { "\xC3\xAA", kbd_DOWN }, /* meta+ h,j (some xterms) */ + { "\xC3\xAB", kbd_UP }, { "\xC3\xAC", kbd_RIGHT }, /* meta+ k,l (some xterms) */ + { "\xC2\x88", kbd_HOME }, { "\xC2\x8A", kbd_PGDN }, /* ctrl+meta+ h,j (some xterms) */ + { "\xC2\x8B", kbd_PGUP }, { "\xC2\x8C", kbd_END }, /* ctrl+meta+ k,l (some xterms) */ + { "\033\011", kbd_BTAB } + }; + static char erase[2]; +#ifdef TERMIOS_ONLY + char buf[SMLBUFSIZ], *pb; +#else + static char buf[MEDBUFSIZ]; + static int pos, len; + char *pb; +#endif + int i; + + if (action == IOKEY_INIT) { +#ifndef _POSIX_VDISABLE +#define _POSIX_VDISABLE fpathconf(STDIN_FILENO, _PC_VDISABLE) +#endif + if (Tty_original.c_cc[VERASE] == _POSIX_VDISABLE) + tinfo_tab[0].str = key_backspace; + else { + *erase = Tty_original.c_cc[VERASE]; + tinfo_tab[0].str = erase; + } + tinfo_tab[1].str = key_ic; + tinfo_tab[2].str = key_dc; + tinfo_tab[3].str = key_left; + tinfo_tab[4].str = key_down; + tinfo_tab[5].str = key_up; + tinfo_tab[6].str = key_right; + tinfo_tab[7].str = key_home; + tinfo_tab[8].str = key_npage; + tinfo_tab[9].str = key_ppage; + tinfo_tab[10].str = key_end; + tinfo_tab[11].str = back_tab; + // next is critical so returned results match bound terminfo keys + if (keypad_xmit) + putp(keypad_xmit); + // ( converse keypad_local issued at pause/pgm end, just in case ) + return 0; + } + + if (action == IOKEY_ONCE) { + memset(buf, '\0', sizeof(buf)); + if (1 > ioch(0, buf, sizeof(buf)-1)) return 0; + } + +#ifndef TERMIOS_ONLY + if (action == IOKEY_NEXT) { + if (pos < len) + return buf[pos++]; // exhaust prior keystrokes + pos = len = 0; + memset(buf, '\0', sizeof(buf)); + if (1 > ioch(0, buf, sizeof(buf)-1)) return 0; + if (!iscntrl(buf[0])) { // no need for translation + len = strlen(buf); + pos = 1; + return buf[0]; + } + } +#endif + + /* some emulators implement 'key repeat' too well and we get duplicate + key sequences -- so we'll focus on the last escaped sequence, while + also allowing use of the meta key... */ + if (!(pb = strrchr(buf, '\033'))) pb = buf; + else if (pb > buf && '\033' == *(pb - 1)) --pb; + + for (i = 0; i < MAXTBL(tinfo_tab); i++) + if (tinfo_tab[i].str && !strcmp(tinfo_tab[i].str, pb)) + return tinfo_tab[i].key; + + // no match, so we'll return single non-escaped keystrokes only + if (buf[0] == '\033' && buf[1]) return -1; + return buf[0]; +} // end: iokey + + +#ifdef TERMIOS_ONLY + /* + * Get line oriented interactive input from the user, + * using native tty support */ +static char *ioline (const char *prompt) { + static const char ws[] = "\b\f\n\r\t\v\x1b\x9b"; // 0x1b + 0x9b are escape + static char buf[MEDBUFSIZ]; + char *p; + + show_pmt(prompt); + memset(buf, '\0', sizeof(buf)); + ioch(1, buf, sizeof(buf)-1); + + if ((p = strpbrk(buf, ws))) *p = '\0'; + // note: we DO produce a valid 'string' + return buf; +} // end: ioline + +#else + /* + * Get some line oriented interactive input from the ol' user, + * going way, way beyond that native tty support by providing: + * . true input line editing, not just a destructive backspace + * . an input limit sensitive to the current screen dimensions + * . an ability to recall prior strings for editing & re-input */ +static char *ioline (const char *prompt) { + #define setLEN ( len = strlen(buf) - utf8_delta(buf) ) + #define setPOS(X) ( pos = utf8_embody(buf, X) ) + #define utfCHR(X) ( (unsigned char *)&buf[X] ) + #define utfTOT(X) ( UTF8_tab[(unsigned char)buf[X]] ) + #define utfCOL(X) ( utf8_cols(utfCHR(X), utfTOT(X)) ) + #define movBKW { setPOS(cur - 1); while (utfTOT(pos) < 0) --pos; } + #define chkCUR { if (cur < 0) cur = 0; if (cur > len) cur = len; } + // thank goodness ol' memmove will safely allow strings to overlap + #define sqzSTR { i = utfTOT(pos); while (i < 0) i = utfTOT(--pos); \ + if (!utfCOL(pos + i)) i += utfTOT(pos + i); \ + memmove(&buf[pos], &buf[pos + i], bufMAX-(pos + i)); \ + memset(&buf[bufMAX - i], '\0', i); } + #define expSTR(X) if (bufNXT < bufMAX && scrNXT < Screen_cols) { \ + memmove(&buf[pos + X], &buf[pos], bufMAX - pos); } + #define savMAX 50 + #define bufNXT ( pos + 4 ) // four equals longest utf8 str + #define scrNXT ( beg + len + 2 ) // two due to multi-column char + #define bufMAX ((int)sizeof(buf)-2) // -1 for '\0' string delimiter + static char buf[MEDBUFSIZ+1]; // +1 for '\0' string delimiter + static int ovt; + int beg, // the physical column where input began, buf[0] + cur, // the logical current column/insertion position + len, // the logical input length, thus the end column + pos, // the physical position in the buffer currently + key, i; + struct lin_s { + struct lin_s *bkw; // pointer for older saved strs + struct lin_s *fwd; // pointer for newer saved strs + char *str; // an actual saved input string + }; + static struct lin_s *anchor, *plin; + + if (!anchor) { + anchor = alloc_c(sizeof(struct lin_s)); + anchor->str = alloc_s(""); // the top-of-stack (empty str) + } + plin = anchor; + cur = len = pos = 0; + beg = show_pmt(prompt); + memset(buf, '\0', sizeof(buf)); + // this may not work under a gui emulator (but linux console is ok) + putp(ovt ? Cap_curs_huge : Cap_curs_norm); + + do { + fflush(stdout); + key = iokey(IOKEY_NEXT); + switch (key) { + case 0: + buf[0] = '\0'; + return buf; + case kbd_ESC: + buf[0] = kbd_ESC; + return buf; + case kbd_ENTER: + case kbd_BTAB: case kbd_PGUP: case kbd_PGDN: + continue; + case kbd_INS: + ovt = !ovt; + putp(ovt ? Cap_curs_huge : Cap_curs_norm); + break; + case kbd_DEL: + sqzSTR + break; + case kbd_BKSP: + if (0 < cur) { movBKW; cur -= utfCOL(pos); setPOS(cur); sqzSTR; } + break; + case kbd_LEFT: + if (0 < cur) { movBKW; cur -= utfCOL(pos); } + break; + case kbd_RIGHT: + if (cur < len) cur += utfCOL(pos); + break; + case kbd_HOME: + cur = pos = 0; + break; + case kbd_END: + cur = len; + pos = strlen(buf); + break; + case kbd_UP: + if (plin->bkw) { + plin = plin->bkw; + memset(buf, '\0', sizeof(buf)); + memccpy(buf, plin->str, '\0', bufMAX); + cur = setLEN; + pos = strlen(buf); + } + break; + case kbd_DOWN: + if (plin->fwd) plin = plin->fwd; + memset(buf, '\0', sizeof(buf)); + memccpy(buf, plin->str, '\0', bufMAX); + cur = setLEN; + pos = strlen(buf); + break; + default: // what we REALLY wanted (maybe) + if (bufNXT < bufMAX && scrNXT < Screen_cols && strlen(buf) < bufMAX) { + int tot = UTF8_tab[(unsigned char)key], + sav = pos; + if (tot < 1) tot = 1; + if (!ovt) { expSTR(tot); } + else { pos = utf8_embody(buf, cur); sqzSTR; expSTR(tot); } + buf[pos++] = key; + while (tot > 1) { + key = iokey(IOKEY_NEXT); + buf[pos++] = key; + --tot; + } + cur += utfCOL(sav); + } + break; + } + setLEN; + chkCUR; + setPOS(cur); + putp(fmtmk("%s%s%s", tg2(beg, Msg_row), Cap_clr_eol, buf)); +#ifdef OVERTYPE_SEE + putp(fmtmk("%s%c", tg2(beg - 1, Msg_row), ovt ? '^' : ' ')); +#endif + putp(tg2(beg + cur, Msg_row)); + } while (key != kbd_ENTER); + + // weed out duplicates, including empty strings (top-of-stack)... + for (i = 0, plin = anchor; ; i++) { +#ifdef RECALL_FIXED + if (!STRCMP(plin->str, buf)) // if matched, retain original order + return buf; +#else + if (!STRCMP(plin->str, buf)) { // if matched, rearrange stack order + if (i > 1) { // but not null str or if already #2 + if (plin->bkw) // splice around this matched string + plin->bkw->fwd = plin->fwd; // if older exists link to newer + plin->fwd->bkw = plin->bkw; // newer linked to older or NULL + anchor->bkw->fwd = plin; // stick matched on top of former #2 + plin->bkw = anchor->bkw; // keep empty string at top-of-stack + plin->fwd = anchor; // then prepare to be the 2nd banana + anchor->bkw = plin; // by sliding us in below the anchor + } + return buf; + } +#endif + if (!plin->bkw) break; // let i equal total stacked strings + plin = plin->bkw; // ( with plin representing bottom ) + } + if (i < savMAX) + plin = alloc_c(sizeof(struct lin_s)); + else { // when a new string causes overflow + plin->fwd->bkw = NULL; // make next-to-last string new last + free(plin->str); // and toss copy but keep the struct + } + plin->str = alloc_s(buf); // copy user's new unique input line + plin->bkw = anchor->bkw; // keep empty string as top-of-stack + if (plin->bkw) // did we have some already stacked? + plin->bkw->fwd = plin; // yep, so point prior to new string + plin->fwd = anchor; // and prepare to be a second banana + anchor->bkw = plin; // by sliding it in as new number 2! + + return buf; // protect our copy, return original + #undef setLEN + #undef setPOS + #undef utfCHR + #undef utfTOT + #undef utfCOL + #undef movBKW + #undef chkCUR + #undef sqzSTR + #undef expSTR + #undef savMAX + #undef bufNXT + #undef scrNXT + #undef bufMAX +} // end: ioline +#endif + + + /* + * Make locale unaware float (but maybe restrict to whole numbers). */ +static int mkfloat (const char *str, float *num, int whole) { + char tmp[SMLBUFSIZ], *ep; + + if (whole) { + *num = (float)strtol(str, &ep, 0); + if (ep != str && *ep == '\0' && *num < INT_MAX) + return 1; + return 0; + } + snprintf(tmp, sizeof(tmp), "%s", str); + *num = strtof(tmp, &ep); + if (*ep != '\0') { + // fallback - try to swap the floating point separator + if (*ep == '.') *ep = ','; + else if (*ep == ',') *ep = '.'; + *num = strtof(tmp, &ep); + } + if (ep != tmp && *ep == '\0' && *num < INT_MAX) + return 1; + return 0; +} // end: mkfloat + + + /* + * This routine provides the i/o in support of files whose size + * cannot be determined in advance. Given a stream pointer, he'll + * try to slurp in the whole thing and return a dynamically acquired + * buffer supporting that single string glob. + * + * He always creates a buffer at least READMINSZ big, possibly + * all zeros (an empty string), even if the file wasn't read. */ +static int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread) { + char chunk[4096*16]; + size_t num; + + *bread = 0; + *bsize = READMINSZ; + *baddr = alloc_c(READMINSZ); + if (fp) { + while (0 < (num = fread(chunk, 1, sizeof(chunk), fp))) { + *baddr = alloc_r(*baddr, num + *bsize); + memcpy(*baddr + *bread, chunk, num); + *bread += num; + *bsize += num; + }; + *(*baddr + *bread) = '\0'; + return ferror(fp); + } + return ENOENT; +} // end: readfile + +/*###### Small Utility routines ########################################*/ + +#define GET_NUM_BAD INT_MIN +#define GET_NUM_ESC (INT_MIN + 1) +#define GET_NUM_NOT (INT_MIN + 2) + + /* + * Get a float from the user */ +static float get_float (const char *prompt) { + char *line; + float f; + + line = ioline(prompt); + if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC; + if (!line[0]) return GET_NUM_NOT; + // note: we're not allowing negative floats + if (!mkfloat(line, &f, 0) || f < 0) { + show_msg(N_txt(BAD_numfloat_txt)); + return GET_NUM_BAD; + } + return f; +} // end: get_float + + + /* + * Get an integer from the user, returning INT_MIN for error */ +static int get_int (const char *prompt) { + char *line; + float f; + + line = ioline(prompt); + if (line[0] == kbd_ESC || Frames_signal) return GET_NUM_ESC; + if (!line[0]) return GET_NUM_NOT; + // note: we've got to allow negative ints (renice) + if (!mkfloat(line, &f, 1)) { + show_msg(N_txt(BAD_integers_txt)); + return GET_NUM_BAD; + } + return (int)f; +} // end: get_int + + + /* + * Make a hex value, and maybe suppress zeroes. */ +static inline const char *hex_make (long num, int noz) { + static char buf[SMLBUFSIZ]; + int i; + +#ifdef CASEUP_HEXES + snprintf(buf, sizeof(buf), "%08lX", num); +#else + snprintf(buf, sizeof(buf), "%08lx", num); +#endif + if (noz) + for (i = 0; buf[i]; i++) + if ('0' == buf[i]) + buf[i] = '.'; + return buf; +} // end: hex_make + + + /* + * Validate the passed string as a user name or number, + * and/or update the window's 'u/U' selection stuff. */ +static const char *user_certify (WIN_t *q, const char *str, char typ) { + struct passwd *pwd; + char *endp; + uid_t num; + + Monpidsidx = 0; + q->usrseltyp = 0; + q->usrselflg = 1; + if (*str) { + if ('!' == *str) { ++str; q->usrselflg = 0; } + num = (uid_t)strtoul(str, &endp, 0); + if ('\0' == *endp) { + pwd = getpwuid(num); + if (!pwd) { + /* allow foreign users, from e.g within chroot + ( thanks Dr. Werner Fink <werner@suse.de> ) */ + q->usrseluid = num; + q->usrseltyp = typ; + return NULL; + } + } else + pwd = getpwnam(str); + if (!pwd) return N_txt(BAD_username_txt); + q->usrseluid = pwd->pw_uid; + q->usrseltyp = typ; + } + return NULL; +} // end: user_certify + +/*###### Basic Formatting support ######################################*/ + + /* + * Just do some justify stuff, then add post column padding. */ +static inline const char *justify_pad (const char *str, int width, int justr) { + static char l_fmt[] = "%-*.*s%s", r_fmt[] = "%*.*s%s"; + static char buf[SCREENMAX]; + + snprintf(buf, sizeof(buf), justr ? r_fmt : l_fmt, width, width, str, COLPADSTR); + return buf; +} // end: justify_pad + + + /* + * Make and then justify a single character. */ +static inline const char *make_chr (const char ch, int width, int justr) { + static char buf[SMLBUFSIZ]; + + snprintf(buf, sizeof(buf), "%c", ch); + return justify_pad(buf, width, justr); +} // end: make_chr + + + /* + * Make and then justify an integer NOT subject to scaling, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_num (long num, int width, int justr, int col, int noz) { + static char buf[SMLBUFSIZ]; + + buf[0] = '\0'; + if (noz && Rc.zero_suppress && 0 == num) + goto end_justifies; + + if (width < snprintf(buf, sizeof(buf), "%ld", num)) { + if (width <= 0 || (size_t)width >= sizeof(buf)) + width = sizeof(buf)-1; + buf[width-1] = COLPLUSCH; + buf[width] = '\0'; + AUTOX_COL(col); + } +end_justifies: + return justify_pad(buf, width, justr); +} // end: make_num + + + /* + * Make and then justify a character string, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_str (const char *str, int width, int justr, int col) { + static char buf[SCREENMAX]; + + if (width < snprintf(buf, sizeof(buf), "%s", str)) { + if (width <= 0 || (size_t)width >= sizeof(buf)) + width = sizeof(buf)-1; + buf[width-1] = COLPLUSCH; + buf[width] = '\0'; + AUTOX_COL(col); + } + return justify_pad(buf, width, justr); +} // end: make_str + + + /* + * Make and then justify a potentially multi-byte character string, + * and include a visual clue should tuncation be necessary. */ +static inline const char *make_str_utf8 (const char *str, int width, int justr, int col) { + static char buf[SCREENMAX]; + int delta = utf8_delta(str); + + if (width + delta < snprintf(buf, sizeof(buf), "%s", str)) { + snprintf(buf, sizeof(buf), "%.*s%c", utf8_embody(str, width-1), str, COLPLUSCH); + delta = utf8_delta(buf); + AUTOX_COL(col); + } + return justify_pad(buf, width + delta, justr); +} // end: make_str_utf8 + + + /* + * Do some scaling then justify stuff. + * We'll interpret 'num' as a kibibytes quantity and try to + * format it to reach 'target' while also fitting 'width'. */ +static const char *scale_mem (int target, float num, int width, int justr) { + // SK_Kb SK_Mb SK_Gb SK_Tb SK_Pb SK_Eb +#ifdef BOOST_MEMORY + static const char *fmttab[] = { "%.0f", "%#.1f%c", "%#.3f%c", "%#.3f%c", "%#.3f%c", NULL }; +#else + static const char *fmttab[] = { "%.0f", "%.1f%c", "%.1f%c", "%.1f%c", "%.1f%c", NULL }; +#endif + static char buf[SMLBUFSIZ]; + char *psfx; + int i; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + + for (i = SK_Kb, psfx = Scaled_sfxtab; i < SK_Eb; psfx++, i++) { + if (i >= target + && (width >= snprintf(buf, sizeof(buf), fmttab[i], num, *psfx))) + goto end_justifies; + num /= 1024.0; + } + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_mem + + + /* + * Do some scaling then justify stuff. */ +static const char *scale_num (float num, int width, int justr) { + static char buf[SMLBUFSIZ]; + char *psfx; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%.0f", num)) + goto end_justifies; + + for (psfx = Scaled_sfxtab; 0 < *psfx; psfx++) { + num /= 1024.0; + if (width >= snprintf(buf, sizeof(buf), "%.1f%c", num, *psfx)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%.0f%c", num, *psfx)) + goto end_justifies; + } + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_num + + + /* + * Make and then justify a percentage, with decreasing precision. */ +static const char *scale_pcnt (float num, int width, int justr, int xtra) { + static char buf[SMLBUFSIZ]; + + buf[0] = '\0'; + if (Rc.zero_suppress && 0 >= num) + goto end_justifies; + if (xtra) { + if (width >= snprintf(buf, sizeof(buf), "%#.3f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%#.2f", num)) + goto end_justifies; + goto carry_on; + } +#ifdef BOOST_PERCNT + if (width >= snprintf(buf, sizeof(buf), "%#.3f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%#.2f", num)) + goto end_justifies; + (void)xtra; +#endif +carry_on: + if (width >= snprintf(buf, sizeof(buf), "%#.1f", num)) + goto end_justifies; + if (width >= snprintf(buf, sizeof(buf), "%*.0f", width, num)) + goto end_justifies; + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); +end_justifies: + return justify_pad(buf, width, justr); +} // end: scale_pcnt + + +#define TICS_AS_SECS 0 +#define TICS_AS_MINS 1 +#define TICS_AS_HOUR 2 +#define TICS_AS_DAY1 3 +#define TICS_AS_DAY2 4 +#define TICS_AS_WEEK 5 +#define TICS_AS_LAST 6 + + /* + * Do some scaling stuff. + * Try to format 'tics' to reach 'target' while also + * fitting in 'width', then justify it. */ +static const char *scale_tics (TIC_t tics, int width, int justr, int target) { +#ifdef CASEUP_SUFIX + #define HH "%luH" // nls_maybe + #define DD "%luD" + #define WW "%luW" +#else + #define HH "%luh" // nls_maybe + #define DD "%lud" + #define WW "%luw" +#endif + static char buf[SMLBUFSIZ]; + TIC_t nt; // for speed on 64-bit +#ifdef SCALE_FORMER + unsigned long cc; // centiseconds + unsigned long nn; // multi-purpose whatever +#else + unsigned long cent, secs, mins, hour, days, week; +#endif + + buf[0] = '\0'; + nt = (tics * 100ull) / Hertz; // lots of room for any time + if (Rc.zero_suppress && 0 >= nt) + goto end_justifies; + +#ifdef SCALE_FORMER + cc = nt % 100; // centiseconds past second + nt /= 100; // total seconds + nn = nt % 60; // seconds past the minute + nt /= 60; // total minutes + if (target < TICS_AS_MINS + && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu.%02lu", nt, nn, cc))) + goto end_justifies; + if (target < TICS_AS_HOUR + && (width >= snprintf(buf, sizeof(buf), "%llu:%02lu", nt, nn))) + goto end_justifies; + nn = nt % 60; // minutes past the hour + nt /= 60; // total hours + if (width >= snprintf(buf, sizeof(buf), "%llu,%02lu", nt, nn)) + goto end_justifies; + nn = nt; // now also hours + if (width >= snprintf(buf, sizeof(buf), HH, nn)) + goto end_justifies; + nn /= 24; // now days + if (width >= snprintf(buf, sizeof(buf), DD, nn)) + goto end_justifies; + nn /= 7; // now weeks + if (width >= snprintf(buf, sizeof(buf), WW, nn)) + goto end_justifies; +#else + #define mmLIMIT 360 // arbitrary 6 hours + #define hhLIMIT 96 // arbitrary 4 days + #define ddLIMIT 14 // arbitrary 2 weeks + + cent = (nt % 100); // cent past secs + secs = (nt /= 100); // total secs + mins = (nt /= 60); // total mins + hour = (nt /= 60); // total hour + days = (nt /= 24); // total days + week = (nt / 7); // total week + + if (Rc.tics_scaled > target) + target += Rc.tics_scaled - target; + + switch (target) { + case TICS_AS_SECS: + if (mins < mmLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu.%02lu", mins, secs % 60, cent)) + goto end_justifies; + } + case TICS_AS_MINS: // fall through + if (mins < mmLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu:%02lu", mins, secs % 60)) + goto end_justifies; + } + case TICS_AS_HOUR: // fall through + if (hour < hhLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), "%lu,%02lu", hour, mins % 60)) + goto end_justifies; + } + case TICS_AS_DAY1: // fall through + if (days < ddLIMIT + 1) { + if (width >= snprintf(buf, sizeof(buf), DD "+" HH, days, hour % 24)) + goto end_justifies; +#ifdef SCALE_POSTFX + if (width >= snprintf(buf, sizeof(buf), DD "+%lu", days, hour % 24)) + goto end_justifies; +#endif + case TICS_AS_DAY2: // fall through + if (width >= snprintf(buf, sizeof(buf), DD, days)) + goto end_justifies; + } + case TICS_AS_WEEK: // fall through + if (width >= snprintf(buf, sizeof(buf), WW "+" DD, week, days % 7)) + goto end_justifies; +#ifdef SCALE_POSTFX + if (width >= snprintf(buf, sizeof(buf), WW "+%lu", week, days % 7)) + goto end_justifies; +#endif + case TICS_AS_LAST: // fall through + if (width >= snprintf(buf, sizeof(buf), WW, week)) + goto end_justifies; + default: // fall through + break; + } + #undef mmLIMIT + #undef hhLIMIT + #undef ddLIMIT +#endif + + // well shoot, this outta' fit... + snprintf(buf, sizeof(buf), "?"); + +end_justifies: + return justify_pad(buf, width, justr); + #undef HH + #undef DD + #undef WW +} // end: scale_tics + +/*###### Fields Management support #####################################*/ + + /* These are our gosh darn 'Fields' ! + They MUST be kept in sync with pflags !! */ +static struct { + int width; // field width, if applicable + int scale; // scaled target, if applicable + const int align; // the default column alignment flag + enum pids_item item; // the new libproc item enum identifier +} Fieldstab[] = { + // these identifiers reflect the default column alignment but they really + // contain the WIN_t flag used to check/change justification at run-time! + #define A_left Show_JRSTRS /* toggled with lower case 'j' */ + #define A_right Show_JRNUMS /* toggled with upper case 'J' */ + +/* .width anomalies: + a -1 width represents variable width columns + a 0 width represents columns set once at startup (see zap_fieldstab) + + .width .scale .align .item + ------ ------ -------- ------------------- */ + { 0, -1, A_right, PIDS_ID_PID }, // s_int EU_PID + { 0, -1, A_right, PIDS_ID_PPID }, // s_int EU_PPD + { 5, -1, A_right, PIDS_ID_EUID }, // u_int EU_UED + { 8, -1, A_left, PIDS_ID_EUSER }, // str EU_UEN + { 5, -1, A_right, PIDS_ID_RUID }, // u_int EU_URD + { 8, -1, A_left, PIDS_ID_RUSER }, // str EU_URN + { 5, -1, A_right, PIDS_ID_SUID }, // u_int EU_USD + { 8, -1, A_left, PIDS_ID_SUSER }, // str EU_USN + { 5, -1, A_right, PIDS_ID_EGID }, // u_int EU_GID + { 8, -1, A_left, PIDS_ID_EGROUP }, // str EU_GRP + { 0, -1, A_right, PIDS_ID_PGRP }, // s_int EU_PGD + { 8, -1, A_left, PIDS_TTY_NAME }, // str EU_TTY + { 0, -1, A_right, PIDS_ID_TPGID }, // s_int EU_TPG + { 0, -1, A_right, PIDS_ID_SESSION }, // s_int EU_SID + { 3, -1, A_right, PIDS_PRIORITY }, // s_int EU_PRI + { 3, -1, A_right, PIDS_NICE }, // s_int EU_NCE + { 3, -1, A_right, PIDS_NLWP }, // s_int EU_THD + { 0, -1, A_right, PIDS_PROCESSOR }, // s_int EU_CPN + { 5, -1, A_right, PIDS_TICS_ALL_DELTA }, // u_int EU_CPU + { 6, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TME + { 9, -1, A_right, PIDS_TICS_ALL }, // ull_int EU_TM2 + { 5, -1, A_right, PIDS_MEM_RES }, // ul_int EU_MEM + { 7, SK_Kb, A_right, PIDS_MEM_VIRT }, // ul_int EU_VRT + { 6, SK_Kb, A_right, PIDS_VM_SWAP }, // ul_int EU_SWP + { 6, SK_Kb, A_right, PIDS_MEM_RES }, // ul_int EU_RES + { 6, SK_Kb, A_right, PIDS_MEM_CODE }, // ul_int EU_COD + { 7, SK_Kb, A_right, PIDS_MEM_DATA }, // ul_int EU_DAT + { 6, SK_Kb, A_right, PIDS_MEM_SHR }, // ul_int EU_SHR + { 4, -1, A_right, PIDS_FLT_MAJ }, // ul_int EU_FL1 + { 4, -1, A_right, PIDS_FLT_MIN }, // ul_int EU_FL2 + { 4, -1, A_right, PIDS_noop }, // ul_int EU_DRT ( always 0 w/ since 2.6 ) + { 1, -1, A_right, PIDS_STATE }, // s_ch EU_STA + { -1, -1, A_left, PIDS_CMD }, // str EU_CMD + { 10, -1, A_left, PIDS_WCHAN_NAME }, // str EU_WCH + { 8, -1, A_left, PIDS_FLAGS }, // ul_int EU_FLG + { -1, -1, A_left, PIDS_CGROUP }, // str EU_CGR + { -1, -1, A_left, PIDS_SUPGIDS }, // str EU_SGD + { -1, -1, A_left, PIDS_SUPGROUPS }, // str EU_SGN + { 0, -1, A_right, PIDS_ID_TGID }, // s_int EU_TGD + { 5, -1, A_right, PIDS_OOM_ADJ }, // s_int EU_OOA + { 4, -1, A_right, PIDS_OOM_SCORE }, // s_int EU_OOM + { -1, -1, A_left, PIDS_ENVIRON }, // str EU_ENV + { 3, -1, A_right, PIDS_FLT_MAJ_DELTA }, // s_int EU_FV1 + { 3, -1, A_right, PIDS_FLT_MIN_DELTA }, // s_int EU_FV2 + { 6, SK_Kb, A_right, PIDS_VM_USED }, // ul_int EU_USE + { 10, -1, A_right, PIDS_NS_IPC }, // ul_int EU_NS1 + { 10, -1, A_right, PIDS_NS_MNT }, // ul_int EU_NS2 + { 10, -1, A_right, PIDS_NS_NET }, // ul_int EU_NS3 + { 10, -1, A_right, PIDS_NS_PID }, // ul_int EU_NS4 + { 10, -1, A_right, PIDS_NS_USER }, // ul_int EU_NS5 + { 10, -1, A_right, PIDS_NS_UTS }, // ul_int EU_NS6 + { 8, -1, A_left, PIDS_LXCNAME }, // str EU_LXC + { 6, SK_Kb, A_right, PIDS_VM_RSS_ANON }, // ul_int EU_RZA + { 6, SK_Kb, A_right, PIDS_VM_RSS_FILE }, // ul_int EU_RZF + { 6, SK_Kb, A_right, PIDS_VM_RSS_LOCKED }, // ul_int EU_RZL + { 6, SK_Kb, A_right, PIDS_VM_RSS_SHARED }, // ul_int EU_RZS + { -1, -1, A_left, PIDS_CGNAME }, // str EU_CGN + { 0, -1, A_right, PIDS_PROCESSOR_NODE }, // s_int EU_NMA + { 5, -1, A_right, PIDS_ID_LOGIN }, // s_int EU_LID + { -1, -1, A_left, PIDS_EXE }, // str EU_EXE + { 6, SK_Kb, A_right, PIDS_SMAP_RSS }, // ul_int EU_RSS + { 6, SK_Kb, A_right, PIDS_SMAP_PSS }, // ul_int EU_PSS + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_ANON }, // ul_int EU_PZA + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_FILE }, // ul_int EU_PZF + { 6, SK_Kb, A_right, PIDS_SMAP_PSS_SHMEM }, // ul_int EU_PZS + { 6, SK_Kb, A_right, PIDS_SMAP_PRV_TOTAL }, // ul_int EU_USS + { 6, -1, A_right, PIDS_IO_READ_BYTES }, // ul_int EU_IRB + { 5, -1, A_right, PIDS_IO_READ_OPS }, // ul_int EU_IRO + { 6, -1, A_right, PIDS_IO_WRITE_BYTES }, // ul_int EU_IWB + { 5, -1, A_right, PIDS_IO_WRITE_OPS }, // ul_int EU_IWO + { 5, -1, A_right, PIDS_AUTOGRP_ID }, // s_int EU_AGI + { 4, -1, A_right, PIDS_AUTOGRP_NICE }, // s_int EU_AGN + { 7, -1, A_right, PIDS_TICS_BEGAN }, // ull_int EU_TM3 + { 7, -1, A_right, PIDS_TIME_ELAPSED }, // real EU_TM4 + { 6, -1, A_right, PIDS_UTILIZATION }, // real EU_CUU + { 7, -1, A_right, PIDS_UTILIZATION_C }, // real EU_CUC + { 10, -1, A_right, PIDS_NS_CGROUP }, // ul_int EU_NS7 + { 10, -1, A_right, PIDS_NS_TIME } // ul_int EU_NS8 +#define eu_LAST EU_NS8 +// xtra Fieldstab 'pseudo pflag' entries for the newlib interface . . . . . . . +#define eu_CMDLINE eu_LAST +1 +#define eu_TICS_ALL_C eu_LAST +2 +#define eu_ID_FUID eu_LAST +3 +#define eu_CMDLINE_V eu_LAST +4 +#define eu_ENVIRON_V eu_LAST +5 +#define eu_TREE_HID eu_LAST +6 +#define eu_TREE_LVL eu_LAST +7 +#define eu_TREE_ADD eu_LAST +8 +#define eu_RESET eu_TREE_HID // demarcation for reset to zero (PIDS_extra) + , { -1, -1, -1, PIDS_CMDLINE } // str ( if Show_CMDLIN, eu_CMDLINE ) + , { -1, -1, -1, PIDS_TICS_ALL_C } // ull_int ( if Show_CTIMES, eu_TICS_ALL_C ) + , { -1, -1, -1, PIDS_ID_FUID } // u_int ( if a usrseltyp, eu_ID_FUID ) + , { -1, -1, -1, PIDS_CMDLINE_V } // strv ( if Ctrlk, eu_CMDLINE_V ) + , { -1, -1, -1, PIDS_ENVIRON_V } // strv ( if CtrlN, eu_ENVIRON_V ) + , { -1, -1, -1, PIDS_extra } // s_ch ( if Show_FOREST, eu_TREE_HID ) + , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_LVL ) + , { -1, -1, -1, PIDS_extra } // s_int ( if Show_FOREST, eu_TREE_ADD ) + #undef A_left + #undef A_right +}; + + + /* + * A calibrate_fields() *Helper* function which refreshes + * all that cached screen geometry plus related variables */ +static void adj_geometry (void) { + static size_t pseudo_max = 0; + static int w_set = 0, w_cols = 0, w_rows = 0; + struct winsize wz; + + Screen_cols = columns; // <term.h> + Screen_rows = lines; // <term.h> + + if (-1 != ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz) + && 0 < wz.ws_col && 0 < wz.ws_row) { + Screen_cols = wz.ws_col; + Screen_rows = wz.ws_row; + } + +#ifndef RMAN_IGNORED + // be crudely tolerant of crude tty emulators + if (Cap_avoid_eol) Screen_cols--; +#endif + + // we might disappoint some folks (but they'll deserve it) + if (Screen_cols > SCREENMAX) Screen_cols = SCREENMAX; + if (Screen_cols < W_MIN_COL) Screen_cols = W_MIN_COL; + + if (!w_set) { + if (Width_mode > 0) // -w with arg, we'll try to honor + w_cols = Width_mode; + else + if (Width_mode < 0) { // -w without arg, try environment + char *env_columns = getenv("COLUMNS"), + *env_lines = getenv("LINES"), + *ep; + if (env_columns && *env_columns) { + long t, tc = 0; + t = strtol(env_columns, &ep, 0); + if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tc = t; + if (0 < tc) w_cols = (int)tc; + } + if (env_lines && *env_lines) { + long t, tr = 0; + t = strtol(env_lines, &ep, 0); + if (!*ep && (t > 0) && (t <= 0x7fffffffL)) tr = t; + if (0 < tr) w_rows = (int)tr; + } + if (!w_cols) w_cols = SCREENMAX; + if (w_cols && w_cols < W_MIN_COL) w_cols = W_MIN_COL; + if (w_rows && w_rows < W_MIN_ROW) w_rows = W_MIN_ROW; + } + if (w_cols > SCREENMAX) w_cols = SCREENMAX; + w_set = 1; + } + + /* keep our support for output optimization in sync with current reality + note: when we're in Batch mode, we don't really need a Pseudo_screen + and when not Batch, our buffer will contain 1 extra 'line' since + Msg_row is never represented -- but it's nice to have some space + between us and the great-beyond... */ + if (Batch) { + if (w_cols) Screen_cols = w_cols; + Screen_rows = w_rows ? w_rows : INT_MAX; + Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ); + } else { + const int max_rows = INT_MAX / (sizeof(*Pseudo_screen) * ROWMAXSIZ); + if (w_cols && w_cols < Screen_cols) Screen_cols = w_cols; + if (w_rows && w_rows < Screen_rows) Screen_rows = w_rows; + if (Screen_rows < 0 || Screen_rows > max_rows) Screen_rows = max_rows; + Pseudo_size = (sizeof(*Pseudo_screen) * ROWMAXSIZ) * Screen_rows; + } + // we'll only grow our Pseudo_screen, never shrink it + if (pseudo_max < Pseudo_size) { + pseudo_max = Pseudo_size; + Pseudo_screen = alloc_r(Pseudo_screen, pseudo_max); + } + // ensure each row is repainted (just in case) + PSU_CLREOS(0); + + // prepare to customize potential cpu/memory graphs + if (Curwin->rc.double_up) { + int num = (Curwin->rc.double_up + 1); + int pfx = (Curwin->rc.double_up < 2) ? GRAPH_prefix_std : GRAPH_prefix_abv; + + Graph_cpus->length = (Screen_cols - (ADJOIN_space * Curwin->rc.double_up) - (num * (pfx + GRAPH_suffix))) / num; + if (Graph_cpus->length > GRAPH_length_max) Graph_cpus->length = GRAPH_length_max; + if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min; + +#ifdef TOG4_MEM_1UP + Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix)); +#else + Graph_mems->length = (Screen_cols - ADJOIN_space - (2 * (pfx + GRAPH_suffix))) / 2; +#endif + if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max; + if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min; + +#if !defined(TOG4_MEM_FIX) && !defined(TOG4_MEM_1UP) + if (num > 2) { + #define cpuGRAPH ( GRAPH_prefix_abv + Graph_cpus->length + GRAPH_suffix ) + #define nxtGRAPH ( cpuGRAPH + ADJOIN_space ) + int len = cpuGRAPH; + for (;;) { + if (len + nxtGRAPH > GRAPH_length_max) break; + len += nxtGRAPH; + } + len -= (GRAPH_prefix_abv + ADJOIN_space); + Graph_mems->length = len; + #undef cpuGRAPH + #undef nxtGRAPH + } +#endif + } else { + Graph_cpus->length = Screen_cols - (GRAPH_prefix_std + GRAPH_length_max + GRAPH_suffix); + if (Graph_cpus->length >= 0) Graph_cpus->length = GRAPH_length_max; + else Graph_cpus->length = Screen_cols - GRAPH_prefix_std - GRAPH_suffix; + if (Graph_cpus->length < GRAPH_length_min) Graph_cpus->length = GRAPH_length_min; +#ifdef TOG4_MEM_1UP + Graph_mems->length = (Screen_cols - (GRAPH_prefix_std + GRAPH_suffix)); + if (Graph_mems->length > GRAPH_length_max) Graph_mems->length = GRAPH_length_max; + if (Graph_mems->length < GRAPH_length_min) Graph_mems->length = GRAPH_length_min; +#else + Graph_mems->length = Graph_cpus->length; +#endif + } + Graph_cpus->adjust = (float)Graph_cpus->length / 100.0; + Graph_cpus->style = Curwin->rc.graph_cpus; + + Graph_mems->adjust = (float)Graph_mems->length / 100.0; + Graph_mems->style = Curwin->rc.graph_mems; + + fflush(stdout); + Frames_signal = BREAK_off; +} // end: adj_geometry + + + /* + * A calibrate_fields() *Helper* function to build the actual + * column headers & ensure necessary item enumerators support */ +static void build_headers (void) { + #define ckITEM(f) do { Pids_itms[f] = Fieldstab[f].item; } while (0) + #define ckCMDS(w) do { if (CHKw(w, Show_CMDLIN)) ckITEM(eu_CMDLINE); } while (0) + FLG_t f; + char *s; + WIN_t *w = Curwin; +#ifdef EQUCOLHDRYES + int x, hdrmax = 0; +#endif + int i; + + // ensure fields not visible incur no significant library costs + for (i = 0; i < eu_RESET; i++) + Pids_itms[i] = PIDS_noop; + for ( ; i < MAXTBL(Fieldstab); i++) + Pids_itms[i] = PIDS_extra; + + ckITEM(EU_PID); // these 2 fields may not display, + ckITEM(EU_STA); // yet we'll always need them both + ckITEM(EU_CMD); // this is used with 'Y' (inspect) + + do { + if (VIZISw(w)) { + memset((s = w->columnhdr), 0, sizeof(w->columnhdr)); + if (Rc.mode_altscr) s = scat(s, fmtmk("%d", w->winnum)); + + for (i = 0; i < w->maxpflgs; i++) { + f = w->procflgs[i]; +#ifdef USE_X_COLHDR + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_msg)); + w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_msg); + } +#else + if (EU_MAXPFLGS <= f) continue; +#endif + ckITEM(f); + switch (f) { + case EU_CMD: + ckCMDS(w); + break; + case EU_CPU: + // cpu calculations depend on number of threads + ckITEM(EU_THD); + break; + case EU_TME: + case EU_TM2: + // for 'cumulative' times, we'll need equivalent of cutime & cstime + if (CHKw(w, Show_CTIMES)) ckITEM(eu_TICS_ALL_C); + break; + default: + break; + } + s = scat(s, utf8_justify(N_col(f) + , VARcol(f) ? w->varcolsz : Fieldstab[f].width + , CHKw(w, Fieldstab[f].align))); +#ifdef USE_X_COLHDR + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + s = scat(s, fmtmk("%s%s", Caps_off, w->capclr_hdr)); + w->hdrcaplen += strlen(Caps_off) + strlen(w->capclr_hdr); + } +#endif + } +#ifdef EQUCOLHDRYES + // prepare to even out column header lengths... + if (hdrmax + w->hdrcaplen < (x = strlen(w->columnhdr))) hdrmax = x - w->hdrcaplen; +#endif + // for 'busy' only processes, we'll need elapsed tics + if (!CHKw(w, Show_IDLEPS)) ckITEM(EU_CPU); + // with forest view mode, we'll need pid, tgid, ppid & start_time... +#ifndef TREE_VCPUOFF + 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); } +#else + if (CHKw(w, Show_FOREST)) { ckITEM(EU_PPD); ckITEM(EU_TGD); ckITEM(EU_TM3); ckITEM(eu_TREE_HID); ckITEM(eu_TREE_LVL); } +#endif + // for 'u/U' filtering we need these too (old top forgot that, oops) + if (w->usrseltyp) { ckITEM(EU_UED); ckITEM(EU_URD); ckITEM(EU_USD); ckITEM(eu_ID_FUID); } + + // we must also accommodate an out of view sort field... + f = w->rc.sortindx; + if (EU_CMD == f) ckCMDS(w); + else ckITEM(f); + + // lastly, accommodate any special non-display 'tagged' needs... + i = 0; + while (Bot_item[i] > BOT_DELIMIT) { + ckITEM(Bot_item[i]); + ++i; + } + } // end: VIZISw(w) + + if (Rc.mode_altscr) w = w->next; + } while (w != Curwin); + +#ifdef EQUCOLHDRYES + /* now we can finally even out column header lengths + (we're assuming entire columnhdr was memset to '\0') */ + if (Rc.mode_altscr && SCREENMAX > Screen_cols) + for (i = 0; i < GROUPSMAX; i++) { + w = &Winstk[i]; + if (CHKw(w, Show_TASKON)) + if (hdrmax + w->hdrcaplen > (x = strlen(w->columnhdr))) + memset(&w->columnhdr[x], ' ', hdrmax + w->hdrcaplen - x); + } +#endif + + #undef ckITEM + #undef ckCMDS +} // end: build_headers + + + /* + * This guy coordinates the activities surrounding the maintenance of + * each visible window's columns headers plus item enumerators needed */ +static void calibrate_fields (void) { + FLG_t f; + char *s; + const char *h; + WIN_t *w = Curwin; + int i, varcolcnt, len, rc; + + adj_geometry(); + + do { + if (VIZISw(w)) { + w->hdrcaplen = 0; // really only used with USE_X_COLHDR + // build window's pflgsall array, establish upper bounds for maxpflgs + for (i = 0, w->totpflgs = 0; i < EU_MAXPFLGS; i++) { + if (FLDviz(w, i)) { + f = FLDget(w, i); +#ifdef USE_X_COLHDR + w->pflgsall[w->totpflgs++] = f; +#else + if (CHKw(w, Show_HICOLS) && f == w->rc.sortindx) { + w->pflgsall[w->totpflgs++] = EU_XON; + w->pflgsall[w->totpflgs++] = f; + w->pflgsall[w->totpflgs++] = EU_XOF; + } else + w->pflgsall[w->totpflgs++] = f; +#endif + } + } + if (!w->totpflgs) w->pflgsall[w->totpflgs++] = EU_PID; + + /* build a preliminary columns header not to exceed screen width + while accounting for a possible leading window number */ + w->varcolsz = varcolcnt = 0; + *(s = w->columnhdr) = '\0'; + if (Rc.mode_altscr) s = scat(s, " "); + for (i = 0; i + w->begpflg < w->totpflgs; i++) { + f = w->pflgsall[i + w->begpflg]; + w->procflgs[i] = f; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS <= f) continue; +#endif + h = N_col(f); + len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ; + // oops, won't fit -- we're outta here... + if (Screen_cols < ((int)(s - w->columnhdr) + len)) break; + if (VARcol(f)) { ++varcolcnt; w->varcolsz += strlen(h); } + s = scat(s, fmtmk("%*.*s", len, len, h)); + } +#ifndef USE_X_COLHDR + if (i >= 1 && EU_XON == w->procflgs[i - 1]) --i; +#endif + + /* establish the final maxpflgs and prepare to grow the variable column + heading(s) via varcolsz - it may be a fib if their pflags weren't + encountered, but that's ok because they won't be displayed anyway */ + w->maxpflgs = i; + w->varcolsz += Screen_cols - strlen(w->columnhdr); + if (varcolcnt) w->varcolsz /= varcolcnt; + + /* establish the field where all remaining fields would still + fit within screen width, including a leading window number */ + *(s = w->columnhdr) = '\0'; + if (Rc.mode_altscr) s = scat(s, " "); + w->endpflg = 0; + for (i = w->totpflgs - 1; -1 < i; i--) { + f = w->pflgsall[i]; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS <= f) { w->endpflg = i; continue; } +#endif + h = N_col(f); + len = (VARcol(f) ? (int)strlen(h) : Fieldstab[f].width) + COLPADSIZ; + if (Screen_cols < ((int)(s - w->columnhdr) + len)) break; + s = scat(s, fmtmk("%*.*s", len, len, h)); + w->endpflg = i; + } +#ifndef USE_X_COLHDR + if (EU_XOF == w->pflgsall[w->endpflg]) ++w->endpflg; +#endif + } // end: if (VIZISw(w)) + + if (Rc.mode_altscr) w = w->next; + } while (w != Curwin); + + build_headers(); + + if ((rc = procps_pids_reset(Pids_ctx, Pids_itms, Pids_itms_tot))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc))); +} // end: calibrate_fields + + + /* + * Display each field represented in the current window's fieldscur + * array along with its description. Mark with bold and a leading + * asterisk those fields associated with the "on" or "active" state. + * + * Special highlighting will be accorded the "focus" field with such + * highlighting potentially extended to include the description. + * + * Below is the current Fieldstab space requirement and how + * we apportion it. The xSUFX is considered sacrificial, + * something we can reduce or do without. + * 0 1 2 3 + * 12345678901234567890123456789012 + * * HEADING = Longest Description! + * xPRFX ----------______________________ xSUFX + * ( xPRFX has pos 2 & 10 for 'extending' when at minimums ) + * + * The first 4 screen rows are reserved for explanatory text, and + * the maximum number of columns is Screen_cols / xPRFX + 1 space + * between columns. Thus, for example, with 42 fields a tty will + * still remain usable under these extremes: + * rows columns what's + * tty top tty top displayed + * --- --- --- --- ------------------ + * 46 42 10 1 xPRFX only + * 46 42 32 1 full xPRFX + xSUFX + * 6 2 231 21 xPRFX only + * 10 6 231 7 full xPRFX + xSUFX + */ +static void display_fields (int focus, int extend) { + #define mkERR { putp("\n"); putp(N_txt(XTRA_winsize_txt)); return; } + #define mxCOL ( (Screen_cols / 11) > 0 ? (Screen_cols / 11) : 1 ) + #define yRSVD 4 + #define xEQUS 2 // length of suffix beginning '= ' + #define xSUFX 22 // total suffix length, incl xEQUS + #define xPRFX (10 + xadd) + #define xTOTL (xPRFX + xSUFX) + static int col_sav, row_sav; + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i; // utility int (a row, tot cols, ix) + int smax; // printable width of xSUFX + int xadd = 0; // spacing between data columns + int cmax = Screen_cols; // total data column width + int rmax = Screen_rows - yRSVD; // total usable rows + + i = (EU_MAXPFLGS % mxCOL) ? 1 : 0; + if (rmax < i + (EU_MAXPFLGS / mxCOL)) mkERR; + i = EU_MAXPFLGS / rmax; + if (EU_MAXPFLGS % rmax) ++i; + if (i > 1) { cmax /= i; xadd = 1; } + if (cmax > xTOTL) cmax = xTOTL; + smax = cmax - xPRFX; + if (smax < 0) mkERR; + + /* we'll go the extra distance to avoid any potential screen flicker + which occurs under some terminal emulators (but it was our fault) */ + if (col_sav != Screen_cols || row_sav != Screen_rows) { + col_sav = Screen_cols; + row_sav = Screen_rows; + putp(Cap_clr_eos); + } + fflush(stdout); + + for (i = 0; i < EU_MAXPFLGS; ++i) { + int b = FLDviz(w, i), x = (i / rmax) * cmax, y = (i % rmax) + yRSVD; + const char *e = (i == focus && extend) ? w->capclr_hdr : ""; + FLG_t f = FLDget(w, i); + char sbuf[xSUFX*4]; // 4 = max multi-byte + int xcol, xfld; + + /* prep sacrificial suffix (allowing for beginning '= ') + note: width passed to 'utf8_embody' may go negative, but he'll be just fine */ + snprintf(sbuf, sizeof(sbuf), "= %.*s", utf8_embody(N_fld(f), smax - xEQUS), N_fld(f)); + // obtain translated deltas (if any) ... + xcol = utf8_delta(fmtmk("%.*s", utf8_embody(N_col(f), 8), N_col(f))); + xfld = utf8_delta(sbuf + xEQUS); // ignore beginning '= ' + + PUTT("%s%c%s%s %s%-*.*s%s%s%s %-*.*s%s" + , tg2(x, y) + , b ? '*' : ' ' + , b ? w->cap_bold : Cap_norm + , e + , i == focus ? w->capclr_hdr : "" + , 8 + xcol, 8 + xcol + , N_col(f) + , Cap_norm + , b ? w->cap_bold : "" + , e + , smax + xfld, smax + xfld + , sbuf + , Cap_norm); + } + + putp(Caps_off); + #undef mkERR + #undef mxCOL + #undef yRSVD + #undef xEQUS + #undef xSUFX + #undef xPRFX + #undef xTOTL +} // end: display_fields + + + /* + * Manage all fields aspects (order/toggle/sort), for all windows. */ +static void fields_utility (void) { +#ifndef SCROLLVAR_NO + #define unSCRL { w->begpflg = w->varcolbeg = 0; OFFw(w, Show_HICOLS); } +#else + #define unSCRL { w->begpflg = 0; OFFw(w, Show_HICOLS); } +#endif + #define swapEM { int c; unSCRL; c = w->rc.fieldscur[i]; \ + w->rc.fieldscur[i] = *p; *p = c; p = &w->rc.fieldscur[i]; } + #define spewFI { int *t; f = w->rc.sortindx; t = msch(w->rc.fieldscur, ENUcvt(f, FLDon), EU_MAXPFLGS); \ + if (!t) t = msch(w->rc.fieldscur, ENUcvt(f, FLDoff), EU_MAXPFLGS); \ + i = (t) ? (int)(t - w->rc.fieldscur) : 0; } + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + const char *h = NULL; + int *p = NULL; + int i, key; + FLG_t f; + + spewFI +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + do { + if (!h) h = N_col(f); + putp(Cap_home); + show_special(1, fmtmk(N_unq(FIELD_header_fmt) + , w->grpname, CHKw(w, Show_FOREST) ? N_txt(FOREST_views_txt) : h)); + display_fields(i, (p != NULL)); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case kbd_UP: + if (i > 0) { --i; if (p) swapEM } + break; + case kbd_DOWN: + if (i + 1 < EU_MAXPFLGS) { ++i; if (p) swapEM } + break; + case kbd_LEFT: + case kbd_ENTER: + p = NULL; + break; + case kbd_RIGHT: + p = &w->rc.fieldscur[i]; + break; + case kbd_HOME: + case kbd_PGUP: + if (!p) i = 0; + break; + case kbd_END: + case kbd_PGDN: + if (!p) i = EU_MAXPFLGS - 1; + break; + case kbd_SPACE: + case 'd': + if (!p) { FLDtog(w, i); unSCRL } + break; + case 's': +#ifdef TREE_NORESET + if (!p && !CHKw(w, Show_FOREST)) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL } +#else + if (!p) { w->rc.sortindx = f = FLDget(w, i); h = NULL; unSCRL; OFFw(w, Show_FOREST); } +#endif + break; + case 'a': + case 'w': + Curwin = w = ('a' == key) ? w->next : w->prev; + spewFI + h = NULL; + p = NULL; + break; + default: // keep gcc happy + break; + } + } while (key != 'q' && key != kbd_ESC); + + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef unSCRL + #undef swapEM + #undef spewFI +} // end: fields_utility + + + /* + * This routine takes care of auto sizing field widths + * if/when the user sets Rc.fixed_widest to -1. Along the + * way he reinitializes some things for the next frame. */ +static inline void widths_resize (void) { + int i; + + // next var may also be set by the guys that actually truncate stuff + Autox_found = 0; + for (i = 0; i < EU_MAXPFLGS; i++) { + if (Autox_array[i]) { + Fieldstab[i].width++; + Autox_array[i] = 0; + Autox_found = 1; + } + } + // trigger a call to calibrate_fields (via zap_fieldstab) + if (Autox_found) Frames_signal = BREAK_autox; +} // end: widths_resize + + + /* + * This routine exists just to consolidate most of the messin' + * around with the Fieldstab array and some related stuff. */ +static void zap_fieldstab (void) { +#ifdef WIDEN_COLUMN + #define maX(E) ( (wtab[E].wnls > wtab[E].wmin) \ + ? wtab[E].wnls : wtab[E].wmin ) + static struct { + int wmin; // minimum field width (-1 == variable width) + int wnls; // translated header column requirements + int watx; // +1 == non-scalable auto sized columns + } wtab[EU_MAXPFLGS]; +#endif + static int once; + int i, digits; + char buf[8]; + + if (!once) { + Fieldstab[EU_CPN].width = 1; + Fieldstab[EU_NMA].width = 2; + Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width + = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width + = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = 5; + if (5 < (digits = (int)procps_pid_length())) { + if (10 < digits) error_exit(N_txt(FAIL_widepid_txt)); + Fieldstab[EU_PID].width = Fieldstab[EU_PPD].width + = Fieldstab[EU_PGD].width = Fieldstab[EU_SID].width + = Fieldstab[EU_TGD].width = Fieldstab[EU_TPG].width = digits; + } +#ifdef WIDEN_COLUMN + // identify our non-scalable auto sized columns + wtab[EU_UED].watx = wtab[EU_UEN].watx = wtab[EU_URD].watx + = wtab[EU_URN].watx = wtab[EU_USD].watx = wtab[EU_USN].watx + = wtab[EU_GID].watx = wtab[EU_GRP].watx = wtab[EU_TTY].watx + = wtab[EU_WCH].watx = wtab[EU_NS1].watx = wtab[EU_NS2].watx + = wtab[EU_NS3].watx = wtab[EU_NS4].watx = wtab[EU_NS5].watx + = wtab[EU_NS6].watx = wtab[EU_NS7].watx = wtab[EU_NS8].watx + = wtab[EU_LXC].watx = wtab[EU_LID].watx + = +1; + /* establish translatable header 'column' requirements + and ensure .width reflects the widest value */ + for (i = 0; i < EU_MAXPFLGS; i++) { + wtab[i].wmin = Fieldstab[i].width; + wtab[i].wnls = (int)strlen(N_col(i)) - utf8_delta(N_col(i)); + if (wtab[i].wmin != -1) + Fieldstab[i].width = maX(i); + } +#endif + once = 1; + } + + Cpu_pmax = 99.9; + if (Rc.mode_irixps && Cpu_cnt > 1 && !Thread_mode) { + Cpu_pmax = 100.0 * Cpu_cnt; + if (Cpu_cnt > 1000) { + if (Cpu_pmax > 9999999.0) Cpu_pmax = 9999999.0; + } else if (Cpu_cnt > 100) { + if (Cpu_cnt > 999999.0) Cpu_pmax = 999999.0; + } else if (Cpu_cnt > 10) { + if (Cpu_pmax > 99999.0) Cpu_pmax = 99999.0; + } else { + if (Cpu_pmax > 999.9) Cpu_pmax = 999.9; + } + } + +#ifdef WIDEN_COLUMN + digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt); + if (wtab[EU_CPN].wmin < digits) { + if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt)); + wtab[EU_CPN].wmin = digits; + Fieldstab[EU_CPN].width = maX(EU_CPN); + } + digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot); + if (wtab[EU_NMA].wmin < digits) { + wtab[EU_NMA].wmin = digits; + Fieldstab[EU_NMA].width = maX(EU_NMA); + } + + // and accommodate optional wider non-scalable columns (maybe) + if (!AUTOX_MODE) { + for (i = 0; i < EU_MAXPFLGS; i++) { + if (wtab[i].watx) + Fieldstab[i].width = Rc.fixed_widest ? Rc.fixed_widest + maX(i) : maX(i); + } + } +#else + digits = snprintf(buf, sizeof(buf), "%d", Cpu_cnt); + if (1 < digits) { + if (5 < digits) error_exit(N_txt(FAIL_widecpu_txt)); + Fieldstab[EU_CPN].width = digits; + } + digits = snprintf(buf, sizeof(buf), "%d", Numa_node_tot); + if (2 < digits) + Fieldstab[EU_NMA].width = digits; + + // and accommodate optional wider non-scalable columns (maybe) + if (!AUTOX_MODE) { + Fieldstab[EU_UED].width = Fieldstab[EU_URD].width + = Fieldstab[EU_USD].width = Fieldstab[EU_GID].width + = Rc.fixed_widest ? 5 + Rc.fixed_widest : 5; + Fieldstab[EU_UEN].width = Fieldstab[EU_URN].width + = Fieldstab[EU_USN].width = Fieldstab[EU_GRP].width + = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8; + Fieldstab[EU_TTY].width = Fieldstab[EU_LXC].width + = Rc.fixed_widest ? 8 + Rc.fixed_widest : 8; + Fieldstab[EU_WCH].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + // the initial namespace fields + for (i = EU_NS1; i <= EU_NS6; i++) + Fieldstab[i].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + // the later namespace additions + for (i = EU_NS7; i <= EU_NS8; i++) + Fieldstab[i].width + = Rc.fixed_widest ? 10 + Rc.fixed_widest : 10; + } +#endif + + /* plus user selectable scaling */ + Fieldstab[EU_VRT].scale = Fieldstab[EU_SWP].scale + = Fieldstab[EU_RES].scale = Fieldstab[EU_COD].scale + = Fieldstab[EU_DAT].scale = Fieldstab[EU_SHR].scale + = Fieldstab[EU_USE].scale = Fieldstab[EU_RZA].scale + = Fieldstab[EU_RZF].scale = Fieldstab[EU_RZL].scale + = Fieldstab[EU_RZS].scale = Fieldstab[EU_RSS].scale + = Fieldstab[EU_PSS].scale = Fieldstab[EU_PZA].scale + = Fieldstab[EU_PZF].scale = Fieldstab[EU_PZS].scale + = Fieldstab[EU_USS].scale = Rc.task_mscale; + + // lastly, ensure we've got proper column headers... + calibrate_fields(); + #undef maX +} // end: zap_fieldstab + +/*###### Library Interface (as separate threads) #######################*/ + + /* + * This guy's responsible for interfacing with the library <stat> API + * and reaping all cpu or numa node tics. + * ( his task is now embarassingly small under the new api ) */ +static void *cpus_refresh (void *unused) { + enum stat_reap_type which; + + do { +#ifdef THREADED_CPU + sem_wait(&Semaphore_cpus_beg); +#endif + which = STAT_REAP_CPUS_ONLY; + if (CHKw(Curwin, View_CPUNOD)) + which = STAT_REAP_NUMA_NODES_TOO; + + Stat_reap = procps_stat_reap(Stat_ctx, which, Stat_items, MAXTBL(Stat_items)); + if (!Stat_reap) + error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno))); +#ifndef PRETEND0NUMA + // adapt to changes in total numa nodes (assuming it's even possible) + if (Stat_reap->numa->total && Stat_reap->numa->total != Numa_node_tot) { + Numa_node_tot = Stat_reap->numa->total; + Numa_node_sel = -1; + } +#endif + if (Stat_reap->cpus->total && Stat_reap->cpus->total != Cpu_cnt) { + Cpu_cnt = Stat_reap->cpus->total; +#ifdef PRETEND48CPU + Cpu_cnt = 48; +#endif + } +#ifdef PRETENDECORE +{ int i, x; + x = Cpu_cnt - (Cpu_cnt / 4); + for (i = 0; i < Cpu_cnt; i++) + Stat_reap->cpus->stacks[i]->head[stat_COR_TYP].result.s_int = (i < x) ? P_CORE : E_CORE; +} +#endif +#ifdef THREADED_CPU + sem_post(&Semaphore_cpus_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; +} // end: cpus_refresh + + + /* + * This serves as our interface to the memory portion of libprocps. + * The sampling frequency is reduced in order to minimize overhead. */ +static void *memory_refresh (void *unused) { + static time_t sav_secs; + time_t cur_secs; + + do { +#ifdef THREADED_MEM + sem_wait(&Semaphore_memory_beg); +#endif + if (Frames_signal) + sav_secs = 0; + cur_secs = time(NULL); + + if (3 <= cur_secs - sav_secs) { + if (!(Mem_stack = procps_meminfo_select(Mem_ctx, Mem_items, MAXTBL(Mem_items)))) + error_exit(fmtmk(N_fmt(LIB_errormem_fmt), __LINE__, strerror(errno))); + sav_secs = cur_secs; + } +#ifdef THREADED_MEM + sem_post(&Semaphore_memory_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; +} // end: memory_refresh + + + /* + * This guy's responsible for interfacing with the library <pids> API + * then refreshing the WIN_t ptr arrays, growing them as appropirate. */ +static void *tasks_refresh (void *unused) { + #define nALIGN(n,m) (((n + m - 1) / m) * m) // unconditionally align + #define nALGN2(n,m) ((n + m - 1) & ~(m - 1)) // with power of 2 align + #define n_reap Pids_reap->counts->total + static double uptime_sav; + static int n_alloc = -1; // size of windows stacks arrays + double uptime_cur; + float et; + int i, what; + + do { +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_beg); +#endif + procps_uptime(&uptime_cur, NULL); + et = uptime_cur - uptime_sav; + if (et < 0.01) et = 0.005; + uptime_sav = uptime_cur; + // if in Solaris mode, adjust our scaling for all cpus + Frame_etscale = 100.0f / ((float)Hertz * (float)et * (Rc.mode_irixps ? 1 : Cpu_cnt)); + + what = Thread_mode ? PIDS_FETCH_THREADS_TOO : PIDS_FETCH_TASKS_ONLY; + if (Monpidsidx) { + what |= PIDS_SELECT_PID; + Pids_reap = procps_pids_select(Pids_ctx, (unsigned *)Monpids, Monpidsidx, what); + } else + Pids_reap = procps_pids_reap(Pids_ctx, what); + if (!Pids_reap) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); + + // now refresh each window's stacks pointer array... + if (n_alloc < n_reap) { +// n_alloc = nALIGN(n_reap, 100); + n_alloc = nALGN2(n_reap, 128); + for (i = 0; i < GROUPSMAX; i++) { + Winstk[i].ppt = alloc_r(Winstk[i].ppt, sizeof(void *) * n_alloc); + memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt); + } + } else { + for (i = 0; i < GROUPSMAX; i++) + memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt); + } +#ifdef THREADED_TSK + sem_post(&Semaphore_tasks_end); + } while (1); +#else + } while (0); +#endif + return NULL; + (void)unused; + #undef nALIGN + #undef nALGN2 + #undef n_reap +} // end: tasks_refresh + +/*###### Inspect Other Output ##########################################*/ + + /* + * HOWTO Extend the top 'inspect' functionality: + * + * To exploit the 'Y' interactive command, one must add entries to + * the top personal configuration file. Such entries simply reflect + * a file to be read or command/pipeline to be executed whose results + * will then be displayed in a separate scrollable window. + * + * Entries beginning with a '#' character are ignored, regardless of + * content. Otherwise they consist of the following 3 elements, each + * of which must be separated by a tab character (thus 2 '\t' total): + * type: literal 'file' or 'pipe' + * name: selection shown on the Inspect screen + * fmts: string representing a path or command + * + * The two types of Inspect entries are not interchangeable. + * Those designated 'file' will be accessed using fopen/fread and must + * reference a single file in the 'fmts' element. Entries specifying + * 'pipe' will employ popen/fread, their 'fmts' element could contain + * many pipelined commands and, none can be interactive. + * + * Here are some examples of both types of inspection entries. + * The first entry will be ignored due to the initial '#' character. + * For clarity, the pseudo tab depictions (^I) are surrounded by an + * extra space but the actual tabs would not be. + * + * # pipe ^I Sockets ^I lsof -n -P -i 2>&1 + * pipe ^I Open Files ^I lsof -P -p %d 2>&1 + * file ^I NUMA Info ^I /proc/%d/numa_maps + * pipe ^I Log ^I tail -n100 /var/log/syslog | sort -Mr + * + * Caution: If the output contains unprintable characters they will + * be displayed in either the ^I notation or hexadecimal <FF> form. + * This applies to tab characters as well. So if one wants a more + * accurate display, any tabs should be expanded within the 'fmts'. + * + * The following example takes what could have been a 'file' entry + * but employs a 'pipe' instead so as to expand the tabs. + * + * # next would have contained '\t' ... + * # file ^I <your_name> ^I /proc/%d/status + * # but this will eliminate embedded '\t' ... + * pipe ^I <your_name> ^I cat /proc/%d/status | expand - + * + * Note: If a pipe such as the following was established, one must + * use Ctrl-C to terminate that pipe in order to review the results. + * This is the single occasion where a '^C' will not terminate top. + * + * pipe ^I Trace ^I /usr/bin/strace -p %d 2>&1 + */ + + /* + * Our driving table support, the basis for generalized inspection, + * built at startup (if at all) from rcfile or demo entries. */ +struct I_ent { + void (*func)(char *, int); // a pointer to file/pipe/demo function + char *type; // the type of entry ('file' or 'pipe') + char *name; // the selection label for display + char *fmts; // format string to build path or command + int farg; // 1 = '%d' in fmts, 0 = not (future use) + const char *caps; // not really caps, show_special() delim's + char *fstr; // entry's current/active search string + int flen; // above's strlen, without call overhead +}; +struct I_struc { + int demo; // do NOT save table entries in rcfile + int total; // total I_ent table entries + char *raw; // all entries for 'W', incl '#' & blank + struct I_ent *tab; +}; +static struct I_struc Inspect; + +static char **Insp_p; // pointers to each line start +static int Insp_nl; // total lines, total Insp_p entries +static int Insp_utf8; // treat Insp_buf as translatable, else raw +static char *Insp_buf; // the results from insp_do_file/pipe +static size_t Insp_bufsz; // allocated size of Insp_buf +static size_t Insp_bufrd; // bytes actually in Insp_buf +static struct I_ent *Insp_sel; // currently selected Inspect entry + + // Our 'make status line' macro +#define INSP_MKSL(big,txt) { int _sz = big ? Screen_cols : 80; \ + const char *_p; \ + _sz += utf8_delta(txt); \ + _p = fmtmk("%-*.*s", _sz, _sz, txt); \ + PUTT("%s%s%.*s%s", tg2(0, (Msg_row = 3)), Curwin->capclr_hdr \ + , utf8_embody(_p, Screen_cols), _p, Cap_clr_eol); \ + putp(Caps_off); fflush(stdout); } + + // Our 'row length' macro, equivalent to a strlen() call +#define INSP_RLEN(idx) (int)(Insp_p[idx +1] - Insp_p[idx] -1) + + // Our 'busy/working' macro +#define INSP_BUSY(enu) { INSP_MKSL(0, N_txt(enu)) } + + + /* + * Establish the number of lines present in the Insp_buf glob plus + * build the all important row start array. It is that array that + * others will rely on since we dare not try to use strlen() on what + * is potentially raw binary data. Who knows what some user might + * name as a file or include in a pipeline (scary, ain't it?). */ +static void insp_cnt_nl (void) { + char *beg = Insp_buf; + char *cur = Insp_buf; + char *end = Insp_buf + Insp_bufrd + 1; + +#ifdef INSP_SAVEBUF +{ + static int n = 1; + char fn[SMLBUFSIZ]; + FILE *fd; + snprintf(fn, sizeof(fn), "%s.Insp_buf.%02d.txt", Myname, n++); + fd = fopen(fn, "w"); + if (fd) { + fwrite(Insp_buf, 1, Insp_bufrd, fd); + fclose(fd); + } +} +#endif + Insp_p = alloc_c(sizeof(char *) * 2); + + for (Insp_nl = 0; beg < end; beg++) { + if (*beg == '\n') { + Insp_p[Insp_nl++] = cur; + // keep our array ahead of next potential need (plus the 2 above) + Insp_p = alloc_r(Insp_p, (sizeof(char *) * (Insp_nl +3))); + cur = beg +1; + } + } + Insp_p[0] = Insp_buf; + Insp_p[Insp_nl++] = cur; + Insp_p[Insp_nl] = end; + if ((end - cur) == 1) // if there's an eof null delimiter, + --Insp_nl; // don't count it as a new line +} // end: insp_cnt_nl + + +#ifndef INSP_OFFDEMO + /* + * The pseudo output DEMO utility. */ +static void insp_do_demo (char *fmts, int pid) { + (void)fmts; (void)pid; + /* next will put us on a par with the real file/pipe read buffers + ( and also avoid a harmless, but evil sounding, valgrind warning ) */ + Insp_bufsz = READMINSZ + strlen(N_txt(YINSP_dstory_txt)); + Insp_buf = alloc_c(Insp_bufsz); + Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s", N_txt(YINSP_dstory_txt)); + insp_cnt_nl(); +} // end: insp_do_demo +#endif + + + /* + * The generalized FILE utility. */ +static void insp_do_file (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + FILE *fp; + int rc; + + snprintf(buf, sizeof(buf), fmts, pid); + fp = fopen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) fclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); +} // end: insp_do_file + + + /* + * The generalized PIPE utility. */ +static void insp_do_pipe (char *fmts, int pid) { + char buf[LRGBUFSIZ]; + struct sigaction sa; + FILE *fp; + int rc; + + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + sigaction(SIGINT, &sa, NULL); + + snprintf(buf, sizeof(buf), fmts, pid); + fp = popen(buf, "r"); + rc = readfile(fp, &Insp_buf, &Insp_bufsz, &Insp_bufrd); + if (fp) pclose(fp); + if (rc) Insp_bufrd = snprintf(Insp_buf, Insp_bufsz, "%s" + , fmtmk(N_fmt(YINSP_failed_fmt), strerror(errno))); + insp_cnt_nl(); + + sa.sa_handler = sig_endpgm; + sigaction(SIGINT, &sa, NULL); +} // end: insp_do_pipe + + + /* + * This guy is a *Helper* function serving the following two masters: + * insp_find_str() - find the next Insp_sel->fstr match + * insp_mkrow_... - highlight any Insp_sel->fstr matches in-view + * If Insp_sel->fstr is found in the designated row, he returns the + * offset from the start of the row, otherwise he returns a huge + * integer so traditional fencepost usage can be employed. */ +static inline int insp_find_ofs (int col, int row) { + #define begFS (int)(fnd - Insp_p[row]) + char *p, *fnd = NULL; + + if (Insp_sel->fstr[0]) { + // skip this row, if there's no chance of a match + if (memchr(Insp_p[row], Insp_sel->fstr[0], INSP_RLEN(row))) { + for ( ; col < INSP_RLEN(row); col++) { + if (!*(p = Insp_p[row] + col)) // skip any empty strings + continue; + fnd = STRSTR(p, Insp_sel->fstr); // with binary data, each + if (fnd) // row may have '\0'. so + break; // our scans must be done + col += strlen(p); // as individual strings. + } + if (fnd && fnd < Insp_p[row + 1]) // and, we must watch out + return begFS; // for potential overrun! + } + } + return INT_MAX; + #undef begFS +} // end: insp_find_ofs + + + /* + * This guy supports the inspect 'L' and '&' search provisions + * and returns the row and *optimal* column for viewing any match + * ( we'll always opt for left column justification since any ) + * ( preceding ctrl chars appropriate an unpredictable amount ) */ +static void insp_find_str (int ch, int *col, int *row) { + #define reDUX (found) ? N_txt(WORD_another_txt) : "" + static int found; + + if ((ch == '&' || ch == 'n') && !Insp_sel->fstr[0]) { + show_msg(N_txt(FIND_no_next_txt)); + return; + } + if (ch == 'L' || ch == '/') { + char *str = ioline(N_txt(GET_find_str_txt)); + if (*str == kbd_ESC) return; + snprintf(Insp_sel->fstr, FNDBUFSIZ, "%s", str); + Insp_sel->flen = strlen(Insp_sel->fstr); + found = 0; + } + if (Insp_sel->fstr[0]) { + int xx, yy; + + INSP_BUSY(YINSP_waitin_txt); + for (xx = *col, yy = *row; yy < Insp_nl; ) { + xx = insp_find_ofs(xx, yy); + if (xx < INSP_RLEN(yy)) { + found = 1; + if (xx == *col && yy == *row) { // matched where we were! + ++xx; // ( was the user maybe ) + continue; // ( trying to fool us? ) + } + *col = xx; + *row = yy; + return; + } + xx = 0; + ++yy; + } + show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Insp_sel->fstr)); + } + #undef reDUX +} // end: insp_find_str + + + /* + * This guy is a *Helper* function responsible for positioning a + * single row in the current 'X axis', then displaying the results. + * Along the way, he makes sure control characters and/or unprintable + * characters display in a less-like fashion: + * '^A' for control chars + * '<BC>' for other unprintable stuff + * Those will be highlighted with the current windows's capclr_msg, + * while visible search matches display with capclr_hdr for emphasis. + * ( we hide ugly plumbing in macros to concentrate on the algorithm ) */ +static void insp_mkrow_raw (int col, int row) { + #define maxSZ ( Screen_cols - to ) + #define capNO { if (hicap) { putp(Caps_off); hicap = 0; } } + #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \ + fr += Insp_sel->flen -1; to += Insp_sel->flen; hicap = 0; } +#ifndef INSP_JUSTNOT + #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \ + PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 2; hicap = 1; } + #define mkUNP { const char *p = fmtmk("<%02X>", uch); \ + PUTT("%s%.*s", (!hicap) ? Curwin->capclr_msg : "", maxSZ, p); to += 4; hicap = 1; } +#else + #define mkCTL { if ((to += 2) <= Screen_cols) \ + PUTT("%s^%c", (!hicap) ? Curwin->capclr_msg : "", uch + '@'); hicap = 1; } + #define mkUNP { if ((to += 4) <= Screen_cols) \ + PUTT("%s<%02X>", (!hicap) ? Curwin->capclr_msg : "", uch); hicap = 1; } +#endif + #define mkSTD { capNO; if (++to <= Screen_cols) { static char _str[2]; \ + _str[0] = uch; putp(_str); } } + unsigned char tline[SCREENMAX]; + int fr, to, ofs; + int hicap = 0; + + if (col < INSP_RLEN(row)) + memcpy(tline, Insp_p[row] + col, sizeof(tline)); + else tline[0] = '\n'; + + for (fr = 0, to = 0, ofs = 0; to < Screen_cols; fr++) { + if (!ofs) + ofs = insp_find_ofs(col + fr, row); + if (col + fr < ofs) { + unsigned char uch = tline[fr]; + if (uch == '\n') break; // a no show (he,he) + if (uch > 126) mkUNP // show as: '<AB>' + else if (uch < 32) mkCTL // show as: '^C' + else mkSTD // a show off (he,he) + } else { mkFND // a big show (he,he) + ofs = 0; + } + if (col + fr >= INSP_RLEN(row)) break; + } + capNO; + putp(Cap_clr_eol); + + #undef maxSZ + #undef capNO + #undef mkFND + #undef mkCTL + #undef mkUNP + #undef mkSTD +} // end: insp_mkrow_raw + + + /* + * This guy is a *Helper* function responsible for positioning a + * single row in the current 'X axis' within a multi-byte string + * then displaying the results. Along the way he ensures control + * characters will then be displayed in two positions like '^A'. + * ( assuming they can even get past those 'gettext' utilities ) */ +static void insp_mkrow_utf8 (int col, int row) { + #define maxSZ ( Screen_cols - to ) + #define mkFND { PUTT("%s%.*s%s", Curwin->capclr_hdr, maxSZ, Insp_sel->fstr, Caps_off); \ + fr += Insp_sel->flen; to += Insp_sel->flen; } +#ifndef INSP_JUSTNOT + #define mkCTL { const char *p = fmtmk("^%c", uch + '@'); \ + PUTT("%s%.*s%s", Curwin->capclr_msg, maxSZ, p, Caps_off); to += 2; } +#else + #define mkCTL { if ((to += 2) <= Screen_cols) \ + PUTT("%s^%c%s", Curwin->capclr_msg, uch + '@', Caps_off); } +#endif + #define mkNUL { buf1[0] = ' '; doPUT(buf1) } + #define doPUT(buf) if ((to += cno) <= Screen_cols) putp(buf); + static char buf1[2], buf2[3], buf3[4], buf4[5]; + unsigned char tline[BIGBUFSIZ]; + int fr, to, ofs; + + col = utf8_proper_col(Insp_p[row], col, 1); + if (col < INSP_RLEN(row)) + memcpy(tline, Insp_p[row] + col, sizeof(tline)); + else tline[0] = '\n'; + + for (fr = 0, to = 0, ofs = 0; to < Screen_cols; ) { + if (!ofs) + ofs = insp_find_ofs(col + fr, row); + if (col + fr < ofs) { + unsigned char uch = tline[fr]; + int bno = UTF8_tab[uch]; + int cno = utf8_cols(&tline[fr++], bno); + switch (bno) { + case 1: + if (uch == '\n') break; + if (uch < 32) mkCTL + else if (uch == 127) mkNUL + else { buf1[0] = uch; doPUT(buf1) } + break; + case 2: + buf2[0] = uch; buf2[1] = tline[fr++]; + doPUT(buf2) + break; + case 3: + buf3[0] = uch; buf3[1] = tline[fr++]; buf3[2] = tline[fr++]; + doPUT(buf3) + break; + case 4: + buf4[0] = uch; buf4[1] = tline[fr++]; buf4[2] = tline[fr++]; buf4[3] = tline[fr++]; + doPUT(buf4) + break; + default: + mkNUL + break; + } + } else { + mkFND + ofs = 0; + } + if (col + fr >= INSP_RLEN(row)) break; + } + putp(Cap_clr_eol); + + #undef maxSZ + #undef mkFND + #undef mkCTL + #undef mkNUL + #undef doPUT +} // end: insp_mkrow_utf8 + + + /* + * This guy is an insp_view_choice() *Helper* function who displays + * a page worth of of the user's damages. He also creates a status + * line based on maximum digits for the current selection's lines and + * hozizontal position (so it serves to inform, not distract, by + * otherwise being jumpy). */ +static inline void insp_show_pgs (int col, int row, int max) { + char buf[SMLBUFSIZ]; + void (*mkrow_func)(int, int); + int r = snprintf(buf, sizeof(buf), "%d", Insp_nl); + int c = snprintf(buf, sizeof(buf), "%d", col +Screen_cols); + int l = row +1, ls = Insp_nl; + + if (!Insp_bufrd) + l = ls = 0; + snprintf(buf, sizeof(buf), N_fmt(YINSP_status_fmt) + , Insp_sel->name + , r, l, r, ls + , c, col + 1, c, col + Screen_cols + , (unsigned long)Insp_bufrd); + INSP_MKSL(0, buf); + + mkrow_func = Insp_utf8 ? insp_mkrow_utf8 : insp_mkrow_raw; + + for ( ; max && row < Insp_nl; row++) { + putp("\n"); + mkrow_func(col, row); + --max; + } + + if (max) + putp(Cap_nl_clreos); +} // end: insp_show_pgs + + + /* + * This guy is responsible for displaying the Insp_buf contents and + * managing all scrolling/locate requests until the user gives up. */ +static int insp_view_choice (struct pids_stack *p) { +#ifdef INSP_SLIDE_1 + #define hzAMT 1 +#else + #define hzAMT 8 +#endif + #define maxLN (Screen_rows - (Msg_row +1)) + #define makHD(b1,b2) { \ + snprintf(b1, sizeof(b1), "%d", PID_VAL(EU_PID, s_int, p)); \ + snprintf(b2, sizeof(b2), "%s", PID_VAL(EU_CMD, str, p)); } + #define makFS(dst) { if (Insp_sel->flen < 22) \ + snprintf(dst, sizeof(dst), "%s", Insp_sel->fstr); \ + else snprintf(dst, sizeof(dst), "%.19s...", Insp_sel->fstr); } + char buf[LRGBUFSIZ]; + int key, curlin = 0, curcol = 0; + +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + for (;;) { + char pid[6], cmd[64]; + + if (curcol < 0) curcol = 0; + if (curlin >= Insp_nl) curlin = Insp_nl -1; + if (curlin < 0) curlin = 0; + + makFS(buf) + makHD(pid,cmd) + putp(Cap_home); + show_special(1, fmtmk(N_unq(YINSP_hdview_fmt) + , pid, cmd, (Insp_sel->fstr[0]) ? buf : " N/A ")); // nls_maybe + insp_show_pgs(curcol, curlin, maxLN); + fflush(stdout); + /* fflush(stdin) didn't do the trick, so we'll just dip a little deeper + lest repeated <Enter> keys produce immediate re-selection in caller */ + tcflush(STDIN_FILENO, TCIFLUSH); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case kbd_ENTER: // must force new iokey() + key = INT_MAX; // fall through ! + case kbd_ESC: + case 'q': + putp(Cap_clr_scr); + return key; + case kbd_LEFT: + curcol -= hzAMT; + break; + case kbd_RIGHT: + curcol += hzAMT; + break; + case kbd_UP: + --curlin; + break; + case kbd_DOWN: + ++curlin; + break; + case kbd_PGUP: + case 'b': + curlin -= maxLN -1; // keep 1 line for reference + break; + case kbd_PGDN: + case kbd_SPACE: + curlin += maxLN -1; // ditto + break; + case kbd_HOME: + case 'g': + curcol = curlin = 0; + break; + case kbd_END: + case 'G': + curcol = 0; + curlin = Insp_nl - maxLN; + break; + case 'L': + case '&': + case '/': + case 'n': + if (!Insp_utf8) + insp_find_str(key, &curcol, &curlin); + else { + int tmpcol = utf8_proper_col(Insp_p[curlin], curcol, 1); + insp_find_str(key, &tmpcol, &curlin); + curcol = utf8_proper_col(Insp_p[curlin], tmpcol, 0); + } + // must re-hide cursor in case a prompt for a string makes it huge + putp((Cursor_state = Cap_curs_hide)); + break; + case '=': + snprintf(buf, sizeof(buf), "%s: %s", Insp_sel->type, Insp_sel->fmts); + INSP_MKSL(1, buf); // show an extended SL + if (iokey(IOKEY_ONCE) < 1) + goto signify_that; + break; + default: // keep gcc happy + break; + } + } + #undef hzAMT + #undef maxLN + #undef makHD + #undef makFS +} // end: insp_view_choice + + + /* + * This is the main Inspect routine, responsible for: + * 1) validating the passed pid (required, but not always used) + * 2) presenting/establishing the target selection + * 3) arranging to fill Insp_buf (via the Inspect.tab[?].func) + * 4) invoking insp_view_choice for viewing/scrolling/searching + * 5) cleaning up the dynamically acquired memory afterwards */ +static void inspection_utility (int pid) { + #define mkSEL(dst) { for (i = 0; i < Inspect.total; i++) Inspect.tab[i].caps = "~1"; \ + Inspect.tab[sel].caps = "~4"; dst[0] = '\0'; \ + for (i = 0; i < Inspect.total; i++) { char _s[SMLBUFSIZ]; \ + snprintf(_s, sizeof(_s), " %s %s", Inspect.tab[i].name, Inspect.tab[i].caps); \ + strncat(dst, _s, (sizeof(dst) - 1) - strlen(dst)); } } + char sels[SCREENMAX]; + static int sel; + int i, key; + struct pids_stack *p; + + for (i = 0, p = NULL; i < PIDSmaxt; i++) + if (pid == PID_VAL(EU_PID, s_int, Curwin->ppt[i])) { + p = Curwin->ppt[i]; + break; + } + if (!p) { + show_msg(fmtmk(N_fmt(YINSP_pidbad_fmt), pid)); + return; + } + // must re-hide cursor since the prompt for a pid made it huge + putp((Cursor_state = Cap_curs_hide)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + key = INT_MAX; + do { + mkSEL(sels); + putp(Cap_home); + show_special(1, fmtmk(N_unq(YINSP_hdsels_fmt) + , pid, PID_VAL(EU_CMD, str, Curwin->ppt[i]), sels)); + INSP_MKSL(0, " "); + + if (Frames_signal) goto signify_that; + if (key == INT_MAX) key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + case 'q': + case kbd_ESC: + break; + case kbd_END: + sel = 0; // fall through ! + case kbd_LEFT: + if (--sel < 0) sel = Inspect.total -1; + key = INT_MAX; + break; + case kbd_HOME: + sel = Inspect.total; // fall through ! + case kbd_RIGHT: + if (++sel >= Inspect.total) sel = 0; + key = INT_MAX; + break; + case kbd_ENTER: + INSP_BUSY(!strcmp("file", Inspect.tab[sel].type) + ? YINSP_waitin_txt : YINSP_workin_txt); + Insp_sel = &Inspect.tab[sel]; + Inspect.tab[sel].func(Inspect.tab[sel].fmts, pid); + Insp_utf8 = utf8_delta(Insp_buf); + key = insp_view_choice(p); + free(Insp_buf); + free(Insp_p); + break; + default: + goto signify_that; + } + } while (key != 'q' && key != kbd_ESC); + + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef mkSEL +} // end: inspection_utility + +#undef INSP_MKSL +#undef INSP_RLEN +#undef INSP_BUSY + +/*###### Other Filtering ###############################################*/ + + /* + * This structure is hung from a WIN_t when other filtering is active */ +struct osel_s { + struct osel_s *nxt; // the next criteria or NULL. + int (*rel)(const char *, const char *); // relational strings compare + char *(*sel)(const char *, const char *); // for selection str compares + char *raw; // raw user input (dup check) + char *val; // value included or excluded + int ops; // filter delimiter/operation + int inc; // include == 1, exclude == 0 + int enu; // field (procflag) to filter + int typ; // typ used to set: rel & sel +}; + + /* + * A function to parse, validate and build a single 'other filter' */ +static const char *osel_add (WIN_t *q, int ch, char *glob, int push) { + int (*rel)(const char *, const char *); + char *(*sel)(const char *, const char *); + char raw[MEDBUFSIZ], ops, *pval; + struct osel_s *osel; + int inc, enu; + + if (ch == 'o') { + rel = strcasecmp; + sel = strcasestr; + } else { + rel = strcmp; + sel = strstr; + } + + if (!snprintf(raw, sizeof(raw), "%s", glob)) + return NULL; + for (osel = q->osel_1st; osel; ) { + if (!strcmp(osel->raw, raw)) // #1: is criteria duplicate? + return N_txt(OSEL_errdups_txt); + osel = osel->nxt; + } + if (*glob != '!') inc = 1; // #2: is it include/exclude? + else { ++glob; inc = 0; } + + if (!(pval = strpbrk(glob, "<=>"))) // #3: do we see a delimiter? + return fmtmk(N_fmt(OSEL_errdelm_fmt) + , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt)); + ops = *(pval); + *(pval++) = '\0'; + + for (enu = 0; enu < EU_MAXPFLGS; enu++) // #4: is this a valid field? + if (!STRCMP(N_col(enu), glob)) break; + if (enu == EU_MAXPFLGS) + return fmtmk(N_fmt(XTRA_badflds_fmt), glob); + + if (!(*pval)) // #5: did we get some value? + return fmtmk(N_fmt(OSEL_errvalu_fmt) + , inc ? N_txt(WORD_include_txt) : N_txt(WORD_exclude_txt)); + + osel = alloc_c(sizeof(struct osel_s)); + osel->typ = ch; + osel->inc = inc; + osel->enu = enu; + osel->ops = ops; + if (ops == '=') osel->val = alloc_s(pval); + else osel->val = alloc_s(justify_pad(pval, Fieldstab[enu].width, Fieldstab[enu].align)); + osel->rel = rel; + osel->sel = sel; + osel->raw = alloc_s(raw); + + if (push) { + // a LIFO queue was used when we're interactive + osel->nxt = q->osel_1st; + q->osel_1st = osel; + } else { + // a FIFO queue must be employed for the rcfile + if (!q->osel_1st) + q->osel_1st = osel; + else { + struct osel_s *prev, *walk = q->osel_1st; + do { + prev = walk; + walk = walk->nxt; + } while (walk); + prev->nxt = osel; + } + } + q->osel_tot += 1; + + return NULL; +} // end: osel_add + + + /* + * A function to turn off entire other filtering in the given window */ +static void osel_clear (WIN_t *q) { + struct osel_s *osel = q->osel_1st; + + while (osel) { + struct osel_s *nxt = osel->nxt; + free(osel->val); + free(osel->raw); + free(osel); + osel = nxt; + } + q->osel_tot = 0; + q->osel_1st = NULL; +} // end: osel_clear + + + /* + * Determine if there are matching values or relationships among the + * other criteria in this passed window -- it's called from only one + * place, and likely inlined even without the directive */ +static inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str) { + struct osel_s *osel = q->osel_1st; + + while (osel) { + if (osel->enu == enu) { + int r; + switch (osel->ops) { + case '<': // '<' needs the r < 0 unless + r = osel->rel(str, osel->val); // '!' which needs an inverse + if ((r >= 0 && osel->inc) || (r < 0 && !osel->inc)) return 0; + break; + case '>': // '>' needs the r > 0 unless + r = osel->rel(str, osel->val); // '!' which needs an inverse + if ((r <= 0 && osel->inc) || (r > 0 && !osel->inc)) return 0; + break; + default: + { char *p = osel->sel(str, osel->val); + if ((!p && osel->inc) || (p && !osel->inc)) return 0; + } + break; + } + } + osel = osel->nxt; + } + return 1; +} // end: osel_matched + +/*###### Startup routines ##############################################*/ + + /* + * No matter what *they* say, we handle the really really BIG and + * IMPORTANT stuff upon which all those lessor functions depend! */ +static void before (char *me) { + #define doALL STAT_REAP_NUMA_NODES_TOO + int i, rc; + int linux_version_code = procps_linux_version(); + + atexit(close_stdout); + + // setup our program name + Myname = strrchr(me, '/'); + if (Myname) ++Myname; else Myname = me; + + // accommodate nls/gettext potential translations + // ( must 'setlocale' before our libproc called ) + initialize_nls(); + + // is /proc mounted? + fatal_proc_unmounted(NULL, 0); + +#ifndef OFF_STDERROR + /* there's a chance that damn libnuma may spew to stderr so we gotta + make sure he does not corrupt poor ol' top's first output screen! + Yes, he provides some overridable 'weak' functions to change such + behavior but we can't exploit that since we don't follow a normal + ld route to symbol resolution (we use that dlopen() guy instead)! */ + Stderr_save = dup(fileno(stderr)); + if (-1 < Stderr_save && freopen("/dev/null", "w", stderr)) + ; // avoid -Wunused-result +#endif + + // establish some cpu particulars + Hertz = procps_hertz_get(); + Cpu_States_fmts = N_unq(STATE_lin2x6_fmt); + if (linux_version_code >= LINUX_VERSION(2, 6, 11)) + Cpu_States_fmts = N_unq(STATE_lin2x7_fmt); + + // get the total cpus (and, if possible, numa node total) + if ((rc = procps_stat_new(&Stat_ctx))) + Restrict_some = Cpu_cnt = 1; + else { + if (!(Stat_reap = procps_stat_reap(Stat_ctx, doALL, Stat_items, MAXTBL(Stat_items)))) + error_exit(fmtmk(N_fmt(LIB_errorcpu_fmt), __LINE__, strerror(errno))); +#ifndef PRETEND0NUMA + Numa_node_tot = Stat_reap->numa->total; +#endif + Cpu_cnt = Stat_reap->cpus->total; +#ifdef PRETEND48CPU + Cpu_cnt = 48; +#endif + } + + // prepare for memory stats from new library API ... + if ((rc = procps_meminfo_new(&Mem_ctx))) + Restrict_some = 1; + + // establish max depth for newlib pids stack (# of result structs) + Pids_itms = alloc_c(sizeof(enum pids_item) * MAXTBL(Fieldstab)); + if (PIDS_noop != 0) + for (i = 0; i < MAXTBL(Fieldstab); i++) + Pids_itms[i] = PIDS_noop; + Pids_itms_tot = MAXTBL(Fieldstab); + // we will identify specific items in the build_headers() function + if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(-rc))); + +#if defined THREADED_CPU || defined THREADED_MEM || defined THREADED_TSK +{ struct sigaction sa; + Thread_id_main = pthread_self(); + /* in case any of our threads have been enabled, they'll inherit this mask + with everything blocked. therefore, signals go to the main thread (us). */ + sigfillset(&sa.sa_mask); + pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL); +} +#endif + +#ifdef THREADED_CPU + if (0 != sem_init(&Semaphore_cpus_beg, 0, 0) + || (0 != sem_init(&Semaphore_cpus_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_cpus, "update cpus"); +#endif +#ifdef THREADED_MEM + if (0 != sem_init(&Semaphore_memory_beg, 0, 0) + || (0 != sem_init(&Semaphore_memory_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_memory, "update memory"); +#endif +#ifdef THREADED_TSK + if (0 != sem_init(&Semaphore_tasks_beg, 0, 0) + || (0 != sem_init(&Semaphore_tasks_end, 0, 0))) + error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt), __LINE__, strerror(errno))); + if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL)) + error_exit(fmtmk(N_fmt(X_THREADINGS_fmt), __LINE__, strerror(errno))); + pthread_setname_np(Thread_id_tasks, "update tasks"); +#endif + // lastly, establish support for graphing cpus & memory + Graph_cpus = alloc_c(sizeof(struct graph_parms)); + Graph_mems = alloc_c(sizeof(struct graph_parms)); + #undef doALL + + // don't distort startup cpu(s) display ... + usleep(LIB_USLEEP); +} // end: before + + + /* + * A configs_file *Helper* function responsible for transforming + * a 3.2.8 - 3.3.17 format 'fieldscur' into our integer based format */ +static int cfg_xform (WIN_t *q, char *flds, const char *defs) { + #define CVTon(c) ((c) |= 0x80) + static struct { + int old, new; + } flags_tab[] = { + #define old_View_NOBOLD 0x000001 + #define old_VISIBLE_tsk 0x000008 + #define old_Qsrt_NORMAL 0x000010 + #define old_Show_HICOLS 0x000200 + #define old_Show_THREAD 0x010000 + { old_View_NOBOLD, View_NOBOLD }, + { old_VISIBLE_tsk, Show_TASKON }, + { old_Qsrt_NORMAL, Qsrt_NORMAL }, + { old_Show_HICOLS, Show_HICOLS }, + { old_Show_THREAD, 0 } + #undef old_View_NOBOLD + #undef old_VISIBLE_tsk + #undef old_Qsrt_NORMAL + #undef old_Show_HICOLS + #undef old_Show_THREAD + }; + static char null_flds[] = "abcdefghijklmnopqrstuvwxyz"; + static const char fields_src[] = CVT_FORMER; + char fields_dst[PFLAGSSIZ], *p1, *p2; + int c, f, i, x, *pn; + + if (Rc.id == 'a') { + // first we'll touch up this window's winflags ... + x = q->rc.winflags; + q->rc.winflags = 0; + for (i = 0; i < MAXTBL(flags_tab); i++) { + if (x & flags_tab[i].old) { + x &= ~flags_tab[i].old; + q->rc.winflags |= flags_tab[i].new; + } + } + q->rc.winflags |= x; + + // now let's convert old top's more limited fields ... + f = strlen(flds); + if (f >= CVT_FLDMAX) + return 1; + strcpy(fields_dst, fields_src); + /* all other fields represent the 'on' state with a capitalized version + of a particular qwerty key. for the 2 additional suse out-of-memory + fields it makes perfect sense to do the exact opposite, doesn't it? + in any case, we must turn them 'off' temporarily ... */ + if ((p1 = strchr(flds, '['))) *p1 = '{'; + if ((p2 = strchr(flds, '\\'))) *p2 = '|'; + for (i = 0; i < f; i++) { + c = flds[i]; + x = tolower(c) - 'a'; + if (x < 0 || x >= CVT_FLDMAX) + return 1; + fields_dst[i] = fields_src[x]; + if (isupper(c)) + CVTon(fields_dst[i]); + } + // if we turned any suse only fields off, turn 'em back on OUR way ... + if (p1) CVTon(fields_dst[p1 - flds]); + if (p2) CVTon(fields_dst[p2 - flds]); + + // next, we must adjust the old sort field enum ... + x = q->rc.sortindx; + c = null_flds[x]; + q->rc.sortindx = 0; + if ((p1 = memchr(flds, c, CVT_FLDMAX)) + || ((p1 = memchr(flds, toupper(c), CVT_FLDMAX)))) { + x = p1 - flds; + q->rc.sortindx = (fields_dst[x] & 0x7f) - FLD_OFFSET; + } + // now we're in a 3.3.0 format (soon to be transformed) ... + strcpy(flds, fields_dst); + } + + // lastly, let's attend to the 3.3.0 - 3.3.17 fieldcurs format ... + pn = &q->rc.fieldscur[0]; + x = strlen(defs); + for (i = 0; i < x; i++) { + f = ((unsigned char)flds[i] & 0x7f); + f = f << 1; + if ((unsigned char)flds[i] & 0x80) f |= FLDon; + *(pn + i) = f; + } + + return 0; + #undef CVTon +} // end: cfg_xform + + + /* + * A configs_file *Helper* function responsible for reading + * and validating a configuration file's 'Inspection' entries */ +static int config_insp (FILE *fp, char *buf, size_t size) { + int i; + + // we'll start off with a 'potential' blank or empty line + // ( only realized if we end up with Inspect.total > 0 ) + if (!buf[0] || buf[0] != '\n') Inspect.raw = alloc_s("\n"); + else Inspect.raw = alloc_c(1); + + for (i = 0;;) { + #define iT(element) Inspect.tab[i].element + #define nxtLINE { buf[0] = '\0'; continue; } + size_t lraw = strlen(Inspect.raw) +1; + int n, x; + char *s1, *s2, *s3; + + if (i < 0 || (size_t)i >= INT_MAX / sizeof(struct I_ent)) break; + if (lraw >= INT_MAX - size) break; + + if (!buf[0] && !fgets(buf, size, fp)) break; + lraw += strlen(buf) +1; + Inspect.raw = alloc_r(Inspect.raw, lraw); + strcat(Inspect.raw, buf); + + if (buf[0] == '#' || buf[0] == '\n') nxtLINE; + Inspect.tab = alloc_r(Inspect.tab, sizeof(struct I_ent) * (i + 1)); + + // part of this is used in a show_special() call, so let's sanitize it + for (n = 0, x = strlen(buf); n < x; n++) { + if ((buf[n] != '\t' && buf[n] != '\n') + && (buf[n] < ' ')) { + buf[n] = '.'; + Rc_questions = 1; + } + } + if (!(s1 = strtok(buf, "\t\n"))) { Rc_questions = 1; nxtLINE; } + if (!(s2 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; } + if (!(s3 = strtok(NULL, "\t\n"))) { Rc_questions = 1; nxtLINE; } + + switch (toupper(buf[0])) { + case 'F': + iT(func) = insp_do_file; + break; + case 'P': + iT(func) = insp_do_pipe; + break; + default: + Rc_questions = 1; + nxtLINE; + } + iT(type) = alloc_s(s1); + iT(name) = alloc_s(s2); + iT(fmts) = alloc_s(s3); + iT(farg) = (strstr(iT(fmts), "%d")) ? 1 : 0; + iT(fstr) = alloc_c(FNDBUFSIZ); + iT(flen) = 0; + + buf[0] = '\0'; + ++i; + #undef iT + #undef nxtLINE + } // end: for ('inspect' entries) + + Inspect.total = i; +#ifndef INSP_OFFDEMO + if (!Inspect.total) { + #define mkS(n) N_txt(YINSP_demo ## n ## _txt) + const char *sels[] = { mkS(01), mkS(02), mkS(03) }; + Inspect.total = Inspect.demo = MAXTBL(sels); + Inspect.tab = alloc_c(sizeof(struct I_ent) * Inspect.total); + for (i = 0; i < Inspect.total; i++) { + Inspect.tab[i].type = alloc_s(N_txt(YINSP_deqtyp_txt)); + Inspect.tab[i].name = alloc_s(sels[i]); + Inspect.tab[i].func = insp_do_demo; + Inspect.tab[i].fmts = alloc_s(N_txt(YINSP_deqfmt_txt)); + Inspect.tab[i].fstr = alloc_c(FNDBUFSIZ); + } + #undef mkS + } +#endif + return 0; +} // end: config_insp + + + /* + * A configs_file *Helper* function responsible for reading + * and validating a configuration file's 'Other Filter' entries */ +static int config_osel (FILE *fp, char *buf, size_t size) { + int i, ch, tot, wno, begun; + char *p; + + for (begun = 0;;) { + if (!fgets(buf, size, fp)) return 0; + if (buf[0] == '\n') continue; + // whoa, must be an 'inspect' entry + if (!begun && !strstr(buf, Osel_delim_1_txt)) + return 0; + // ok, we're now beginning + if (!begun && strstr(buf, Osel_delim_1_txt)) { + begun = 1; + continue; + } + // this marks the end of our stuff + if (begun && strstr(buf, Osel_delim_2_txt)) + break; + + if (2 != sscanf(buf, Osel_window_fmts, &wno, &tot)) + goto end_oops; + if (wno < 0 || wno >= GROUPSMAX) goto end_oops; + if (tot < 0) goto end_oops; + + for (i = 0; i < tot; i++) { + if (!fgets(buf, size, fp)) return 1; + if (1 > sscanf(buf, Osel_filterI_fmt, &ch)) goto end_oops; + if ((p = strchr(buf, '\n'))) *p = '\0'; + if (!(p = strstr(buf, OSEL_FILTER))) goto end_oops; + p += sizeof(OSEL_FILTER) - 1; + if (osel_add(&Winstk[wno], ch, p, 0)) goto end_oops; + } + } + // let's prime that buf for the next guy... + fgets(buf, size, fp); + return 0; + +end_oops: + Rc_questions = 1; + return 1; +} // end: config_osel + + + /* + * A configs_reads *Helper* function responsible for processing + * a configuration file (personal or system-wide default) */ +static const char *configs_file (FILE *fp, const char *name, float *delay) { + char fbuf[LRGBUFSIZ]; + int i, n, tmp_whole, tmp_fract; + const char *p = NULL; + + p = fmtmk(N_fmt(RC_bad_files_fmt), name); + (void)fgets(fbuf, sizeof(fbuf), fp); // ignore eyecatcher + if (6 != fscanf(fp + , "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n" + , &Rc.id, &Rc.mode_altscr, &Rc.mode_irixps, &tmp_whole, &tmp_fract, &i)) { + return p; + } + if (Rc.id < 'a' || Rc.id > RCF_VERSION_ID) + return p; + if (strchr("bcde", Rc.id)) + return p; + if (Rc.mode_altscr < 0 || Rc.mode_altscr > 1) + return p; + if (Rc.mode_irixps < 0 || Rc.mode_irixps > 1) + return p; + if (tmp_whole < 0) + return p; + // you saw that, right? (fscanf stickin' it to 'i') + if (i < 0 || i >= GROUPSMAX) + return p; + Curwin = &Winstk[i]; + // this may be ugly, but it keeps us locale independent... + *delay = (float)tmp_whole + (float)tmp_fract / 1000; + + for (i = 0 ; i < GROUPSMAX; i++) { + static const char *def_flds[] = { DEF_FORMER, JOB_FORMER, MEM_FORMER, USR_FORMER }; + int j, x; + WIN_t *w = &Winstk[i]; + p = fmtmk(N_fmt(RC_bad_entry_fmt), i+1, name); + + if (1 != fscanf(fp, "%3s\tfieldscur=", w->rc.winname)) + return p; + if (Rc.id < RCF_XFORMED_ID) + fscanf(fp, "%s\n", fbuf); + else { + for (j = 0; ; j++) + if (1 != fscanf(fp, "%d", &w->rc.fieldscur[j])) + break; + } + + // be tolerant of missing release 3.3.10 graph modes additions + if (3 > fscanf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d" + ", double_up=%d, combine_cpus=%d, core_types=%d\n" + , &w->rc.winflags, &w->rc.sortindx, &w->rc.maxtasks, &w->rc.graph_cpus, &w->rc.graph_mems + , &w->rc.double_up, &w->rc.combine_cpus, &w->rc.core_types)) + return p; + if (w->rc.sortindx < 0 || w->rc.sortindx >= EU_MAXPFLGS) + return p; + if (w->rc.maxtasks < 0) + return p; + if (w->rc.graph_cpus < 0 || w->rc.graph_cpus > 2) + return p; + if (w->rc.graph_mems < 0 || w->rc.graph_mems > 2) + return p; + if (w->rc.double_up < 0 || w->rc.double_up >= ADJOIN_limit) + return p; + // can't check upper bounds until Cpu_cnt is known + if (w->rc.combine_cpus < 0) + return p; + if (w->rc.core_types < 0 || w->rc.core_types > E_CORES_ONLY) + return p; + + if (4 != fscanf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n" + , &w->rc.summclr, &w->rc.msgsclr, &w->rc.headclr, &w->rc.taskclr)) + return p; + // would prefer to use 'max_colors', but it isn't available yet... + if (w->rc.summclr < 0 || w->rc.summclr > 255) return p; + if (w->rc.msgsclr < 0 || w->rc.msgsclr > 255) return p; + if (w->rc.headclr < 0 || w->rc.headclr > 255) return p; + if (w->rc.taskclr < 0 || w->rc.taskclr > 255) return p; + + switch (Rc.id) { + case 'a': // 3.2.8 (former procps) + // fall through + case 'f': // 3.3.0 thru 3.3.3 (ng) + SETw(w, Show_JRNUMS); + // fall through + case 'g': // from 3.3.4 thru 3.3.8 + if (Rc.id > 'a') scat(fbuf, RCF_PLUS_H); + // fall through + case 'h': // this is release 3.3.9 + w->rc.graph_cpus = w->rc.graph_mems = 0; + // these next 2 are really global, but best documented here + Rc.summ_mscale = Rc.task_mscale = SK_Kb; + // fall through + case 'i': // from 3.3.10 thru 3.3.16 + if (Rc.id > 'a') scat(fbuf, RCF_PLUS_J); + w->rc.double_up = w->rc.combine_cpus = 0; + // fall through + case 'j': // this is release 3.3.17 + if (cfg_xform(w, fbuf, def_flds[i])) + return p; + Rc.tics_scaled = 0; + // fall through + case 'k': // current RCF_VERSION_ID + // fall through + default: + if (mlen(w->rc.fieldscur) < EU_MAXPFLGS) + return p; + for (x = 0; x < EU_MAXPFLGS; x++) { + FLG_t f = FLDget(w, x); + if (f >= EU_MAXPFLGS || f < 0) + return p; + } + break; + } + // ensure there's been no manual alteration of fieldscur + for (n = 0 ; n < EU_MAXPFLGS; n++) { + if (&w->rc.fieldscur[n] != msch(w->rc.fieldscur, w->rc.fieldscur[n], EU_MAXPFLGS)) + return p; + } + } // end: for (GROUPSMAX) + + // any new addition(s) last, for older rcfiles compatibility... + (void)fscanf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n" + , &Rc.fixed_widest, &Rc.summ_mscale, &Rc.task_mscale, &Rc.zero_suppress, &Rc.tics_scaled); + if (Rc.fixed_widest < -1 || Rc.fixed_widest > SCREENMAX) + Rc.fixed_widest = 0; + if (Rc.summ_mscale < 0 || Rc.summ_mscale > SK_Eb) + Rc.summ_mscale = 0; + if (Rc.task_mscale < 0 || Rc.task_mscale > SK_Pb) + Rc.task_mscale = 0; + if (Rc.zero_suppress < 0 || Rc.zero_suppress > 1) + Rc.zero_suppress = 0; + if (Rc.tics_scaled < 0 || Rc.tics_scaled > TICS_AS_LAST) + Rc.tics_scaled = 0; + + // prepare to warn that older top can no longer read rcfile ... + if (Rc.id != RCF_VERSION_ID) + Rc_compatibilty = 1; + + // lastly, let's process any optional glob(s) ... + // (darn, must do osel 1st even though alphabetically 2nd) + fbuf[0] = '\0'; + config_osel(fp, fbuf, sizeof(fbuf)); + config_insp(fp, fbuf, sizeof(fbuf)); + + return NULL; +} // end: configs_file + + + /* + * A configs_reads *Helper* function responsible for ensuring the + * complete path was established, otherwise force the 'W' to fail */ +static int configs_path (const char *const fmts, ...) __attribute__((format(printf,1,2))); +static int configs_path (const char *const fmts, ...) { + int len; + va_list ap; + + va_start(ap, fmts); + len = vsnprintf(Rc_name, sizeof(Rc_name), fmts, ap); + va_end(ap); + if (len <= 0 || (size_t)len >= sizeof(Rc_name)) { + Rc_name[0] = '\0'; + len = 0; + } + return len; +} // end: configs_path + + + /* + * Try reading up to 3 rcfiles + * 1. 'SYS_RCRESTRICT' contains two lines consisting of the secure + * mode switch and an update interval. Its presence limits what + * ordinary users are allowed to do. + * 2. 'Rc_name' contains multiple lines - both global & per window. + * line 1 : an eyecatcher and creating program/alias name + * line 2 : an id, Mode_altcsr, Mode_irixps, Delay_time, Curwin. + * For each of the 4 windows: + * lines a: contains w->winname, fieldscur + * line b: contains w->winflags, sortindx, maxtasks, etc + * line c: contains w->summclr, msgsclr, headclr, taskclr + * global : miscellaneous additional settings + * Any remaining lines are devoted to the optional entries + * supporting the 'Other Filter' and 'Inspect' provisions. + * 3. 'SYS_RCDEFAULTS' system-wide defaults if 'Rc_name' absent + * format is identical to #2 above */ +static void configs_reads (void) { + float tmp_delay = DEF_DELAY; + const char *p, *p_home; + FILE *fp; + + fp = fopen(SYS_RCRESTRICT, "r"); + if (fp) { + char fbuf[SMLBUFSIZ]; + if (fgets(fbuf, sizeof(fbuf), fp)) { // sys rc file, line 1 + Secure_mode = 1; + if (fgets(fbuf, sizeof(fbuf), fp)) // sys rc file, line 2 + sscanf(fbuf, "%f", &Rc.delay_time); + } + fclose(fp); + } + + Rc_name[0] = '\0'; // "fopen() shall fail if pathname is an empty string." + // attempt to use the legacy file first, if we cannot access that file, use + // the new XDG basedir locations (XDG_CONFIG_HOME or HOME/.config) instead. + p_home = getenv("HOME"); + if (!p_home || p_home[0] != '/') { + const struct passwd *const pwd = getpwuid(getuid()); + if (!pwd || !(p_home = pwd->pw_dir) || p_home[0] != '/') { + p_home = NULL; + } + } + if (p_home) + configs_path("%s/.%src", p_home, Myname); + + if (!(fp = fopen(Rc_name, "r"))) { + p = getenv("XDG_CONFIG_HOME"); + // ensure the path we get is absolute, fallback otherwise. + if (!p || p[0] != '/') { + if (!p_home) goto system_default; + p = fmtmk("%s/.config", p_home); + (void)mkdir(p, 0700); + } + if (!configs_path("%s/procps", p)) goto system_default; + (void)mkdir(Rc_name, 0700); + if (!configs_path("%s/procps/%src", p, Myname)) goto system_default; + fp = fopen(Rc_name, "r"); + } + + if (fp) { + p = configs_file(fp, Rc_name, &tmp_delay); + fclose(fp); + if (p) goto default_or_error; + } else { +system_default: + fp = fopen(SYS_RCDEFAULTS, "r"); + if (fp) { + p = configs_file(fp, SYS_RCDEFAULTS, &tmp_delay); + fclose(fp); + if (p) goto default_or_error; + } + } + + // lastly, establish the true runtime secure mode and delay time + if (!getuid()) Secure_mode = 0; + if (!Secure_mode) Rc.delay_time = tmp_delay; + return; + +default_or_error: +#ifdef RCFILE_NOERR +{ RCF_t rcdef = DEF_RCFILE; + int i; + Rc = rcdef; + for (i = 0 ; i < GROUPSMAX; i++) + Winstk[i].rc = Rc.win[i]; +} +#else + error_exit(p); +#endif +} // end: configs_reads + + + /* + * Parse command line arguments. + * Note: it's assumed that the rc file(s) have already been read + * and our job is to see if any of those options are to be + * overridden -- we'll force some on and negate others in our + * best effort to honor the loser's (oops, user's) wishes... */ +static void parse_args (int argc, char **argv) { + static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1"; + static const struct option lopts[] = { + { "batch-mode", no_argument, NULL, 'b' }, + { "cmdline-toggle", no_argument, NULL, 'c' }, + { "delay", required_argument, NULL, 'd' }, + { "scale-summary-mem", required_argument, NULL, 'E' }, + { "scale-task-mem", required_argument, NULL, 'e' }, + { "threads-show", no_argument, NULL, 'H' }, + { "help", no_argument, NULL, 'h' }, + { "idle-toggle", no_argument, NULL, 'i' }, + { "iterations", required_argument, NULL, 'n' }, + { "list-fields", no_argument, NULL, 'O' }, + { "sort-override", required_argument, NULL, 'o' }, + { "pid", required_argument, NULL, 'p' }, + { "accum-time-toggle", no_argument, NULL, 'S' }, + { "secure-mode", no_argument, NULL, 's' }, + { "filter-any-user", required_argument, NULL, 'U' }, + { "filter-only-euser", required_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { "width", optional_argument, NULL, 'w' }, + { "single-cpu-toggle", no_argument, NULL, '1' }, + { NULL, 0, NULL, 0 } + }; + float tmp_delay = FLT_MAX; + int ch; + + while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) { + int i; + float tmp; + char *cp = optarg; + +#ifndef GETOPTFIX_NO + /* first, let's plug some awful gaps in the getopt implementation, + especially relating to short options with (optional) arguments! */ + if (!cp && optind < argc && argv[optind][0] != '-') + cp = argv[optind++]; + if (cp) { + if (*cp == '=') ++cp; + /* here, if we're actually accessing argv[argc], we'll rely on + the required NULL delimiter which yields an error_exit next */ + if (*cp == '\0') cp = argv[optind++]; + if (!cp) error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch)); + } +#endif + switch (ch) { + case '1': // ensure behavior identical to run-time toggle + if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM); + else TOGw(Curwin, View_CPUSUM); + OFFw(Curwin, View_CPUNOD); + SETw(Curwin, View_STATES); + break; + case 'b': + Batch = 1; + break; + case 'c': + TOGw(Curwin, Show_CMDLIN); + break; + case 'd': + if (!mkfloat(cp, &tmp_delay, 0)) + error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp)); + if (0 > tmp_delay) + error_exit(N_txt(DELAY_badarg_txt)); + continue; + case 'E': + { const char *get = "kmgtpe", *got; + if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1) + error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp)); + Rc.summ_mscale = (int)(got - get); + } continue; + case 'e': + { const char *get = "kmgtp", *got; + if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1) + error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp)); + Rc.task_mscale = (int)(got - get); + } continue; + case 'H': + Thread_mode = 1; + break; + case 'h': + puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname)); + bye_bye(NULL); + case 'i': + TOGw(Curwin, Show_IDLEPS); + Curwin->rc.maxtasks = 0; + break; + case 'n': + if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp) + error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp)); + Loops = (int)tmp; + continue; + case 'O': + for (i = 0; i < EU_MAXPFLGS; i++) + puts(N_col(i)); + bye_bye(NULL); + case 'o': + if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; } + else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; } + for (i = 0; i < EU_MAXPFLGS; i++) + if (!STRCMP(cp, N_col(i))) break; + if (i == EU_MAXPFLGS) + error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp)); + OFFw(Curwin, Show_FOREST); + Curwin->rc.sortindx = i; + continue; + case 'p': + { int pid; char *p; + if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt)); + do { + if (Monpidsidx >= MONPIDMAX) + error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX)); + if (1 != sscanf(cp, "%d", &pid) + || strpbrk(cp, "+-.")) + error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp)); + if (!pid) pid = getpid(); + for (i = 0; i < Monpidsidx; i++) + if (Monpids[i] == pid) goto next_pid; + Monpids[Monpidsidx++] = pid; + next_pid: + if (!(p = strchr(cp, ','))) break; + cp = p + 1; + } while (*cp); + } continue; + case 'S': + TOGw(Curwin, Show_CTIMES); + break; + case 's': + Secure_mode = 1; + break; + case 'U': + case 'u': + { const char *errmsg; + if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt)); + if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg); + } continue; + case 'V': + puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING)); + bye_bye(NULL); + case 'w': + tmp = -1; + if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX)) + error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp)); + Width_mode = (int)tmp; + continue; + default: + /* we'll rely on getopt for any error message while + forcing an EXIT_FAILURE with an empty string ... */ + bye_bye(""); + } // end: switch (ch) +#ifndef GETOPTFIX_NO + if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp)); +#endif + } // end: while getopt_long + + if (optind < argc) + error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind])); + + // fixup delay time, maybe... + if (FLT_MAX > tmp_delay) { + if (Secure_mode) + error_exit(N_txt(DELAY_secure_txt)); + Rc.delay_time = tmp_delay; + } +} // end: parse_args + + + /* + * Establish a robust signals environment */ +static void signals_set (void) { + #ifndef SIGRTMAX // not available on hurd, maybe others too + #define SIGRTMAX 32 + #endif + int i; + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + // with user position preserved through SIGWINCH, we must avoid SA_RESTART + sa.sa_flags = 0; + for (i = SIGRTMAX; i; i--) { + switch (i) { + case SIGHUP: + if (Batch) + sa.sa_handler = SIG_IGN; + else + sa.sa_handler = sig_endpgm; + break; + case SIGALRM: case SIGINT: case SIGPIPE: + case SIGQUIT: case SIGTERM: case SIGUSR1: + case SIGUSR2: + sa.sa_handler = sig_endpgm; + break; + case SIGTSTP: case SIGTTIN: case SIGTTOU: + sa.sa_handler = sig_paused; + break; + case SIGCONT: case SIGWINCH: + sa.sa_handler = sig_resize; + break; + default: + sa.sa_handler = sig_abexit; + break; + case SIGKILL: case SIGSTOP: + // because uncatchable, fall through + case SIGCHLD: // we can't catch this + continue; // when opening a pipe + } + sigaction(i, &sa, NULL); + } +} // end: signals_set + + + /* + * Set up the terminal attributes */ +static void whack_terminal (void) { + static char dummy[] = "dumb"; + struct termios tmptty; + + // the curses part... + if (Batch) { + setupterm(dummy, STDOUT_FILENO, NULL); + return; + } +#ifdef PRETENDNOCAP + setupterm(dummy, STDOUT_FILENO, NULL); +#else + setupterm(NULL, STDOUT_FILENO, NULL); +#endif + // our part... + if (-1 == tcgetattr(STDIN_FILENO, &Tty_original)) + error_exit(N_txt(FAIL_tty_get_txt)); + // ok, haven't really changed anything but we do have our snapshot + Ttychanged = 1; + + // first, a consistent canonical mode for interactive line input + tmptty = Tty_original; + tmptty.c_lflag |= (ECHO | ECHOCTL | ECHOE | ICANON | ISIG); + tmptty.c_lflag &= ~NOFLSH; + tmptty.c_oflag &= ~TAB3; + tmptty.c_iflag |= BRKINT; + tmptty.c_iflag &= ~IGNBRK; +#ifdef TERMIOS_ONLY + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + tcgetattr(STDIN_FILENO, &Tty_tweaked); +#endif + // lastly, a nearly raw mode for unsolicited single keystrokes + tmptty.c_lflag &= ~(ECHO | ECHOCTL | ECHOE | ICANON); + tmptty.c_cc[VMIN] = 1; + tmptty.c_cc[VTIME] = 0; + if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &tmptty)) + error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno))); + tcgetattr(STDIN_FILENO, &Tty_raw); + +#ifndef OFF_STDIOLBF + // thanks anyway stdio, but we'll manage buffering at the frame level... + setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf)); +#endif +#ifdef OFF_SCROLLBK + // this has the effect of disabling any troublesome scrollback buffer... + if (enter_ca_mode) putp(enter_ca_mode); +#endif + // and don't forget to ask iokey to initialize his tinfo_tab + iokey(IOKEY_INIT); +} // end: whack_terminal + +/*###### Windows/Field Groups support #################################*/ + + /* + * Value a window's name and make the associated group name. */ +static void win_names (WIN_t *q, const char *name) { + /* note: sprintf/snprintf results are "undefined" when src==dst, + according to C99 & POSIX.1-2001 (thanks adc) */ + if (q->rc.winname != name) + snprintf(q->rc.winname, sizeof(q->rc.winname), "%s", name); + snprintf(q->grpname, sizeof(q->grpname), "%d:%s", q->winnum, name); +} // end: win_names + + + /* + * This guy just resets (normalizes) a single window + * and he ensures pid monitoring is no longer active. */ +static void win_reset (WIN_t *q) { + SETw(q, Show_IDLEPS | Show_TASKON); +#ifndef SCROLLVAR_NO + q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->varcolbeg = q->focus_pid = 0; +#else + q->rc.maxtasks = q->usrseltyp = q->begpflg = q->begtask = q->focus_pid = 0; +#endif + mkVIZoff(q) + osel_clear(q); + q->findstr[0] = '\0'; + q->rc.combine_cpus = 0; + q->rc.core_types = 0; + + // these next guys are global, not really windows based + Monpidsidx = 0; + Rc.tics_scaled = 0; + BOT_TOSS; +} // end: win_reset + + + /* + * Display a window/field group (ie. make it "current"). */ +static WIN_t *win_select (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + /* if there's no ch, it means we're supporting the external interface, + so we must try to get our own darn ch by begging the user... */ + if (!ch) { + show_pmt(N_txt(CHOOSE_group_txt)); + if (1 > (ch = iokey(IOKEY_ONCE))) return w; + } + switch (ch) { + case 'a': // we don't carry 'a' / 'w' in our + w = w->next; // pmt - they're here for a good + break; // friend of ours -- wins_colors. + case 'w': // (however those letters work via + w = w->prev; // the pmt too but gee, end-loser + break; // should just press the darn key) + case '1': case '2' : case '3': case '4': + w = &Winstk[ch - '1']; + break; + default: // keep gcc happy + break; + } + Curwin = w; + return Curwin; +} // end: win_select + + + /* + * Just warn the user when a command can't be honored. */ +static int win_warn (int what) { + switch (what) { + case Warn_ALT: + show_msg(N_txt(DISABLED_cmd_txt)); + break; + case Warn_VIZ: + show_msg(fmtmk(N_fmt(DISABLED_win_fmt), Curwin->grpname)); + break; + default: // keep gcc happy + break; + } + /* we gotta' return false 'cause we're somewhat well known within + macro society, by way of that sassy little tertiary operator... */ + return 0; +} // end: win_warn + + + /* + * Change colors *Helper* function to save/restore settings; + * ensure colors will show; and rebuild the terminfo strings. */ +static void wins_clrhlp (WIN_t *q, int save) { + static int flgssav, summsav, msgssav, headsav, tasksav; + + if (save) { + flgssav = q->rc.winflags; summsav = q->rc.summclr; + msgssav = q->rc.msgsclr; headsav = q->rc.headclr; tasksav = q->rc.taskclr; + SETw(q, Show_COLORS); + } else { + q->rc.winflags = flgssav; q->rc.summclr = summsav; + q->rc.msgsclr = msgssav; q->rc.headclr = headsav; q->rc.taskclr = tasksav; + } + capsmk(q); +} // end: wins_clrhlp + + + /* + * Change colors used in display */ +static void wins_colors (void) { + #define kbdABORT 'q' + #define kbdAPPLY kbd_ENTER + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int clr = w->rc.taskclr, *pclr = &w->rc.taskclr; + char tgt = 'T'; + int key; + + if (0 >= max_colors) { + show_msg(N_txt(COLORS_nomap_txt)); + return; + } + wins_clrhlp(w, 1); + putp((Cursor_state = Cap_curs_huge)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + do { + putp(Cap_home); + // this string is well above ISO C89's minimum requirements! + show_special(1, fmtmk(N_unq(COLOR_custom_fmt) + , w->grpname + , CHKw(w, View_NOBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , CHKw(w, Show_COLORS) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , CHKw(w, Show_HIBOLD) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , tgt, max_colors, clr, w->grpname)); + putp(Cap_clr_eos); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + if (key == kbd_ESC) break; + + switch (key) { + case 'S': + pclr = &w->rc.summclr; + clr = *pclr; + tgt = key; + break; + case 'M': + pclr = &w->rc.msgsclr; + clr = *pclr; + tgt = key; + break; + case 'H': + pclr = &w->rc.headclr; + clr = *pclr; + tgt = key; + break; + case 'T': + pclr = &w->rc.taskclr; + clr = *pclr; + tgt = key; + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + clr = key - '0'; + *pclr = clr; + break; + case kbd_UP: + ++clr; + if (clr >= max_colors) clr = 0; + *pclr = clr; + break; + case kbd_DOWN: + --clr; + if (clr < 0) clr = max_colors - 1; + *pclr = clr; + break; + case 'B': + TOGw(w, View_NOBOLD); + break; + case 'b': + TOGw(w, Show_HIBOLD); + break; + case 'z': + TOGw(w, Show_COLORS); + break; + case 'a': + case 'w': + wins_clrhlp((w = win_select(key)), 1); + clr = w->rc.taskclr, pclr = &w->rc.taskclr; + tgt = 'T'; + break; + default: + break; // keep gcc happy + } + capsmk(w); + } while (key != kbdAPPLY && key != kbdABORT); + + if (key == kbdABORT || key == kbd_ESC) wins_clrhlp(w, 0); + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + #undef kbdABORT + #undef kbdAPPLY +} // end: wins_colors + + + /* + * Manipulate flag(s) for all our windows. */ +static void wins_reflag (int what, int flg) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + do { + switch (what) { + case Flags_TOG: + TOGw(w, flg); + break; + case Flags_SET: // Ummmm, i can't find anybody + SETw(w, flg); // who uses Flags_set ... + break; + case Flags_OFF: + OFFw(w, flg); + break; + default: // keep gcc happy + break; + } + /* a flag with special significance -- user wants to rebalance + display so we gotta' off some stuff then force on two flags... */ + if (EQUWINS_xxx == flg) + win_reset(w); + + w = w->next; + } while (w != Curwin); +} // end: wins_reflag + + + /* + * Set up the raw/incomplete field group windows -- + * they'll be finished off after startup completes. + * [ and very likely that will override most/all of our efforts ] + * [ --- life-is-NOT-fair --- ] */ +static void wins_stage_1 (void) { + WIN_t *w; + int i; + + for (i = 0; i < GROUPSMAX; i++) { + w = &Winstk[i]; + w->winnum = i + 1; + w->rc = Rc.win[i]; + w->captab[0] = Cap_norm; + w->captab[1] = Cap_norm; + w->captab[2] = w->cap_bold; + w->captab[3] = w->capclr_sum; + w->captab[4] = w->capclr_msg; + w->captab[5] = w->capclr_pmt; + w->captab[6] = w->capclr_hdr; + w->captab[7] = w->capclr_rowhigh; + w->captab[8] = w->capclr_rownorm; + w->next = w + 1; + w->prev = w - 1; + } + + // fixup the circular chains... + Winstk[GROUPSMAX - 1].next = &Winstk[0]; + Winstk[0].prev = &Winstk[GROUPSMAX - 1]; + Curwin = Winstk; + + for (i = 1; i < BOT_MSGSMAX; i++) + Msg_tab[i].prev = &Msg_tab[i - 1]; + Msg_tab[0].prev = &Msg_tab[BOT_MSGSMAX -1]; +} // end: wins_stage_1 + + + /* + * This guy just completes the field group windows after the + * rcfiles have been read and command line arguments parsed. + * And since he's the cabose of startup, he'll also tidy up + * a few final things... */ +static void wins_stage_2 (void) { + int i; + + for (i = 0; i < GROUPSMAX; i++) { + win_names(&Winstk[i], Winstk[i].rc.winname); + capsmk(&Winstk[i]); + Winstk[i].findstr = alloc_c(FNDBUFSIZ); + Winstk[i].findlen = 0; + if (Winstk[i].rc.combine_cpus >= Cpu_cnt) + Winstk[i].rc.combine_cpus = 0; + if (CHKw(&Winstk[i], (View_CPUSUM | View_CPUNOD))) + Winstk[i].rc.double_up = 0; + } + if (!Batch) + putp((Cursor_state = Cap_curs_hide)); + else { + OFFw(Curwin, View_SCROLL); + signal(SIGHUP, SIG_IGN); // allow running under nohup + } + // fill in missing Fieldstab members and build each window's columnhdr + zap_fieldstab(); + + // with preserved 'other filters' & command line 'user filters', + // we must ensure that we always have a visible task on row one. + mkVIZrow1 + + // lastly, initialize a signal set used to throttle one troublesome signal + sigemptyset(&Sigwinch_set); +#ifdef SIGNALS_LESS + sigaddset(&Sigwinch_set, SIGWINCH); +#endif +} // end: wins_stage_2 + + + /* + * Determine if this task matches the 'u/U' selection + * criteria for a given window */ +static inline int wins_usrselect (const WIN_t *q, int idx) { + // a tailored 'results stack value' extractor macro + #define rSv(E) PID_VAL(E, u_int, p) + struct pids_stack *p = q->ppt[idx]; + + switch (q->usrseltyp) { + case 0: // uid selection inactive + return 1; + case 'U': // match any uid + if (rSv(EU_URD) == (unsigned)q->usrseluid) return q->usrselflg; + if (rSv(EU_USD) == (unsigned)q->usrseluid) return q->usrselflg; + if (rSv(eu_ID_FUID) == (unsigned)q->usrseluid) return q->usrselflg; + // fall through... + case 'u': // match effective uid + if (rSv(EU_UED) == (unsigned)q->usrseluid) return q->usrselflg; + // fall through... + default: // no match... + ; + } + return !q->usrselflg; + #undef rSv +} // end: wins_usrselect + +/*###### Forest View support ###########################################*/ + + /* + * We try keeping most existing code unaware of these activities | + * ( plus, maintain alphabetical order within carefully chosen ) | + * ( names beginning forest_a, forest_b, forest_c and forest_d ) | + * ( with each name exactly 1 letter more than its predecessor ) | */ +static struct pids_stack **Seed_ppt; // temporary win ppt pointer | +static struct pids_stack **Tree_ppt; // forest_begin resizes this | +static int Tree_idx; // frame_make resets to zero | + /* those next two support collapse/expand children. the Hide_pid | + array holds parent pids whose children have been manipulated. | + positive pid values represent parents with collapsed children | + while a negative pid value means children have been expanded. | + ( both of these are managed under the 'keys_task()' routine ) | */ +static int *Hide_pid; // collapsible process array | +static int Hide_tot; // total used in above array | + + /* + * This little recursive guy was the real forest view workhorse. | + * He fills in the Tree_ppt array and also sets the child indent | + * level which is stored in an 'extra' result struct as a u_int. | */ +static void forest_adds (const int self, int level) { + // tailored 'results stack value' extractor macros + #define rSv(E,X) PID_VAL(E, s_int, Seed_ppt[X]) + // if xtra-procps-debug.h active, can't use PID_VAL with assignment + #define rSv_Lvl Tree_ppt[Tree_idx]->head[eu_TREE_LVL].result.s_int + int i; + + if (Tree_idx < PIDSmaxt) { // immunize against insanity | + if (level > 100) level = 101; // our arbitrary nests limit | + Tree_ppt[Tree_idx] = Seed_ppt[self]; // add this as root or child | + rSv_Lvl = level; // while recording its level | + ++Tree_idx; +#ifdef TREE_SCANALL + for (i = 0; i < PIDSmaxt; i++) { + if (i == self) continue; +#else + for (i = self + 1; i < PIDSmaxt; i++) { +#endif + if (rSv(EU_PID, self) == rSv(EU_TGD, i) + || (rSv(EU_PID, self) == rSv(EU_PPD, i) && rSv(EU_PID, i) == rSv(EU_TGD, i))) + forest_adds(i, level + 1); // got one child any others? + } + } + #undef rSv + #undef rSv_Lvl +} // end: forest_adds + + + /* + * This function is responsible for making that stacks ptr array | + * a forest display in that designated window. After completion, | + * he'll replace that original window ppt array with a specially | + * ordered forest view version. He'll also mark hidden children! | */ +static void forest_begin (WIN_t *q) { + static int hwmsav; + int i, j; + + Seed_ppt = q->ppt; // avoid passing pointers | + if (!Tree_idx) { // do just once per frame | + if (hwmsav < PIDSmaxt) { // grow, but never shrink | + hwmsav = PIDSmaxt; + Tree_ppt = alloc_r(Tree_ppt, sizeof(void *) * hwmsav); + } + +#ifndef TREE_SCANALL + if (!(procps_pids_sort(Pids_ctx, Seed_ppt, PIDSmaxt + , PIDS_TICS_BEGAN, PIDS_SORT_ASCEND))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); +#endif + for (i = 0; i < PIDSmaxt; i++) { // avoid hidepid distorts | + if (!PID_VAL(eu_TREE_LVL, s_int, Seed_ppt[i])) // parents lvl 0 | + forest_adds(i, 0); // add parents + children | + } + + /* we use up to three additional 'PIDS_extra' results in our stack | + eu_TREE_HID (s_ch) : where 'x' == collapsed & 'z' == unseen | + eu_TREE_LVL (s_int): where level number is stored (0 - 100) | + eu_TREE_ADD (u_int): where a children's tics stored (maybe) | */ + for (i = 0; i < Hide_tot; i++) { + + // if have xtra-procps-debug.h, cannot use PID_VAL w/ assignment | + #define rSv(E,T,X) Tree_ppt[X]->head[E].result.T + #define rSv_Pid(X) rSv(EU_PID, s_int, X) + #define rSv_Lvl(X) rSv(eu_TREE_LVL, s_int, X) + #define rSv_Hid(X) rSv(eu_TREE_HID, s_ch, X) + /* next 2 aren't needed if TREE_VCPUOFF but they cost us nothing | + & the EU_CPU slot will now always be present (even if it's 0) | */ + #define rSv_Add(X) rSv(eu_TREE_ADD, u_int, X) + #define rSv_Cpu(X) rSv(EU_CPU, u_int, X) + + if (Hide_pid[i] > 0) { + for (j = 0; j < PIDSmaxt; j++) { + if (rSv_Pid(j) == Hide_pid[i]) { + int parent = j; + int children = 0; + int level = rSv_Lvl(parent); + while (j+1 < PIDSmaxt && rSv_Lvl(j+1) > level) { + ++j; + rSv_Hid(j) = 'z'; +#ifndef TREE_VCPUOFF + rSv_Add(parent) += rSv_Cpu(j); +#endif + children = 1; + } + /* if any children found (& collapsed) mark the parent | + ( when children aren't found don't negate the pid ) | + ( to prevent future scans since who's to say such ) | + ( tasks will not fork more children in the future ) | */ + if (children) rSv_Hid(parent) = 'x'; + // this will force a check of next Hide_pid[i], if any | + j = PIDSmaxt + 1; + } + } + // if a target task disappeared prevent any further scanning | + if (j == PIDSmaxt) Hide_pid[i] = -Hide_pid[i]; + } + #undef rSv + #undef rSv_Pid + #undef rSv_Lvl + #undef rSv_Hid + #undef rSv_Add + #undef rSv_Cpu + } + } // end: !Tree_idx + memcpy(Seed_ppt, Tree_ppt, sizeof(void *) * PIDSmaxt); +} // end: forest_begin + + + /* + * When there's a 'focus_pid' established for a window, this guy | + * determines that window's 'focus_beg' plus 'focus_end' values. | + * But, if the pid can no longer be found, he'll turn off focus! | */ +static void forest_config (WIN_t *q) { + // tailored 'results stack value' extractor macro + #define rSv(x) PID_VAL(eu_TREE_LVL, s_int, q->ppt[(x)]) + int i, level = 0; + + for (i = 0; i < PIDSmaxt; i++) { + if (q->focus_pid == PID_VAL(EU_PID, s_int, q->ppt[i])) { + level = rSv(i); + q->focus_beg = i; + break; + } + } + if (i == PIDSmaxt) + q->focus_pid = q->begtask = 0; + else { +#ifdef FOCUS_TREE_X + q->focus_lvl = rSv(i); +#endif + while (i+1 < PIDSmaxt && rSv(i+1) > level) + ++i; + q->focus_end = i + 1; // make 'focus_end' a proper fencpost + // watch out for newly forked/cloned tasks 'above' us ... + if (q->begtask < q->focus_beg) { + q->begtask = q->focus_beg; + mkVIZoff(q) + } +#ifdef FOCUS_HARD_Y + // if some task 'above' us ended, try to maintain focus + // ( but allow scrolling when there are many children ) + if (q->begtask > q->focus_beg + && (SCREEN_ROWS > (q->focus_end - q->focus_beg))) { + q->begtask = q->focus_beg; + mkVIZoff(q) + } +#endif + } + #undef rSv +} // end: forest_config + + + /* + * This guy adds the artwork to either 'cmd' or 'cmdline' values | + * if we are in forest view mode otherwise he just returns them. | */ +static inline const char *forest_display (const WIN_t *q, int idx) { + // tailored 'results stack value' extractor macros + #define rSv(E) PID_VAL(E, str, p) + #define rSv_Lvl PID_VAL(eu_TREE_LVL, s_int, p) + #define rSv_Hid PID_VAL(eu_TREE_HID, s_ch, p) +#ifndef SCROLLVAR_NO + static char buf[MAXBUFSIZ]; +#else + static char buf[ROWMINSIZ]; +#endif + struct pids_stack *p = q->ppt[idx]; + const char *which = (CHKw(q, Show_CMDLIN)) ? rSv(eu_CMDLINE) : rSv(EU_CMD); + int level = rSv_Lvl; + +#ifdef FOCUS_TREE_X + if (q->focus_pid) { + if (idx >= q->focus_beg && idx < q->focus_end) + level -= q->focus_lvl; + } +#endif + if (!CHKw(q, Show_FOREST) || level == 0) return which; +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (rSv_Hid == 'x') { +#ifdef TREE_VALTMRK + snprintf(buf, sizeof(buf), "%*s%s", (4 * level), "`+ ", which); +#else + snprintf(buf, sizeof(buf), "+%*s%s", ((4 * level) - 1), "`- ", which); +#endif + return buf; + } + if (level > 100) { + snprintf(buf, sizeof(buf), "%400s%s", " + ", which); + return buf; + } +#ifndef FOCUS_VIZOFF + if (q->focus_pid) snprintf(buf, sizeof(buf), "|%*s%s", ((4 * level) - 1), "`- ", which); + else snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which); +#else + snprintf(buf, sizeof(buf), "%*s%s", (4 * level), " `- ", which); +#endif + return buf; + #undef rSv + #undef rSv_Lvl + #undef rSv_Hid +} // end: forest_display + +/*###### Special Separate Bottom Window support ########################*/ + + /* + * This guy actually draws the parsed strings | + * including adding a highlight if necessary. | */ +static void bot_do (const char *str, int focus) { + char *cap = Cap_norm; + + while (*str == ' ') putchar(*(str++)); + if (focus) +#ifdef BOT_STRV_OFF + cap = Cap_reverse; +#else + cap = strchr(str, Bot_sep) ? Curwin->capclr_msg : Cap_reverse; +#endif + putp(fmtmk("%s%s%s", cap, str, Cap_norm)); +} // end: bot_do + + + /* + * This guy draws that bottom window's header | + * then parses/arranges to show the contents. | + * ( returns relative # of elements printed ) | */ +static int bot_focus_str (const char *hdr, const char *str) { + #define maxRSVD ( Screen_rows - 1 ) + char *beg, *end; + char tmp[BIGBUFSIZ]; + int n, x; + + if (str) { + // we're a little careless with overhead here (it's a one time cost) + memset(Bot_buf, '\0', sizeof(Bot_buf)); + n = strlen(str); + if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1; + if (!*str || !strcmp(str, "-")) strcpy(Bot_buf, N_txt(X_BOT_nodata_txt)); + else memccpy(Bot_buf, str, '\0', n); + Bot_rsvd = 1 + BOT_RSVD + ((strlen(Bot_buf) - utf8_delta(Bot_buf)) / Screen_cols); + if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD; + // somewhere down call chain fmtmk() may be used, so we'll old school it + snprintf(tmp, sizeof(tmp), "%s%s%-*s" + , tg2(0, SCREEN_ROWS) + , Curwin->capclr_hdr + , Screen_cols + utf8_delta(hdr) + , hdr); + putp(tmp); + } + // now fmtmk is safe to use ... + putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm)); + + beg = &Bot_buf[0]; + x = BOT_UNFOCUS; + + while (*beg) { + if (!(end = strchr(beg, Bot_sep))) + end = beg + strlen(beg); + if ((n = end - beg) >= sizeof(tmp)) + n = sizeof(tmp) - 1; + memccpy(tmp, beg, '\0', n); + tmp[n] = '\0'; + bot_do(tmp, (++x == Bot_indx)); + if (*(beg += n)) + putchar(*(beg++)); + while (*beg == ' ') putchar(*(beg++)); + } + return x; + #undef maxRSVD +} // end: bot_focus_str + + + /* + * This guy draws that bottom window's header | + * & parses/arranges to show vector contents. | + * ( returns relative # of elements printed ) | */ +static int bot_focus_strv (const char *hdr, const char **strv) { + #define maxRSVD ( Screen_rows - 1 ) + static int nsav; + char tmp[SCREENMAX], *p; + int i, n, x; + + if (strv) { + // we're a little careless with overhead here (it's a one time cost) + memset(Bot_buf, '\0', sizeof(Bot_buf)); + n = (char *)&strv[0] - strv[0]; + if (n >= sizeof(Bot_buf)) n = sizeof(Bot_buf) - 1; + memcpy(Bot_buf, strv[0], n); + if ((!Bot_buf[0] || !strcmp(Bot_buf, "-")) && n <= sizeof(char *)) + strcpy(Bot_buf, N_txt(X_BOT_nodata_txt)); + for (nsav= 0, p = Bot_buf, x = 0; strv[nsav] != NULL; nsav++) { + p += strlen(strv[nsav]) + 1; + if ((p - Bot_buf) >= sizeof(Bot_buf)) + break; + x += utf8_delta(strv[nsav]); + } + n = (p - Bot_buf) - (x + 1); + Bot_rsvd = 1 + BOT_RSVD + (n / Screen_cols); + if (Bot_rsvd > maxRSVD) Bot_rsvd = maxRSVD; + // somewhere down call chain fmtmk() may be used, so we'll old school it + snprintf(tmp, sizeof(tmp), "%s%s%-*s" + , tg2(0, SCREEN_ROWS) + , Curwin->capclr_hdr + , Screen_cols + utf8_delta(hdr) + , hdr); + putp(tmp); + } + // now fmtmk is safe to use ... + putp(fmtmk("%s%s%s", tg2(0, SCREEN_ROWS + 1), Cap_clr_eos, Cap_norm)); + + p = Bot_buf; + x = BOT_UNFOCUS; + + for (i = 0; i < nsav; i++) { + bot_do(p, (++x == Bot_indx)); + p += strlen(p) + 1; + putchar(' '); + } + return x; + #undef maxRSVD +} // end: bot_focus_strv + + +static struct { + enum pflag this; + enum namespace_type that; +} ns_tab[] = { + // careful, cgroup & time were late additions ... + { EU_NS7, PROCPS_NS_CGROUP }, { EU_NS1, PROCPS_NS_IPC }, + { EU_NS2, PROCPS_NS_MNT }, { EU_NS3, PROCPS_NS_NET }, + { EU_NS4, PROCPS_NS_PID }, { EU_NS8, PROCPS_NS_TIME }, + { EU_NS5, PROCPS_NS_USER }, { EU_NS6, PROCPS_NS_UTS } +}; + + + /* + * A helper function that will gather various | + * stuff for display by the bot_item_show guy. | */ +static void *bot_item_hlp (struct pids_stack *p) { + static char buf[BIGBUFSIZ]; + char tmp[SMLBUFSIZ], *b; + struct msg_node *m; + int i; + + switch (Bot_what) { + case BOT_MSG_LOG: + *(b = &buf[0]) = '\0'; + m = Msg_this->prev; + do { + if (m->msg[0]) { + b = scat(b, m->msg); + if (m != Msg_this && m->prev->msg[0]) { + // caller itself may have used fmtmk, so we'll old school it ... + snprintf(tmp, sizeof(tmp), "%c ", BOT_SEP_SMI); + b = scat(b, tmp); + } + } + m = m->prev; + } while (m != Msg_this->prev); + return buf; + case BOT_ITEM_NS: + *(b = &buf[0]) = '\0'; + for (i = 0; i < MAXTBL(ns_tab); i++) { + // caller itself may have used fmtmk, so we'll old school it ... + snprintf(tmp, sizeof(tmp), "%s: %-10lu" + , procps_ns_get_name(ns_tab[i].that) + , PID_VAL(ns_tab[i].this, ul_int, p)); + b = scat(b, tmp); + if (i < (MAXTBL(ns_tab) - 1)) b = scat(b, ", "); + } + return buf; + case eu_CMDLINE_V: + case eu_ENVIRON_V: + return p->head[Bot_item[0]].result.strv; + default: + return p->head[Bot_item[0]].result.str; + } +} // end: bot_item_hlp + + + /* + * This guy manages that bottom margin window | + * which shows various process related stuff. | */ +static void bot_item_show (void) { + #define mkHDR fmtmk(Bot_head, Bot_task, PID_VAL(EU_CMD, str, p)) + struct pids_stack *p; + int i; + + for (i = 0; i < PIDSmaxt; i++) { + p = Curwin->ppt[i]; + if (Bot_task == PID_VAL(EU_PID, s_int, p)) + break; + } + if (i < PIDSmaxt) { + Bot_focus_func(mkHDR, bot_item_hlp(p)); + } +#ifdef BOT_DEAD_ZAP + else + BOT_TOSS; +#else + BOT_KEEP; +#endif + #undef mkHDR +} // end: bot_item_show + + + /* + * This guy can toggle between displaying the | + * bottom window or arranging to turn it off. | */ +static void bot_item_toggle (int what, const char *head, char sep) { + int i; + + // if already targeted, assume user wants to turn it off ... + if (Bot_what == what) { + BOT_TOSS; + } else { + // accommodate transition from larger to smaller window + Bot_rsvd = 0; + switch (what) { + case BOT_ITEM_NS: + for (i = 0; i < MAXTBL(ns_tab); i++) + Bot_item[i] = ns_tab[i].this; + Bot_item[i] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_str; + break; + case eu_CMDLINE_V: + case eu_ENVIRON_V: + Bot_item[0] = what; + Bot_item[1] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_strv; + break; + default: + Bot_item[0] = what; + Bot_item[1] = BOT_DELIMIT; + Bot_focus_func = (BOT_f)bot_focus_str; + break; + } + Bot_sep = sep; + Bot_what = what; + Bot_indx = BOT_UNFOCUS; + Bot_head = (char *)head; + Bot_show_func = bot_item_show; + Bot_task = PID_VAL(EU_PID, s_int, Curwin->ppt[Curwin->begtask]); + } +} // end: bot_item_toggle + +/*###### Interactive Input Tertiary support ############################*/ + + /* + * This section exists so as to offer some function naming freedom + * while also maintaining the strict alphabetical order protocol + * within each section. */ + + /* + * This guy is a *Helper* function serving the following two masters: + * find_string() - find the next match in a given window + * task_show() - highlight all matches currently in-view + * If q->findstr is found in the designated buffer, he returns the + * offset from the start of the buffer, otherwise he returns -1. */ +static inline int find_ofs (const WIN_t *q, const char *buf) { + char *fnd; + + if (q->findstr[0] && (fnd = STRSTR(buf, q->findstr))) + return (int)(fnd - buf); + return -1; +} // end: find_ofs + + + + /* This is currently the only true prototype required by top. + It is placed here, instead of top.h, to avoid one compiler + warning when the top_nls.c source was compiled separately. */ +static const char *task_show (const WIN_t *q, int idx); + +static void find_string (int ch) { + #define reDUX (found) ? N_txt(WORD_another_txt) : "" + static int found; + int i; + + if ('&' == ch && !Curwin->findstr[0]) { + show_msg(N_txt(FIND_no_next_txt)); + return; + } + if ('L' == ch) { + char *str = ioline(N_txt(GET_find_str_txt)); + if (*str == kbd_ESC) return; + snprintf(Curwin->findstr, FNDBUFSIZ, "%s", str); + Curwin->findlen = strlen(Curwin->findstr); + found = 0; + } + if (Curwin->findstr[0]) { + SETw(Curwin, NOPRINT_xxx); + for (i = Curwin->begtask; i < PIDSmaxt; i++) { + const char *row = task_show(Curwin, i); + if (*row && -1 < find_ofs(Curwin, row)) { + found = 1; + if (i == Curwin->begtask) continue; + Curwin->begtask = i; + return; + } + } + show_msg(fmtmk(N_fmt(FIND_no_find_fmt), reDUX, Curwin->findstr)); + } + #undef reDUX +} // end: find_string + + +static void help_view (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + char key = 1; + + putp((Cursor_state = Cap_curs_huge)); +signify_that: + putp(Cap_clr_scr); + adj_geometry(); + + show_special(1, fmtmk(N_unq(KEYS_helpbas_fmt) + , PACKAGE_STRING + , w->grpname + , CHKw(w, Show_CTIMES) ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , Rc.delay_time + , Secure_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt) + , Secure_mode ? "" : N_unq(KEYS_helpext_fmt))); + putp(Cap_clr_eos); + fflush(stdout); + + if (Frames_signal) goto signify_that; + key = iokey(IOKEY_ONCE); + if (key < 1) goto signify_that; + + switch (key) { + // these keys serve the primary help screen + case kbd_ESC: case 'q': + break; + case '?': case 'h': case 'H': + do { +signify_this: + putp(Cap_clr_scr); + adj_geometry(); + show_special(1, fmtmk(N_unq(WINDOWS_help_fmt) + , w->grpname + , Winstk[0].rc.winname, Winstk[1].rc.winname + , Winstk[2].rc.winname, Winstk[3].rc.winname)); + putp(Cap_clr_eos); + fflush(stdout); + if (Frames_signal || (key = iokey(IOKEY_ONCE)) < 1) + goto signify_this; + else w = win_select(key); + // these keys serve the secondary help screen + } while (key != kbd_ENTER && key != kbd_ESC && key != 'q'); + break; + default: + goto signify_that; + } + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; +} // end: help_view + + +static void other_filters (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + const char *txt, *p; + char *glob; + + switch (ch) { + case 'o': + case 'O': + if (ch == 'o') txt = N_txt(OSEL_casenot_txt); + else txt = N_txt(OSEL_caseyes_txt); + glob = ioline(fmtmk(N_fmt(OSEL_prompts_fmt), w->osel_tot + 1, txt)); + if (*glob == kbd_ESC || *glob == '\0') + return; + if ((p = osel_add(w, ch, glob, 1))) { + show_msg(p); + return; + } + break; + case kbd_CtrlO: + if (VIZCHKw(w)) { + char buf[SCREENMAX], **pp; + struct osel_s *osel; + int i; + + i = 0; + osel = w->osel_1st; + pp = alloc_c((w->osel_tot + 1) * sizeof(char *)); + while (osel && i < w->osel_tot) { + pp[i++] = osel->raw; + osel = osel->nxt; + } + buf[0] = '\0'; + for ( ; i > 0; ) + strncat(buf, fmtmk("%s'%s'", " + " , pp[--i]), sizeof(buf) - (strlen(buf) + 1)); + if (buf[0]) p = buf + strspn(buf, " + "); + else p = N_txt(WORD_noneone_txt); + ioline(fmtmk(N_fmt(OSEL_statlin_fmt), p)); + free(pp); + } + break; + default: // keep gcc happy + break; + } +} // end: other_filters + + +static void write_rcfile (void) { + FILE *fp; + int i, j, n; + + if (Rc_questions) { + show_pmt(N_txt(XTRA_warncfg_txt)); + if ('y' != tolower(iokey(IOKEY_ONCE))) + return; + Rc_questions = 0; + } + if (Rc_compatibilty) { + show_pmt(N_txt(XTRA_warnold_txt)); + if ('y' != tolower(iokey(IOKEY_ONCE))) + return; + Rc_compatibilty = 0; + } + if (!(fp = fopen(Rc_name, "w"))) { + show_msg(fmtmk(N_fmt(FAIL_rc_open_fmt), Rc_name, strerror(errno))); + return; + } + fprintf(fp, "%s's " RCF_EYECATCHER, Myname); + fprintf(fp, "Id:%c, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%d.%d, Curwin=%d\n" + , RCF_VERSION_ID + , Rc.mode_altscr, Rc.mode_irixps + // this may be ugly, but it keeps us locale independent... + , (int)Rc.delay_time, (int)((Rc.delay_time - (int)Rc.delay_time) * 1000) + , (int)(Curwin - Winstk)); + + for (i = 0 ; i < GROUPSMAX; i++) { + n = mlen(Winstk[i].rc.fieldscur); + fprintf(fp, "%s\tfieldscur=", Winstk[i].rc.winname); + for (j = 0; j < n; j++) { + if (j && !(j % FLD_ROWMAX) && j < n) + fprintf(fp, "\n\t\t "); + fprintf(fp, "%4d ", (int)Winstk[i].rc.fieldscur[j]); + } + fprintf(fp, "\n"); + fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d, graph_cpus=%d, graph_mems=%d" + ", double_up=%d, combine_cpus=%d, core_types=%d\n" + , Winstk[i].rc.winflags, Winstk[i].rc.sortindx, Winstk[i].rc.maxtasks + , Winstk[i].rc.graph_cpus, Winstk[i].rc.graph_mems, Winstk[i].rc.double_up + , Winstk[i].rc.combine_cpus, Winstk[i].rc.core_types); + fprintf(fp, "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n" + , Winstk[i].rc.summclr, Winstk[i].rc.msgsclr + , Winstk[i].rc.headclr, Winstk[i].rc.taskclr); + } + + // any new addition(s) last, for older rcfiles compatibility... + fprintf(fp, "Fixed_widest=%d, Summ_mscale=%d, Task_mscale=%d, Zero_suppress=%d, Tics_scaled=%d\n" + , Rc.fixed_widest, Rc.summ_mscale, Rc.task_mscale, Rc.zero_suppress, Rc.tics_scaled); + + if (Winstk[0].osel_tot + Winstk[1].osel_tot + + Winstk[2].osel_tot + Winstk[3].osel_tot) { + fprintf(fp, "\n"); + fprintf(fp, Osel_delim_1_txt); + for (i = 0 ; i < GROUPSMAX; i++) { + struct osel_s *osel = Winstk[i].osel_1st; + if (osel) { + fprintf(fp, Osel_window_fmts, i, Winstk[i].osel_tot); + do { + fprintf(fp, Osel_filterO_fmt, osel->typ, osel->raw); + osel = osel->nxt; + } while (osel); + } + } + fprintf(fp, Osel_delim_2_txt); + } + + if (Inspect.raw && strcmp(Inspect.raw, "\n")) + fputs(Inspect.raw, fp); + + fclose(fp); + show_msg(fmtmk(N_fmt(WRITE_rcfile_fmt), Rc_name)); +} // end: write_rcfile + +/*###### Interactive Input Secondary support (do_key helpers) ##########*/ + + /* + * These routines exist just to keep the do_key() function + * a reasonably modest size. */ + +static void keys_global (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, num, def, pid; + + switch (ch) { + case '?': + case 'h': + help_view(); + break; + case 'B': + TOGw(w, View_NOBOLD); + capsmk(w); + break; + case 'd': + case 's': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + float tmp = + get_float(fmtmk(N_fmt(DELAY_change_fmt), Rc.delay_time)); + if (tmp > -1) Rc.delay_time = tmp; + } + break; + case 'E': + if (++Rc.summ_mscale > SK_Eb) Rc.summ_mscale = SK_Kb; + break; + case 'e': + if (++Rc.task_mscale > SK_Pb) Rc.task_mscale = SK_Kb; + break; + case 'f': + fields_utility(); + break; + case 'g': + win_select(0); + break; + case 'H': + Thread_mode = !Thread_mode; + if (!CHKw(w, View_STATES)) + show_msg(fmtmk(N_fmt(THREADS_show_fmt) + , Thread_mode ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + for (i = 0 ; i < GROUPSMAX; i++) + Winstk[i].begtask = Winstk[i].focus_pid = 0; + // force an extra procs refresh to avoid %cpu distortions... + Pseudo_row = PROC_XTRA; + // signal that we just corrupted entire screen + Frames_signal = BREAK_screen; + break; + case 'I': + if (Cpu_cnt > 1) { + Rc.mode_irixps = !Rc.mode_irixps; + show_msg(fmtmk(N_fmt(IRIX_curmode_fmt) + , Rc.mode_irixps ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + } else + show_msg(N_txt(NOT_smp_cpus_txt)); + break; + case 'k': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + num = SIGTERM; + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_txt(GET_pid2kill_fmt), def)); + if (pid > GET_NUM_ESC) { + char *str; + if (pid == GET_NUM_NOT) pid = def; + str = ioline(fmtmk(N_fmt(GET_sigs_num_fmt), pid, SIGTERM)); + if (*str != kbd_ESC) { + if (*str) num = signal_name_to_number(str); + if (Frames_signal) break; + if (0 < num && kill(pid, num)) + show_msg(fmtmk(N_fmt(FAIL_signals_fmt) + , pid, num, strerror(errno))); + else if (0 > num) show_msg(N_txt(BAD_signalid_txt)); + } + } + } + break; + case 'r': + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def)); + if (pid > GET_NUM_ESC) { + if (pid == GET_NUM_NOT) pid = def; + num = get_int(fmtmk(N_fmt(GET_nice_num_fmt), pid)); + if (num > GET_NUM_NOT + && setpriority(PRIO_PROCESS, (unsigned)pid, num)) + show_msg(fmtmk(N_fmt(FAIL_re_nice_fmt) + , pid, num, strerror(errno))); + } + } + break; + case 'X': + num = get_int(fmtmk(N_fmt(XTRA_fixwide_fmt), Rc.fixed_widest)); + if (num > GET_NUM_NOT) { + if (num >= 0 && num <= SCREENMAX) Rc.fixed_widest = num; + else Rc.fixed_widest = -1; + } + break; + case 'Y': + if (!Inspect.total) +#ifndef INSP_OFFDEMO + ioline(N_txt(YINSP_noent1_txt)); +#else + ioline(N_txt(YINSP_noent2_txt)); +#endif + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(YINSP_pidsee_fmt), def)); + if (pid > GET_NUM_ESC) { + if (pid == GET_NUM_NOT) pid = def; + if (pid) inspection_utility(pid); + } + } + break; + case 'Z': + wins_colors(); + break; + case '0': + Rc.zero_suppress = !Rc.zero_suppress; + break; + case kbd_CtrlE: +#ifndef SCALE_FORMER + Rc.tics_scaled++; + if (Rc.tics_scaled > TICS_AS_LAST) + Rc.tics_scaled = 0; +#endif + break; + case kbd_CtrlG: + bot_item_toggle(EU_CGR, N_fmt(X_BOT_ctlgrp_fmt), BOT_SEP_SLS); + break; + case kbd_CtrlI: + if (BOT_PRESENT) { + ++Bot_indx; + if (Bot_indx > Bot_focus_func(NULL, NULL)) + Bot_indx = BOT_UNFOCUS; + } + break; + case kbd_CtrlK: + // with string vectors, the 'separator' may serve a different purpose + bot_item_toggle(eu_CMDLINE_V, N_fmt(X_BOT_cmdlin_fmt), BOT_SEP_SPC); + break; + case kbd_CtrlL: + bot_item_toggle(BOT_MSG_LOG, N_txt(X_BOT_msglog_txt), BOT_SEP_SMI); + break; + case kbd_CtrlN: + // with string vectors, the 'separator' may serve a different purpose + bot_item_toggle(eu_ENVIRON_V, N_fmt(X_BOT_envirn_fmt), BOT_SEP_SPC); + break; + case kbd_CtrlP: + bot_item_toggle(BOT_ITEM_NS, N_fmt(X_BOT_namesp_fmt), BOT_SEP_CMA); + break; + case kbd_CtrlR: + if (Secure_mode) + show_msg(N_txt(NOT_onsecure_txt)); + else { + def = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + pid = get_int(fmtmk(N_fmt(GET_pid2nice_fmt), def)); + if (pid > GET_NUM_ESC) { + int fd; + if (pid == GET_NUM_NOT) pid = def; + num = get_int(fmtmk(N_fmt(AGNI_valueof_fmt), pid)); + if (num > GET_NUM_NOT) { + if (num < -20 || num > +19) + show_msg(N_txt(AGNI_invalid_txt)); + else if (0 > (fd = open(fmtmk("/proc/%d/autogroup", pid), O_WRONLY))) + show_msg(fmtmk(N_fmt(AGNI_notopen_fmt), strerror(errno))); + else { + char buf[TNYBUFSIZ]; + snprintf(buf, sizeof(buf), "%d", num); + if (0 >= write(fd, buf, strlen(buf))) + show_msg(fmtmk(N_fmt(AGNI_nowrite_fmt), strerror(errno))); + close(fd); + } + } + } + } + break; + case kbd_CtrlU: + bot_item_toggle(EU_SGN, N_fmt(X_BOT_supgrp_fmt), BOT_SEP_CMA); + break; + case kbd_BTAB: + if (BOT_PRESENT) { + --Bot_indx; + num = Bot_focus_func(NULL, NULL); + if (Bot_indx <= BOT_UNFOCUS) + Bot_indx = num + 1; + } + break; + case kbd_ENTER: // these two have the effect of waking us + case kbd_SPACE: // from 'pselect', refreshing the display + break; // and updating any hot-plugged resources + default: // keep gcc happy + break; + } +} // end: keys_global + + +static void keys_summary (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + if (Restrict_some && ch != 'C') { + show_msg(N_txt(X_RESTRICTED_txt)); + return; + } + switch (ch) { + case '!': + if (CHKw(w, View_CPUSUM) || CHKw(w, View_CPUNOD)) + show_msg(N_txt(XTRA_modebad_txt)); + else { + if (!w->rc.combine_cpus) w->rc.combine_cpus = 2; + else w->rc.combine_cpus *= 2; + if (w->rc.combine_cpus >= Cpu_cnt) w->rc.combine_cpus = 0; + w->rc.core_types = 0; + } + break; + case '1': + if (CHKw(w, View_CPUNOD)) OFFw(w, View_CPUSUM); + else TOGw(w, View_CPUSUM); + OFFw(w, View_CPUNOD); + SETw(w, View_STATES); + w->rc.double_up = 0; + w->rc.core_types = 0; + break; + case '2': + if (!Numa_node_tot) + show_msg(N_txt(NUMA_nodenot_txt)); + else { + if (Numa_node_sel < 0) TOGw(w, View_CPUNOD); + if (!CHKw(w, View_CPUNOD)) SETw(w, View_CPUSUM); + SETw(w, View_STATES); + Numa_node_sel = -1; + w->rc.double_up = 0; + w->rc.core_types = 0; + } + break; + case '3': + if (!Numa_node_tot) + show_msg(N_txt(NUMA_nodenot_txt)); + else { + int num = get_int(fmtmk(N_fmt(NUMA_nodeget_fmt), Numa_node_tot -1)); + if (num > GET_NUM_NOT) { + if (num >= 0 && num < Numa_node_tot) { + Numa_node_sel = num; + SETw(w, View_CPUNOD | View_STATES); + OFFw(w, View_CPUSUM); + w->rc.double_up = 0; + w->rc.core_types = 0; + } else + show_msg(N_txt(NUMA_nodebad_txt)); + } + } + break; + case '4': + w->rc.double_up += 1; + if ((w->rc.double_up >= ADJOIN_limit) + || ((w->rc.double_up >= Cpu_cnt))) + w->rc.double_up = 0; + if ((w->rc.double_up > 1) + && (!w->rc.graph_cpus)) + w->rc.double_up = 0; + OFFw(w, (View_CPUSUM | View_CPUNOD)); + break; +#ifndef CORE_TYPE_NO + case '5': + if (!CHKw(w, View_STATES) + || ((CHKw(w, View_CPUSUM | View_CPUNOD)) + || ((w->rc.combine_cpus)))) + show_msg(N_txt(XTRA_modebad_txt)); + else { + int scanned; + for (scanned = 0; scanned < Cpu_cnt; scanned++) + if (CPU_VAL(stat_COR_TYP, scanned) == E_CORE) + break; + if (scanned < Cpu_cnt) { + w->rc.core_types += 1; + if (w->rc.core_types > E_CORES_ONLY) + w->rc.core_types = 0; + } else w->rc.core_types = 0; + } + break; +#endif + case 'C': + VIZTOGw(w, View_SCROLL); + break; + case 'l': + TOGw(w, View_LOADAV); + break; + case 'm': + if (!CHKw(w, View_MEMORY)) + SETw(w, View_MEMORY); + else if (++w->rc.graph_mems > 2) { + w->rc.graph_mems = 0; + OFFw(w, View_MEMORY); + } + break; + case 't': + if (!CHKw(w, View_STATES)) + SETw(w, View_STATES); + else if (++w->rc.graph_cpus > 2) { + w->rc.graph_cpus = 0; + OFFw(w, View_STATES); + } + if ((w->rc.double_up > 1) + && (!w->rc.graph_cpus)) + w->rc.double_up = 0; + break; + default: // keep gcc happy + break; + } +} // end: keys_summary + + +static void keys_task (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + switch (ch) { + case '#': + case 'n': + if (VIZCHKw(w)) { + int num = get_int(fmtmk(N_fmt(GET_max_task_fmt), w->rc.maxtasks)); + if (num > GET_NUM_NOT) { + if (-1 < num ) w->rc.maxtasks = num; + else show_msg(N_txt(BAD_max_task_txt)); + } + } + break; + case '<': +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) break; +#endif + if (VIZCHKw(w)) { + FLG_t *p = w->procflgs + w->maxpflgs - 1; + while (p > w->procflgs && *p != w->rc.sortindx) --p; + if (*p == w->rc.sortindx) { + --p; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS < *p) --p; +#endif + if (p >= w->procflgs) { + w->rc.sortindx = *p; +#ifndef TREE_NORESET + OFFw(w, Show_FOREST); +#endif + } + } + } + break; + case '>': +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) break; +#endif + if (VIZCHKw(w)) { + FLG_t *p = w->procflgs + w->maxpflgs - 1; + while (p > w->procflgs && *p != w->rc.sortindx) --p; + if (*p == w->rc.sortindx) { + ++p; +#ifndef USE_X_COLHDR + if (EU_MAXPFLGS < *p) ++p; +#endif + if (p < w->procflgs + w->maxpflgs) { + w->rc.sortindx = *p; +#ifndef TREE_NORESET + OFFw(w, Show_FOREST); +#endif + } + } + } + break; + case 'b': + TOGw(w, Show_HIBOLD); + capsmk(w); + break; + case 'c': + VIZTOGw(w, Show_CMDLIN); + break; + case 'F': + if (VIZCHKw(w)) { + if (CHKw(w, Show_FOREST)) { + int n = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); + if (w->focus_pid == n) w->focus_pid = 0; + else w->focus_pid = n; + } + } + break; + case 'i': + { static WIN_t *w_sav; + static int beg_sav; + if (w_sav != w) { beg_sav = 0; w_sav = w; } + if (CHKw(w, Show_IDLEPS)) { beg_sav = w->begtask; w->begtask = 0; } + else { w->begtask = beg_sav; beg_sav = 0; } + } + VIZTOGw(w, Show_IDLEPS); + break; + case 'J': + VIZTOGw(w, Show_JRNUMS); + break; + case 'j': + VIZTOGw(w, Show_JRSTRS); + break; + case 'R': +#ifdef TREE_NORESET + if (!CHKw(w, Show_FOREST)) VIZTOGw(w, Qsrt_NORMAL); +#else + if (VIZCHKw(w)) { + TOGw(w, Qsrt_NORMAL); + OFFw(w, Show_FOREST); + } +#endif + break; + case 'S': + if (VIZCHKw(w)) { + TOGw(w, Show_CTIMES); + show_msg(fmtmk(N_fmt(TIME_accumed_fmt) , CHKw(w, Show_CTIMES) + ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + } + break; + case 'O': + case 'o': + case kbd_CtrlO: + if (VIZCHKw(w)) other_filters(ch); + break; + case 'U': + case 'u': + if (VIZCHKw(w)) { + const char *errmsg, *str = ioline(N_txt(GET_user_ids_txt)); + if (*str != kbd_ESC + && (errmsg = user_certify(w, str, ch))) + show_msg(errmsg); + } + break; + case 'V': + if (VIZCHKw(w)) { + TOGw(w, Show_FOREST); + if (!ENUviz(w, EU_CMD)) + show_msg(fmtmk(N_fmt(FOREST_modes_fmt) , CHKw(w, Show_FOREST) + ? N_txt(ON_word_only_txt) : N_txt(OFF_one_word_txt))); + if (!CHKw(w, Show_FOREST)) w->focus_pid = 0; + } + break; + case 'v': + if (VIZCHKw(w)) { + if (CHKw(w, Show_FOREST)) { + int i, pid = PID_VAL(EU_PID, s_int, w->ppt[w->begtask]); +#ifdef TREE_VPROMPT + int got = get_int(fmtmk(N_txt(XTRA_vforest_fmt), pid)); + if (got < GET_NUM_NOT) break; + if (got > GET_NUM_NOT) pid = got; +#endif + for (i = 0; i < Hide_tot; i++) { + if (Hide_pid[i] == pid || Hide_pid[i] == -pid) { + Hide_pid[i] = -Hide_pid[i]; + break; + } + } + if (i == Hide_tot) { + static int totsav; + if (Hide_tot >= totsav) { + totsav += 128; + Hide_pid = alloc_r(Hide_pid, sizeof(int) * totsav); + } + Hide_pid[Hide_tot++] = pid; + } else { + // if everything's expanded, let's empty the array ... + for (i = 0; i < Hide_tot; i++) + if (Hide_pid[i] > 0) break; + if (i == Hide_tot) Hide_tot = 0; + } + } + } + break; + case 'x': + if (VIZCHKw(w)) { +#ifdef USE_X_COLHDR + TOGw(w, Show_HICOLS); + capsmk(w); +#else + if (ENUviz(w, w->rc.sortindx)) { + TOGw(w, Show_HICOLS); + if (ENUpos(w, w->rc.sortindx) < w->begpflg) { + if (CHKw(w, Show_HICOLS)) w->begpflg += 2; + else w->begpflg -= 2; + if (0 > w->begpflg) w->begpflg = 0; + } + capsmk(w); + } +#endif + } + break; + case 'y': + if (VIZCHKw(w)) { + TOGw(w, Show_HIROWS); + capsmk(w); + } + break; + case 'z': + if (VIZCHKw(w)) { + TOGw(w, Show_COLORS); + capsmk(w); + } + break; + default: // keep gcc happy + break; + } +} // end: keys_task + + +static void keys_window (int ch) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + + switch (ch) { + case '+': + if (ALTCHKw) wins_reflag(Flags_OFF, EQUWINS_xxx); + Hide_tot = 0; + break; + case '-': + if (ALTCHKw) TOGw(w, Show_TASKON); + break; + case '=': + win_reset(w); + Hide_tot = 0; + break; + case '_': + if (ALTCHKw) wins_reflag(Flags_TOG, Show_TASKON); + break; + case '&': + case 'L': + if (VIZCHKw(w)) find_string(ch); + break; + case 'A': + Rc.mode_altscr = !Rc.mode_altscr; + break; + case 'a': + case 'w': + if (ALTCHKw) win_select(ch); + break; + case 'G': + if (ALTCHKw) { + char tmp[SMLBUFSIZ]; + STRLCPY(tmp, ioline(fmtmk(N_fmt(NAME_windows_fmt), w->rc.winname))); + if (tmp[0] && tmp[0] != kbd_ESC) win_names(w, tmp); + } + break; + case kbd_UP: + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(-1) + break; + case kbd_DOWN: + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) mkVIZrowX(+1) + break; +#ifdef USE_X_COLHDR // ------------------------------------ + case kbd_LEFT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARleft(w)) + w->varcolbeg -= SCROLLAMT; + else if (0 < w->begpflg) + w->begpflg -= 1; + } +#else + if (VIZCHKw(w)) if (0 < w->begpflg) w->begpflg -= 1; +#endif + break; + case kbd_RIGHT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARright(w)) { + w->varcolbeg += SCROLLAMT; + if (0 > w->varcolbeg) w->varcolbeg = 0; + } else if (w->begpflg + 1 < w->totpflgs) + w->begpflg += 1; + } +#else + if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) w->begpflg += 1; +#endif + break; +#else // USE_X_COLHDR ------------------------------------ + case kbd_LEFT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARleft(w)) + w->varcolbeg -= SCROLLAMT; + else if (0 < w->begpflg) { + w->begpflg -= 1; + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2; + } + } +#else + if (VIZCHKw(w)) if (0 < w->begpflg) { + w->begpflg -= 1; + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) w->begpflg -= 2; + } +#endif + break; + case kbd_RIGHT: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) { + if (VARright(w)) { + w->varcolbeg += SCROLLAMT; + if (0 > w->varcolbeg) w->varcolbeg = 0; + } else if (w->begpflg + 1 < w->totpflgs) { + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) + w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0; + else w->begpflg += 1; + } + } +#else + if (VIZCHKw(w)) if (w->begpflg + 1 < w->totpflgs) { + if (EU_MAXPFLGS < w->pflgsall[w->begpflg]) + w->begpflg += (w->begpflg + 3 < w->totpflgs) ? 3 : 0; + else w->begpflg += 1; + } +#endif + break; +#endif // USE_X_COLHDR ------------------------------------ + case kbd_PGUP: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS) && 0 < w->begtask) { + mkVIZrowX(-(w->winlines - (Rc.mode_altscr ? 1 : 2))) + } + } + break; + case kbd_PGDN: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS) && w->begtask < PIDSmaxt - 1) { + mkVIZrowX(+(w->winlines - (Rc.mode_altscr ? 1 : 2))) + } + } + break; + case kbd_HOME: +#ifndef SCROLLVAR_NO + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = w->varcolbeg = 0; +#else + if (VIZCHKw(w)) if (CHKw(w, Show_IDLEPS)) w->begtask = w->begpflg = 0; +#endif + break; + case kbd_END: + if (VIZCHKw(w)) { + if (CHKw(w, Show_IDLEPS)) { + mkVIZrowX((PIDSmaxt - w->winlines) + 1) + w->begpflg = w->endpflg; +#ifndef SCROLLVAR_NO + w->varcolbeg = 0; +#endif + } + } + break; + default: // keep gcc happy + break; + } +} // end: keys_window + + +static void keys_xtra (int ch) { +// const char *xmsg; + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + +#ifdef TREE_NORESET + if (CHKw(w, Show_FOREST)) return; +#else + OFFw(w, Show_FOREST); +#endif + /* these keys represent old-top compatibility -- + they're grouped here so that if users could ever be weaned, + we would just whack do_key's key_tab entry and this function... */ + switch (ch) { + case 'M': + w->rc.sortindx = EU_MEM; +// xmsg = "Memory"; + break; + case 'N': + w->rc.sortindx = EU_PID; +// xmsg = "Numerical"; + break; + case 'P': + w->rc.sortindx = EU_CPU; +// xmsg = "CPU"; + break; + case 'T': + w->rc.sortindx = EU_TM2; +// xmsg = "Time"; + break; + default: // keep gcc happy + break; + } +// some have objected to this message, so we'll just keep silent... +// show_msg(fmtmk("%s sort compatibility key honored", xmsg)); +} // end: keys_xtra + +/*###### Tertiary summary display support (summary_show helpers) #######*/ + + /* + * note how alphabetical order is maintained within carefully chosen | + * function names such as: (s)sum_see, (t)sum_tics, and (u)sum_unify | + * with every name exactly 1 letter more than the preceding function | + * ( surely, this must make us run much more efficiently. amirite? ) | */ + +struct rx_st { + float pcnt_one, pcnt_two, pcnt_tot; + char graph[MEDBUFSIZ]; +}; + + /* + * A *Helper* function to produce the actual cpu & memory graphs for | + * these functions -- sum_tics (tertiary) and do_memory (secondary). | + * (sorry about the name, but it keeps the above comment commitment) | */ +static struct rx_st *sum_rx (struct graph_parms *these) { + static struct { + const char *part1, *part2, *style; + } gtab[] = { + { "%-.*s~7", "%-.*s~8", Graph_bars }, + { "%-.*s~4", "%-.*s~6", Graph_blks } + }; + static __thread struct rx_st rx; + char buf1[SMLBUFSIZ], buf2[SMLBUFSIZ], buf3[MEDBUFSIZ]; + int ix, num1, num2, width; + float scale = 0.0; + + if (these->total > 0) + scale = 100.0 / these->total; + rx.pcnt_one = scale * these->part1; + rx.pcnt_two = scale * these->part2; + if (rx.pcnt_one + rx.pcnt_two > 100.0 || rx.pcnt_two < 0) + rx.pcnt_two = 0; + rx.pcnt_tot = rx.pcnt_one + rx.pcnt_two; + + num1 = (int)((rx.pcnt_one * these->adjust) + .5); + num2 = (int)((rx.pcnt_two * these->adjust) + .5); + if (num1 + num2 > these->length) { + if (num1 > these->length) num1 = these->length; + num2 = these->length - num1; + } + + width = these->length; + buf1[0] = buf2[0] = buf3[0] = '\0'; + ix = these->style - 1; // now relative to zero + if (num1) { + snprintf(buf1, sizeof(buf1), gtab[ix].part1, num1, gtab[ix].style); + width += 2; + } + if (num2) { + snprintf(buf2, sizeof(buf2), gtab[ix].part2, num2, gtab[ix].style); + width += 2; + } + snprintf(buf3, sizeof(buf3), "%s%s", buf1, buf2); + // 'width' has accounted for any show_special directives embedded above + snprintf(rx.graph, sizeof(rx.graph), "[~1%-*.*s] ~1", width, width, buf3); + + return ℞ +} // end: sum_rx + + + /* + * A *Helper* function to show multiple lines of summary information | + * as a single line. We return the number of lines actually printed. | */ +static inline int sum_see (const char *str, int nobuf) { + static char row[ROWMAXSIZ]; + static int tog; + char *p; + + p = scat(row, str); + if (!str[0]) goto flush_it; + if (Curwin->rc.double_up + && (!nobuf)) { + if (++tog <= Curwin->rc.double_up) { + scat(p, Adjoin_sp); + return 0; + } + } +flush_it: + if (!row[0]) return 0; + scat(p, "\n"); + show_special(0, row); + row[0] = '\0'; + tog = 0; + return 1; +} // end: sum_see + + + /* + * State display *Helper* function to calculate plus display (maybe) | + * the percentages for a single cpu. In this way, we'll support the | + * following environments without (hopefully) that usual code bloat: | + * 1) single cpu platforms (no matter the paucity of these types) | + * 2) modest smp boxes with ample room for each cpu's percentages | + * 3) massive smp guys leaving little or no room for that process | + * display and thus requiring the '1', '4', or '!' cpu toggles | + * ( we return the number of lines printed, as reported by sum_see ) | */ +static int sum_tics (struct stat_stack *this, const char *pfx, int nobuf) { + // tailored 'results stack value' extractor macros + #define qSv(E) STAT_VAL(E, s_int, this, Stat_ctx) + #define rSv(E) TIC_VAL(E, this) + SIC_t idl_frme, tot_frme; + struct rx_st *rx; + float scale; + +#ifndef CORE_TYPE_NO + if (Curwin->rc.core_types == P_CORES_ONLY && qSv(stat_COR_TYP) != P_CORE) return 0; + if (Curwin->rc.core_types == E_CORES_ONLY && qSv(stat_COR_TYP) != E_CORE) return 0; +#endif + idl_frme = rSv(stat_IL); + tot_frme = rSv(stat_SUM_TOT); + if (1 > tot_frme) idl_frme = tot_frme = 1; + scale = 100.0 / (float)tot_frme; + + /* account for VM tics not otherwise provided for ... + ( with xtra-procps-debug.h, can't use PID_VAL w/ assignment ) */ + this->head[stat_SY].result.sl_int += rSv(stat_GU) + rSv(stat_GN); + this->head[stat_SUM_SYS].result.sl_int += rSv(stat_GU) + rSv(stat_GN); + + /* display some kinda' cpu state percentages + (who or what is explained by the passed prefix) */ + if (Curwin->rc.graph_cpus) { + Graph_cpus->total = tot_frme; + Graph_cpus->part1 = rSv(stat_SUM_USR); + Graph_cpus->part2 = rSv(stat_SUM_SYS); + rx = sum_rx(Graph_cpus); + if (Curwin->rc.double_up > 1) + return sum_see(fmtmk("%s~3%3.0f%s", pfx, rx->pcnt_tot, rx->graph), nobuf); + else { + return sum_see(fmtmk("%s ~3%#5.1f~2/%-#5.1f~3 %3.0f%s" + , pfx, rx->pcnt_one, rx->pcnt_two, rx->pcnt_tot + , rx->graph) + , nobuf); + } + } else { + return sum_see(fmtmk(Cpu_States_fmts, pfx + , (float)rSv(stat_US) * scale, (float)rSv(stat_SY) * scale + , (float)rSv(stat_NI) * scale, (float)idl_frme * scale + , (float)rSv(stat_IO) * scale, (float)rSv(stat_IR) * scale + , (float)rSv(stat_SI) * scale, (float)rSv(stat_ST) * scale), nobuf); + } + #undef qSv + #undef rSv +} // end: sum_tics + + + /* + * Cpu *Helper* function to combine additional cpu statistics in our | + * efforts to reduce the total number of processors that'll be shown | + * ( we return the number of lines printed, as reported by sum_see ) | */ +static int sum_unify (struct stat_stack *this, int nobuf) { + // a tailored 'results stack value' extractor macro + #define rSv(E,T) STAT_VAL(E, T, this, Stat_ctx) + static struct stat_result stack[MAXTBL(Stat_items)]; + static struct stat_stack accum = { &stack[0] }; + static int ix, beg; + char pfx[16]; + int n; + + // entries for stat_ID & stat_NU are unused + stack[stat_US].result.sl_int += rSv(stat_US, sl_int); + stack[stat_SY].result.sl_int += rSv(stat_SY, sl_int); + stack[stat_NI].result.sl_int += rSv(stat_NI, sl_int); + stack[stat_IL].result.sl_int += rSv(stat_IL, sl_int); + stack[stat_IO].result.sl_int += rSv(stat_IO, sl_int); + stack[stat_IR].result.sl_int += rSv(stat_IR, sl_int); + stack[stat_SI].result.sl_int += rSv(stat_SI, sl_int); + stack[stat_ST].result.sl_int += rSv(stat_ST, sl_int); + stack[stat_GU].result.sl_int += rSv(stat_GU, sl_int); + stack[stat_GN].result.sl_int += rSv(stat_GN, sl_int); + stack[stat_SUM_USR].result.sl_int += rSv(stat_SUM_USR, sl_int); + stack[stat_SUM_SYS].result.sl_int += rSv(stat_SUM_SYS, sl_int); + stack[stat_SUM_TOT].result.sl_int += rSv(stat_SUM_TOT, sl_int); + + if (!ix) beg = rSv(stat_ID, s_int); + if (nobuf || ix >= (Curwin->rc.combine_cpus - 1)) { + snprintf(pfx, sizeof(pfx), "%-7.7s:", fmtmk("%d-%d", beg, rSv(stat_ID, s_int))); + n = sum_tics(&accum, pfx, nobuf); + memset(&stack, 0, sizeof(stack)); + ix = 0; + return n; + } + ++ix; + return 0; + #undef rSv +} // end: sum_unify + +/*###### Secondary summary display support (summary_show helpers) ######*/ + + /* + * A helper function that displays cpu and/or numa node stuff | + * ( so as to keep the 'summary_show' guy a reasonable size ) | */ +static void do_cpus (void) { + #define noMAS (Msg_row + 1 >= SCREEN_ROWS - 1) + #define eachCPU(x) N_fmt(WORD_eachcpu_fmt), 'u', x + char tmp[MEDBUFSIZ]; + int i; + + if (CHKw(Curwin, View_CPUNOD)) { + if (Numa_node_sel < 0) { +numa_oops: + /* + * display the 1st /proc/stat line, then the nodes (if room) ... */ + Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1); + // display each cpu node's states + for (i = 0; i < Numa_node_tot; i++) { + struct stat_stack *nod_ptr = Stat_reap->numa->stacks[i]; + if (NOD_VAL(stat_NU, i) == STAT_NODE_INVALID) continue; + if (noMAS) break; + snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), NOD_VAL(stat_ID, i)); + Msg_row += sum_tics(nod_ptr, tmp, 1); + } + } else { + /* + * display the node summary, then the associated cpus (if room) ... */ + for (i = 0; i < Numa_node_tot; i++) { + if (Numa_node_sel == NOD_VAL(stat_ID, i) + && (NOD_VAL(stat_NU, i) != STAT_NODE_INVALID)) break; + } + if (i == Numa_node_tot) { + Numa_node_sel = -1; + goto numa_oops; + } + snprintf(tmp, sizeof(tmp), N_fmt(NUMA_nodenam_fmt), Numa_node_sel); + Msg_row += sum_tics(Stat_reap->numa->stacks[Numa_node_sel], tmp, 1); +#ifdef PRETEND48CPU + #define deLIMIT Stat_reap->cpus->total +#else + #define deLIMIT Cpu_cnt +#endif + for (i = 0; i < deLIMIT; i++) { + if (Numa_node_sel == CPU_VAL(stat_NU, i)) { + if (noMAS) break; + snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i))); + Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, 1); + } + } + #undef deLIMIT + } + + } else if (!CHKw(Curwin, View_CPUSUM)) { + /* + * display each cpu's states separately, screen height permitting ... */ +#ifdef PRETEND48CPU + int j; + if (Curwin->rc.combine_cpus) { + for (i = 0, j = 0; i < Cpu_cnt; i++) { + Stat_reap->cpus->stacks[j]->head[stat_ID].result.s_int = i; + Msg_row += sum_unify(Stat_reap->cpus->stacks[j], (i+1 >= Cpu_cnt)); + if (++j >= Stat_reap->cpus->total) j = 0; + if (noMAS) break; + } + } else { + for (i = 0, j = 0; i < Cpu_cnt; i++) { + snprintf(tmp, sizeof(tmp), eachCPU(i)); + Msg_row += sum_tics(Stat_reap->cpus->stacks[j], tmp, (i+1 >= Cpu_cnt)); + if (++j >= Stat_reap->cpus->total) j = 0; + if (noMAS) break; + } + } +#else + if (Curwin->rc.combine_cpus) { + for (i = 0; i < Cpu_cnt; i++) { + Msg_row += sum_unify(Stat_reap->cpus->stacks[i], (i+1 >= Cpu_cnt)); + if (noMAS) break; + } + } else { + for (i = 0; i < Cpu_cnt; i++) { +#ifndef CORE_TYPE_NO + #ifdef CORE_TYPE_LO + char ctab[] = { 'u', 'e', 'p' }; + #else + char ctab[] = { 'u', 'E', 'P' }; + #endif + int cid = CPU_VAL(stat_ID, i), typ = CPU_VAL(stat_COR_TYP, i); + char chr = Curwin->rc.core_types ? ctab[typ] : 'u' ; + snprintf(tmp, sizeof(tmp), N_fmt(WORD_eachcpu_fmt), chr, cid); +#else + snprintf(tmp, sizeof(tmp), eachCPU(CPU_VAL(stat_ID, i))); +#endif + Msg_row += sum_tics(Stat_reap->cpus->stacks[i], tmp, (i+1 >= Cpu_cnt)); + if (noMAS) break; + } + } +#endif + + } else { + /* + * display just the 1st /proc/stat line ... */ + Msg_row += sum_tics(Stat_reap->summary, N_txt(WORD_allcpus_txt), 1); + } + + // coax this guy into flushing any pending cpu stuff ... + Msg_row += sum_see("", 1); + #undef noMAS + #undef eachCPU +} // end: do_cpus + + + /* + * A helper function which will display the memory/swap stuff | + * ( so as to keep the 'summary_show' guy a reasonable size ) | */ +static void do_memory (void) { + #define bfT(n) buftab[n].buf + #define scT(e) scaletab[Rc.summ_mscale]. e + #define mkM(x) (float) x / scT(div) + #define prT(b,z) { if (9 < snprintf(b, 10, scT(fmts), z)) b[8] = '+'; } +#ifdef TOG4_MEM_1UP + #define mem2UP 1 +#else + #define mem2UP 0 +#endif + static struct { + float div; + const char *fmts; + const char *label; + } scaletab[] = { + { 1, "%.0f ", NULL }, // kibibytes +#ifdef BOOST_MEMORY + { 1024.0, "%#.3f ", NULL }, // mebibytes + { 1024.0*1024, "%#.3f ", NULL }, // gibibytes + { 1024.0*1024*1024, "%#.3f ", NULL }, // tebibytes + { 1024.0*1024*1024*1024, "%#.3f ", NULL }, // pebibytes + { 1024.0*1024*1024*1024*1024, "%#.3f ", NULL } // exbibytes +#else + { 1024.0, "%#.1f ", NULL }, // mebibytes + { 1024.0*1024, "%#.1f ", NULL }, // gibibytes + { 1024.0*1024*1024, "%#.1f ", NULL }, // tebibytes + { 1024.0*1024*1024*1024, "%#.1f ", NULL }, // pebibytes + { 1024.0*1024*1024*1024*1024, "%#.1f ", NULL } // exbibytes +#endif + }; + struct { // 0123456789 + // snprintf contents of each buf (after SK_Kb): 'nnnn.nnn 0' + // & prT macro might replace space at buf[8] with: -------> + + char buf[10]; // MEMORY_lines_fmt provides for 8+1 bytes + } buftab[8]; + char row[ROWMINSIZ]; + long my_qued, my_misc, my_used; + struct rx_st *rx; + + if (!scaletab[0].label) { + scaletab[0].label = N_txt(AMT_kilobyte_txt); + scaletab[1].label = N_txt(AMT_megabyte_txt); + scaletab[2].label = N_txt(AMT_gigabyte_txt); + scaletab[3].label = N_txt(AMT_terabyte_txt); + scaletab[4].label = N_txt(AMT_petabyte_txt); + scaletab[5].label = N_txt(AMT_exxabyte_txt); + } + my_qued = MEM_VAL(mem_BUF) + MEM_VAL(mem_QUE); + + if (Curwin->rc.graph_mems) { + my_used = MEM_VAL(mem_TOT) - MEM_VAL(mem_FRE) - my_qued; +#ifdef MEMGRAPH_OLD + my_misc = my_qued; +#else + my_misc = MEM_VAL(mem_TOT) - MEM_VAL(mem_AVL) - my_used; +#endif + Graph_mems->total = MEM_VAL(mem_TOT); + Graph_mems->part1 = my_used; + Graph_mems->part2 = my_misc; + rx = sum_rx(Graph_mems); +#ifdef TOG4_MEM_1UP + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph); +#else + if (Curwin->rc.double_up > 1) + snprintf(row, sizeof(row), "%s %s~3%3.0f%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, rx->graph); + else { + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_mem_txt), rx->pcnt_tot, bfT(0), rx->graph); + } +#endif + Msg_row += sum_see(row, mem2UP); + + Graph_mems->total = MEM_VAL(swp_TOT); + Graph_mems->part1 = 0; + Graph_mems->part2 = MEM_VAL(swp_USE); + rx = sum_rx(Graph_mems); +#ifdef TOG4_MEM_1UP + prT(bfT(1), mkM(MEM_VAL(swp_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph); +#else + if (Curwin->rc.double_up > 1) + snprintf(row, sizeof(row), "%s %s~3%3.0f%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, rx->graph); + else { + prT(bfT(1), mkM(MEM_VAL(swp_TOT))); + snprintf(row, sizeof(row), "%s %s:~3%#5.1f~2/%-9.9s~3%s" + , scT(label), N_txt(WORD_abv_swp_txt), rx->pcnt_two, bfT(1), rx->graph); + } +#endif + Msg_row += sum_see(row, 1); + + } else { + prT(bfT(0), mkM(MEM_VAL(mem_TOT))); prT(bfT(1), mkM(MEM_VAL(mem_FRE))); + prT(bfT(2), mkM(MEM_VAL(mem_USE))); prT(bfT(3), mkM(my_qued)); + prT(bfT(4), mkM(MEM_VAL(swp_TOT))); prT(bfT(5), mkM(MEM_VAL(swp_FRE))); + prT(bfT(6), mkM(MEM_VAL(swp_USE))); prT(bfT(7), mkM(MEM_VAL(mem_AVL))); + + snprintf(row, sizeof(row), N_unq(MEMORY_line1_fmt) + , scT(label), N_txt(WORD_abv_mem_txt), bfT(0), bfT(1), bfT(2), bfT(3)); + Msg_row += sum_see(row, mem2UP); + + snprintf(row, sizeof(row), N_unq(MEMORY_line2_fmt) + , scT(label), N_txt(WORD_abv_swp_txt), bfT(4), bfT(5), bfT(6), bfT(7) + , N_txt(WORD_abv_mem_txt)); + Msg_row += sum_see(row, 1); + } + #undef bfT + #undef scT + #undef mkM + #undef prT + #undef mem2UP +} // end: do_memory + +/*###### Main Screen routines ##########################################*/ + + /* + * Process keyboard input during the main loop */ +static void do_key (int ch) { + static struct { + void (*func)(int ch); + char keys[SMLBUFSIZ]; + } key_tab[] = { + { keys_global, + { '?', 'B', 'd', 'E', 'e', 'f', 'g', 'H', 'h' + , 'I', 'k', 'r', 's', 'X', 'Y', 'Z', '0' + , kbd_CtrlE, kbd_CtrlG, kbd_CtrlI, kbd_CtrlK, kbd_CtrlL + , kbd_CtrlN, kbd_CtrlP, kbd_CtrlR, kbd_CtrlU + , kbd_ENTER, kbd_SPACE, kbd_BTAB, '\0' } }, + { keys_summary, + #ifdef CORE_TYPE_NO + { '!', '1', '2', '3', '4', 'C', 'l', 'm', 't', '\0' } }, + #else + { '!', '1', '2', '3', '4', '5', 'C', 'l', 'm', 't', '\0' } }, + #endif + { keys_task, + { '#', '<', '>', 'b', 'c', 'F', 'i', 'J', 'j', 'n', 'O', 'o' + , 'R', 'S', 'U', 'u', 'V', 'v', 'x', 'y', 'z' + , kbd_CtrlO, '\0' } }, + { keys_window, + { '+', '-', '=', '_', '&', 'A', 'a', 'G', 'L', 'w' + , kbd_UP, kbd_DOWN, kbd_LEFT, kbd_RIGHT, kbd_PGUP, kbd_PGDN + , kbd_HOME, kbd_END, '\0' } }, + { keys_xtra, + { 'M', 'N', 'P', 'T', '\0'} } + }; + int i; + + Frames_signal = BREAK_off; + switch (ch) { + case 0: // ignored (always) + case kbd_ESC: // ignored (sometimes) + goto all_done; + case 'q': // no return from this guy + bye_bye(NULL); + case 'W': // no need for rebuilds + write_rcfile(); + goto all_done; + default: // and now, the real work... + // and just in case 'Monpids' is active but matched no processes ... + if (!PIDSmaxt && ch != '=') goto all_done; + for (i = 0; i < MAXTBL(key_tab); ++i) + if (strchr(key_tab[i].keys, ch)) { + key_tab[i].func(ch); + if (Frames_signal == BREAK_off) + Frames_signal = BREAK_kbd; + /* due to the proliferation of the need for 'mkVIZrow1', | + aside from 'wins_stage_2' use, we'll now issue it one | + time here. there will remain several places where the | + companion 'mkVIZrowX' macro is issued, thus the check | + for a value already in 'begnext' in this conditional. | */ + if (CHKw(Curwin, Show_TASKON) && !mkVIZyes) + mkVIZrow1 + goto all_done; + } + }; + /* The Frames_signal above will force a rebuild of column headers. + It's NOT simply lazy programming. Below are some keys that may + require new column headers and/or new library item enumerators: + 'A' - likely + 'c' - likely when !Mode_altscr, maybe when Mode_altscr + 'F' - likely + 'f' - likely + 'g' - likely + 'H' - likely + 'I' - likely + 'J' - always + 'j' - always + 'Z' - likely, if 'Curwin' changed when !Mode_altscr + '-' - likely (restricted to Mode_altscr) + '_' - likely (restricted to Mode_altscr) + '=' - maybe, but only when Mode_altscr + '+' - likely (restricted to Mode_altscr) + PLUS, likely for FOUR of the EIGHT cursor motion keys (scrolled) + ( At this point we have a human being involved and so have all the time ) + ( in the world. We can afford a few extra cpu cycles every now & then! ) + */ + + show_msg(N_txt(UNKNOWN_cmds_txt)); +all_done: + putp((Cursor_state = Cap_curs_hide)); +} // end: do_key + + + /* + * In support of a new frame: + * 1) Display uptime and load average (maybe) + * 2) Display task/cpu states (maybe) + * 3) Display memory & swap usage (maybe) */ +static void summary_show (void) { + #define isROOM(f,n) (CHKw(Curwin, f) && Msg_row + (n) < SCREEN_ROWS - 1) + + if (Restrict_some) { +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); +#endif + // Display Task States only + if (isROOM(View_STATES, 1)) { + show_special(0, fmtmk(N_unq(STATE_line_1_fmt) + , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt) + , PIDSmaxt, Pids_reap->counts->running + , Pids_reap->counts->sleeping + Pids_reap->counts->other + , Pids_reap->counts->stopped, Pids_reap->counts->zombied)); + Msg_row += 1; + } + return; + } + + // Display Uptime and Loadavg + if (isROOM(View_LOADAV, 1)) { + if (!Rc.mode_altscr) + show_special(0, fmtmk(LOADAV_line, Myname, procps_uptime_sprint())); + else + show_special(0, fmtmk(CHKw(Curwin, Show_TASKON)? LOADAV_line_alt : LOADAV_line + , Curwin->grpname, procps_uptime_sprint())); + Msg_row += 1; + } // end: View_LOADAV + +#ifdef THREADED_CPU + sem_wait(&Semaphore_cpus_end); +#endif +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); +#endif + // Display Task and Cpu(s) States + if (isROOM(View_STATES, 2)) { + show_special(0, fmtmk(N_unq(STATE_line_1_fmt) + , Thread_mode ? N_txt(WORD_threads_txt) : N_txt(WORD_process_txt) + , PIDSmaxt, Pids_reap->counts->running + , Pids_reap->counts->sleeping + Pids_reap->counts->other + , Pids_reap->counts->stopped, Pids_reap->counts->zombied)); + Msg_row += 1; + + do_cpus(); + } + +#ifdef THREADED_MEM + sem_wait(&Semaphore_memory_end); +#endif + // Display Memory and Swap stats + if (isROOM(View_MEMORY, 2)) { + do_memory(); + } + + #undef isROOM +} // end: summary_show + + + /* + * Build the information for a single task row and + * display the results or return them to the caller. */ +static const char *task_show (const WIN_t *q, int idx) { + // a tailored 'results stack value' extractor macro + #define rSv(E,T) PID_VAL(E, T, p) +#ifndef SCROLLVAR_NO + #define makeVAR(S) { const char *pv = S; \ + if (!q->varcolbeg) cp = make_str(pv, q->varcolsz, Js, AUTOX_NO); \ + else cp = make_str(q->varcolbeg < (int)strlen(pv) ? pv + q->varcolbeg : "", q->varcolsz, Js, AUTOX_NO); } + #define varUTF8(S) { const char *pv = S; \ + if (!q->varcolbeg) cp = make_str_utf8(pv, q->varcolsz, Js, AUTOX_NO); \ + else cp = make_str_utf8((q->varcolbeg < ((int)strlen(pv) - utf8_delta(pv))) \ + ? pv + utf8_embody(pv, q->varcolbeg) : "", q->varcolsz, Js, AUTOX_NO); } +#else + #define makeVAR(S) { cp = make_str(S, q->varcolsz, Js, AUTOX_NO); } + #define varUTF8(S) { cp = make_str_utf8(S, q->varcolsz, Js, AUTOX_NO); } +#endif + struct pids_stack *p = q->ppt[idx]; + static char rbuf[ROWMINSIZ]; + char *rp; + int x; + + /* we use up to three additional 'PIDS_extra' results in our stacks + eu_TREE_HID (s_ch) : where 'x' == collapsed and 'z' == unseen + eu_TREE_LVL (s_int): where a level number is stored (0 - 100) + eu_TREE_ADD (u_int): where children's tics are stored (maybe) */ +#ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented +#endif + if (CHKw(q, Show_FOREST) && rSv(eu_TREE_HID, s_ch) == 'z') + return ""; + + // we must begin a row with a possible window number in mind... + *(rp = rbuf) = '\0'; + if (Rc.mode_altscr) rp = scat(rp, " "); + + for (x = 0; x < q->maxpflgs; x++) { + const char *cp = NULL; + FLG_t i = q->procflgs[x]; + #define S Fieldstab[i].scale // these used to be variables + #define W Fieldstab[i].width // but it's much better if we + #define Js CHKw(q, Show_JRSTRS) // represent them as #defines + #define Jn CHKw(q, Show_JRNUMS) // and only exec code if used + + /* except for the XOF/XON pseudo flags the following case labels are grouped + by result type according to capacity (small -> large) and then ordered by + additional processing requirements (as in plain, scaled, decorated, etc.) */ + + switch (i) { +#ifndef USE_X_COLHDR + // these 2 aren't real procflgs, they're used in column highlighting! + case EU_XOF: + case EU_XON: + cp = NULL; + if (!CHKw(q, NOPRINT_xxx)) { + /* treat running tasks specially - entire row may get highlighted + so we needn't turn it on and we MUST NOT turn it off */ + if (!('R' == rSv(EU_STA, s_ch) && CHKw(q, Show_HIROWS))) + cp = (EU_XON == i ? q->capclr_rowhigh : q->capclr_rownorm); + } + break; +#endif + /* s_ch, make_chr */ + case EU_STA: // PIDS_STATE + cp = make_chr(rSv(EU_STA, s_ch), W, Js); + break; + /* s_int, make_num with auto width */ + case EU_LID: // PIDS_ID_LOGIN + cp = make_num(rSv(EU_LID, s_int), W, Jn, EU_LID, 0); + break; + /* s_int, make_num without auto width */ + case EU_AGI: // PIDS_AUTOGRP_ID + case EU_CPN: // PIDS_PROCESSOR + case EU_NMA: // PIDS_PROCESSOR_NODE + case EU_PGD: // PIDS_ID_PGRP + case EU_PID: // PIDS_ID_PID + case EU_PPD: // PIDS_ID_PPID + case EU_SID: // PIDS_ID_SESSION + case EU_TGD: // PIDS_ID_TGID + case EU_THD: // PIDS_NLWP + case EU_TPG: // PIDS_ID_TPGID + cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 0); + break; + /* s_int, make_num without auto width, but with zero suppression */ + case EU_AGN: // PIDS_AUTOGRP_NICE + case EU_NCE: // PIDS_NICE + case EU_OOA: // PIDS_OOM_ADJ + case EU_OOM: // PIDS_OOM_SCORE + cp = make_num(rSv(i, s_int), W, Jn, AUTOX_NO, 1); + break; + /* s_int, scale_num */ + case EU_FV1: // PIDS_FLT_MAJ_DELTA + case EU_FV2: // PIDS_FLT_MIN_DELTA + cp = scale_num(rSv(i, s_int), W, Jn); + break; + /* s_int, make_num or make_str */ + case EU_PRI: // PIDS_PRIORITY + if (-99 > rSv(EU_PRI, s_int) || 999 < rSv(EU_PRI, s_int)) + cp = make_str("rt", W, Jn, AUTOX_NO); + else + cp = make_num(rSv(EU_PRI, s_int), W, Jn, AUTOX_NO, 0); + break; + /* s_int, scale_pcnt with special handling */ + case EU_CPU: // PIDS_TICS_ALL_DELTA + { float u = (float)rSv(EU_CPU, u_int); + int n = rSv(EU_THD, s_int); + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } +#ifndef TREE_VCPUOFF + #ifndef TREE_VWINALL + if (q == Curwin) // note: the following is NOT indented + #endif + if (CHKw(q, Show_FOREST)) u += rSv(eu_TREE_ADD, u_int); + u *= Frame_etscale; + /* technically, eu_TREE_HID is only valid if Show_FOREST is active + but its zeroed out slot will always be present now */ + if (rSv(eu_TREE_HID, s_ch) != 'x' && u > 100.0 * n) u = 100.0 * n; +#else + u *= Frame_etscale; + /* process can't use more %cpu than number of threads it has + ( thanks Jaromir Capik <jcapik@redhat.com> ) */ + if (u > 100.0 * n) u = 100.0 * n; +#endif + if (u > Cpu_pmax) u = Cpu_pmax; + cp = scale_pcnt(u, W, Jn, 0); + } + break; + /* ull_int, scale_pcnt for 'utilization' */ + case EU_CUU: // PIDS_UTILIZATION + case EU_CUC: // PIDS_UTILIZATION_C + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } + cp = scale_pcnt(rSv(i, real), W, Jn, 1); + break; + /* u_int, make_num with auto width */ + case EU_GID: // PIDS_ID_EGID + case EU_UED: // PIDS_ID_EUID + case EU_URD: // PIDS_ID_RUID + case EU_USD: // PIDS_ID_SUID + cp = make_num(rSv(i, u_int), W, Jn, i, 0); + break; + /* ul_int, make_num with auto width and zero suppression */ + case EU_NS1: // PIDS_NS_IPC + case EU_NS2: // PIDS_NS_MNT + case EU_NS3: // PIDS_NS_NET + case EU_NS4: // PIDS_NS_PID + case EU_NS5: // PIDS_NS_USER + case EU_NS6: // PIDS_NS_UTS + case EU_NS7: // PIDS_NS_CGROUP + case EU_NS8: // PIDS_NS_TIME + cp = make_num(rSv(i, ul_int), W, Jn, i, 1); + break; + /* ul_int, scale_mem */ + case EU_COD: // PIDS_MEM_CODE + case EU_DAT: // PIDS_MEM_DATA + case EU_DRT: // PIDS_noop, really # pgs, but always 0 since 2.6 + case EU_PZA: // PIDS_SMAP_PSS_ANON + case EU_PZF: // PIDS_SMAP_PSS_FILE + case EU_PZS: // PIDS_SMAP_PSS_SHMEM + case EU_PSS: // PIDS_SMAP_PSS + case EU_RES: // PIDS_MEM_RES + case EU_RSS: // PIDS_SMAP_RSS + case EU_RZA: // PIDS_VM_RSS_ANON + case EU_RZF: // PIDS_VM_RSS_FILE + case EU_RZL: // PIDS_VM_RSS_LOCKED + case EU_RZS: // PIDS_VM_RSS_SHARED + case EU_SHR: // PIDS_MEM_SHR + case EU_SWP: // PIDS_VM_SWAP + case EU_USE: // PIDS_VM_USED + case EU_USS: // PIDS_SMAP_PRV_TOTAL + case EU_VRT: // PIDS_MEM_VIRT + cp = scale_mem(S, rSv(i, ul_int), W, Jn); + break; + /* ul_int, scale_num */ + case EU_FL1: // PIDS_FLT_MAJ + case EU_FL2: // PIDS_FLT_MIN + case EU_IRB: // PIDS_IO_READ_BYTES + case EU_IRO: // PIDS_IO_READ_OPS + case EU_IWB: // PIDS_IO_WRITE_BYTES + case EU_IWO: // PIDS_IO_WRITE_OPS + cp = scale_num(rSv(i, ul_int), W, Jn); + break; + /* ul_int, scale_pcnt */ + case EU_MEM: // derive from PIDS_MEM_RES + if (Restrict_some) { + cp = justify_pad("?", W, Jn); + break; + } + cp = scale_pcnt((float)rSv(EU_MEM, ul_int) * 100 / MEM_VAL(mem_TOT), W, Jn, 0); + break; + /* ul_int, make_str with special handling */ + case EU_FLG: // PIDS_FLAGS + cp = make_str(hex_make(rSv(EU_FLG, ul_int), 1), W, Js, AUTOX_NO); + break; + /* ull_int, scale_tics (try 'minutes:seconds.hundredths') */ + case EU_TM2: // PIDS_TICS_ALL + case EU_TME: // PIDS_TICS_ALL + { TIC_t t; + if (CHKw(q, Show_CTIMES)) t = rSv(eu_TICS_ALL_C, ull_int); + else t = rSv(i, ull_int); + cp = scale_tics(t, W, Jn, TICS_AS_SECS); + } + break; + /* ull_int, scale_tics (try 'minutes:seconds') */ + case EU_TM3: // PIDS_TICS_BEGAN + cp = scale_tics(rSv(EU_TM3, ull_int), W, Jn, TICS_AS_MINS); + break; + /* real, scale_tics (try 'hour,minutes') */ + case EU_TM4: // PIDS_TIME_ELAPSED + cp = scale_tics(rSv(EU_TM4, real) * Hertz, W, Jn, TICS_AS_HOUR); + break; + /* str, make_str (all AUTOX yes) */ + case EU_LXC: // PIDS_LXCNAME + case EU_TTY: // PIDS_TTY_NAME + case EU_WCH: // PIDS_WCHAN_NAME + cp = make_str(rSv(i, str), W, Js, i); + break; + /* str, make_str_utf8 (all AUTOX yes) */ + case EU_GRP: // PIDS_ID_EGROUP + case EU_UEN: // PIDS_ID_EUSER + case EU_URN: // PIDS_ID_RUSER + case EU_USN: // PIDS_ID_SUSER + cp = make_str_utf8(rSv(i, str), W, Js, i); + break; + /* str, make_str_utf8 with variable width */ + case EU_CGN: // PIDS_CGNAME + case EU_CGR: // PIDS_CGROUP + case EU_ENV: // PIDS_ENVIRON + case EU_EXE: // PIDS_EXE + case EU_SGN: // PIDS_SUPGROUPS + varUTF8(rSv(i, str)) + break; + /* str, make_str with variable width */ + case EU_SGD: // PIDS_SUPGIDS + makeVAR(rSv(EU_SGD, str)) + break; + /* str, make_str with variable width + additional decoration */ + case EU_CMD: // PIDS_CMD or PIDS_CMDLINE + varUTF8(forest_display(q, idx)) + break; + default: // keep gcc happy + continue; + } // end: switch 'procflag' + + if (cp) { + if (q->osel_tot && !osel_matched(q, i, cp)) return ""; + rp = scat(rp, cp); + } + #undef S + #undef W + #undef Js + #undef Jn + } // end: for 'maxpflgs' + + if (!CHKw(q, NOPRINT_xxx)) { + const char *cap = ((CHKw(q, Show_HIROWS) && 'R' == rSv(EU_STA, s_ch))) + ? q->capclr_rowhigh : q->capclr_rownorm; + char *row = rbuf; + int ofs; + /* since we can't predict what the search string will be and, + considering what a single space search request would do to + potential buffer needs, when any matches are found we skip + normal output routing and send all of the results directly + to the terminal (and we sound asthmatic: poof, putt, puff) */ + if (-1 < (ofs = find_ofs(q, row))) { + POOF("\n", cap); + do { + row[ofs] = '\0'; + PUTT("%s%s%s%s", row, q->capclr_hdr, q->findstr, cap); + row += (ofs + q->findlen); + ofs = find_ofs(q, row); + } while (-1 < ofs); + PUTT("%s%s", row, Caps_endline); + // with a corrupted rbuf, ensure row is 'counted' by window_show + rbuf[0] = '!'; + } else + PUFF("\n%s%s%s", cap, row, Caps_endline); + } + return rbuf; + #undef rSv + #undef makeVAR + #undef varUTF8 +} // end: task_show + + + /* + * A window_show *Helper* function ensuring that a window 'begtask' | + * represents a visible process (not any hidden/filtered-out task). | + * In reality this function is called exclusively for the 'current' | + * window and only after available user keystroke(s) are processed. | + * Note: it's entirely possible there are NO visible tasks to show! | */ +static void window_hlp (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, reversed; + int beg = w->focus_pid ? w->focus_beg : 0; + int end = w->focus_pid ? w->focus_end : PIDSmaxt; + + SETw(w, NOPRINT_xxx); + w->begtask += w->begnext; + // next 'if' will force a forward scan ... + if (w->begtask <= beg) { w->begtask = beg; w->begnext = +1; } + else if (w->begtask >= end) w->begtask = end - 1; + + reversed = 0; + // potentially scroll forward ... + if (w->begnext > 0) { +fwd_redux: + for (i = w->begtask; i < end; i++) { + if (wins_usrselect(w, i) + && (*task_show(w, i))) + break; + } + if (i < end) { + w->begtask = i; + goto wrap_up; + } + // no luck forward, so let's try backward + w->begtask = end - 1; + } + + // potentially scroll backward ... + for (i = w->begtask; i > beg; i--) { + if (wins_usrselect(w, i) + && (*task_show(w, i))) + break; + } + w->begtask = i; + + // reached the top, but maybe this guy ain't visible + if (w->begtask == beg && !reversed) { + if (!(wins_usrselect(w, beg)) + || (!(*task_show(w, beg)))) { + reversed = 1; + goto fwd_redux; + } + } + +wrap_up: + mkVIZoff(w) + OFFw(w, NOPRINT_xxx); +} // end: window_hlp + + + /* + * Squeeze as many tasks as we can into a single window, + * after sorting the passed proc table. */ +static int window_show (WIN_t *q, int wmax) { + #define sORDER CHKw(q, Qsrt_NORMAL) ? PIDS_SORT_DESCEND : PIDS_SORT_ASCEND + /* the isBUSY macro determines if a task is 'active' -- + it returns true if some cpu was used since the last sample. + ( actual 'running' tasks will be a subset of those selected ) */ + #define isBUSY(x) (0 < PID_VAL(EU_CPU, u_int, (x))) + #define winMIN(a,b) (((a) < (b)) ? (a) : (b)) + int i, lwin, numtasks; + + // Display Column Headings -- and distract 'em while we sort (maybe) + PUFF("\n%s%s%s", q->capclr_hdr, q->columnhdr, Caps_endline); + // and just in case 'Monpids' is active but matched no processes ... + if (!PIDSmaxt) return 1; // 1 for the column header + + if (CHKw(q, Show_FOREST)) { + forest_begin(q); + if (q->focus_pid) forest_config(q); + } else { + enum pids_item item = Fieldstab[q->rc.sortindx].item; + if (item == PIDS_CMD && CHKw(q, Show_CMDLIN)) + item = PIDS_CMDLINE; + else if (item == PIDS_TICS_ALL && CHKw(q, Show_CTIMES)) + item = PIDS_TICS_ALL_C; + if (!(procps_pids_sort(Pids_ctx, q->ppt , PIDSmaxt, item, sORDER))) + error_exit(fmtmk(N_fmt(LIB_errorpid_fmt), __LINE__, strerror(errno))); + } + + if (mkVIZyes) window_hlp(); + else OFFw(q, NOPRINT_xxx); + + i = q->begtask; + lwin = 1; // 1 for the column header + wmax = winMIN(wmax, q->winlines + 1); // ditto for winlines, too + numtasks = q->focus_pid ? winMIN(q->focus_end, PIDSmaxt) : PIDSmaxt; + + /* the least likely scenario is also the most costly, so we'll try to avoid + checking some stuff with each iteration and check it just once... */ + if (CHKw(q, Show_IDLEPS) && !q->usrseltyp) + while (i < numtasks && lwin < wmax) { + if (*task_show(q, i++)) + ++lwin; + } + else + while (i < numtasks && lwin < wmax) { + if ((CHKw(q, Show_IDLEPS) || isBUSY(q->ppt[i])) + && wins_usrselect(q, i) + && *task_show(q, i)) + ++lwin; + ++i; + } + + return lwin; + #undef sORDER + #undef isBUSY + #undef winMIN +} // end: window_show + +/*###### Entry point plus two ##########################################*/ + + /* + * This guy's just a *Helper* function who apportions the + * remaining amount of screen real estate under multiple windows */ +static void frame_hlp (int wix, int max) { + int i, size, wins; + + // calc remaining number of visible windows + for (i = wix, wins = 0; i < GROUPSMAX; i++) + if (CHKw(&Winstk[i], Show_TASKON)) + ++wins; + + if (!wins) wins = 1; + // deduct 1 line/window for the columns heading + size = (max - wins) / wins; + + /* for subject window, set WIN_t winlines to either the user's + maxtask (1st choice) or our 'foxized' size calculation + (foxized adj. - 'fair and balanced') */ + Winstk[wix].winlines = + Winstk[wix].rc.maxtasks ? Winstk[wix].rc.maxtasks : size; +} // end: frame_hlp + + + /* + * Initiate the Frame Display Update cycle at someone's whim! + * This routine doesn't do much, mostly he just calls others. + * + * (Whoa, wait a minute, we DO caretake those row guys, plus) + * (we CALCULATE that IMPORTANT Max_lines thingy so that the) + * (*subordinate* functions invoked know WHEN the user's had) + * (ENOUGH already. And at Frame End, it SHOULD be apparent) + * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring) + * (that those auto-sized columns are addressed, know what I) + * (mean? Huh, "doesn't DO MUCH"! Never, EVER think or say) + * (THAT about THIS function again, Ok? Good that's better.) + * + * (ps. we ARE the UNEQUALED justification KING of COMMENTS!) + * (No, I don't mean significance/relevance, only alignment.) + */ +static void frame_make (void) { + WIN_t *w = Curwin; // avoid gcc bloat with a local copy + int i, scrlins; + + // check auto-sized width increases from the last iteration... + if (AUTOX_MODE && Autox_found) + widths_resize(); + + /* deal with potential signal(s) since the last time around + plus any input which may change 'tasks_refresh' needs... */ + if (Frames_signal) { + if (Frames_signal == BREAK_sig + || (Frames_signal == BREAK_screen)) + BOT_TOSS; + zap_fieldstab(); + } + +#ifdef THREADED_TSK + sem_post(&Semaphore_tasks_beg); +#else + tasks_refresh(NULL); +#endif + + if (!Restrict_some) { +#ifdef THREADED_CPU + sem_post(&Semaphore_cpus_beg); +#else + cpus_refresh(NULL); +#endif +#ifdef THREADED_MEM + sem_post(&Semaphore_memory_beg); +#else + memory_refresh(NULL); +#endif + } + + // whoa either first time or thread/task mode change, (re)prime the pump... + if (Pseudo_row == PROC_XTRA) { + usleep(LIB_USLEEP); +#ifdef THREADED_TSK + sem_wait(&Semaphore_tasks_end); + sem_post(&Semaphore_tasks_beg); +#else + tasks_refresh(NULL); +#endif + putp(Cap_clr_scr); + } else + putp(Batch ? "\n\n" : Cap_home); + + Tree_idx = Pseudo_row = Msg_row = scrlins = 0; + summary_show(); + Max_lines = (SCREEN_ROWS - Msg_row) - 1; + + // we're now on Msg_row so clear out any residual messages ... + putp(Cap_clr_eol); + + if (!Rc.mode_altscr) { + // only 1 window to show so, piece o' cake + w->winlines = w->rc.maxtasks ? w->rc.maxtasks : Max_lines; + scrlins = window_show(w, Max_lines); + } else { + // maybe NO window is visible but assume, pieces o' cakes + for (i = 0 ; i < GROUPSMAX; i++) { + if (CHKw(&Winstk[i], Show_TASKON)) { + frame_hlp(i, Max_lines - scrlins); + scrlins += window_show(&Winstk[i], Max_lines - scrlins); + } + if (Max_lines <= scrlins) break; + } + } + + /* clear to end-of-screen - critical if last window is 'idleps off' + (main loop must iterate such that we're always called before sleep) */ + if (!Batch && scrlins < Max_lines) { + if (!BOT_PRESENT) + putp(Cap_nl_clreos); + else { + for (i = scrlins + Msg_row + 1; i < SCREEN_ROWS; i++) { + putp(tg2(0, i)); + putp(Cap_clr_eol); + } + } + PSU_CLREOS(Pseudo_row); + } + + if (CHKw(w, View_SCROLL) && VIZISw(Curwin)) show_scroll(); + if (Bot_show_func) Bot_show_func(); + fflush(stdout); + + /* we'll deem any terminal not supporting tgoto as dumb and disable + the normal non-interactive output optimization... */ + if (!Cap_can_goto) PSU_CLREOS(0); +} // end: frame_make + + + /* + * duh... */ +int main (int argc, char *argv[]) { + before(*argv); + // +-------------+ + wins_stage_1(); // top (sic) slice + configs_reads(); // > spread etc, < + parse_args(argc, argv); // > onions etc, < + signals_set(); // > lean stuff, < + whack_terminal(); // > more stuff. < + wins_stage_2(); // as bottom slice + // +-------------+ + + for (;;) { + struct timespec ts; + + frame_make(); + + if (0 < Loops) --Loops; + if (!Loops) bye_bye(NULL); + + ts.tv_sec = Rc.delay_time; + ts.tv_nsec = (Rc.delay_time - (int)Rc.delay_time) * 1000000000; + + if (Batch) + pselect(0, NULL, NULL, NULL, &ts, NULL); + else { + if (ioa(&ts)) + do_key(iokey(IOKEY_ONCE)); + } + /* note: that above ioa routine exists to consolidate all logic + which is susceptible to signal interrupt and must then + produce a screen refresh. in this main loop frame_make + assumes responsibility for such refreshes. other logic + in contact with users must deal more obliquely with an + interrupt/refresh (hint: Frames_signal + return code)! + + (everything is perfectly justified plus right margins) + (are completely filled, but of course it must be luck) + */ + } + return 0; +} // end: main |