diff options
Diffstat (limited to 'src/top')
-rw-r--r-- | src/top/top.c | 7404 | ||||
-rw-r--r-- | src/top/top.h | 792 | ||||
-rw-r--r-- | src/top/top_nls.c | 867 | ||||
-rw-r--r-- | src/top/top_nls.h | 108 |
4 files changed, 9171 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 diff --git a/src/top/top.h b/src/top/top.h new file mode 100644 index 0000000..5ba79b3 --- /dev/null +++ b/src/top/top.h @@ -0,0 +1,792 @@ +/* top.h - Header 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> + */ +#ifndef _Itop +#define _Itop + + /* Defines represented in configure.ac ----------------------------- */ +//#define BOOST_MEMORY /* enable extra precision in memory fields */ +//#define BOOST_PERCNT /* enable extra precision for two % fields */ +//#define ORIG_TOPDEFS /* with no rcfile retain original defaults */ +//#define SIGNALS_LESS /* favor reduced signal load over response */ + + /* Development/Debugging defines ----------------------------------- */ +//#define ATEOJ_RPTSTD /* report on some miscellany at end-of-job */ +//#define BOT_DEAD_ZAP /* zap Ctrl bottom window when target dies */ +//#define BOT_STRV_OFF /* don't emphasize strv w/ focus if spaces */ +//#define CASEUP_HEXES /* show all those hex values in upper case */ +//#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */ +//#define CORE_TYPE_LO /* show the type of cpu core in lower case */ +//#define CORE_TYPE_NO /* don't distinguish the types of cpu core */ +//#define EQUCOLHDRYES /* yes, equalize the column header lengths */ +//#define FOCUS_HARD_Y /* 'F' will avoid topmost task distortions */ +//#define FOCUS_TREE_X /* 'F' resets forest view indentation to 0 */ +//#define FOCUS_VIZOFF /* 'F' doesn't provide the visual clue '|' */ +//#define GETOPTFIX_NO /* do not address getopt_long deficiencies */ +//#define INSP_JUSTNOT /* do not smooth unprintable right margins */ +//#define INSP_OFFDEMO /* disable demo screens, issue msg instead */ +//#define INSP_SAVEBUF /* preserve 'Insp_buf' contents via a file */ +//#define INSP_SLIDE_1 /* when scrolling left/right, don't move 8 */ +//#define MEMGRAPH_OLD /* don't use 'available' when graphing Mem */ +//#define NLS_INCLUDED /* provides for excluding from translation */ +//#define NLS_VALIDATE /* ensure the integrity of four nls tables */ +//#define OFF_SCROLLBK /* disable tty emulators scrollback buffer */ +//#define OFF_STDERROR /* disable our stderr buffering (redirect) */ +//#define OFF_STDIOLBF /* disable our own stdout 'IOFBF' override */ +//#define OFF_XTRAWIDE /* disable our extra wide multi-byte logic */ +//#define OVERTYPE_SEE /* display a visual hint for overtype mode */ +//#define PRETEND0NUMA /* pretend that there ain't any numa nodes */ +//#define PRETEND48CPU /* pretend we're smp with 48 ticsers (sic) */ +//#define PRETENDECORE /* pretend we've got some e-core type cpus */ +//#define PRETENDNOCAP /* pretend terminal missing essential caps */ +//#define RCFILE_NOERR /* rcfile errs silently default, vs. fatal */ +//#define RECALL_FIXED /* don't reorder saved strings if recalled */ +//#define RMAN_IGNORED /* don't consider auto right margin glitch */ +//#define SCALE_FORMER /* scale_tics() guy shouldn't mimic uptime */ +//#define SCALE_POSTFX /* scale_tics() try without a 'h,d' suffix */ +//#define SCROLLVAR_NO /* disable intra-column horizontal scrolls */ +//#define SCROLLV_BY_1 /* when scrolling left/right do not move 8 */ +//#define STRINGCASENO /* case insensitive compare/locate version */ +//#define TERMIOS_ONLY /* use native input only (just limp along) */ +//#define THREADED_CPU /* separate background thread for cpu updt */ +//#define THREADED_MEM /* separate background thread for mem updt */ +//#define THREADED_TSK /* separate background thread for tsk updt */ +//#define TOG4_MEM_1UP /* don't show two abreast memory statistic */ +//#define TOG4_MEM_FIX /* no variable mem graphs, thus misaligned */ +//#define TOG4_SEP_OFF /* don't show two abreast visual separator */ +//#define TOG4_SEP_STD /* normal mem sep if 2 abreast & no graphs */ +//#define TREE_NORESET /* sort keys should not force 'V' view off */ +//#define TREE_SCANALL /* rescan array w/ forest view, avoid sort */ +//#define TREE_VALTMRK /* use an indented '+' with collapsed pids */ +//#define TREE_VCPUOFF /* a collapsed parent excludes child's cpu */ +//#define TREE_VPROMPT /* pid collapse/expand prompt, vs. top row */ +//#define TREE_VWINALL /* pid collapse/expand impacts all windows */ +//#define USE_X_COLHDR /* emphasize header vs. whole col, for 'x' */ +//#define WIDEN_COLUMN /* base column widths on translated header */ + + +/*###### Notes, etc. ###################################################*/ + + /* For introducing inaugural cgroup support, thanks to: + Jan Gorig <jgorig@redhat.com> - April, 2011 */ + + /* For the motivation and path to nls support, thanks to: + Sami Kerola, <kerolasa@iki.fi> - December, 2011 */ + + /* There are still some short strings that may yet be candidates + for nls support inclusion. They're identified with: + // nls_maybe */ + + /* For the impetus and NUMA/Node prototype design, thanks to: + Lance Shelton <LShelton@fusionio.com> - April, 2013 */ + + /* For prompting & helping with top's utf-8 support, thanks to: + Göran Uddeborg <goeran@uddeborg.se> - September, 2017 */ + + // pretend as if #define _GNU_SOURCE +char *strcasestr(const char *haystack, const char *needle); + +#ifdef STRINGCASENO +#define STRSTR strcasestr +#define STRCMP strcasecmp +#else +#define STRSTR strstr +#define STRCMP strcmp +#endif + + +/*###### Some Miscellaneous constants ##################################*/ + + /* The default delay twix updates */ +#ifdef ORIG_TOPDEFS +#define DEF_DELAY 3.0 +#else +#define DEF_DELAY 1.5 +#endif + + /* Length of time a message is displayed and the duration + of a 'priming' wait during library startup (in microseconds) */ +#define MSG_USLEEP 1250000 +#define LIB_USLEEP 100000 + + /* Specific process id monitoring support (command line only) */ +#define MONPIDMAX 20 + + /* Output override minimums (the -w switch and/or env vars) */ +#define W_MIN_COL 3 +#define W_MIN_ROW 3 + + /* Miscellaneous buffers with liberal values and some other defines + -- mostly just to pinpoint source code usage/dependencies */ +#define SCREENMAX 512 + /* the above might seem pretty stingy, until you consider that with every + field displayed the column header would be approximately 250 bytes + -- so SCREENMAX provides for all fields plus a 250+ byte command line */ +#define TNYBUFSIZ 16 +#define CAPBUFSIZ 32 +#define CLRBUFSIZ 64 +#define PFLAGSSIZ 128 +#define SMLBUFSIZ 128 +#define MEDBUFSIZ 256 +#define LRGBUFSIZ 512 +#define OURPATHSZ 1024 +#define BIGBUFSIZ 2048 +#define BOTBUFSIZ 16384 + // next is same as library's max buffer size +#define MAXBUFSIZ (1024*64*2) + /* in addition to the actual display data, our row might have to accommodate + many termcap/color transitions - these definitions ensure we have room */ +#define ROWMINSIZ ( SCREENMAX + 8 * (CAPBUFSIZ + CLRBUFSIZ) ) +#define ROWMAXSIZ ( SCREENMAX + 16 * (CAPBUFSIZ + CLRBUFSIZ) ) + // minimum size guarantee for dynamically acquired 'readfile' buffer +#define READMINSZ 2048 + // size of preallocated search string buffers, same as ioline() +#define FNDBUFSIZ MEDBUFSIZ + + + // space between task fields/columns +#define COLPADSTR " " +#define COLPADSIZ ( sizeof(COLPADSTR) - 1 ) + // continuation ch when field/column truncated +#define COLPLUSCH '+' + + // support for keyboard stuff (cursor motion keystrokes, mostly) +#define kbd_ESC '\033' +#define kbd_SPACE ' ' +#define kbd_ENTER '\n' +#define kbd_UP 129 +#define kbd_DOWN 130 +#define kbd_LEFT 131 +#define kbd_RIGHT 132 +#define kbd_PGUP 133 +#define kbd_PGDN 134 +#define kbd_HOME 135 +#define kbd_END 136 +#define kbd_BKSP 137 +#define kbd_INS 138 +#define kbd_DEL 139 +#define kbd_BTAB 140 +#define kbd_CtrlE '\005' +#define kbd_CtrlG '\007' +#define kbd_CtrlI '\011' +#define kbd_CtrlK '\013' +#define kbd_CtrlL '\014' +#define kbd_CtrlN '\016' +#define kbd_CtrlO '\017' +#define kbd_CtrlP '\020' +#define kbd_CtrlR '\022' +#define kbd_CtrlU '\025' + + /* Special value in Pseudo_row to force an additional procs refresh + -- used at startup and for task/thread mode transitions */ +#define PROC_XTRA -1 + + +/* ##### Enum's and Typedef's ############################################ */ + + /* Flags for each possible field (and then some) -- + these MUST be kept in sync with the Fieldstab[] array !! */ +enum pflag { + EU_PID = 0, EU_PPD, + EU_UED, EU_UEN, EU_URD, EU_URN, EU_USD, EU_USN, + EU_GID, EU_GRP, EU_PGD, EU_TTY, EU_TPG, EU_SID, + EU_PRI, EU_NCE, EU_THD, + EU_CPN, EU_CPU, EU_TME, EU_TM2, + EU_MEM, EU_VRT, EU_SWP, EU_RES, EU_COD, EU_DAT, EU_SHR, + EU_FL1, EU_FL2, EU_DRT, + EU_STA, EU_CMD, EU_WCH, EU_FLG, EU_CGR, + EU_SGD, EU_SGN, EU_TGD, + EU_OOA, EU_OOM, + EU_ENV, + EU_FV1, EU_FV2, + EU_USE, + EU_NS1, EU_NS2, EU_NS3, EU_NS4, EU_NS5, EU_NS6, + EU_LXC, + EU_RZA, EU_RZF, EU_RZL, EU_RZS, + EU_CGN, + EU_NMA, + EU_LID, + EU_EXE, + EU_RSS, EU_PSS, EU_PZA, EU_PZF, EU_PZS, EU_USS, + EU_IRB, EU_IRO, EU_IWB, EU_IWO, + EU_AGI, EU_AGN, + EU_TM3, EU_TM4, EU_CUU, EU_CUC, + EU_NS7, EU_NS8, +#ifdef USE_X_COLHDR + // not really pflags, used with tbl indexing + EU_MAXPFLGS +#else + // not really pflags, used with tbl indexing & col highlighting + EU_MAXPFLGS, EU_XON, EU_XOF +#endif +}; + + /* The scaling 'target' used with memory fields */ +enum scale_enum { + SK_Kb, SK_Mb, SK_Gb, SK_Tb, SK_Pb, SK_Eb +}; + + /* Used to manipulate (and document) the Frames_signal states */ +enum resize_states { + BREAK_off = 0, BREAK_kbd, BREAK_sig, BREAK_autox, BREAK_screen +}; + + /* This typedef just ensures consistent 'process flags' handling */ +typedef int FLG_t; + + /* These typedefs attempt to ensure consistent 'ticks' handling */ +typedef unsigned long long TIC_t; +typedef long long SIC_t; + + + /* /////////////////////////////////////////////////////////////// */ + /* Special Section: multiple windows/field groups --------------- */ + /* ( kind of a header within a header: constants, types & macros ) */ + +#define CAPTABMAX 9 /* max entries in each win's caps table */ +#define GROUPSMAX 4 /* the max number of simultaneous windows */ +#define WINNAMSIZ 4 /* size of RCW_t winname buf (incl '\0') */ +#define GRPNAMSIZ WINNAMSIZ+2 /* window's name + number as in: '#:...' */ + + /* The Persistent 'Mode' flags! + These are preserved in the rc file, as a single integer and the + letter shown is the corresponding 'command' toggle */ + // 'View_' flags affect the summary (minimum), taken from 'Curwin' +#define View_CPUSUM 0x008000 // '1' - show combined cpu stats (vs. each) +#define View_CPUNOD 0x400000 // '2' - show numa node cpu stats ('3' also) +#define View_LOADAV 0x004000 // 'l' - display load avg and uptime summary +#define View_STATES 0x002000 // 't' - display task/cpu(s) states summary +#define View_MEMORY 0x001000 // 'm' - display memory summary +#define View_NOBOLD 0x000008 // 'B' - disable 'bold' attribute globally +#define View_SCROLL 0x080000 // 'C' - enable coordinates msg w/ scrolling + // 'Show_' & 'Qsrt_' flags are for task display in a visible window +#define Show_COLORS 0x000800 // 'z' - show in color (vs. mono) +#define Show_HIBOLD 0x000400 // 'b' - rows and/or cols bold (vs. reverse) +#define Show_HICOLS 0x000200 // 'x' - show sort column emphasized +#define Show_HIROWS 0x000100 // 'y' - show running tasks highlighted +#define Show_CMDLIN 0x000080 // 'c' - show cmdline vs. name +#define Show_CTIMES 0x000040 // 'S' - show times as cumulative +#define Show_IDLEPS 0x000020 // 'i' - show idle processes (all tasks) +#define Show_TASKON 0x000010 // '-' - tasks showable when Mode_altscr +#define Show_FOREST 0x000002 // 'V' - show cmd/cmdlines with ascii art +#define Qsrt_NORMAL 0x000004 // 'R' - reversed column sort (high to low) +#define Show_JRSTRS 0x040000 // 'j' - right justify "string" data cols +#define Show_JRNUMS 0x020000 // 'J' - right justify "numeric" data cols + // these flag(s) have no command as such - they're for internal use +#define NOPRINT_xxx 0x010000 // build task rows only (not for display) +#define EQUWINS_xxx 0x000001 // rebalance all wins & tasks (off i,n,u/U) + + // Default flags if there's no rcfile to provide user customizations +#ifdef ORIG_TOPDEFS +#define DEF_WINFLGS ( View_LOADAV | View_STATES | View_CPUSUM | View_MEMORY \ + | Show_HIBOLD | Show_HIROWS | Show_IDLEPS | Show_TASKON | Show_JRNUMS \ + | Qsrt_NORMAL ) +#define DEF_GRAPHS2 0, 0 +#define DEF_SCALES2 SK_Mb, SK_Kb +#define ALT_WINFLGS DEF_WINFLGS +#define ALT_GRAPHS2 0, 0 +#else +#define DEF_WINFLGS ( View_LOADAV | View_STATES | View_MEMORY | Show_CMDLIN \ + | Show_COLORS | Show_FOREST | Show_HIROWS | Show_IDLEPS | Show_JRNUMS | Show_TASKON \ + | Qsrt_NORMAL ) +#define DEF_GRAPHS2 1, 2 +#define DEF_SCALES2 SK_Gb, SK_Mb +#define ALT_WINFLGS (DEF_WINFLGS | Show_HIBOLD) & ~Show_FOREST +#define ALT_GRAPHS2 2, 0 +#endif + + /* These are used to direct wins_reflag */ +enum reflag_enum { + Flags_TOG, Flags_SET, Flags_OFF +}; + + /* These are used to direct win_warn */ +enum warn_enum { + Warn_ALT, Warn_VIZ +}; + + /* This type helps support both a window AND the rcfile */ +typedef struct RCW_t { // the 'window' portion of an rcfile + int sortindx, // sort field (represented as procflag) + winflags, // 'view', 'show' and 'sort' mode flags + maxtasks, // user requested maximum, 0 equals all + graph_cpus, // 't' - View_STATES supplementary vals + graph_mems, // 'm' - View_MEMORY supplememtary vals + double_up, // '4' - show multiple cpus on one line + combine_cpus, // '!' - keep combining additional cpus + core_types, // '5' - show/filter P-core/E-core cpus + summclr, // a colors 'number' used for summ info + msgsclr, // " in msgs/pmts + headclr, // " in cols head + taskclr; // " in task rows + char winname [WINNAMSIZ]; // name for the window, user changeable + FLG_t fieldscur [PFLAGSSIZ]; // the fields for display & their order +} RCW_t; + + /* This represents the complete rcfile */ +typedef struct RCF_t { + char id; // rcfile version id + int mode_altscr; // 'A' - Alt display mode (multi task windows) + int mode_irixps; // 'I' - Irix vs. Solaris mode (SMP-only) + float delay_time; // 'd'/'s' - How long to sleep twixt updates + int win_index; // Curwin, as index + RCW_t win [GROUPSMAX]; // a 'WIN_t.rc' for each window + int fixed_widest; // 'X' - wider non-scalable col addition + int summ_mscale; // 'E' - scaling of summary memory values + int task_mscale; // 'e' - scaling of process memory values + int zero_suppress; // '0' - suppress scaled zeros toggle + int tics_scaled; // ^E - scale TIME and/or TIME+ columns +} RCF_t; + + /* This structure stores configurable information for each window. + By expending a little effort in its creation and user requested + maintenance, the only real additional per frame cost of having + windows is an extra sort -- but that's just on pointers! */ +typedef struct WIN_t { + FLG_t pflgsall [PFLAGSSIZ], // all 'active/on' fieldscur, as enum + procflgs [PFLAGSSIZ]; // fieldscur subset, as enum + RCW_t rc; // stuff that gets saved in the rcfile + int winnum, // a window's number (array pos + 1) + winlines, // current task window's rows (volatile) + maxpflgs, // number of displayed procflgs ("on" in fieldscur) + totpflgs, // total of displayable procflgs in pflgsall array + begpflg, // scrolled beginning pos into pflgsall array + endpflg, // scrolled ending pos into pflgsall array + begtask, // scrolled beginning pos into total tasks + begnext, // new scrolled delta for next frame's begtask +#ifndef SCROLLVAR_NO + varcolbeg, // scrolled position within variable width col +#endif + varcolsz, // max length of variable width column(s) + usrseluid, // validated uid for 'u/U' user selection + usrseltyp, // the basis for matching above uid + usrselflg, // flag denoting include/exclude matches + hdrcaplen; // column header xtra caps len, if any + char capclr_sum [CLRBUFSIZ], // terminfo strings built from + capclr_msg [CLRBUFSIZ], // RCW_t colors (& rebuilt too), + capclr_pmt [CLRBUFSIZ], // but NO recurring costs ! + capclr_hdr [CLRBUFSIZ], // note: sum, msg and pmt strs + capclr_rowhigh [SMLBUFSIZ], // are only used when this + capclr_rownorm [CLRBUFSIZ], // window is the 'Curwin'! + cap_bold [CAPBUFSIZ], // support for View_NOBOLD toggle + grpname [GRPNAMSIZ], // window number:name, printable +#ifdef USE_X_COLHDR + columnhdr [ROWMINSIZ], // column headings for procflgs +#else + columnhdr [SCREENMAX], // column headings for procflgs +#endif + *captab [CAPTABMAX]; // captab needed by show_special() + struct osel_s *osel_1st; // other selection criteria anchor + int osel_tot; // total of other selection criteria + char *findstr; // window's current/active search string + int findlen; // above's strlen, without call overhead + int focus_pid; // target pid when 'F' toggle is active + int focus_beg; // ppt index where 'F' toggle has begun + int focus_end; // ppt index where 'F' toggle has ended +#ifdef FOCUS_TREE_X + int focus_lvl; // the indentation level of parent task +#endif + struct pids_stack **ppt; // this window's stacks ptr array + struct WIN_t *next, // next window in window stack + *prev; // prior window in window stack +} WIN_t; + + // Used to test/manipulate the window flags +#define CHKw(q,f) (int)((q)->rc.winflags & (f)) +#define TOGw(q,f) (q)->rc.winflags ^= (f) +#define SETw(q,f) (q)->rc.winflags |= (f) +#define OFFw(q,f) (q)->rc.winflags &= ~(f) +#define ALTCHKw (Rc.mode_altscr ? 1 : win_warn(Warn_ALT)) +#define VIZISw(q) (!Rc.mode_altscr || CHKw(q,Show_TASKON)) +#define VIZCHKw(q) (VIZISw(q)) ? 1 : win_warn(Warn_VIZ) +#define VIZTOGw(q,f) (VIZISw(q)) ? TOGw(q,(f)) : win_warn(Warn_VIZ) + + // Used to test/manipulte fieldscur values +#define FLDon 0x01 +#define FLDoff 0x00 +#define FLDget(q,i) ( (((q)->rc.fieldscur[i]) >> 1) - FLD_OFFSET ) +#define FLDtog(q,i) ( (q)->rc.fieldscur[i] ^= FLDon ) +#define FLDviz(q,i) ( (q)->rc.fieldscur[i] & FLDon ) +#define ENUviz(w,E) ( NULL != msch((w)->procflgs, E, w->maxpflgs) ) +#define ENUpos(w,E) ( (int)(msch((w)->pflgsall, E, (w)->totpflgs) - (w)->pflgsall) ) +#define ENUcvt(E,x) ( (int)((E + FLD_OFFSET) << 1) | x ) + + // Support for variable width columns (and potentially scrolling too) +#define VARcol(E) (-1 == Fieldstab[E].width) +#ifndef SCROLLVAR_NO +#ifdef USE_X_COLHDR +#define VARright(w) (1 == w->maxpflgs && VARcol(w->procflgs[0])) +#else +#define VARright(w) ((1 == w->maxpflgs && VARcol(w->procflgs[0])) || \ + (3 == w->maxpflgs && EU_XON == w->procflgs[0] && VARcol(w->procflgs[1]))) +#endif +#define VARleft(w) (w->varcolbeg && VARright(w)) +#ifdef SCROLLV_BY_1 +#define SCROLLAMT 1 +#else +#define SCROLLAMT 8 +#endif +#endif + + // Support for a proper (visible) row #1 whenever Curwin changes + // ( or a key which might affect vertical scrolling was struck ) +#define mkVIZyes ( Curwin->begnext != 0 ) +#define mkVIZrow1 { Curwin->begnext = +1; Curwin->begtask -= 1; } +#define mkVIZrowX(n) { Curwin->begnext = (n); } +#define mkVIZoff(w) { w->begnext = 0; } + + /* Special Section: end ------------------------------------------ */ + /* /////////////////////////////////////////////////////////////// */ + + +/*###### Some Miscellaneous Macro definitions ##########################*/ + + /* Yield table size as 'int' */ +#define MAXTBL(t) (int)(sizeof(t) / sizeof(t[0])) + + /* A null-terminating strncpy, assuming strlcpy is not available. + ( and assuming callers don't need the string length returned ) */ +#define STRLCPY(dst,src) { memccpy(dst, src, '\0', sizeof(dst)); dst[sizeof(dst) - 1] = '\0'; } + + /* Used to clear all or part of our Pseudo_screen */ +#define PSU_CLREOS(y) memset(&Pseudo_screen[ROWMAXSIZ*y], '\0', Pseudo_size-(ROWMAXSIZ*y)) + +/* + * The following three macros are used to 'inline' those portions of the + * display process involved in formatting, while protecting against any + * potential embedded 'millesecond delay' escape sequences. + */ + /** PUTT - Put to Tty (used in many places) + . for temporary, possibly interactive, 'replacement' output + . may contain ANY valid terminfo escape sequences + . need NOT represent an entire screen row */ +#define PUTT(fmt,arg...) do { \ + char _str[ROWMAXSIZ]; \ + snprintf(_str, sizeof(_str), fmt, ## arg); \ + putp(_str); \ + } while (0) + + /** PUFF - Put for Frame (used in only 3 places) + . for more permanent frame-oriented 'update' output + . may NOT contain cursor motion terminfo escapes + . assumed to represent a complete screen ROW + . subject to optimization, thus MAY be discarded */ +#define PUFF(fmt,arg...) do { \ + char _str[ROWMAXSIZ]; \ + const int _len = snprintf(_str, sizeof(_str), fmt, ## arg); \ + if (Batch) { \ + char *_eol = _str + (_len < 0 ? 0 : (size_t)_len >= sizeof(_str) ? sizeof(_str)-1 : (size_t)_len); \ + while (_eol > _str && _eol[-1] == ' ') _eol--; \ + *_eol = '\0'; putp(_str); } \ + else if (Pseudo_row >= 0 && Pseudo_row < Screen_rows) { \ + char *_ptr = &Pseudo_screen[Pseudo_row++ * ROWMAXSIZ]; \ + if (!strcmp(_ptr, _str)) putp("\n"); \ + else { \ + strcpy(_ptr, _str); \ + putp(_ptr); } } \ + } while (0) + + /** POOF - Pulled Out of Frame (used in only 1 place) + . for output that is/was sent directly to the terminal + but would otherwise have been counted as a Pseudo_row */ +#define POOF(str,cap) do { \ + putp(str); putp(cap); \ + Pseudo_screen[Pseudo_row * ROWMAXSIZ] = '\0'; \ + if (Pseudo_row + 1 < Screen_rows) ++Pseudo_row; \ + } while (0) + + /* Orderly end, with any sort of message - see fmtmk */ +#define debug_END(s) { \ + void error_exit (const char *); \ + fputs(Cap_clr_scr, stdout); \ + error_exit(s); \ + } + + /* A poor man's breakpoint, if he's too lazy to learn gdb */ +#define its_YOUR_fault { *((char *)0) = '!'; } + + +/*###### Some Display Support *Data* ###################################*/ +/* ( see module top_nls.c for the nls translatable data ) */ + + /* Configuration files support */ +#define SYS_RCRESTRICT "/etc/toprc" +#define SYS_RCDEFAULTS "/etc/topdefaultrc" +#define RCF_EYECATCHER "Config File (Linux processes with windows)\n" +#define RCF_PLUS_H "\\]^_`abcdefghij" +#define RCF_PLUS_J "klmnopqrstuvwxyz" + // this next guy must never, ever change + // ( transitioned from 'char' to 'int' ) +#define RCF_XFORMED_ID 'k' + // this next guy is incremented when columns change + // ( to prevent older top versions from accessing ) +#define RCF_VERSION_ID 'k' + +#define FLD_OFFSET ( (int)'%' ) +#define FLD_ROWMAX 20 + + /* The default fields displayed and their order, + if nothing is specified by the loser, oops user. */ +#ifdef ORIG_TOPDEFS +#define DEF_FORMER "¥¨³´»½ÀÄ·º¹Å&')*+,-./012568<>?ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#else +#define DEF_FORMER "¥&K¨³´»½@·º¹56ÄFÅ')*+,-./0128<>?ABCGHIJLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#endif + /* Pre-configured windows/field groups */ +#define JOB_FORMER "¥¦¹·º(³´Ä»½@<§Å)*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define MEM_FORMER "¥º»<½¾¿ÀÁMBNÃD34·Å&'()*+,-./0125689FGHIJKLOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define USR_FORMER "¥¦§¨ª°¹·ºÄÅ)+,-./1234568;<=>?@ABCFGHIJKLMNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J + // old top fields ( 'a'-'z' ) in positions 0-25 + // other suse old top fields ( '{|' ) in positions 26-27 +#define CVT_FORMER "%&*'(-0346789:;<=>?@ACDEFGML)+,./125BHIJKNOPQRSTUVWXYZ[" RCF_PLUS_H RCF_PLUS_J +#define CVT_FLDMAX 28 + +#ifdef ORIG_TOPDEFS +#define DEF_FIELDS { \ + 75, 81, 103, 105, 119, 123, 129, 137, 111, 117, 115, 139, 76, 78, 82, 84, 86, 88, 90, 92, \ + 94, 96, 98, 100, 106, 108, 112, 120, 124, 126, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#else +#define DEF_FIELDS { \ + 75, 76, 150, 81, 103, 105, 119, 123, 128, 111, 117, 115, 106, 108, 137, 140, 139, 78, 82, 84, \ + 86, 88, 90, 92, 94, 96, 98, 100, 112, 120, 124, 126, 130, 132, 134, 142, 144, 146, 148, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#endif +#define JOB_FIELDS { \ + 75, 77, 115, 111, 117, 80, 103, 105, 137, 119, 123, 128, 120, 79, 139, 82, 84, 86, 88, 90, \ + 92, 94, 96, 98, 100, 106, 108, 112, 124, 126, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#define MEM_FIELDS { \ + 75, 117, 119, 120, 123, 125, 127, 129, 131, 154, 132, 156, 135, 136, 102, 104, 111, 139, 76, 78, \ + 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 106, 108, 112, 114, 140, 142, 144, 146, 148, \ + 150, 152, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } +#define USR_FIELDS { \ + 75, 77, 79, 81, 85, 97, 115, 111, 117, 137, 139, 82, 86, 88, 90, 92, 94, 98, 100, 102, \ + 104, 106, 108, 112, 118, 120, 122, 124, 126, 128, 130, 132, 134, 140, 142, 144, 146, 148, 150, 152, \ + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190, 192, \ + 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 230, 232, \ + 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272 } + + /* The default values for the local config file */ +#define DEF_RCFILE { \ + RCF_VERSION_ID, 0, 1, DEF_DELAY, 0, { \ + { EU_CPU, DEF_WINFLGS, 0, DEF_GRAPHS2, 1, 0, 0, \ + COLOR_RED, COLOR_RED, COLOR_YELLOW, COLOR_RED, \ + "Def", DEF_FIELDS }, \ + { EU_PID, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_CYAN, COLOR_CYAN, COLOR_WHITE, COLOR_CYAN, \ + "Job", JOB_FIELDS }, \ + { EU_MEM, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLUE, COLOR_MAGENTA, \ + "Mem", MEM_FIELDS }, \ + { EU_UEN, ALT_WINFLGS, 0, ALT_GRAPHS2, 0, 0, 0, \ + COLOR_YELLOW, COLOR_YELLOW, COLOR_GREEN, COLOR_YELLOW, \ + "Usr", USR_FIELDS } \ + }, 0, DEF_SCALES2, 0, 0 } + + /* Summary Lines specially formatted string(s) -- + see 'show_special' for syntax details + other cautions. */ +#define LOADAV_line "%s -%s\n" +#define LOADAV_line_alt "%s~6 -%s\n" + +/*###### For Piece of mind #############################################*/ + + /* just sanity check(s)... */ +#if defined(RECALL_FIXED) && defined(TERMIOS_ONLY) +# error 'RECALL_FIXED' conflicts with 'TERMIOS_ONLY' +#endif +#if (LRGBUFSIZ < SCREENMAX) +# error 'LRGBUFSIZ' must NOT be less than 'SCREENMAX' +#endif +#if defined(TERMIOS_ONLY) +# warning 'TERMIOS_ONLY' disables input recall and makes man doc incorrect +#endif +#if defined(MEMGRAPH_OLD) +# warning 'MEMGRAPH_OLD' will make the man document Section 2c. misleading +#endif +#if defined(SCALE_FORMER) && defined(SCALE_POSTFX) +# warning 'SCALE_POSTFX' is ignored when 'SCALE_FORMER' is active +#endif +#if defined(USE_X_COLHDR) +# warning 'USE_X_COLHDR' makes parts of man page misleading (4e, 5d & 5e) +#endif +#if defined(TOG4_SEP_STD) && defined(TOG4_SEP_OFF) +# warning 'TOG4_SEP_STD' has no effect when 'TOG4_SEP_OFF' is active +#endif + +/*###### Some Prototypes (ha!) #########################################*/ + + /* These 'prototypes' are here exclusively for documentation purposes. */ + /* ( see the find_string routine for the one true required prototype ) */ +/*------ Tiny useful routine(s) ----------------------------------------*/ +//atic const char *fmtmk (const char *fmts, ...); +//atic inline int mlen (const int *mem); +//atic inline int *msch (const int *mem, int obj, int max); +//atic inline char *scat (char *dst, const char *src); +//atic const char *tg2 (int x, int y); +/*------ Exit/Interrupt routines ---------------------------------------*/ +//atic void at_eoj (void); +//atic void bye_bye (const char *str); +//atic void error_exit (const char *str); +//atic void sig_abexit (int sig); +//atic void sig_endpgm (int dont_care_sig); +//atic void sig_paused (int dont_care_sig); +//atic void sig_resize (int dont_care_sig); +/*------ Special UTF-8 Multi-Byte support ------------------------------*/ +/*atic char UTF8_tab[] = { ... } */ +//atic inline int utf8_cols (const unsigned char *p, int n); +//atic int utf8_delta (const char *str); +//atic int utf8_embody (const char *str, int width); +//atic const char *utf8_justify (const char *str, int width, int justr); +//atic int utf8_proper_col (const char *str, int col, int tophysical); +/*------ Misc Color/Display support ------------------------------------*/ +//atic void capsmk (WIN_t *q); +//atic void show_msg (const char *str); +//atic int show_pmt (const char *str); +//atic void show_scroll (void); +//atic void show_special (int interact, const char *glob); +/*------ Low Level Memory/Keyboard/File I/O support --------------------*/ +//atic void *alloc_c (size_t num); +//atic void *alloc_r (void *ptr, size_t num); +//atic char *alloc_s (const char *str); +//atic inline int ioa (struct timespec *ts); +//atic int ioch (int ech, char *buf, unsigned cnt); +//atic int iokey (int action); +//atic char *ioline (const char *prompt); +//atic int mkfloat (const char *str, float *num, int whole); +//atic int readfile (FILE *fp, char **baddr, size_t *bsize, size_t *bread); +/*------ Small Utility routines ----------------------------------------*/ +//atic float get_float (const char *prompt); +//atic int get_int (const char *prompt); +//atic inline const char *hex_make (long num, int noz); +//atic const char *user_certify (WIN_t *q, const char *str, char typ); +/*------ Basic Formatting support --------------------------------------*/ +//atic inline const char *justify_pad (const char *str, int width, int justr); +//atic inline const char *make_chr (const char ch, int width, int justr); +//atic inline const char *make_num (long num, int width, int justr, int col, int noz); +//atic inline const char *make_str (const char *str, int width, int justr, int col); +//atic inline const char *make_str_utf8 (const char *str, int width, int justr, int col); +//atic const char *scale_mem (int target, float num, int width, int justr); +//atic const char *scale_num (float num, int width, int justr); +//atic const char *scale_pcnt (float num, int width, int justr, int xtra); +//atic const char *scale_tics (TIC_t tics, int width, int justr, int target); +/*------ Fields Management support -------------------------------------*/ +/*atic struct Fieldstab[] = { ... } */ +//atic void adj_geometry (void); +//atic void build_headers (void); +//atic void calibrate_fields (void); +//atic void display_fields (int focus, int extend); +//atic void fields_utility (void); +//atic inline void widths_resize (void); +//atic void zap_fieldstab (void); +/*------ Library Interface (as separate threads) -----------------------*/ +//atic void *cpus_refresh (void *unused); +//atic void *memory_refresh (void *unused); +//atic void *tasks_refresh (void *unused); +/*------ Inspect Other Output ------------------------------------------*/ +//atic void insp_cnt_nl (void); +#ifndef INSP_OFFDEMO +//atic void insp_do_demo (char *fmts, int pid); +#endif +//atic void insp_do_file (char *fmts, int pid); +//atic void insp_do_pipe (char *fmts, int pid); +//atic inline int insp_find_ofs (int col, int row); +//atic void insp_find_str (int ch, int *col, int *row); +//atic void insp_mkrow_raw (int col, int row); +//atic void insp_mkrow_utf8 (int col, int row); +//atic void insp_show_pgs (int col, int row, int max); +//atic int insp_view_choice (struct pids_stack *p); +//atic void inspection_utility (int pid); +/*------ Other Filtering ------------------------------------------------*/ +//atic const char *osel_add (WIN_t *q, int ch, char *glob, int push); +//atic void osel_clear (WIN_t *q); +//atic inline int osel_matched (const WIN_t *q, FLG_t enu, const char *str); +/*------ Startup routines ----------------------------------------------*/ +//atic void before (char *me); +//atic int cfg_xform (WIN_t *q, char *flds, const char *defs); +//atic int config_insp (FILE *fp, char *buf, size_t size); +//atic int config_osel (FILE *fp, char *buf, size_t size); +//atic const char *configs_file (FILE *fp, const char *name, float *delay); +//atic int configs_path (const char *const fmts, ...); +//atic void configs_reads (void); +//atic void parse_args (int argc, char **argv); +//atic void signals_set (void); +//atic void whack_terminal (void); +/*------ Windows/Field Groups support ----------------------------------*/ +//atic void win_names (WIN_t *q, const char *name); +//atic void win_reset (WIN_t *q); +//atic WIN_t *win_select (int ch); +//atic int win_warn (int what); +//atic void wins_clrhlp (WIN_t *q, int save); +//atic void wins_colors (void); +//atic void wins_reflag (int what, int flg); +//atic void wins_stage_1 (void); +//atic void wins_stage_2 (void); +//atic inline int wins_usrselect (const WIN_t *q, int idx); +/*------ Forest View support -------------------------------------------*/ +//atic void forest_adds (const int self, int level); +//atic void forest_begin (WIN_t *q); +//atic void forest_config (WIN_t *q); +//atic inline const char *forest_display (const WIN_t *q, int idx); +/*------ Special Separate Bottom Window support ------------------------*/ +//atic void bot_do (const char *str, int focus); +//atic int bot_focus_str (const char *hdr, const char *str); +//atic int bot_focus_strv (const char *hdr, const char **strv); +//atic void *bot_item_hlp (struct pids_stack *p); +//atic void bot_item_show (void); +//atic void bot_item_toggle (int what, const char *head, char sep); +/*------ Interactive Input Tertiary support ----------------------------*/ +//atic inline int find_ofs (const WIN_t *q, const char *buf); +//atic void find_string (int ch); +//atic void help_view (void); +//atic void other_filters (int ch); +//atic void write_rcfile (void); +/*------ Interactive Input Secondary support (do_key helpers) ----------*/ +//atic void keys_global (int ch); +//atic void keys_summary (int ch); +//atic void keys_task (int ch); +//atic void keys_window (int ch); +//atic void keys_xtra (int ch); +/*------ Tertiary summary display support (summary_show helpers) -------*/ +//atic struct rx_st *sum_rx (struct graph_parms *these); +//atic inline int sum_see (const char *str, int nobuf); +//atic int sum_tics (struct stat_stack *this, const char *pfx, int nobuf); +//atic int sum_unify (struct stat_stack *this, int nobuf); +/*------ Secondary summary display support (summary_show helpers) ------*/ +//atic void do_cpus (void); +//atic void do_memory (void); +/*------ Main Screen routines ------------------------------------------*/ +//atic void do_key (int ch); +//atic void summary_show (void); +//atic const char *task_show (const WIN_t *q, int idx); +//atic void window_hlp (void); +//atic int window_show (WIN_t *q, int wmax); +/*------ Entry point plus two ------------------------------------------*/ +//atic void frame_hlp (int wix, int max); +//atic void frame_make (void); +// int main (int argc, char *argv[]); + +#endif /* _Itop */ + diff --git a/src/top/top_nls.c b/src/top/top_nls.c new file mode 100644 index 0000000..d7ab8dc --- /dev/null +++ b/src/top/top_nls.c @@ -0,0 +1,867 @@ +/* top_nls.c - provide the basis for future nls translations */ +/* + * Copyright © 2011-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> + * Sami Kerola, <kerolasa@iki.fi> + */ + +#include <locale.h> +#include <stdio.h> +#include <string.h> + +#include "nls.h" + +#include "top.h" +#include "top_nls.h" + +#ifdef NLS_VALIDATE +#include <stdlib.h> +#endif + + + /* + * The provision excluding some strings is intended to be + * used very sparingly. It exists in case we collide with + * some translation project person in a position to delay + * a future release over his or her personal preferences. + * + * If it's ever enabled, it will produce a fatal compiler + * error as our only option since those gettext tools are + * far too primitive to be influenced with a conditional. + * They always ignore a '_X()' macro no matter its state. */ + +#ifndef NLS_INCLUDED +# define _X(str) (str) +#else +# define _X(str) +# error instead of this #define, restore the true gettext macro(s) +#endif + + // Programmer Note(s): + // Preparation --------------------------------------------- + // Unless you have *something* following the gettext macro, + // xgettext will refuse to see any TRANSLATORS comments. + // Thus empty strings have been added for potential future + // comment additions. + // + // Also, by omitting the argument for the --add-comments + // XGETTEXT_OPTION in po/Makevars, *any* preceding c style + // comment will be propagated to the .pot file, providing + // that the gettext macro isn't empty as discussed above. + // However, this is far too aggressive so we have chosen + // the word 'Translation' to denote xgettext comments. + // + // /* Need Not Say 'TRANSLATORS': ... + // snprintf(buf, sizeof(buf), "%s", _( // unseen comment + // + // /* Translation Hint: ... + // snprintf(buf, sizeof(buf), "%s", _("" // now it's seen! + // + // Translation, from po/ directory after make -------------- + // ( this is the procedure used before any translations were ) + // ( available in the po/ directory, which contained only the ) + // ( procps-ng.pot, this domain's template file. ) + // + // ( below: ll_CC = language/country as in 'zh_CN' or 'en_AU' ) + // + // msginit --locale=ll_CC --no-wrap + // . creates a ll_CC.po file from the template procps-ng.pot + // . may also duplicate msgid as msgstr if languages similar + // msgen --no-wrap ll_CC.po --output-file=ll_CC.po + // . duplicates every msgid literal as msgstr value + // . this is the file that's edited + // . replace "Content-Type: ... charset=ASCII\n" + // with "... charset=UTF-8\n" + // . translate msgstr values, leaving msgid unchanged + // msgfmt ll_CC.po --strict --output-file=procps-ng.mo + // . after which ensure, chmod 644 + // . then copy + // to /usr/share/locale-langpack/ll_CC/LC_MESSAGES/ + // or /usr/share/locale/ll_CC/LC_MESSAGES/ + // Testing ------------------------------------------------- + // export LC_ALL= && export LANGUAGE=ll_CC + // run some capable program like top + // + + /* + * These are our string tables with the following contents: + * Head : column headings with varying size limits + * Desc : fields descriptions not to exceed 20 screen positions + * Norm : regular text possibly also containing c-format specifiers + * Uniq : show_special specially formatted strings + * + * The latter table presents the greatest translation challenge ! + * + * We go to the trouble of creating the nls string tables to achieve + * these objectives: + * + the overhead of repeated runtime calls to gettext() + * will be avoided + * + the order of the strings in the template (.pot) file + * can be completely controlled + * + none of the important translator only comments will + * clutter and obscure the main program + */ +const char *Head_nlstab[EU_MAXPFLGS]; +const char *Desc_nlstab[EU_MAXPFLGS]; +const char *Norm_nlstab[norm_MAX]; +const char *Uniq_nlstab[uniq_MAX]; + + + /* + * This routine builds the nls table containing plain text only + * used as the field descriptions. Each translated line MUST be + * kept to a maximum of 20 characters or less! */ +static void build_two_nlstabs (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . The following line pairs contain only plain text and consist of: + . 1) a field name/column header - mostly upper case + . 2) the related description - both upper and lower case + . + . To avoid truncation on the main top display, each column header + . is noted with its maximum size, while a few are 'variable' width. + . Names for the latter should probably be 10 or fewer characters. + . + . Those fields shown with a '+' are also eligible for user resizing + . using the 'X' command. That means the default width might produce + . truncation but need not if widened (see the man page 'X' command). + . + . All headers are subject to a maximum of 8 on the Fields Management + . screen where truncation is entirely acceptable. + . + . The associated descriptions are always limited to 20 characters, + . and are used only on the Fields Management screen. + . + . In all cases, fewer characters are just fine. + . */ + +/* Translation Hint: maximum 'PID' = 5 */ + Head_nlstab[EU_PID] = _("PID"); + Desc_nlstab[EU_PID] = _("Process Id"); +/* Translation Hint: maximum 'PPID' = 5 */ + Head_nlstab[EU_PPD] = _("PPID"); + Desc_nlstab[EU_PPD] = _("Parent Process pid"); +/* Translation Hint: maximum 'UID' = 5 + */ + Head_nlstab[EU_UED] = _("UID"); + Desc_nlstab[EU_UED] = _("Effective User Id"); +/* Translation Hint: maximum 'USER' = 8 + */ + Head_nlstab[EU_UEN] = _("USER"); + Desc_nlstab[EU_UEN] = _("Effective User Name"); +/* Translation Hint: maximum 'RUID' = 5 + */ + Head_nlstab[EU_URD] = _("RUID"); + Desc_nlstab[EU_URD] = _("Real User Id"); +/* Translation Hint: maximum 'RUSER' = 8 + */ + Head_nlstab[EU_URN] = _("RUSER"); + Desc_nlstab[EU_URN] = _("Real User Name"); +/* Translation Hint: maximum 'SUID' = 5 + */ + Head_nlstab[EU_USD] = _("SUID"); + Desc_nlstab[EU_USD] = _("Saved User Id"); +/* Translation Hint: maximum 'SUSER' = 8 + */ + Head_nlstab[EU_USN] = _("SUSER"); + Desc_nlstab[EU_USN] = _("Saved User Name"); +/* Translation Hint: maximum 'GID' = 5 + */ + Head_nlstab[EU_GID] = _("GID"); + Desc_nlstab[EU_GID] = _("Group Id"); +/* Translation Hint: maximum 'GROUP' = 8 + */ + Head_nlstab[EU_GRP] = _("GROUP"); + Desc_nlstab[EU_GRP] = _("Group Name"); +/* Translation Hint: maximum 'PGRP' = 5 */ + Head_nlstab[EU_PGD] = _("PGRP"); + Desc_nlstab[EU_PGD] = _("Process Group Id"); +/* Translation Hint: maximum 'TTY' = 8 + */ + Head_nlstab[EU_TTY] = _("TTY"); + Desc_nlstab[EU_TTY] = _("Controlling Tty"); +/* Translation Hint: maximum 'TPGID' = 5 */ + Head_nlstab[EU_TPG] = _("TPGID"); + Desc_nlstab[EU_TPG] = _("Tty Process Grp Id"); +/* Translation Hint: maximum 'SID' = 5 */ + Head_nlstab[EU_SID] = _("SID"); + Desc_nlstab[EU_SID] = _("Session Id"); +/* Translation Hint: maximum 'PR' = 3 */ + Head_nlstab[EU_PRI] = _("PR"); + Desc_nlstab[EU_PRI] = _("Priority"); +/* Translation Hint: maximum 'NI' = 3 */ + Head_nlstab[EU_NCE] = _("NI"); + Desc_nlstab[EU_NCE] = _("Nice Value"); +/* Translation Hint: maximum 'nTH' = 3 */ + Head_nlstab[EU_THD] = _("nTH"); + Desc_nlstab[EU_THD] = _("Number of Threads"); +/* Translation Hint: maximum 'P' = 1 */ + Head_nlstab[EU_CPN] = _("P"); + Desc_nlstab[EU_CPN] = _("Last Used Cpu (SMP)"); +/* Translation Hint: maximum '%CPU' = 4 */ + Head_nlstab[EU_CPU] = _("%CPU"); + Desc_nlstab[EU_CPU] = _("CPU Usage"); +/* Translation Hint: maximum '' = 6 */ + Head_nlstab[EU_TME] = _("TIME"); + Desc_nlstab[EU_TME] = _("CPU Time"); +/* Translation Hint: maximum 'TIME+' = 9 */ + Head_nlstab[EU_TM2] = _("TIME+"); + Desc_nlstab[EU_TM2] = _("CPU Time, hundredths"); +/* Translation Hint: maximum '%MEM' = 4 */ + Head_nlstab[EU_MEM] = _("%MEM"); + Desc_nlstab[EU_MEM] = _("Memory Usage (RES)"); +/* Translation Hint: maximum 'VIRT' = 7 */ + Head_nlstab[EU_VRT] = _("VIRT"); + Desc_nlstab[EU_VRT] = _("Virtual Image (KiB)"); +/* Translation Hint: maximum 'SWAP' = 6 */ + Head_nlstab[EU_SWP] = _("SWAP"); + Desc_nlstab[EU_SWP] = _("Swapped Size (KiB)"); +/* Translation Hint: maximum 'RES' = 6 */ + Head_nlstab[EU_RES] = _("RES"); + Desc_nlstab[EU_RES] = _("Resident Size (KiB)"); +/* Translation Hint: maximum 'CODE' = 4 */ + Head_nlstab[EU_COD] = _("CODE"); + Desc_nlstab[EU_COD] = _("Code Size (KiB)"); +/* Translation Hint: maximum 'DATA' = 7 */ + Head_nlstab[EU_DAT] = _("DATA"); + Desc_nlstab[EU_DAT] = _("Data+Stack (KiB)"); +/* Translation Hint: maximum 'SHR' = 6 */ + Head_nlstab[EU_SHR] = _("SHR"); + Desc_nlstab[EU_SHR] = _("Shared Memory (KiB)"); +/* Translation Hint: maximum 'nMaj' = 4 */ + Head_nlstab[EU_FL1] = _("nMaj"); + Desc_nlstab[EU_FL1] = _("Major Page Faults"); +/* Translation Hint: maximum 'nMin' = 4 */ + Head_nlstab[EU_FL2] = _("nMin"); + Desc_nlstab[EU_FL2] = _("Minor Page Faults"); +/* Translation Hint: maximum 'nDRT' = 4 */ + Head_nlstab[EU_DRT] = _("nDRT"); + Desc_nlstab[EU_DRT] = _("Dirty Pages Count"); +/* Translation Hint: maximum 'S' = 1 */ + Head_nlstab[EU_STA] = _("S"); + Desc_nlstab[EU_STA] = _("Process Status"); +/* Translation Hint: maximum 'COMMAND' = variable */ + Head_nlstab[EU_CMD] = _("COMMAND"); + Desc_nlstab[EU_CMD] = _("Command Name/Line"); +/* Translation Hint: maximum 'WCHAN' = 10 + */ + Head_nlstab[EU_WCH] = _("WCHAN"); + Desc_nlstab[EU_WCH] = _("Sleeping in Function"); +/* Translation Hint: maximum 'Flags' = 8 */ + Head_nlstab[EU_FLG] = _("Flags"); + Desc_nlstab[EU_FLG] = _("Task Flags <sched.h>"); +/* Translation Hint: maximum 'CGROUPS' = variable */ + Head_nlstab[EU_CGR] = _("CGROUPS"); + Desc_nlstab[EU_CGR] = _("Control Groups"); +/* Translation Hint: maximum 'SUPGIDS' = variable */ + Head_nlstab[EU_SGD] = _("SUPGIDS"); + Desc_nlstab[EU_SGD] = _("Supp Groups IDs"); +/* Translation Hint: maximum 'SUPGRPS' = variable */ + Head_nlstab[EU_SGN] = _("SUPGRPS"); + Desc_nlstab[EU_SGN] = _("Supp Groups Names"); +/* Translation Hint: maximum 'TGID' = 5 */ + Head_nlstab[EU_TGD] = _("TGID"); + Desc_nlstab[EU_TGD] = _("Thread Group Id"); +/* Translation Hint: maximum 'OOMa' = 5 */ + Head_nlstab[EU_OOA] = _("OOMa"); + Desc_nlstab[EU_OOA] = _("OOMEM Adjustment"); +/* Translation Hint: maximum 'OOMs' = 4 */ + Head_nlstab[EU_OOM] = _("OOMs"); + Desc_nlstab[EU_OOM] = _("OOMEM Score current"); +/* Translation Hint: maximum 'ENVIRON' = variable */ + Head_nlstab[EU_ENV] = _("ENVIRON"); +/* Translation Hint: the abbreviation 'vars' below is shorthand for + 'variables' */ + Desc_nlstab[EU_ENV] = _("Environment vars"); +/* Translation Hint: maximum 'vMj' = 3 */ + Head_nlstab[EU_FV1] = _("vMj"); + Desc_nlstab[EU_FV1] = _("Major Faults delta"); +/* Translation Hint: maximum 'vMn' = 3 */ + Head_nlstab[EU_FV2] = _("vMn"); + Desc_nlstab[EU_FV2] = _("Minor Faults delta"); +/* Translation Hint: maximum 'USED' = 6 */ + Head_nlstab[EU_USE] = _("USED"); + Desc_nlstab[EU_USE] = _("Res+Swap Size (KiB)"); +/* Translation Hint: maximum 'nsIPC' = 10 + */ + Head_nlstab[EU_NS1] = _("nsIPC"); + Desc_nlstab[EU_NS1] = _("IPC namespace Inode"); +/* Translation Hint: maximum 'nsMNT' = 10 + */ + Head_nlstab[EU_NS2] = _("nsMNT"); + Desc_nlstab[EU_NS2] = _("MNT namespace Inode"); +/* Translation Hint: maximum 'nsNET' = 10 + */ + Head_nlstab[EU_NS3] = _("nsNET"); + Desc_nlstab[EU_NS3] = _("NET namespace Inode"); +/* Translation Hint: maximum 'nsPID' = 10 + */ + Head_nlstab[EU_NS4] = _("nsPID"); + Desc_nlstab[EU_NS4] = _("PID namespace Inode"); +/* Translation Hint: maximum 'nsUSER' = 10 + */ + Head_nlstab[EU_NS5] = _("nsUSER"); + Desc_nlstab[EU_NS5] = _("USER namespace Inode"); +/* Translation Hint: maximum 'nsUTS' = 10 + */ + Head_nlstab[EU_NS6] = _("nsUTS"); + Desc_nlstab[EU_NS6] = _("UTS namespace Inode"); +/* Translation Hint: maximum 'LXC' = 8 + */ + Head_nlstab[EU_LXC] = _("LXC"); + Desc_nlstab[EU_LXC] = _("LXC container name"); +/* Translation Hint: maximum 'RSan' = 6 */ + Head_nlstab[EU_RZA] = _("RSan"); + Desc_nlstab[EU_RZA] = _("RES Anonymous (KiB)"); +/* Translation Hint: maximum 'RSfd' = 6 */ + Head_nlstab[EU_RZF] = _("RSfd"); + Desc_nlstab[EU_RZF] = _("RES File-based (KiB)"); +/* Translation Hint: maximum 'RSlk' = 6 */ + Head_nlstab[EU_RZL] = _("RSlk"); + Desc_nlstab[EU_RZL] = _("RES Locked (KiB)"); +/* Translation Hint: maximum 'RSsh' = 6 */ + Head_nlstab[EU_RZS] = _("RSsh"); + Desc_nlstab[EU_RZS] = _("RES Shared (KiB)"); +/* Translation Hint: maximum 'CGNAME' = variable */ + Head_nlstab[EU_CGN] = _("CGNAME"); + Desc_nlstab[EU_CGN] = _("Control Group name"); +/* Translation Hint: maximum 'NU' = 2 */ + Head_nlstab[EU_NMA] = _("NU"); + Desc_nlstab[EU_NMA] = _("Last Used NUMA node"); +/* Translation Hint: maximum 'LOGID' = 5 + */ + Head_nlstab[EU_LID] = _("LOGID"); + Desc_nlstab[EU_LID] = _("Login User Id"); +/* Translation Hint: maximum 'EXE' = variable */ + Head_nlstab[EU_EXE] = _("EXE"); + Desc_nlstab[EU_EXE] = _("Executable Path"); +/* Translation Hint: With the next 5 fields, notice how an extra space + has been added ahead of one 'KiB' so that they all + align. You need not preserve such alignment. */ +/* Translation Hint: maximum 'RSS' = 6 */ + Head_nlstab[EU_RSS] = _("RSS"); + Desc_nlstab[EU_RSS] = _("Res Mem (smaps), KiB"); +/* Translation Hint: maximum 'PSS' = 6 */ + Head_nlstab[EU_PSS] = _("PSS"); + Desc_nlstab[EU_PSS] = _("Proportion RSS, KiB"); +/* Translation Hint: maximum 'PSan' = 6 */ + Head_nlstab[EU_PZA] = _("PSan"); + Desc_nlstab[EU_PZA] = _("Proportion Anon, KiB"); +/* Translation Hint: maximum 'PSfd' = 6 */ + Head_nlstab[EU_PZF] = _("PSfd"); + Desc_nlstab[EU_PZF] = _("Proportion File, KiB"); +/* Translation Hint: maximum 'PSsh' = 6 */ + Head_nlstab[EU_PZS] = _("PSsh"); + Desc_nlstab[EU_PZS] = _("Proportion Shrd, KiB"); +/* Translation Hint: maximum 'USS' = 6 */ + Head_nlstab[EU_USS] = _("USS"); + Desc_nlstab[EU_USS] = _("Unique RSS, KiB"); +/* Translation Hint: maximum 'ioR' = 6 */ + Head_nlstab[EU_IRB] = _("ioR"); + Desc_nlstab[EU_IRB] = _("I/O Bytes Read"); +/* Translation Hint: maximum 'ioRop' = 5 */ + Head_nlstab[EU_IRO] = _("ioRop"); + Desc_nlstab[EU_IRO] = _("I/O Read Operations"); +/* Translation Hint: maximum 'ioW' = 6 */ + Head_nlstab[EU_IWB] = _("ioW"); + Desc_nlstab[EU_IWB] = _("I/O Bytes Written"); +/* Translation Hint: maximum 'ioWop' = 5 */ + Head_nlstab[EU_IWO] = _("ioWop"); + Desc_nlstab[EU_IWO] = _("I/O Write Operations"); +/* Translation Hint: maximum 'AGID' = 5 */ + Head_nlstab[EU_AGI] = _("AGID"); + Desc_nlstab[EU_AGI] = _("Autogroup Identifier"); +/* Translation Hint: maximum 'AGNI' = 4 */ + Head_nlstab[EU_AGN] = _("AGNI"); + Desc_nlstab[EU_AGN] = _("Autogroup Nice Value"); +/* Translation Hint: maximum 'STARTED' = 7 */ + Head_nlstab[EU_TM3] = _("STARTED"); + Desc_nlstab[EU_TM3] = _("Start Time from boot"); +/* Translation Hint: maximum 'ELAPSED' = 7 */ + Head_nlstab[EU_TM4] = _("ELAPSED"); + Desc_nlstab[EU_TM4] = _("Elapsed Running Time"); +/* Translation Hint: maximum '%CUU' = 6 */ + Head_nlstab[EU_CUU] = _("%CUU"); + Desc_nlstab[EU_CUU] = _("CPU Utilization"); +/* Translation Hint: maximum '%CUC' = 7 */ + Head_nlstab[EU_CUC] = _("%CUC"); + Desc_nlstab[EU_CUC] = _("Utilization + child"); +/* Translation Hint: maximum 'nsCGROUP' = 10 + */ + Head_nlstab[EU_NS7] = _("nsCGROUP"); + Desc_nlstab[EU_NS7] = _("CGRP namespace Inode"); +/* Translation Hint: maximum 'nsTIME' = 10 + */ + Head_nlstab[EU_NS8] = _("nsTIME"); + Desc_nlstab[EU_NS8] = _("TIME namespace Inode"); +} + + + /* + * This routine builds the nls table containing both plain text + * and regular c-format strings. */ +static void build_norm_nlstab (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . This group of lines contains both plain text and c-format strings. + . + . Some strings reflect switches used to affect the running program + . and should not be translated without also making corresponding + . c-code logic changes. + . */ + + Norm_nlstab[EXIT_signals_fmt] = _("" + "\tsignal %d (%s) was caught by %s, please\n" + "\tsend bug reports to <procps@freelists.org>\n"); + Norm_nlstab[HELP_cmdline_fmt] = _X("\n" + "Usage:\n" + " %s [options]\n" + "\n" + "Options:\n" + " -b, --batch-mode run in non-interactive batch mode\n" + " -c, --cmdline-toggle reverse last remembered 'c' state\n" + " -d, --delay =SECS [.TENTHS] iterative delay as SECS [.TENTHS]\n" + " -E, --scale-summary-mem =SCALE set mem as: k,m,g,t,p,e for SCALE\n" + " -e, --scale-task-mem =SCALE set mem with: k,m,g,t,p for SCALE\n" + " -H, --threads-show show tasks plus all their threads\n" + " -i, --idle-toggle reverse last remembered 'i' state\n" + " -n, --iterations =NUMBER exit on maximum iterations NUMBER\n" + " -O, --list-fields output all field names, then exit\n" + " -o, --sort-override =FIELD force sorting on this named FIELD\n" + " -p, --pid =PIDLIST monitor only the tasks in PIDLIST\n" + " -S, --accum-time-toggle reverse last remembered 'S' state\n" + " -s, --secure-mode run with secure mode restrictions\n" + " -U, --filter-any-user =USER show only processes owned by USER\n" + " -u, --filter-only-euser =USER show only processes owned by USER\n" + " -w, --width [=COLUMNS] change print width [,use COLUMNS]\n" + " -1, --single-cpu-toggle reverse last remembered '1' state\n" + "\n" + " -h, --help display this help text, then exit\n" + " -V, --version output version information & exit\n" + "\n" + "For more details see top(1)."); + Norm_nlstab[BAD_delayint_fmt] = _("bad delay interval '%s'"); + Norm_nlstab[BAD_niterate_fmt] = _("bad iterations argument '%s'"); + Norm_nlstab[LIMIT_exceed_fmt] = _("pid limit (%d) exceeded"); + Norm_nlstab[BAD_mon_pids_fmt] = _("bad pid '%s'"); + Norm_nlstab[MISSING_args_fmt] = _("-%c argument missing"); + Norm_nlstab[BAD_widtharg_fmt] = _("bad width arg '%s'"); + Norm_nlstab[UNKNOWN_opts_fmt] = _("unknown option '%s'"); + Norm_nlstab[DELAY_secure_txt] = _("-d disallowed in \"secure\" mode"); + Norm_nlstab[DELAY_badarg_txt] = _("-d requires positive argument"); + Norm_nlstab[ON_word_only_txt] = _("On"); + Norm_nlstab[OFF_one_word_txt] = _("Off"); + Norm_nlstab[VERSION_opts_fmt] = _("%s from %s"); + Norm_nlstab[FOREST_modes_fmt] = _("Forest mode %s"); + Norm_nlstab[FAIL_tty_get_txt] = _("failed tty get"); + Norm_nlstab[FAIL_tty_set_fmt] = _("failed tty set: %s"); + Norm_nlstab[CHOOSE_group_txt] = _("Choose field group (1 - 4)"); + Norm_nlstab[DISABLED_cmd_txt] = _("Command disabled, 'A' mode required"); + Norm_nlstab[DISABLED_win_fmt] = _("Command disabled, activate %s with '-' or '_'"); + Norm_nlstab[COLORS_nomap_txt] = _("No colors to map!"); + Norm_nlstab[FAIL_rc_open_fmt] = _("Failed '%s' open: %s"); + Norm_nlstab[WRITE_rcfile_fmt] = _("Wrote configuration to '%s'"); + Norm_nlstab[DELAY_change_fmt] = _("Change delay from %.1f to"); + Norm_nlstab[THREADS_show_fmt] = _("Show threads %s"); + Norm_nlstab[IRIX_curmode_fmt] = _("Irix mode %s"); + Norm_nlstab[GET_pid2kill_fmt] = _("PID to signal/kill [default pid = %d]"); + Norm_nlstab[GET_sigs_num_fmt] = _("Send pid %d signal [%d/sigterm]"); + Norm_nlstab[FAIL_signals_fmt] = _("Failed signal pid '%d' with '%d': %s"); + Norm_nlstab[BAD_signalid_txt] = _("Invalid signal"); + Norm_nlstab[GET_pid2nice_fmt] = _("PID to renice [default pid = %d]"); + Norm_nlstab[GET_nice_num_fmt] = _("Renice PID %d to value"); + Norm_nlstab[FAIL_re_nice_fmt] = _("Failed renice of PID %d to %d: %s"); + Norm_nlstab[NAME_windows_fmt] = _("Rename window '%s' to (1-3 chars)"); + Norm_nlstab[TIME_accumed_fmt] = _("Cumulative time %s"); + Norm_nlstab[GET_max_task_fmt] = _("Maximum tasks = %d, change to (0 is unlimited)"); + Norm_nlstab[BAD_max_task_txt] = _("Invalid maximum"); + Norm_nlstab[GET_user_ids_txt] = _("Which user (blank for all)"); + Norm_nlstab[UNKNOWN_cmds_txt] = _("Unknown command - try 'h' for help"); + Norm_nlstab[SCROLL_coord_fmt] = _("scroll coordinates: y = %d/%d (tasks), x = %d/%d (fields)"); + Norm_nlstab[FAIL_alloc_c_txt] = _("failed memory allocate"); + Norm_nlstab[FAIL_alloc_r_txt] = _("failed memory re-allocate"); + Norm_nlstab[BAD_numfloat_txt] = _("Unacceptable floating point"); + Norm_nlstab[BAD_username_txt] = _("Invalid user"); + Norm_nlstab[FOREST_views_txt] = _("forest view"); + Norm_nlstab[FAIL_widepid_txt] = _("failed pid maximum size test"); + Norm_nlstab[FAIL_widecpu_txt] = _("failed number of cpus test"); + Norm_nlstab[RC_bad_files_fmt] = _("incompatible rcfile, you should delete '%s'"); + Norm_nlstab[RC_bad_entry_fmt] = _("window entry #%d corrupt, please delete '%s'"); + Norm_nlstab[NOT_onsecure_txt] = _("Unavailable in secure mode"); + Norm_nlstab[NOT_smp_cpus_txt] = _("Only 1 cpu detected"); + Norm_nlstab[BAD_integers_txt] = _("Unacceptable integer"); + Norm_nlstab[SELECT_clash_txt] = _("conflicting process selections (U/p/u)"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . kibibytes (1024 bytes) */ + Norm_nlstab[AMT_kilobyte_txt] = _("KiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . mebibytes (1,048,576 bytes) */ + Norm_nlstab[AMT_megabyte_txt] = _("MiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . gibibytes (1,073,741,824 bytes) */ + Norm_nlstab[AMT_gigabyte_txt] = _("GiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . tebibytes (1,099,511,627,776 bytes) */ + Norm_nlstab[AMT_terabyte_txt] = _("TiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . pebibytes (1,024 tebibytes) */ + Norm_nlstab[AMT_petabyte_txt] = _("PiB"); +/* Translation Hint: This is an abbreviation (limit 3 characters) for: + . exbibytes (1,024 pebibytes) */ + Norm_nlstab[AMT_exxabyte_txt] = _("EiB"); + Norm_nlstab[WORD_threads_txt] = _("Threads"); + Norm_nlstab[WORD_process_txt] = _("Tasks"); +/* Translation Hint: The following "word" is meant to represent either a single + . cpu or all of the processors in a multi-processor computer + . (should be exactly 6 characters, excluding leading % & colon) */ + Norm_nlstab[WORD_allcpus_txt] = _("%Cpu(s):"); +/* Translation Hint: The following "word" is meant to represent a single processor + and 'the Cp' prefix will be combined with a core id: 'p', 'e' or 'u' + . (if 'Cp' is translated, it must be exactly 2 characters long) */ + Norm_nlstab[WORD_eachcpu_fmt] = _("%%Cp%c%-3d:"); +/* Translation Hint: The following word "another" must have 1 trailing space */ + Norm_nlstab[WORD_another_txt] = _("another "); + Norm_nlstab[FIND_no_next_txt] = _("Locate next inactive, use \"L\""); + Norm_nlstab[GET_find_str_txt] = _("Locate string"); + Norm_nlstab[FIND_no_find_fmt] = _("%s\"%s\" not found"); + Norm_nlstab[XTRA_fixwide_fmt] = _("width incr is %d, change to (0 default, -1 auto)"); + Norm_nlstab[XTRA_warncfg_txt] = _("rcfile has inspect/other-filter error(s), save anyway?"); + Norm_nlstab[XTRA_badflds_fmt] = _("unrecognized field name '%s'"); + Norm_nlstab[XTRA_winsize_txt] = _("even using field names only, window is now too small"); + Norm_nlstab[YINSP_demo01_txt] = _("Open Files"); + Norm_nlstab[YINSP_demo02_txt] = _("NUMA Info"); + Norm_nlstab[YINSP_demo03_txt] = _("Log"); + Norm_nlstab[YINSP_deqfmt_txt] = _("the '=' key will eventually show the actual file read or command(s) executed ..."); + Norm_nlstab[YINSP_deqtyp_txt] = _("demo"); + Norm_nlstab[YINSP_dstory_txt] = _("" + "This is simulated output representing the contents of some file or the output\n" + "from some command. Exactly which commands and/or files are solely up to you.\n" + "\n" + "Although this text is for information purposes only, it can still be scrolled\n" + "and searched like real output will be. You are encouraged to experiment with\n" + "those features as explained in the prologue above.\n" + "\n" + "To enable real Inspect functionality, entries must be added to the end of the\n" + "top personal personal configuration file. You could use your favorite editor\n" + "to accomplish this, taking care not to disturb existing entries.\n" + "\n" + "Another way to add entries is illustrated below, but it risks overwriting the\n" + "rcfile. Redirected echoes must not replace (>) but append (>>) to that file.\n" + "\n" + " /bin/echo -e \"pipe\\tOpen Files\\tlsof -P -p %d 2>&1\" >> ~/.toprc\n" + " /bin/echo -e \"file\\tNUMA Info\\t/proc/%d/numa_maps\" >> ~/.toprc\n" + " /bin/echo -e \"pipe\\tLog\\ttail -n200 /var/log/syslog | sort -Mr\" >> ~/.toprc\n" + "\n" + "If you don't know the location or name of the top rcfile, use the 'W' command\n" + "and note those details. After backing up the current rcfile, try issuing the\n" + "above echoes exactly as shown, replacing '.toprc' as appropriate. The safest\n" + "approach would be to use copy then paste to avoid any typing mistakes.\n" + "\n" + "Finally, restart top to reveal what actual Inspect entries combined with this\n" + "new command can offer. The possibilities are endless, especially considering\n" + "that 'pipe' type entries can include shell scripts too!\n" + "\n" + "For additional important information, please consult the top(1) man document.\n" + "Then, enhance top with your very own customized 'file' and/or 'pipe' entries.\n" + "\n" + "Enjoy!\n"); + Norm_nlstab[YINSP_noent1_txt] = _("to enable 'Y' press <Enter> then type 'W' and restart top"); + Norm_nlstab[YINSP_noent2_txt] = _("to enable 'Y' please consult the top man page (press Enter)"); + Norm_nlstab[YINSP_failed_fmt] = _("Selection failed with: %s\n"); + Norm_nlstab[YINSP_pidbad_fmt] = _("unable to inspect, pid %d not found"); + Norm_nlstab[YINSP_pidsee_fmt] = _("inspect at PID [default pid = %d]"); + Norm_nlstab[YINSP_status_fmt] = _("%s: %*d-%-*d lines, %*d-%*d columns, %lu bytes read"); + Norm_nlstab[YINSP_waitin_txt] = _("patience please, working ..."); + Norm_nlstab[YINSP_workin_txt] = _("working, use Ctrl-C to end ..."); +/* Translation Hint: Below are 2 abbreviations which can be as long as needed: + . FLD = FIELD, VAL = VALUE */ + Norm_nlstab[OSEL_prompts_fmt] = _("add filter #%d (%s) as: [!]FLD?VAL"); + Norm_nlstab[OSEL_casenot_txt] = _("ignoring case"); + Norm_nlstab[OSEL_caseyes_txt] = _("case sensitive"); + Norm_nlstab[OSEL_errdups_txt] = _("duplicate filter was ignored"); + Norm_nlstab[OSEL_errdelm_fmt] = _("'%s' filter delimiter is missing"); + Norm_nlstab[OSEL_errvalu_fmt] = _("'%s' filter value is missing"); + Norm_nlstab[WORD_include_txt] = _("include"); + Norm_nlstab[WORD_exclude_txt] = _("exclude"); + Norm_nlstab[OSEL_statlin_fmt] = _("<Enter> to resume, filters: %s"); + Norm_nlstab[WORD_noneone_txt] = _("none"); +/* Translation Hint: The following word 'Node' should be exactly + 4 characters, excluding leading %%, fmt chars & colon) */ + Norm_nlstab[NUMA_nodenam_fmt] = _("%%Node%-2d:"); + Norm_nlstab[NUMA_nodeget_fmt] = _("expand which numa node (0-%d)"); + Norm_nlstab[NUMA_nodebad_txt] = _("invalid numa node"); + Norm_nlstab[NUMA_nodenot_txt] = _("sorry, NUMA extensions unavailable"); +/* Translation Hint: 'Mem ' is an abbreviation for physical memory/ram + . 'Swap' represents the linux swap file -- + . please make both translations exactly 4 characters, + . padding with extra spaces as necessary */ + Norm_nlstab[WORD_abv_mem_txt] = _("Mem "); + Norm_nlstab[WORD_abv_swp_txt] = _("Swap"); + Norm_nlstab[LIB_errormem_fmt] = _("library failed memory statistics, at %d: %s"); + Norm_nlstab[LIB_errorcpu_fmt] = _("library failed cpu statistics, at %d: %s"); + Norm_nlstab[LIB_errorpid_fmt] = _("library failed pids statistics, at %d: %s"); + Norm_nlstab[BAD_memscale_fmt] = _("bad memory scaling arg '%s'"); + Norm_nlstab[XTRA_vforest_fmt] = _("PID to collapse/expand [default pid = %d]"); + Norm_nlstab[XTRA_modebad_txt] = _("wrong mode, command inactive"); + Norm_nlstab[XTRA_warnold_txt] = _("saving prevents older top from reading, save anyway?"); + Norm_nlstab[X_SEMAPHORES_fmt] = _("failed sem_init() at %d: %s"); + Norm_nlstab[X_THREADINGS_fmt] = _("failed pthread_create() at %d: %s"); + Norm_nlstab[X_RESTRICTED_txt] = _("sorry, restricted namespace with reduced functionality"); + Norm_nlstab[AGNI_valueof_fmt] = _("set pid %d AGNI value to"); + Norm_nlstab[AGNI_invalid_txt] = _("valid AGNI range is -20 to +19"); + Norm_nlstab[AGNI_notopen_fmt] = _("autogroup open failed, %s"); + Norm_nlstab[AGNI_nowrite_fmt] = _("autogroup write failed, %s"); + Norm_nlstab[X_BOT_cmdlin_fmt] = _("command line for pid %d, %s"); + Norm_nlstab[X_BOT_ctlgrp_fmt] = _("control groups for pid %d, %s"); + Norm_nlstab[X_BOT_envirn_fmt] = _("environment for pid %d, %s"); + Norm_nlstab[X_BOT_namesp_fmt] = _("namespaces for pid %d, %s"); + Norm_nlstab[X_BOT_nodata_txt] = _("n/a"); + Norm_nlstab[X_BOT_supgrp_fmt] = _("supplementary groups for pid %d, %s"); + Norm_nlstab[X_BOT_msglog_txt] = _("message log, last 10 messages:"); +} + + + /* + * This routine builds the nls table containing specially + * formatted strings designed to fit within an 80x24 terminal. */ +static void build_uniq_nlstab (void) { + +/* Translation Notes ------------------------------------------------ + . It is strongly recommend that the --no-wrap command line option + . be used with all supporting translation tools, when available. + . + . The next several text groups contain special escape sequences + . representing values used to index a table at run-time. + . + . Each such sequence consists of a tilde (~) followed by an ascii + . number in the range of '1' - '8'. Examples are '~2', '~8', etc. + . These escape sequences must never themselves be translated but + . could be deleted. + . + . If you remove these escape sequences (both tilde and number) it + . would make translation easier. However, the ability to display + . colors and bold text at run-time will have been lost. + . + . Additionally, each of these text groups was designed to display + . in a 80x24 terminal window. Hopefully, any translations will + . adhere to that goal lest the translated text be truncated. + . + . If you would like additional information regarding these strings, + . please see the prologue to the show_special function in the top.c + . source file. + . + . Caution: + . The next three items represent pages for interacting with a user. + . In all cases, the last lines of text must be treated with care. + . + . They must not end with a newline character so that at runtime the + . cursor will remain on that final text line. + . + . Also, the special sequences (tilde+number) must not appear on the + . last line (the one without the newline). So please avoid any line + . wraps that could place them there. + . */ + + Uniq_nlstab[KEYS_helpbas_fmt] = _("" + "Help for Interactive Commands~2 - %s\n" + "Window ~1%s~6: ~1Cumulative mode ~3%s~2. ~1System~6: ~1Delay ~3%.1f secs~2; ~1Secure mode ~3%s~2.\n" + "\n" + " Z~5,~1B~5,E,e Global: '~1Z~2' colors; '~1B~2' bold; '~1E~2'/'~1e~2' summary/task memory scale\n" + " l,t,m,I,0 Toggle: '~1l~2' load avg; '~1t~2' task/cpu; '~1m~2' memory; '~1I~2' Irix; '~10~2' zeros\n" + " 1,2,3,4,5 Toggle: '~11~2/~12~2/~13~2' cpu/numa views; '~14~2' cpus abreast; '~15~2' P/E-cores\n" + " f,X Fields: '~1f~2' add/remove/order/sort; '~1X~2' increase fixed-width fields\n" + "\n" + " L,&,<,> . Locate: '~1L~2'/'~1&~2' find/again; Move sort column: '~1<~2'/'~1>~2' left/right\n" + " R,H,J,C . Toggle: '~1R~2' Sort; '~1H~2' Threads; '~1J~2' Num justify; '~1C~2' Coordinates\n" + " c,i,S,j . Toggle: '~1c~2' Cmd name/line; '~1i~2' Idle; '~1S~2' Time; '~1j~2' Str justify\n" + " x~5,~1y~5 . Toggle highlights: '~1x~2' sort field; '~1y~2' running tasks\n" + " z~5,~1b~5 . Toggle: '~1z~2' color/mono; '~1b~2' bold/reverse (only if 'x' or 'y')\n" + " u,U,o,O . Filter by: '~1u~2'/'~1U~2' effective/any user; '~1o~2'/'~1O~2' other criteria\n" + " n,#,^O . Set: '~1n~2'/'~1#~2' max tasks displayed; Show: ~1Ctrl~2+'~1O~2' other filter(s)\n" + " V,v,F . Toggle: '~1V~2' forest view; '~1v~2' hide/show children; '~1F~2' keep focused\n" + "\n" + "%s" + " ^G,K,N,U View: ctl groups ~1^G~2; cmdline ~1^K~2; environment ~1^N~2; supp groups ~1^U~2\n" + " Y,!,^E,P Inspect '~1Y~2'; Combine Cpus '~1!~2'; Scale time ~1^E~2; View namespaces ~1^P~2\n" + " W,q Write config file '~1W~2'; Quit '~1q~2'\n" + " ( commands shown with '.' require a ~1visible~2 task display ~1window~2 ) \n" + "Press '~1h~2' or '~1?~2' for help with ~1Windows~2,\n" + "Type 'q' or <Esc> to continue "); + + Uniq_nlstab[WINDOWS_help_fmt] = _("" + "Help for Windows / Field Groups~2 - \"Current Window\" = ~1 %s ~6\n" + "\n" + ". Use multiple ~1windows~2, each with separate config opts (color,fields,sort,etc)\n" + ". The 'current' window controls the ~1Summary Area~2 and responds to your ~1Commands~2\n" + " . that window's ~1task display~2 can be turned ~1Off~2 & ~1On~2, growing/shrinking others\n" + " . with ~1NO~2 task display, some commands will be ~1disabled~2 ('i','R','n','c', etc)\n" + " until a ~1different window~2 has been activated, making it the 'current' window\n" + ". You ~1change~2 the 'current' window by: ~1 1~2) cycling forward/backward;~1 2~2) choosing\n" + " a specific field group; or~1 3~2) exiting the color mapping or fields screens\n" + ". Commands ~1available anytime -------------~2\n" + " A . Alternate display mode toggle, show ~1Single~2 / ~1Multiple~2 windows\n" + " g . Choose another field group and make it 'current', or change now\n" + " by selecting a number from: ~1 1~2 =%s;~1 2~2 =%s;~1 3~2 =%s; or~1 4~2 =%s\n" + ". Commands ~1requiring~2 '~1A~2' mode~1 -------------~2\n" + " G . Change the ~1Name~5 of the 'current' window/field group\n" + " ~1*~4 a , w . Cycle through all four windows: '~1a~5' Forward; '~1w~5' Backward\n" + " ~1*~4 - , _ . Show/Hide: '~1-~5' ~1Current~2 window; '~1_~5' all ~1Visible~2/~1Invisible~2\n" + " The screen will be divided evenly between task displays. But you can make\n" + " some ~1larger~2 or ~1smaller~2, using '~1n~2' and '~1i~2' commands. Then later you could:\n" + " ~1*~4 = , + . Rebalance tasks: '~1=~5' ~1Current~2 window; '~1+~5' ~1Every~2 window\n" + " (this also forces the ~1current~2 or ~1every~2 window to become visible)\n" + "\n" + "In '~1A~2' mode, '~1*~4' keys are your ~1essential~2 commands. Please try the '~1a~2' and '~1w~2'\n" + "commands plus the 'g' sub-commands NOW. Press <Enter> to make 'Current' "); + +/* Translation Notes ------------------------------------------------ + . The following 'Help for color mapping' simulated screen should + . probably NOT be translated. It is terribly hard to follow in + . this form and any translation could produce unpleasing results + . that are unlikely to parallel the running top program. + . + . If you decide to proceed with translation, please take care + . to not disturb the spaces and the tilde + number delimiters. + . */ + Uniq_nlstab[COLOR_custom_fmt] = _("" + "Help for color mapping~2 - \"Current Window\" = ~1 %s ~6\n" + "\n" + " color - 04:25:44 up 8 days, 50 min, 7 users, load average:\n" + " Tasks:~3 64 ~2total,~3 2 ~3running,~3 62 ~2sleeping,~3 0 ~2stopped,~3\n" + " %%Cpu(s):~3 76.5 ~2user,~3 11.2 ~2system,~3 0.0 ~2nice,~3 12.3 ~2idle~3\n" + " ~1 Nasty Message! ~4 -or- ~1Input Prompt~5\n" + " ~1 PID TTY PR NI %%CPU TIME+ VIRT SWAP S COMMAND ~6\n" + " 17284 ~8pts/2 ~7 8 0 0.0 0:00.75 1380 0 S /bin/bash ~8\n" + " ~1 8601 pts/1 7 -10 0.4 0:00.03 916 0 R color -b -z~7\n" + " 11005 ~8? ~7 9 0 0.0 0:02.50 2852 1008 S amor -sessi~8\n" + " available toggles: ~1B~2 =disable bold globally (~1%s~2),\n" + " ~1z~2 =color/mono (~1%s~2), ~1b~2 =tasks \"bold\"/reverse (~1%s~2)\n" + "\n" + "1) Select a ~1target~2 as an upper case letter, ~1current target~2 is ~1 %c ~4:\n" + " S~2 = Summary Data,~1 M~2 = Messages/Prompts,\n" + " H~2 = Column Heads,~1 T~2 = Task Information\n" + "2) Select a ~1color~2 as a number or use the up/down arrow keys\n" + " to raise/lower the %d colors value, ~1current color~2 is ~1 %d ~4:\n" + " 0~2 = black,~1 1~2 = red, ~1 2~2 = green,~1 3~2 = yellow,\n" + " 4~2 = blue, ~1 5~2 = magenta,~1 6~2 = cyan, ~1 7~2 = white\n" + "\n" + "3) Then use these keys when finished:\n" + " 'q' or <Esc> to abort changes to window '~1%s~2'\n" + " 'a' or 'w' to commit & change another, <Enter> to commit and end "); + +/* Translation Hint: As is true for the text above, the "keys" shown to the left and + . also embedded in the translatable text (along with escape seqs) + . should never themselves be translated. */ + Uniq_nlstab[KEYS_helpext_fmt] = _("" + " d,k,r,^R '~1d~2' set delay; '~1k~2' kill; '~1r~2' renice; ~1Ctrl~2+'~1R~2' renice autogroup\n"); + +/* Translation Hint: + . This Fields Management header should be 3 lines long so as + . to allow 1 blank line before the fields & descriptions. + . If absolutely necessary, 4 lines could be used (but never more). + . */ + Uniq_nlstab[FIELD_header_fmt] = _("" + "Fields Management~2 for window ~1%s~6, whose current sort field is ~1%s~2\n" + " Navigate with Up/Dn, Right selects for move then <Enter> or Left commits,\n" + " 'd' or <Space> toggles display, 's' sets sort. Use 'q' or <Esc> to end!\n"); + +/* Translation Hint: + . The next 5 items must each be translated as a single line. + . */ + Uniq_nlstab[STATE_line_1_fmt] = _("%s:~3" + " %3u ~2total,~3 %3u ~2running,~3 %3u ~2sleeping,~3 %3u ~2stopped,~3 %3u ~2zombie~3\n"); + +/* Translation Hint: Only the following abbreviations need be translated + . us = user, sy = system, ni = nice, id = idle, wa = wait, + . hi hardware interrupt, si = software interrupt */ + Uniq_nlstab[STATE_lin2x6_fmt] = _("%s~3" + " %#5.1f ~2us,~3 %#5.1f ~2sy,~3 %#5.1f ~2ni,~3 %#5.1f ~2id,~3 %#5.1f ~2wa,~3 %#5.1f ~2hi,~3 %#5.1f ~2si~3 ~1"); + +/* Translation Hint: Only the following abbreviations need be translated + . us = user, sy = system, ni = nice, id = idle, wa = wait, + . hi hardware interrupt, si = software interrupt, st = steal time */ + Uniq_nlstab[STATE_lin2x7_fmt] = _("%s~3" + "%#5.1f ~2us,~3%#5.1f ~2sy,~3%#5.1f ~2ni,~3%#5.1f ~2id,~3%#5.1f ~2wa,~3%#5.1f ~2hi,~3%#5.1f ~2si,~3%#5.1f ~2st~3 ~1"); + +/* Translation Hint: next 2 must be treated together, with WORDS above & below aligned */ + Uniq_nlstab[MEMORY_line1_fmt] = _("" + "%s %s:~3 %9.9s~2total,~3 %9.9s~2free,~3 %9.9s~2used,~3 %9.9s~2buff/cache~3 ~1 "); + Uniq_nlstab[MEMORY_line2_fmt] = _("" + "%s %s:~3 %9.9s~2total,~3 %9.9s~2free,~3 %9.9s~2used.~3 %9.9s~2avail %s~3"); + +/* Translation Hint: + . The next 2 headers for 'Inspection' must each be 3 lines or less + . */ + Uniq_nlstab[YINSP_hdsels_fmt] = _("" + "Inspection~2 Pause at: pid ~1%d~6, running ~1%s~6\n" + "Use~2: left/right then <Enter> to ~1select~5 an option; 'q' or <Esc> to ~1end~5 !\n" + "Options~2: ~1%s\n"); + + Uniq_nlstab[YINSP_hdview_fmt] = _("" + "Inspection~2 View at pid: ~1%s~3, running ~1%s~3. Locating: ~1%s~6\n" + "Use~2: left/right/up/down/etc to ~1navigate~5 the output; 'L'/'&' to ~1locate~5/~1next~5.\n" + "Or~2: <Enter> to ~1select another~5; 'q' or <Esc> to ~1end~5 !\n"); +} + + + /* + * This function must be called very early at startup, before + * any other function call, and especially before any changes + * have been made to the terminal if NLS_VALIDATE is defined! + * + * The gettext documentation suggests that alone among locale + * variables LANGUAGE=ll_CC may be abbreviated as LANGUAGE=ll + * to denote the language's main dialect. Unfortunately this + * does not appear to be true. One must specify the complete + * ll_CC. Optionally, a '.UTF-8' or '.uft8' suffix, as shown + * in the following examples, may also be included: + * export LANGUAGE=ll_CC # minimal requirement + * export LANGUAGE=ll_CC.UTF-8 # optional convention + * export LANGUAGE=ll_CC.utf8 # ok, too + * + * Additionally, as suggested in the gettext documentation, a + * user will also have to export an empty LC_ALL= to actually + * enable any translations. + */ +void initialize_nls (void) { +#ifdef NLS_VALIDATE + static const char *nls_err ="\t%s_nlstab[%d] == NULL\n"; + int i; + + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + memset(Head_nlstab, 0, sizeof(Head_nlstab)); + memset(Desc_nlstab, 0, sizeof(Desc_nlstab)); + build_two_nlstabs(); + for (i = 0; i < EU_MAXPFLGS; i++) { + if (!Head_nlstab[i]) { + fprintf(stderr, nls_err, "Head", i); + exit(1); + } + if (!Desc_nlstab[i]) { + fprintf(stderr, nls_err, "Desc", i); + exit(1); + } + } + memset(Norm_nlstab, 0, sizeof(Norm_nlstab)); + build_norm_nlstab(); + for (i = 0; i < norm_MAX; i++) + if (!Norm_nlstab[i]) { + fprintf(stderr, nls_err, "Norm", i); + exit(1); + } + memset(Uniq_nlstab, 0, sizeof(Uniq_nlstab)); + build_uniq_nlstab(); + for (i = 0; i < uniq_MAX; i++) + if (!Uniq_nlstab[i]) { + fprintf(stderr, nls_err, "Uniq", i); + exit(1); + } +#else + setlocale (LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + build_two_nlstabs(); + build_norm_nlstab(); + build_uniq_nlstab(); +#endif +} diff --git a/src/top/top_nls.h b/src/top/top_nls.h new file mode 100644 index 0000000..ed6bad6 --- /dev/null +++ b/src/top/top_nls.h @@ -0,0 +1,108 @@ +/* top_nls.h - provide the basis for future nls translations */ +/* + * Copyright © 2011-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> + * Sami Kerola, <kerolasa@iki.fi> + */ +#ifndef _Itop_nls +#define _Itop_nls + + /* + * These are our string tables with the following contents: + * Head : column headings with varying size limits + * Desc : field descriptions not to exceed 20 screen positions + * Norm : regular text possibly also containing c-format specifiers + * Uniq : show_special specially formatted strings + * + * The latter table presents the greatest translation challenge ! + * + * We go to the trouble of creating the nls string tables to achieve + * these objectives: + * + the overhead of repeated runtime calls to gettext() + * will be avoided + * + the order of the strings in the template (.pot) file + * can be completely controlled + * + none of the important translator only comments will + * clutter and obscure the main program + */ +extern const char *Head_nlstab[]; +extern const char *Desc_nlstab[]; +extern const char *Norm_nlstab[]; +extern const char *Uniq_nlstab[]; + + /* + * Simple optional macros to ease table access. + * The N_txt and N_fmt macros are interchangeable but + * highlight the two types of strings found in Norm_nlstable. + */ +#define N_col(e) Head_nlstab[e] +#define N_fld(e) Desc_nlstab[e] +#define N_txt(e) Norm_nlstab[e] +#define N_fmt(e) Norm_nlstab[e] +#define N_unq(e) Uniq_nlstab[e] + + /* + * These enums are the means to access two of our four tables. + * The Head_nlstab and Desc_nlstab are accessed with standard + * top pflag enums. + * + * The norm_nls enums carry a suffix distinguishing plain text + * from any text also containiing c-format specifiers. + */ +enum norm_nls { + AGNI_invalid_txt, AGNI_notopen_fmt, AGNI_nowrite_fmt, AGNI_valueof_fmt, + AMT_exxabyte_txt, AMT_gigabyte_txt, AMT_kilobyte_txt, AMT_megabyte_txt, + AMT_petabyte_txt, AMT_terabyte_txt, BAD_delayint_fmt, BAD_integers_txt, + BAD_max_task_txt, BAD_memscale_fmt, BAD_mon_pids_fmt, BAD_niterate_fmt, + BAD_numfloat_txt, BAD_signalid_txt, BAD_username_txt, BAD_widtharg_fmt, + CHOOSE_group_txt, COLORS_nomap_txt, DELAY_badarg_txt, DELAY_change_fmt, + DELAY_secure_txt, DISABLED_cmd_txt, DISABLED_win_fmt, EXIT_signals_fmt, + FAIL_alloc_c_txt, FAIL_alloc_r_txt, FAIL_rc_open_fmt, FAIL_re_nice_fmt, + FAIL_signals_fmt, FAIL_tty_get_txt, FAIL_tty_set_fmt, FAIL_widecpu_txt, + FAIL_widepid_txt, FIND_no_find_fmt, FIND_no_next_txt, FOREST_modes_fmt, + FOREST_views_txt, GET_find_str_txt, GET_max_task_fmt, GET_nice_num_fmt, + GET_pid2kill_fmt, GET_pid2nice_fmt, GET_sigs_num_fmt, GET_user_ids_txt, + HELP_cmdline_fmt, IRIX_curmode_fmt, LIB_errorcpu_fmt, LIB_errormem_fmt, + LIB_errorpid_fmt, LIMIT_exceed_fmt, MISSING_args_fmt, NAME_windows_fmt, + NOT_onsecure_txt, NOT_smp_cpus_txt, NUMA_nodebad_txt, NUMA_nodeget_fmt, + NUMA_nodenam_fmt, NUMA_nodenot_txt, OFF_one_word_txt, ON_word_only_txt, + OSEL_casenot_txt, OSEL_caseyes_txt, OSEL_errdelm_fmt, OSEL_errdups_txt, + OSEL_errvalu_fmt, OSEL_prompts_fmt, OSEL_statlin_fmt, RC_bad_entry_fmt, + RC_bad_files_fmt, SCROLL_coord_fmt, SELECT_clash_txt, THREADS_show_fmt, + TIME_accumed_fmt, UNKNOWN_cmds_txt, UNKNOWN_opts_fmt, VERSION_opts_fmt, + WORD_abv_mem_txt, WORD_abv_swp_txt, WORD_allcpus_txt, WORD_another_txt, + WORD_eachcpu_fmt, WORD_exclude_txt, WORD_include_txt, WORD_noneone_txt, + WORD_process_txt, WORD_threads_txt, WRITE_rcfile_fmt, + XTRA_badflds_fmt, XTRA_fixwide_fmt, XTRA_modebad_txt, XTRA_vforest_fmt, + XTRA_warncfg_txt, XTRA_warnold_txt, XTRA_winsize_txt, + X_BOT_cmdlin_fmt, X_BOT_ctlgrp_fmt, X_BOT_envirn_fmt, X_BOT_msglog_txt, + X_BOT_namesp_fmt, X_BOT_nodata_txt, X_BOT_supgrp_fmt, X_RESTRICTED_txt, + X_SEMAPHORES_fmt, X_THREADINGS_fmt, + YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_deqfmt_txt, + YINSP_deqtyp_txt, YINSP_dstory_txt, + YINSP_failed_fmt, YINSP_noent1_txt, YINSP_noent2_txt, YINSP_pidbad_fmt, + YINSP_pidsee_fmt, YINSP_status_fmt, YINSP_waitin_txt, YINSP_workin_txt, + norm_MAX +}; + +enum uniq_nls { + COLOR_custom_fmt, FIELD_header_fmt, KEYS_helpbas_fmt, KEYS_helpext_fmt, + MEMORY_line1_fmt, MEMORY_line2_fmt, STATE_lin2x6_fmt, STATE_lin2x7_fmt, + STATE_line_1_fmt, WINDOWS_help_fmt, YINSP_hdsels_fmt, YINSP_hdview_fmt, + uniq_MAX +}; + +void initialize_nls (void); + +#endif /* _Itop_nls */ + |