/* top.c - Source file: show Linux processes */ /* * Copyright (c) 2002 - James C. Warner, * All rights reserved. * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* I am listing precisely why each header is needed because of the construction of libproc -- separate header files may not always be available and function names are not normalized. We have avoided some library routine(s) as overkill and have subsumed some others. */ /* need: 1 define + dev_to_tty */ #include "proc/devname.h" /* need: (ksym.c) open_psdb_message, wchan, close_psdb (redhat only) */ #include "proc/procps.h" /* need: 2 types + openproc, readproc, closeproc */ #include "proc/readproc.h" #ifdef UGH_ITS_4_RH /* need: get_signal2 */ #include "proc/signals.h" #else /* need: signal_name_to_number */ #include "proc/sig.h" #endif /* need: status */ #include "proc/status.h" /* need: meminfo stuff */ #include "proc/sysinfo.h" /* need: procps_version */ #include "proc/version.h" /* need: sprint_uptime */ #include "proc/whattime.h" #include "top.h" /*###### Miscellaneous global stuff ####################################*/ /* The original and new terminal attributes */ static struct termios Savedtty, Rawtty; static int Ttychanged = 0; /* Program names used in error messages and 'rc' file names */ static char *Myname, *Myrealname; /* The Name of the config file(s), dynamically constructed */ static char RCfile [OURPATHSZ], RCfile_Sys [SMLBUFSIZ]; /* The run-time acquired page size */ static int Page_size; /* SMP and Irix/Solaris mode support */ static int Cpu_tot, *Cpu_map; /* Specific process id monitoring support */ static pid_t Monpids [MONPIDMAX] = { 0 }; static int Monpidsidx = 0; /* A postponed error message */ static char Msg_delayed [SMLBUFSIZ]; static int Msg_awaiting = 0; /* Configurable Display support ##################################*/ static unsigned PFlags [PFLAGSSIZ]; static char CurFields [PFLAGSSIZ], ColHeadings [SMLBUFSIZ], ColUsername [USRNAMSIZ]; static int NumFields; /* Current window size. note: Max_tasks is the specified number of processes to display (zero for infinite), and Max_lines is the actual number which, in turn, depends on screen size and summary info. */ static int Screen_cols, Screen_rows, Max_lines, Max_tasks; /* Number of lines needed to display the summary information which is never less than 2 since it also must accomodate the message line + column headings -- set soley by mkheadings! */ static int HSum_lines; /* Maximum length to display of a process' command name/line (excluding padding due to terminfo strings) */ static int Max_cmd; /* Controls how long we sleep between screen updates. Accurate to microseconds (when not in 'Batch' mode). */ static float Delay_time = DEF_DELAY; /* Special flags dealing with the kernel symbol table */ static int No_ksyms = -1, /* set to '0' if ksym avail, '1' otherwise */ PSDBopen = 0; /* set to '1' if psdb opened (now postponed) */ /* The Mode flags. Except for Batch and Loops, all of them are preserved in the rc file. Thus, once personalized it stays personalized! note: the 'letter' shown is the On condition, some default to Off */ 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 */ Irix_mode = 1, /* 'I' - Irix vs. Solaris view (SMP-only) */ Sort_normal = 1, /* 'R' - reversed column sort order */ HSum_loadav = 1, /* 'l' - display load avg and uptime summary */ HSum_states = 1, /* 't' - display task/cpu(s) states summary */ HSum_memory = 1, /* 'm' - display memory summary */ Show_ctimes = 1, /* 'S' - show times as cumulative */ Show_cmdlin = 0, /* 'c' - show cmdline vs. name */ Show_idleps = 1, /* 'i' - show idle processes (all processes) */ Show_hicols = 0, /* 'x' - show sort column highlighted */ Show_hirows = 1, /* 'y' - show running task(s) highlighted */ Show_colors = 0, /* 'z' - show in color (vs. mono) */ Show_hibold = 1, /* 'b' - rows and/or col bold (vs. reverse) */ Show_cpusum = 1; /* '1' - show combined cpu stats (vs. each) */ /* Current current sort type (this too is preserved in the rc file) * and handler -- goodbye libproc mult_lvl_cmp, etc. overkill. */ static int Sort_type = -1; static QSORT_t Sort_func = NULL; /* These are our gosh darn 'Fields' ! They MUST be kept in sync with pflags !! */ static FTAB_t Fieldstab[] = { /* head fmts width scale sort desc ----------- ------- ------ ------ ------ ---------------------- */ { " PID ", "%5d ", -1, -1, S_PID, "Process Id" }, { " PPID ", "%5d ", -1, -1, -1, "Parent Process Pid" }, { " UID ", "%4d ", -1, -1, -1, "User Id" }, { "USER ", "%-8.8s ", -1, -1, S_USR, "User Name" }, { "GROUP ", "%-8.8s ", -1, -1, -1, "Group Name" }, { "TTY ", "%-8.8s ", 8, -1, S_TTY, "Controlling Tty" }, { " PR ", "%3ld ", -1, -1, -1, "Priority" }, { " NI ", "%3ld ", -1, -1, -1, "Nice value" }, { "#C ", "%2d ", -1, -1, -1, "Last used cpu (SMP)" }, { "%CPU ", "%#4.1f ", -1, -1, S_CPU, "CPU usage" }, { " TIME ", "%6.6s ", 6, -1, S_TME, "CPU Time" }, { " TIME+ ", "%9.9s ", 9, -1, S_TME, "CPU Time, hundredths" }, { "%MEM ", "%#4.1f ", -1, -1, S_MEM, "Memory usage (RES)" }, { " VIRT ", "%5.5s ", 5, SK_Kb, -1, "Virtual Image (kb)" }, { "SWAP ", "%4.4s ", 4, SK_Kb, -1, "Swapped size (kb)" }, { " RES ", "%4.4s ", 4, SK_Kb, S_MEM, "Resident size (kb)" }, { "CODE ", "%4.4s ", 4, SK_Kb, -1, "Code size (kb)" }, { "DATA ", "%4.4s ", 4, SK_Kb, -1, "Data+Stack size (kb)" }, { " SHR ", "%4.4s ", 4, SK_Kb, -1, "Shared Mem size (kb)" }, { "nFLT ", "%4.4s ", 4, SK_no, -1, "Page Fault count" }, { "nDRT ", "%4.4s ", 4, SK_no, -1, "Dirty Pages count" }, { "STA ", "%3.3s ", -1, -1, -1, "Process Status" }, /** next entry's special: '.head' will be formatted using table entry's own '.fmts' plus runtime supplied conversion args! */ { "Command ", "%-*.*s ", -1, -1, S_CMD, "Command line/name" }, { "WCHAN ", "%-9.9s ", -1, -1, -1, "Sleeping in Function" }, /** next entry's special: the 0's will be replaced with '.'! */ #ifdef UPCASE_HEXES { "Flags ", "%08lX ", -1, -1, -1, "Task Flags " } #else { "Flags ", "%08lx ", -1, -1, -1, "Task Flags " } #endif }; /* Miscellaneous Color stuff #####################################*/ /* Our three basic colors -- they can be manipulated by the 'tweak_colors' routine */ static int Base_color = BASEcolor, Msgs_color = MSGScolor, Head_color = HEADcolor; /* Some cap's stuff to reduce runtime calls -- to accomodate 'Batch' mode, they begin life as empty strings */ static char Cap_bold [CAPBUFSIZ] = "", Cap_clr_eol [CAPBUFSIZ] = "", Cap_clr_eos [CAPBUFSIZ] = "", Cap_clr_scr [CAPBUFSIZ] = "", Cap_curs_norm [CAPBUFSIZ] = "", Cap_curs_huge [CAPBUFSIZ] = "", Cap_home [CAPBUFSIZ] = "", Cap_norm [CAPBUFSIZ] = "", Cap_reverse [CAPBUFSIZ] = "", Caps_off [CAPBUFSIZ] = ""; static char Sum_color [CLRBUFSIZ] = "", Msg_color [CLRBUFSIZ] = "", Pmt_color [CLRBUFSIZ] = "", Hdr_color [CLRBUFSIZ] = "", Row_color_norm [CLRBUFSIZ] = "", Row_color_high [CLRBUFSIZ] = ""; static int Cap_can_goto = 0, Len_row_high = 0, Len_row_norm = 0; /* Development/debug stuff #######################################*/ #ifdef TRAK_MAXCAPS static int Max_pads = 0, Min_pads = MAXINT, Max_rbuf = 0, Min_rbuf = MAXINT; #endif #ifdef TRAK_MAXBUFS BUF2INT(fmtmk, buf) BUF2INT(show_special, tmp) BUF2INT(ask_str, buf) BUF2INT(scale_num, buf) BUF2INT(scale_tics, buf) BUF2INT(std_err, buf) BUF2INT(frame_states, tmp) BUF2INT(mkcol, tmp) BUF2INT(show_a_task, cbuf) BUF2INT(show_a_task, rbuf) BUF2INT(rcfiles_read, fbuf) BUF2INT(rcfiles_read, RCfile) BUF2INT(rcfiles_read, RCfile_Sys) BUF2INT(do_key, ColUsername) BUF2INT(mkheadings, CurFields) BUF2INT(mkheadings, ColHeadings) BUF2INT(main, not_really_tmp) #endif /*###### Sort callbacks ################################################*/ static int sort_cmd (proc_t **P, proc_t **Q) { /* if a process doesn't have a cmdline, we'll consider it a kernel thread -- since show_a_task gives such tasks special treatment, we must too */ if (Show_cmdlin && ((*P)->cmdline || (*Q)->cmdline)) { if (!(*P)->cmdline) return SORT_lt; if (!(*Q)->cmdline) return SORT_gt; if ( 0 > strncmp((*P)->cmdline[0], (*Q)->cmdline[0], (unsigned)Max_cmd) ) return SORT_lt; if ( 0 < strncmp((*P)->cmdline[0], (*Q)->cmdline[0], (unsigned)Max_cmd) ) return SORT_gt; } else { /* this part also handles the compare if both are kernel threads */ if ( 0 > strcmp((*P)->cmd, (*Q)->cmd) ) return SORT_lt; if ( 0 < strcmp((*P)->cmd, (*Q)->cmd) ) return SORT_gt; } return 0; } static int sort_cpu (proc_t **P, proc_t **Q) { if ( (*P)->pcpu < (*Q)->pcpu ) return SORT_lt; if ( (*P)->pcpu > (*Q)->pcpu ) return SORT_gt; return 0; } static int sort_mem (proc_t **P, proc_t **Q) { if ( (*P)->resident < (*Q)->resident ) return SORT_lt; if ( (*P)->resident > (*Q)->resident ) return SORT_gt; /* still equal? we'll try to fix that... */ if ( (*P)->size < (*Q)->size ) return SORT_lt; if ( (*P)->size > (*Q)->size ) return SORT_gt; return 0; } static int sort_pid (proc_t **P, proc_t **Q) { if ( (*P)->pid < (*Q)->pid ) return SORT_lt; if ( (*P)->pid > (*Q)->pid ) return SORT_gt; return 0; } static int sort_tme (proc_t **P, proc_t **Q) { if (Show_ctimes) { if ( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) < ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) return SORT_lt; if ( ((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime) > ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime) ) return SORT_gt; } else { if ( ((*P)->utime + (*P)->stime) < ((*Q)->utime + (*Q)->stime)) return SORT_lt; if ( ((*P)->utime + (*P)->stime) > ((*Q)->utime + (*Q)->stime)) return SORT_gt; } return 0; } static int sort_tty (proc_t **P, proc_t **Q) { if ( (*P)->tty < (*Q)->tty ) return SORT_lt; if ( (*P)->tty > (*Q)->tty ) return SORT_gt; /* still equal? we'll try to fix that... */ return sort_pid(P, Q); } static int sort_usr (proc_t **P, proc_t **Q) { if ( 0 > strcmp((*P)->euser, (*Q)->euser) ) return SORT_lt; if ( 0 < strcmp((*P)->euser, (*Q)->euser) ) return SORT_gt; /* still equal? we'll try to fix that... */ return sort_pid(P, Q); } /*###### Tiny useful routine(s) ########################################*/ /* * This routine isolates ALL user input and ensures that we * wont be mixing I/O from stdio and low-level read() requests */ static int chin (int ech, char *buf, unsigned cnt) { int rc; fflush(stdout); if (!ech) rc = read(STDIN_FILENO, buf, cnt); else { tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); rc = read(STDIN_FILENO, buf, cnt); tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty); } /* may be the beginning of a lengthy escape sequence */ tcflush(STDIN_FILENO, TCIFLUSH); return rc; /* note: we do NOT produce a vaid 'string' */ } /* * 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, ...) { static char buf[BIGBUFSIZ]; /* with help stuff, our buffer */ va_list va; /* requirements exceed 1k */ va_start(va, fmts); vsprintf(buf, fmts, va); va_end(va); #ifdef TRAK_MAXBUFS MAXBUFS(fmtmk, buf); #endif return (const char *)buf; } /* * What need be said... */ static char *strim (int sp, char *str) { const char *ws = "\b\f\n\r\t\v"; char *p; /* this guy was originally designed just to trim the rc file lines and any 'open_psdb_message' result which arrived with an inappropriate newline (thanks to 'sysmap_mmap') -- but when tabs (^I) were found in some proc cmdlines, a choice was offered twix space or null... */ if (sp) while ((p = strpbrk(str, ws))) *p = ' '; else if ((p = strpbrk(str, ws))) *p = 0; return str; } /* * This guy just facilitates Batch and protects against dumb ttys. */ static char *tg2 (int x, int y) { return Cap_can_goto ? tgoto(cursor_address, x, y) : (char *)"\n"; } /*###### Misc Color/Highlighting support ###############################*/ /* * Make the appropriate caps/color strings and set some * lengths which are used to distinguish twix the displayed * columns and an actual printf row! * note: we avoid the use of background color so as to maximize * compatibility with the user's xterm settings */ static void capsmk (void) { /* macro to test if a basic (non-color) capability is valid thanks: Floyd Davidson */ #define tIF(s) s ? s : "" static int capsdone = 0; /* just a precaution for the future, we aren't called now... */ if (Batch) return; /* these are the unchangeable puppies, so we only do 'em once */ if (!capsdone) { strcpy(Cap_bold, tIF(enter_bold_mode)); strcpy(Cap_clr_eol, tIF(clr_eol)); strcpy(Cap_clr_eos, tIF(clr_eos)); strcpy(Cap_clr_scr, tIF(clear_screen)); strcpy(Cap_curs_huge, tIF(cursor_visible)); strcpy(Cap_curs_norm, tIF(cursor_normal)); strcpy(Cap_home, tIF(cursor_home)); strcpy(Cap_norm, tIF(exit_attribute_mode)); strcpy(Cap_reverse, tIF(enter_reverse_mode)); sprintf(Caps_off, "%s%s", Cap_norm, tIF(orig_pair)); if (tgoto(cursor_address, 1, 1)) Cap_can_goto = 1; capsdone = 1; } /* now do the changeable guys... */ if (Show_colors && max_colors > 0) { strcpy(Sum_color, tparm(set_a_foreground, Base_color)); sprintf(Msg_color, "%s%s" , tparm(set_a_foreground, Msgs_color), Cap_reverse); sprintf(Pmt_color, "%s%s" , tparm(set_a_foreground, Msgs_color), Cap_bold); sprintf(Hdr_color, "%s%s" , tparm(set_a_foreground, Head_color), Cap_reverse); sprintf(Row_color_norm, "%s%s" , Caps_off, tparm(set_a_foreground, Base_color)); } else { Sum_color[0] = '\0'; strcpy(Msg_color, Cap_reverse); strcpy(Pmt_color, Cap_bold); strcpy(Hdr_color, Cap_reverse); strcpy(Row_color_norm, Cap_norm); } sprintf(Row_color_high, "%s%s" , Row_color_norm, Show_hibold ? Cap_bold : Cap_reverse); Len_row_norm = strlen(Row_color_norm); Len_row_high = strlen(Row_color_high); #undef tIF } /* * Show an error, but not right now. * Due to the postponed opening of ksym, using open_psdb_message, * if P_WCHAN had been selected and the program is restarted, the * message would otherwise be displayed prematurely */ static void msg_save (const char *fmts, ...) { char tmp[SMLBUFSIZ]; va_list va; va_start(va, fmts); vsprintf(tmp, fmts, va); va_end(va); /* we'll add some extra attention grabbers to whatever this is */ sprintf(Msg_delayed, "\a*** %s ***", strim(0, tmp)); Msg_awaiting = 1; } /* * Show an error message (caller may include a '\a' for sound) */ static void show_msg (const char *str) { printf("%s%s %s %s%s" , tg2(0, MSG_line) , Msg_color , str , Caps_off , Cap_clr_eol); fflush(stdout); sleep(MSG_SLEEP); Msg_awaiting = 0; } /* * Show an input prompt + larger cursor */ static void show_pmt (const char *str) { printf("%s%s%s: %s%s" , tg2(0, MSG_line) , Pmt_color , str , Cap_curs_huge , Caps_off); fflush(stdout); } /* * 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 single byte in the range of: * \01 through \07 (more properly \0001 - \0007) * and is used to select an 'attribute' from a capabilities table * which 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 (const char *glob) { static char *captab[] = { /* Cap's/Delim's */ Cap_norm, Cap_norm, Cap_bold, /* \00, \01, \02 */ Sum_color, /* \03 */ Msg_color, Pmt_color, /* \04, \05 */ Hdr_color, /* \06 */ Row_color_high }; /* \07 */ char tmp[BIGBUFSIZ], *cap, *lin_end, *sub_beg, *sub_end; int room; /* handle multiple lines passed in a bunch */ while ((lin_end = strchr(glob, '\n'))) { /* create a local copy we can extend and otherwise abuse */ memcpy(tmp, glob, (unsigned)(lin_end - glob)); #ifdef TRAK_MAXBUFS MAXBUFS(show_special, tmp); #endif /* zero terminate this part and prepare to parse substrings */ tmp[lin_end - glob] = '\0'; room = Screen_cols; sub_beg = sub_end = tmp; while (*sub_beg) { switch (*sub_end) { case '\00' : /* no end delim, captab makes normal */ *(sub_end + 1) = '\0'; /* extend str end, then fall through */ case '\01' ... '\07' : cap = captab[(int)*sub_end]; *sub_end = '\0'; printf("%s%.*s%s", cap, room, sub_beg, Caps_off); room -= (sub_end - sub_beg); sub_beg = ++sub_end; break; default : /* nothin' special, just text */ ++sub_end; } if (0 >= room) break; /* skip substrings that won't fit */ } /* end: while 'subtrings' */ printf("%s\n", Cap_clr_eol); /* emulate truncated newline */ glob = ++lin_end; /* point to next line (maybe) */ } /* 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 -- ok then, we'll just do our 'fit-to-screen' thingy... */ if (strlen(glob)) printf("%.*s", Screen_cols, glob); fflush(stdout); } /* * Change colors used in display */ static void tweak_colors (void) { #define kbdABORT 'q' #define kbdAPPLY '\n' int clrssav = Show_colors, boldsav = Show_hibold, basesav = Base_color, msgssav = Msgs_color, headsav = Head_color; int clr = Base_color, *pclr = &Base_color; char ch, tgt = 'B'; if (0 >= max_colors) { show_msg("\aNo colors to map!"); return; } if (!Show_colors) { Show_colors = 1; capsmk(); } printf("%s%s", Cap_clr_scr, Cap_curs_huge); do { /* this string is well above ISO C89's minimum requirements! */ show_special(fmtmk(COLOR_sample , Cap_home, Myname, procps_version, tgt, clr)); chin(0, &ch, 1); switch (ch) { case 'B' : pclr = &Base_color; clr = *pclr; tgt = ch; break; case 'M' : pclr = &Msgs_color; clr = *pclr; tgt = ch; break; case 'H' : pclr = &Head_color; clr = *pclr; tgt = ch; break; case '0' ... '7' : clr = ch - '0'; break; case 'b' : Show_hibold = !Show_hibold; break; case 'z' : Show_colors = !Show_colors; break; } *pclr = clr; capsmk(); } while (kbdAPPLY != ch && kbdABORT != ch); if (kbdABORT == ch) { Show_colors = clrssav; Show_hibold = boldsav; Base_color = basesav; Msgs_color = msgssav; Head_color = headsav; capsmk(); } putp(Cap_curs_norm); #undef kbdABORT #undef kbdAPPLY } /*###### Small utility routines ########################################*/ /* * Get a string from the user */ static char *ask_str (const char *prompt) { static char buf[GETBUFSIZ]; show_pmt(prompt); memset(buf, '\0', sizeof(buf)); chin(1, buf, sizeof(buf) - 1); putp(Cap_curs_norm); #ifdef TRAK_MAXBUFS MAXBUFS(ask_str, buf); #endif return strim(0, buf); } /* * Get a float from the user */ static float get_float (const char *prompt) { char *line; float f; if (!(*(line = ask_str(prompt)))) return -1; /* note: we're not allowing negative floats */ if (strcspn(line, ".1234567890")) { show_msg("\aNot valid"); return -1; } sscanf(line, "%f", &f); return f; } /* * Get an integer from the user */ static int get_int (const char *prompt) { char *line; int n; if (!(*(line = ask_str(prompt)))) return -1; /* note: we've got to allow negative ints (renice) */ if (strcspn(line, "-1234567890")) { show_msg("\aNot valid"); return -1; } sscanf(line, "%d", &n); return n; } /* * Set the number of fields/columns to display. * Create the field/column headings and set maximum cmdline length. * Establish the heading/summary lines currently in use. * Adjust the number of tasks to display. */ static void mkheadings (void) { const char *h; int i, needpsdb = 0; /* build our PFlags array and establish a tentative NumFields */ for (i = 0, NumFields = 0; i < (int)strlen(CurFields); i++) { if (isupper(CurFields[i])) PFlags[NumFields++] = CurFields[i] - 'A'; } /* build a preliminary ColHeadings not to exceed screen width */ ColHeadings[0] = '\0'; for (i = 0; i < NumFields; i++) { h = Fieldstab[PFlags[i]].head; /* oops, won't fit -- we're outta here... */ if (Screen_cols < (int)(strlen(ColHeadings) + strlen(h))) break; strcat(ColHeadings, h); } /* establish the final NumFields and prepare to grow the command column heading via Max_cmd -- it may be a fib if P_CMD wasn't encountered, but that's ok because it won't be displayed anyway */ NumFields = i; Max_cmd = Screen_cols - (strlen(ColHeadings) - strlen(Fieldstab[P_CMD].head)) - 1; /* now we can build the true run-time ColHeadings and format the command column heading if P_CMD is really being displayed */ ColHeadings[0] = '\0'; for (i = 0; i < NumFields; i++) { /* are we gonna' need the kernel symbol table? */ if (P_WCHAN == PFlags[i]) needpsdb = 1; h = Fieldstab[PFlags[i]].head; if (P_CMD == PFlags[i]) strcat(ColHeadings, fmtmk(Fieldstab[P_CMD].fmts, Max_cmd, Max_cmd, h)); else strcat(ColHeadings, h); } /* set the number of heading lines and calc display height */ HSum_lines = SUMMINLINS; if (HSum_loadav) HSum_lines += 1; if (HSum_states) { if (Show_cpusum) HSum_lines += 2; /* no tellin' how darn many cpus they might have -- if they exceed screen height, they'll have to suffer scroll... (Max_lines may go negative, which is as good as 0) */ else HSum_lines += Cpu_tot + 1; } if (HSum_memory) HSum_lines += 2; Max_lines = Screen_rows - HSum_lines; if (Max_tasks && Max_tasks < Max_lines) Max_lines = Max_tasks; /* do we (still) need the kernel symbol table? */ if (needpsdb) { if (-1 == No_ksyms) { No_ksyms = 0; if (open_psdb_message(NULL, msg_save)) /* why so counter-intuitive, couldn't open_psdb_message mirror sysmap_mmap -- that func does all the work anyway? */ No_ksyms = 1; else PSDBopen = 1; } } #ifdef UGH_ITS_4_RH else if (PSDBopen) { close_psdb(); PSDBopen = 0; No_ksyms = -1; } #endif #ifdef TRAK_MAXBUFS MAXBUFS(mkheadings, CurFields); MAXBUFS(mkheadings, ColHeadings); #endif } /* * Do some scaling stuff. * We'll interpret 'num' as one of the following types and * try to format it to fit 'width'. * SK_no (0) it's a byte count * SK_Kb (1) it's kilobytes * SK_Mb (2) it's megabytes * SK_Gb (3) it's gigabytes */ static char *scale_num (unsigned num, const unsigned width, const unsigned type) { /* kilobytes, megabytes, gigabytes, too-big-for-int-bytes */ static double scale[] = { 1024, 1024*1024, 1024*1024*1024, 0 }; /* kilo, mega, giga, none */ #ifdef UPCASE_SCALE static char nextup[] = { 'K', 'M', 'G', 0 }; #else static char nextup[] = { 'k', 'm', 'g', 0 }; #endif static char buf[TNYBUFSIZ]; double *dp; char *up; /* try an unscaled version first... */ sprintf(buf, "%d", num); #ifdef TRAK_MAXBUFS MAXBUFS(scale_num, buf); #endif if (strlen(buf) <= width) return buf; /* now try successively higher types until it fits */ for (up = nextup + type, dp = scale; *dp; ++dp, ++up) { /* the most accurate version */ sprintf(buf, "%.1f%c", num / *dp, *up); if (strlen(buf) <= width) return buf; /* the integer version */ sprintf(buf, "%d%c", (int)(num / *dp), *up); if (strlen(buf) <= width) return buf; } /* well shoot, this outta' fit... */ return (char *)"?"; } /* * Do some scaling stuff. * Format 'tics' to fit 'width' */ static char *scale_tics (TICS_t tics, const unsigned width) { static struct { unsigned div; const char *fmt; } ttab[] = { /* minutes hours days weeks */ #ifdef UPCASE_SCALE { 60, "%uM" }, { 60, "%uH" }, { 24, "%uD" }, { 7, "%uW" } #else { 60, "%um" }, { 60, "%uh" }, { 24, "%ud" }, { 7, "%uw" } #endif }; static char buf[TNYBUFSIZ]; unsigned i, t; /* try successively higher units until it fits */ t = tics / Hertz; sprintf(buf, "%d:%02d.%02d" /* minutes:seconds.tenths */ , t/60, t%60, (int)((tics*100)/Hertz)%100); #ifdef TRAK_MAXBUFS MAXBUFS(scale_tics, buf); #endif if (strlen(buf) <= width) return buf; sprintf(buf, "%d:%02d", t/60, t%60); /* minutes:seconds */ if (strlen(buf) <= width) return buf; /* try successively: minutes; hours; days; weeks */ for (i = 0; i < MAXtbl(ttab); i++) { t /= ttab[i].div; sprintf(buf, ttab[i].fmt, t); if (strlen(buf) <= width) return buf; }; /* well shoot, this outta' fit... */ return (char *)"?"; } /* * Calculate and return the elapsed time since the last update * which is then used in % CPU calc's. */ static float time_elapsed (void) { static struct timeval oldtimev; struct timeval timev; struct timezone timez; float et; gettimeofday(&timev, &timez); et = (timev.tv_sec - oldtimev.tv_sec) + (float)(timev.tv_usec - oldtimev.tv_usec) / 1000000.0; oldtimev.tv_sec = timev.tv_sec; oldtimev.tv_usec = timev.tv_usec; return et; } /*###### Exit and Signal handled routines ##############################*/ /* * The usual program end -- * called only by functions in this section. */ static void bye_bye (int eno, const char *str) { #ifdef UGH_ITS_4_RH if (PSDBopen) close_psdb(); #endif if (!Batch) tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); printf("%s%s\n", tg2(0, Screen_rows), Cap_curs_norm); if (str) { if (eno) perror(str); else fputs(str, stderr); } #ifdef TRAK_MAXCAPS fprintf(stderr, "\nTRAK_MAXCAPS report:" "\n\tBasic lengths:" "\n\t Cap_home = %d, Cap_bold = %d, Cap_norm = %d, Cap_reverse = %d" "\n\t Cap_clr_eol = %d, Cap_clr_eos = %d, Cap_clr_scr = %d" "\n\t Cap_curs_norm = %d, Cap_curs_huge = %d" "\n\t Caps_off = %d" "\n\tColor lengths:" "\n\t Sum_color = %d, Msg_color = %d, Pmt_color = %d, Hdr_color = %d" "\n\t Row_color_norm = %d, Len_row_norm = '%d'" "\n\t Row_color_high = %d, Len_row_high = '%d'" "\n\tMax_pads = %d, Min_pads = %d" "\n\tMax_rbuf = %d, Min_rbuf = %d" "\n" , strlen(Cap_home), strlen(Cap_bold), strlen(Cap_norm), strlen(Cap_reverse) , strlen(Cap_clr_eol), strlen(Cap_clr_eos), strlen(Cap_clr_scr) , strlen(Cap_curs_norm), strlen(Cap_curs_huge) , strlen(Caps_off) , strlen(Sum_color), strlen(Msg_color), strlen(Pmt_color), strlen(Hdr_color) , strlen(Row_color_norm), Len_row_norm , strlen(Row_color_high), Len_row_high , Max_pads, Min_pads , Max_rbuf, Min_rbuf); #endif #ifdef TRAK_MAXBUFS fprintf(stderr, "\nTRAK_MAXBUFS report:" "\n\tused size\tfunction, buffer" "\n\t---- ----\t-----------------------" ARPTBUF(fmtmk, buf) ARPTBUF(show_special, tmp) ARPTBUF(ask_str, buf) ARPTBUF(scale_num, buf) ARPTBUF(scale_tics, buf) ARPTBUF(std_err, buf) ARPTBUF(frame_states, tmp) ARPTBUF(mkcol, tmp) ARPTBUF(show_a_task, cbuf) ARPTBUF(show_a_task, rbuf) ARPTBUF(rcfiles_read, fbuf) ARPTBUF(rcfiles_read, RCfile) ARPTBUF(rcfiles_read, RCfile_Sys) ARPTBUF(do_key, ColUsername) ARPTBUF(mkheadings, CurFields) ARPTBUF(mkheadings, ColHeadings) ARPTBUF(main, not_really_tmp) "\n" , AUSEBUF(fmtmk, buf), ASIZBUF(fmtmk, buf) , AUSEBUF(show_special, tmp), ASIZBUF(show_special, tmp) , AUSEBUF(ask_str, buf), ASIZBUF(ask_str, buf) , AUSEBUF(scale_num, buf), ASIZBUF(scale_num, buf) , AUSEBUF(scale_tics, buf), ASIZBUF(scale_tics, buf) , AUSEBUF(std_err, buf), ASIZBUF(std_err, buf) , AUSEBUF(frame_states, tmp), ASIZBUF(frame_states, tmp) , AUSEBUF(mkcol, tmp), ASIZBUF(mkcol, tmp) , AUSEBUF(show_a_task, cbuf), ASIZBUF(show_a_task, cbuf) , AUSEBUF(show_a_task, rbuf), ASIZBUF(show_a_task, rbuf) , AUSEBUF(rcfiles_read, fbuf), ASIZBUF(rcfiles_read, fbuf) , AUSEBUF(rcfiles_read, RCfile) , ASIZBUF(rcfiles_read, RCfile) , AUSEBUF(rcfiles_read, RCfile_Sys) , ASIZBUF(rcfiles_read, RCfile_Sys) , AUSEBUF(do_key, ColUsername), ASIZBUF(do_key, ColUsername) , AUSEBUF(mkheadings, CurFields), ASIZBUF(mkheadings, CurFields) , AUSEBUF(mkheadings, ColHeadings), ASIZBUF(mkheadings, ColHeadings) , AUSEBUF(main, not_really_tmp), ASIZBUF(main, not_really_tmp)); #endif #if defined(TRAK_MAXCAPS) || defined(TRAK_MAXBUFS) fprintf(stderr, "\nbye_bye's Summary report:" "\n\tprogram names:" "\n\t Myrealname = %s, Myname = %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\tScreen_cols = %d, Screen_rows = %d" "\n\tNumFields = %d, HSum_lines = %d" "\n\tMax_lines = %d, Max_cmd = %d, Max_tasks = %d" "\n\tPage_size = %d" "\n" , Myrealname, Myname #ifdef PRETENDNOCAP , "dumb" #else , termname() #endif , ttyname(STDOUT_FILENO), NCURSES_VERSION , max_colors, max_pairs , Cap_can_goto ? "yes" : "No!" , Screen_cols, Screen_rows , NumFields, HSum_lines , Max_lines, Max_cmd, Max_tasks , Page_size); #endif if (str && !eno) eno = 1; exit(eno); } /* * Standard error handler to normalize the look of all err o/p */ static void std_err (const char *str) { static char buf[SMLBUFSIZ]; fflush(stdout); /* we'll use our own buffer so callers can still use fmtmk() */ sprintf(buf, "\t%s: %s\n", Myname, str); #ifdef TRAK_MAXBUFS MAXBUFS(std_err, buf); #endif if (!Ttychanged) { fprintf(stderr, buf); exit(1); } /* not to worry, he'll change our exit code to 1 due to 'buf' */ bye_bye(0, buf); } /* * Normal end of execution. * catches: * SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT and SIGTERM */ static void stop (int dont_care_sig) { bye_bye(0, NULL); } /* * Suspend ourself. * catches: * SIGTSTP, SIGTTIN and SIGTTOU */ static void suspend (int dont_care_sig) { /* reset terminal */ tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty); printf("%s%s", tg2(0, Screen_rows), Cap_curs_norm); fflush(stdout); raise(SIGSTOP); /* later, after SIGCONT... */ if (!Batch) tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty); } /* * Set the screen dimensions and call the real workhorse. * catches: * SIGWINCH and SIGCONT */ static void window_resize (int dont_care_sig) { struct winsize w; Screen_cols = columns; Screen_rows = lines; if (-1 != (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w))) { Screen_cols = w.ws_col; Screen_rows = w.ws_row; } /* he'll calculate header size, length of command field, etc ... */ mkheadings(); } /*###### Startup routines ##############################################*/ /* * Parse command line arguments * (and force ol' main into a much needed diet) * 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 user's wishes... */ static void parse_argvs (char **argv) { /* difference(s) from traditional top: -q (zero delay time) eliminated as redundant, use -d0 -p (pid monitoring) allows comma delimited list no deprecated/illegal use of 'goto' with 'breakargv:' bunched args are actually handled properly and none are ignored (we tolerate No whitespace and No switches -- maybe too tolerant) */ const char *usage = " -hv | -bcisS -d delay -n iterations -p pid [,pid ...]\n"; float tmp_delay = MAXFLOAT; char *p; ++argv; while (*argv) { char *cp = *(argv++); while (*cp) { switch (*cp) { case '\0': case '-': break; case 'b': Batch = 1; break; case 'c': Show_cmdlin = !Show_cmdlin; break; case 'd': if (cp[1]) ++cp; else if (*argv) cp = *argv++; else std_err("-d requires argument"); /* a negative delay will be dealt with shortly... */ if (1 != sscanf(cp, "%f", &tmp_delay)) std_err(fmtmk("bad delay '%s'", cp)); break; case 'h': case 'H': case 'v': case 'V': std_err(fmtmk("\t%s\nusage:\t%s%s" , procps_version, Myname, usage)); case 'i': Show_idleps = !Show_idleps; break; case 'n': if (cp[1]) cp++; else if (*argv) cp = *argv++; else std_err("-n requires argument"); if (1 != sscanf(cp, "%d", &Loops) || 0 > Loops) std_err(fmtmk("bad iterations arg '%s'", cp)); break; case 'p': if (cp[1]) cp++; else if (*argv) cp = *argv++; else std_err("-p requires argument"); do { if (Monpidsidx >= MONPIDMAX) std_err(fmtmk("pid limit (%d) exceeded", MONPIDMAX)); if (1 != sscanf(cp, "%d", &Monpids[Monpidsidx]) || 0 > Monpids[Monpidsidx]) std_err(fmtmk("bad pid '%s'", cp)); if (!Monpids[Monpidsidx]) Monpids[Monpidsidx] = getpid(); Monpidsidx++; if (!(p = strchr(cp, ','))) break; cp = ++p; } while (*cp); break; case 's': Secure_mode = 1; break; case 'S': Show_ctimes = !Show_ctimes; break; default : std_err(fmtmk("unknown argument '%c'\nusage:\t%s%s" , *cp, Myname, usage)); } /* end: switch */ /* advance cp and jump over any numerical args used above */ cp += strspn(++cp, "-.1234567890 "); } /* end: while *cp */ } /* end: while *argv */ /* fixup delay time, maybe... */ if (MAXFLOAT != tmp_delay) { if (Secure_mode || 0 > tmp_delay) msg_save("Delay time Not changed"); else Delay_time = tmp_delay; } /* set the default sort type, maybe... */ if (-1 == Sort_type || Monpidsidx) { Sort_type = S_PID; Sort_func = (QSORT_t)sort_pid; } } /* * Parse the options string as read from line two of the "local" * configuration file. */ static void parse_rc (char *opts) { unsigned i; for (i = 0; i < strlen(opts); i++) { switch (opts[i]) { case 'b': Show_hibold = 1; break; case 'c': Show_cmdlin = 1; break; case 'C': Sort_type = S_CMD; Sort_func = (QSORT_t)sort_cmd; break; case 'E': Sort_type = S_USR; Sort_func = (QSORT_t)sort_usr; break; case 'i': Show_idleps = 1; break; case 'I': Irix_mode = 1; break; case 'l': HSum_loadav = 1; break; case 'm': HSum_memory = 1; break; case 'M': Sort_type = S_MEM; Sort_func = (QSORT_t)sort_mem; break; case 'P': Sort_type = S_PID; Sort_func = (QSORT_t)sort_pid; break; case 'R': Sort_normal = 1; break; case 'S': Show_ctimes = 1; break; case 't': HSum_states = 1; break; case 'T': Sort_type = S_TME; Sort_func = (QSORT_t)sort_tme; break; case 'U': Sort_type = S_CPU; Sort_func = (QSORT_t)sort_cpu; break; case 'x': Show_hicols = 1; break; case 'y': Show_hirows = 1; break; case 'Y': Sort_type = S_TTY; Sort_func = (QSORT_t)sort_tty; break; case 'z': Show_colors = 1; break; case '1': Show_cpusum = 1; break; case ' ' : /* these serve as rc file comments */ case '\t': /* and will be treated as 'eol' */ case '\n': return; default : std_err(fmtmk("bad config option - '%c'", opts[i])); } } } /* * Build the two RC file names then try to read 'em. * * '/etc/RCfile_Sys' contains two lines consisting of the secure mode * switch and an update interval. It's presence limits what ordinary * users are allowed to do. * * '$HOME/RCfile' contains five lines. * line 1: specifies the fields that are to be displayed * and their order. Uppercase letters == displayed, * lowercase letters == not shown. * line 2: specifies miscellaneous display options and * toggles along with the prefered sort order. * line 3: specifies maximum processes, 0 == unlimited. * line 4: specifies the update interval. If run in secure * mode, this value will be ignored except for root. * line 5: specifies the 3 basic color values. */ static void rcfiles_read (void) { char fbuf[RCFBUFSIZ]; FILE *fp; strcpy(RCfile_Sys, fmtmk("/etc/%src", Myrealname)); #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, RCfile_Sys); #endif if (getenv("HOME")) strcpy(RCfile, fmtmk("%s%c", getenv("HOME"), '/')); strcat(RCfile, fmtmk(".%src", Myname)); #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, RCfile); #endif fp = fopen(RCfile_Sys, "r"); if (fp) { fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* sys rc file, line #1 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif if (strchr(fbuf, 's')) Secure_mode = 1; fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* sys rc file, line #2 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif fclose(fp); sscanf(fbuf, "%f", &Delay_time); } fp = fopen(RCfile, "r"); if (fp) { fbuf[0] = '\0'; if (fgets(fbuf, sizeof(fbuf), fp)) { /* rc file, line #1 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif strcpy(CurFields, strim(0, fbuf)); /* Now that we've found an rc file, we'll honor those preferences by first turning off everything... */ Irix_mode = 0; HSum_states = HSum_memory = HSum_loadav = Show_cpusum = 0; Show_cmdlin = Show_ctimes = Show_idleps = Sort_normal = 0; Show_colors = Show_hicols = Show_hirows = Show_hibold = 0; fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #2 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif /* we could subsume this next guy since we're the only caller -- but we're both too fat already... */ parse_rc(strim(0, fbuf)); fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #3 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif sscanf(fbuf, "%d", &Max_tasks); fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #4 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif if (!Secure_mode || !getuid()) sscanf(fbuf, "%f", &Delay_time); fbuf[0] = '\0'; fgets(fbuf, sizeof(fbuf), fp); /* rc file, line #5 */ #ifdef TRAK_MAXBUFS MAXBUFS(rcfiles_read, fbuf); #endif sscanf(fbuf, "%d,%d,%d", &Base_color, &Msgs_color, &Head_color); } fclose(fp); } /* protect against meddling leading to a possible fault -- shorter would kinda' work, longer ain't healthy for us! */ if (strlen(CurFields) != strlen(DEF_FIELDS)) strcpy(CurFields, DEF_FIELDS); /* lastly, establish the true runtime secure mode */ Secure_mode = getuid() ? Secure_mode : 0; } /* * Set up the terminal attributes */ static void terminal_set (void) { struct termios newtty; /* first the ncurses part... */ #ifdef PRETENDNOCAP setupterm((char *)"dumb", STDOUT_FILENO, NULL); #else setupterm(NULL, STDOUT_FILENO, NULL); #endif /* now our part... */ if (!Batch) { if (-1 == tcgetattr(STDIN_FILENO, &Savedtty)) std_err("tcgetattr() failed"); capsmk(); newtty = Savedtty; newtty.c_lflag &= ~ICANON; newtty.c_lflag &= ~ECHO; newtty.c_cc[VMIN] = 1; newtty.c_cc[VTIME] = 0; Ttychanged = 1; if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtty)) { putp(Cap_clr_scr); std_err(fmtmk("Failed tty set: %s", strerror(errno))); } tcgetattr(STDIN_FILENO, &Rawtty); putp(Cap_clr_scr); fflush(stdout); } } /*###### Field Selection/Ordering routines #############################*/ /* * Display the current fields and their order. * Upper case indicates a displayed field, display order is * according to the order of the letters. * * A short description of each field is shown as well and is * marked by a leading asterisk (*) if currently displayed. * * After all fields have been displayed, some extra explanatory * text is then output */ static void display_fields (void) { const char *p, *x; int i, cmax = Screen_cols / 2, rmax = Screen_rows - 3; /* we're relying on our callers to first clear the screen -- thus 'fields_toggle' can avoid screen flicker since he's too lazy to handle his own asterisk (*) logic */ putp(Cap_bold); for (i = 0; i < MAXtbl(Fieldstab); ++i) { /* advance past any leading spaces */ for (p = Fieldstab[i].head; ' ' == *p; ++p) ; printf("%s%c %c: %-10s = %s" , tg2((i / rmax) * cmax, (i % rmax) + 3) , strchr(CurFields, i + 'A') ? '*' : ' ' , i + 'A' , p , Fieldstab[i].desc); } putp(Row_color_norm); x = FIELDS_xtra; while ((p = strchr(x, '\n'))) { ++i; printf("%s%.*s" , tg2((i / rmax) * cmax, (i % rmax) + 3) , p - x, x); x = ++p; } putp(Caps_off); } /* * Change order of displayed fields. */ static void fields_reorder (void) { static char prompt[] = "Upper case characters move field left, lower case right"; char c, *p; int i; printf("%s%s", Cap_clr_scr, Cap_curs_huge); display_fields(); do { show_special(fmtmk(FIELDS_current , Cap_home, Myname, CurFields, prompt)); chin(0, &c, 1); i = toupper(c) - 'A'; if (i < 0 || i >= MAXtbl(Fieldstab)) break; if (((p = strchr(CurFields, i + 'A'))) || ((p = strchr(CurFields, i + 'a')))) { if (isupper(c)) p--; if (('\0' != p[1]) && (p >= CurFields)) { c = p[0]; p[0] = p[1]; p[1] = c; } } } while (1); putp(Cap_curs_norm); mkheadings(); } /* * Toggle displayed fields. */ static void fields_toggle (void) { static char prompt[] = "Toggle fields with a-x, type any other key to return"; char c, *p; int i; printf("%s%s", Cap_clr_scr, Cap_curs_huge); do { display_fields(); show_special(fmtmk(FIELDS_current , Cap_home, Myname, CurFields, prompt)); chin(0, &c, 1); i = toupper(c) - 'A'; if (i < 0 || i >= MAXtbl(Fieldstab)) break; if ((p = strchr(CurFields, i + 'A'))) *p = i + 'a'; else if ((p = strchr(CurFields, i + 'a'))) *p = i + 'A'; } while (1); putp(Cap_curs_norm); mkheadings(); } /*###### Library Alternatives ##########################################*/ /* * Handle our own memory stuff without the risk of leaving the * user's terminal in an ugly state should things go sour. */ static const char *alloc_msg = "Failed memory allocate (%d bytes)"; static void *alloc_c (unsigned numb) { void * p; if (!numb) ++numb; if (!(p = calloc(1, numb))) std_err(fmtmk(alloc_msg, numb)); return p; } static void *alloc_r (void *q, unsigned numb) { void *p; if (!numb) ++numb; if (!(p = realloc(q, numb))) std_err(fmtmk(alloc_msg, numb)); return p; } /* * This guy is modeled on libproc's readproctab function except * we reuse and extend any prior proc_t's. He's been customized * for our specific needs and to avoid the use of */ static proc_t **readprocs (proc_t **tbl) { #define PTRsz sizeof(proc_t *) /* eyeball candy */ #define ENTsz sizeof(proc_t) static int flags = PROC_FILLMEM | PROC_FILLCMD | PROC_FILLUSR | PROC_FILLSTATUS | PROC_FILLSTAT; static unsigned savmax = 0; /* first time, bypass: (i) */ proc_t *ptsk = (proc_t *)-1; /* first time, force: (ii) */ unsigned curmax = 0; /* every time */ PROCTAB* PT; if (Monpidsidx) { PT = openproc(flags | PROC_PID, Monpids); /* work around a previous bug in openproc (now corrected) */ PT->procfs = NULL; } else PT = openproc(flags); /* i) Allocated Chunks: *Existing* table; refresh + reuse */ while (curmax < savmax) { if (tbl[curmax]->cmdline) { free(*tbl[curmax]->cmdline); tbl[curmax]->cmdline = NULL; } if (!(ptsk = readproc(PT, tbl[curmax]))) break; ++curmax; } /* ii) Unallocated Chunks: *New* or *Existing* table; extend + fill */ while (ptsk) { /* realloc as we go, keeping 'tbl' ahead of 'currmax++' */ tbl = alloc_r(tbl, (curmax + 1) * PTRsz); /* here, readproc will allocate the underlying proc_t stg */ if ((ptsk = readproc(PT, NULL))) tbl[curmax++] = ptsk; } closeproc(PT); /* iii) Chunkless: make 'eot' entry, after possible extension */ if (curmax >= savmax) { tbl = alloc_r(tbl, (curmax + 1) * PTRsz); /* here, we must allocate the underlying proc_t stg ourselves */ tbl[curmax] = alloc_c(ENTsz); savmax = curmax + 1; } /* this frame's end, but not necessarily end of allocated space */ tbl[curmax]->pid = -1; return tbl; #undef PTRsz #undef ENTsz } /*###### Main screen routines ##########################################*/ /* * Process keyboard input during the main loop plus the three * special immediate keys used with help processing. * (thus making us only slightly recursive) */ static void do_key (unsigned c) { #define kbdCTRL_L 12 /* standardized 'secure mode' errors */ const char *smerror = "\aCan't %s in secure mode"; switch (c) { case 'b': if (!Show_hicols && !Show_hirows) show_msg("\aNothing to highlight!"); else { Show_hibold = !Show_hibold; } capsmk(); break; case 'c': Show_cmdlin = !Show_cmdlin; break; case 'C': Sort_type = S_CMD; Sort_func = (QSORT_t)sort_cmd; break; case 'E': Sort_type = S_USR; Sort_func = (QSORT_t)sort_usr; break; case 'f': case 'F': fields_toggle(); break; case 'i': Show_idleps = !Show_idleps; break; case 'I': if (Cpu_tot > 1) { Irix_mode = !Irix_mode; show_msg(fmtmk("Irix mode %s", Irix_mode ? "On" : "Off")); } else show_msg("\aIrix mode requires SMP!"); break; case 'k': if (Secure_mode) { show_msg(fmtmk(smerror, "kill")); } else { int sig, pid = get_int("PID to kill"); if (-1 != pid) { #ifdef UGH_ITS_4_RH sig = get_signal2( #else sig = signal_name_to_number( #endif ask_str(fmtmk("Kill PID %d with signal [%i]" , pid, DEF_SIGNAL))); if (-1 == sig) sig = DEF_SIGNAL; if (sig && kill(pid, sig)) show_msg(fmtmk("\aKill of PID '%d' with '%d' failed: %s" , pid, sig, strerror(errno))); } } break; case 'l': HSum_loadav = !HSum_loadav; mkheadings(); break; case 'm': HSum_memory = !HSum_memory; mkheadings(); break; case 'M': Sort_type = S_MEM; Sort_func = (QSORT_t)sort_mem; break; case 'n': case '#': { int num; if (-1 != (num = get_int("Processes to display (0 = unlimited)"))) { Max_tasks = num; window_resize(0); } } break; case 'o': case 'O': fields_reorder(); break; case 'P': Sort_type = S_PID; Sort_func = (QSORT_t)sort_pid; break; case 'q': stop(0); case 'r': if (Secure_mode) show_msg(fmtmk(smerror, "renice")); else { int pid, val; pid = get_int("PID to renice"); if (-1 == pid) break; val = get_int(fmtmk("Renice PID %d to value", pid)); if (setpriority(PRIO_PROCESS, (unsigned)pid, val)) show_msg(fmtmk("\aRenice of PID %d to %d failed: %s" , pid, val, strerror(errno))); } break; case 'R': Sort_normal = !Sort_normal; break; case 's': case 'd': if (Secure_mode) show_msg(fmtmk(smerror, "change delay")); else { float tmp = get_float(fmtmk("Change delay from %.1f to", Delay_time)); if (tmp > -1) Delay_time = tmp; } break; case 'S': Show_ctimes = !Show_ctimes; show_msg(fmtmk("Cumulative time %s", Show_ctimes ? "On" : "Off")); break; case 't': HSum_states = !HSum_states; mkheadings(); break; case 'T': Sort_type = S_TME; Sort_func = (QSORT_t)sort_tme; break; case 'u': strcpy(ColUsername, ask_str("Which User (Blank for All)")); #ifdef TRAK_MAXBUFS MAXBUFS(do_key, ColUsername); #endif break; case 'U': Sort_type = S_CPU; Sort_func = (QSORT_t)sort_cpu; break; case 'W': { FILE *fp = fopen(RCfile, "w"); if (fp) { fprintf(fp, "%s\t\t# Fields displayed and ordering\n" , CurFields); fprintf(fp, "%s%s%s%s%s%s%s%s%s%s%s%s%s%c" "\t\t\t\t# Misc options (spelled poorly)\n" , Show_cmdlin ? "c" : "", Sort_normal ? "R" : "" , HSum_loadav ? "l" : "", Show_hirows ? "y" : "" , HSum_memory ? "m" : "", Show_hicols ? "x" : "" , Show_idleps ? "i" : "", Show_ctimes ? "S" : "" , Irix_mode ? "I" : "", HSum_states ? "t" : "" , Show_hibold ? "b" : "", Show_colors ? "z" : "" , Show_cpusum ? "1" : "", Sort_type); fprintf(fp, "%d\t\t\t\t\t# Number of tasks shown\n" , Max_tasks); fprintf(fp, "%.1f\t\t\t\t\t# Delay between updates\n" , Delay_time); fprintf(fp, "%u,%u,%u\t\t\t\t\t# Base, Msgs & Head colors\n" , Base_color, Msgs_color, Head_color); fclose(fp); show_msg(fmtmk("Wrote configuration to '%s'", RCfile)); } else show_msg(fmtmk("\aFailed '%s' open: %s", RCfile, strerror(errno))); } break; case 'x': Show_hicols = !Show_hicols; capsmk(); break; case 'y': Show_hirows = !Show_hirows; capsmk(); break; case 'Y': Sort_type = S_TTY; Sort_func = (QSORT_t)sort_tty; break; case 'z': Show_colors = !Show_colors; capsmk(); break; case 'Z': tweak_colors(); break; case '?': case 'h': { char ch; printf("%s%s", Cap_clr_scr, Cap_curs_huge); /* this string is well above ISO C89's minimum requirements! */ show_special(fmtmk(HELP_data , Myname, procps_version , Secure_mode ? "On" : "Off", Show_ctimes ? "On" : "Off", Delay_time , Secure_mode ? "" : HELP_unsecured)); chin(0, &ch, 1); putp(Cap_curs_norm); /* our help screen currently provides for three 'immediate' keys, two of which conflict with the main process display keys */ switch (ch) { case 'j' : strcpy(CurFields, JOB_FIELDS); ch = 'T'; /* force sort on time */ break; case 'm' : strcpy(CurFields, MEM_FIELDS); ch = 'M'; /* force sort on %mem/res */ break; case 'u' : strcpy(CurFields, USR_FIELDS); ch = 'E'; /* force sort on user */ break; default : ch = 0; } if (ch) { /* besides resetting the sort environment with the call to us below, the 'Show_' manipulations will provide our most subtle hint as to what the user has just wrought */ Show_hibold = Show_hicols = Sort_normal = 1; capsmk(); mkheadings(); do_key((unsigned)ch); } } break; case ' ': case kbdCTRL_L: putp(Cap_clr_scr); break; case '1': Show_cpusum = !Show_cpusum; mkheadings(); break; case '\n': /* just ignore it */ break; default: show_msg("\aUnknown command -- try 'h' for help"); } #undef kbdCTRL_L #undef smERROR } #ifdef UGH_ITS_4_RH /* * Obtain memory information and display it. * Return the total memory available as a page count which is * then used in % memory calc's. */ static unsigned frame_memory (void) { /* don't be mislead by the proc/sysinfo subscripts, they're just poorly chosen names for enumerations apparently designed to make source lines as imbalanced and as long as possible */ unsigned long long **memarray; if (!(memarray = meminfo())) std_err("Failed /proc/meminfo read"); if (HSum_memory) { show_special(fmtmk(MEMORY_line1 , BYTES_2K(memarray[meminfo_main][meminfo_total]) , BYTES_2K(memarray[meminfo_main][meminfo_used]) , BYTES_2K(memarray[meminfo_main][meminfo_free]) , BYTES_2K(memarray[meminfo_main][meminfo_buffers]))); show_special(fmtmk(MEMORY_line2 , BYTES_2K(memarray[meminfo_swap][meminfo_total]) , BYTES_2K(memarray[meminfo_swap][meminfo_used]) , BYTES_2K(memarray[meminfo_swap][meminfo_free]) , BYTES_2K(memarray[meminfo_total][meminfo_cached]))); } return PAGE_CNT(memarray[meminfo_main][meminfo_total]); } #else /* * Obtain memory information and display it. */ static void frame_memory (void) { meminfo(); if (HSum_memory) { show_special(fmtmk(MEMORY_line1 , kb_main_total , kb_main_used , kb_main_free , kb_main_buffers)); show_special(fmtmk(MEMORY_line2 , kb_swap_total , kb_swap_used , kb_swap_free , kb_main_cached)); } } #endif /* end: UGH_ITS_4_RH */ /* * State display *Helper* function to calc and display the state * percentages for a single cpu. In this way, we can support * the following environments without the usual code bloat. * 1 - single cpu machines * 2 - modest smp boxes with room for each cpu's percentages * 3 - massive smp guys leaving little or no room for process * display and thus requiring the Show_cpusum toggle */ static void frame_smp (FILE *fp, const char *fmt, CPUS_t *cpu, const char *pfx) { /* we'll trim to zero if we get negative time ticks, which has happened with some SMP kernels (pre-2.4?) */ #define TRIMz(x) ((tz = (long)x) < 0 ? 0 : tz) TICS_t u_tics, s_tics, n_tics, i_tics; long u_frme, s_frme, n_frme, i_frme, tot_frme, tz; if (4 != fscanf(fp, fmt, &u_tics, &n_tics, &s_tics, &i_tics)) std_err("Failed /proc/stat read"); u_frme = TRIMz(u_tics - cpu->u); s_frme = TRIMz(s_tics - cpu->s); n_frme = TRIMz(n_tics - cpu->n); i_frme = TRIMz(i_tics - cpu->i); tot_frme = u_frme + s_frme + n_frme + i_frme; if (1 > tot_frme) tot_frme = 1; /* display some kinda' cpu state percentages (who or what is explained by the passed prefix) */ show_special(fmtmk(STATES_line2 , pfx , (float)u_frme * 100 / tot_frme , (float)s_frme * 100 / tot_frme , (float)n_frme * 100 / tot_frme , (float)i_frme * 100 / tot_frme)); /* remember for next time around */ cpu->u = u_tics; cpu->s = s_tics; cpu->n = n_tics; cpu->i = i_tics; #undef TRIMz } /* * Calc the number of tasks in each state (run, sleep, etc) * Calc percent cpu usage for each task (pcpu) * Calc the cpu(s) percent in each state (user, system, nice, idle) */ static void frame_states (proc_t **p, int show) { static HIST_t *hist_sav = NULL; static unsigned hist_siz; static int hist_tot, showsav; static CPUS_t *smpcpu; HIST_t *hist_new; unsigned total, running, sleeping, stopped, zombie; float etime; int i; if (!hist_sav) { hist_tot = 0; /* room for 512 HIST_t's (if Page_size == 4k) */ hist_siz = (Page_size / sizeof(HIST_t)); hist_sav = alloc_c(hist_siz); /* note: we allocate one more CPUS_t than Cpu_tot so that the last slot can hold tics representing the /proc/stat cpu summary (first line read) -- that slot supports Show_cpusum */ smpcpu = alloc_c((1 + Cpu_tot) * sizeof(CPUS_t)); showsav = Show_cpusum; } hist_new = alloc_c(hist_siz); total = running = sleeping = stopped = zombie = 0; etime = time_elapsed(); /* make a pass through the data to get stats */ while (-1 != p[total]->pid) { TICS_t tics; proc_t *this = p[total]; switch (this->state) { case 'S': case 'D': sleeping++; break; case 'T': stopped++; break; case 'Z': zombie++; break; case 'R': running++; break; } if (total * sizeof(HIST_t) >= hist_siz) { hist_siz += (Page_size / sizeof(HIST_t)); hist_sav = alloc_r(hist_sav, hist_siz); hist_new = alloc_r(hist_new, hist_siz); } /* calculate time in this process; the sum of user time (utime) + system time (stime) -- but PLEASE dont waste time and effort calculating and saving data that goes unused, like the old top! */ hist_new[total].pid = this->pid; hist_new[total].tics = tics = (this->utime + this->stime); /* find matching entry from previous pass and make ticks elapsed */ for (i = 0; i < hist_tot; i++) { if (this->pid == hist_sav[i].pid) { tics -= hist_sav[i].tics; break; } } /* finally calculate an integer version of %cpu for this task and plug it into the unfilled slot in proc_t */ this->pcpu = (tics * 1000 / Hertz) / etime; if (this->pcpu > 999) this->pcpu = 999; /* if in Solaris mode, adjust cpu percentage not only for the cpu the process is running on, but for all cpus together */ if (!Irix_mode) this->pcpu /= Cpu_tot; total++; } /* end: while 'p[total]->pid' */ if (show) { FILE *fp; /* whoa, we've changed modes -- gotta' clean old histories */ if (Show_cpusum != showsav) { /* fresh start for the last slot in the history area */ if (Show_cpusum) memset(&smpcpu[Cpu_tot], '\0', sizeof(CPUS_t)); /* fresh start for the true smpcpu history area */ else memset(smpcpu, '\0', Cpu_tot * sizeof(CPUS_t)); showsav = Show_cpusum; } /* display Task states */ show_special(fmtmk(STATES_line1 , total, running, sleeping, stopped, zombie)); /* now arrange to calculate and display states for all Cpu's */ if (!(fp = fopen("/proc/stat", "r"))) std_err(fmtmk("Failed /proc/stat open: %s", strerror(errno))); if (Show_cpusum) /* retrieve and display just the 1st /proc/stat line */ frame_smp(fp, CPU_FMTS_JUST1, &smpcpu[Cpu_tot], "Cpu(s) state:"); else { char tmp[SMLBUFSIZ]; /* skip the 1st line, which reflects total cpu states */ if (!fgets(tmp, sizeof(tmp), fp)) std_err("Failed /proc/stat read"); #ifdef TRAK_MAXBUFS MAXBUFS(frame_states, tmp); #endif /* now do each cpu's states separately */ for (i = 0; i < Cpu_tot; i++) { sprintf(tmp, "%-6scpu%-2d:" /* [ cpu states as ] */ , i ? " " : "State" /* 'State cpu0 : ... ' */ , Irix_mode ? i : Cpu_map[i]); /* ' cpu1 : ... ' */ frame_smp(fp, CPU_FMTS_MULTI, &smpcpu[i], tmp); } } fclose(fp); } /* end: if 'show' */ /* save this frame's information */ hist_tot = total; memcpy(hist_sav, hist_new, hist_siz); free(hist_new); /* finally, sort the processes on whatever... */ qsort(p, total, sizeof(proc_t *), (QSORT_t)Sort_func); } /* * Task display *Helper* function to handle highlighted * column transitions. */ static void mkcol (unsigned idx, int sta, int *pad, char *buf, ...) { char tmp[COLBUFSIZ]; va_list va; va_start(va, buf); if (!Show_hicols || Sort_type != Fieldstab[idx].sort) { vsprintf(buf, Fieldstab[idx].fmts, va); } else { vsprintf(tmp, Fieldstab[idx].fmts, va); sprintf(buf, "%s%s", Row_color_high, tmp); *pad += Len_row_high; if (!Show_hirows || 'R' != sta) { strcat(buf, Row_color_norm); *pad += Len_row_norm; } } va_end(va); #ifdef TRAK_MAXBUFS MAXBUFS(mkcol, tmp); #endif } /* * Displays information for a single task. */ #ifdef UGH_ITS_4_RH static void show_a_task (proc_t *task, unsigned mempgs) #else static void show_a_task (proc_t *task) #endif { /* the following macro is used for those columns that are NOT sortable so as to avoid the function call overhead since mkcol cannot be made inline -- if additional sort columns are added, change the appropriate switch label's usage to lower case and thus invoke the real function */ #define MKCOL(idx,sta,pad,buf,arg) \ sprintf(buf, Fieldstab[idx].fmts, arg) char rbuf[ROWBUFSIZ]; int i, x, pad; pad = 0; rbuf[0] = '\0'; for (i = 0; i < NumFields; i++) { char cbuf[COLBUFSIZ]; unsigned f, s, w; cbuf[0] = '\0'; f = PFlags[i]; s = Fieldstab[f].scale; w = Fieldstab[f].width; switch (f) { case P_CMD: { char *cmdptr, cmdnam[ROWBUFSIZ]; if (Show_cmdlin) { cmdnam[0] = '\0'; if (task->cmdline) { x = 0; do { /* whoa, during a kernel build, parts of the make process will create cmdlines in excess of 3000 bytes but *without* the typical intervening nulls */ strcat(cmdnam , fmtmk("%.*s ", Max_cmd, task->cmdline[x++])); /* whoa, gnome's xscreensaver had a ^I in his cmdline creating a line wrap when the window was maximized & the tab came into view -- that in turn, whacked our heading lines so we'll strim those !#@*#!... */ strim(1, cmdnam); /* enough, don't ya think? */ if (Max_cmd < (int)strlen(cmdnam)) break; } while (task->cmdline[x]); } else { /* if this process really doesn't have a cmdline, we'll consider it a kernel thread and display it uniquely [ we need sort_cmd's complicity in this plot ] */ strcpy(cmdnam, fmtmk("( %s )", task->cmd)); } cmdptr = cmdnam; } else cmdptr = task->cmd; /* hurry up, before cmdptr goes out of scope... */ mkcol(f, task->state, &pad, cbuf, Max_cmd, Max_cmd, cmdptr); } break; case P_CODE: MKCOL(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->trs), w, s)); break; case P_CPU: mkcol(f, task->state, &pad, cbuf, (float)task->pcpu / 10); break; case P_DATA: MKCOL(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->drs), w, s)); break; case P_DIRTY: MKCOL(f, task->state, &pad, cbuf , scale_num((unsigned)task->dt, w, s)); break; case P_FAULT: MKCOL(f, task->state, &pad, cbuf , scale_num(task->maj_flt, w, s)); break; case P_FLAGS: MKCOL(f, task->state, &pad, cbuf, task->flags); for (x = 0; x < (int)strlen(cbuf); x++) if ('0' == cbuf[x]) cbuf[x] = '.'; break; case P_GROUP: MKCOL(f, task->state, &pad, cbuf, task->egroup); break; case P_MEM: mkcol(f, task->state, &pad, cbuf #ifdef UGH_ITS_4_RH , (float)task->resident * 100 / mempgs); #else , (float)PAGES_2K(task->resident) * 100 / kb_main_total); #endif break; case P_NCPU: #ifdef UGH_ITS_4_RH MKCOL(f, task->state, &pad, cbuf, task->lproc); #else MKCOL(f, task->state, &pad, cbuf, task->processor); #endif break; case P_NI: MKCOL(f, task->state, &pad, cbuf, task->nice); break; case P_PID: mkcol(f, task->state, &pad, cbuf, task->pid); break; case P_PPID: MKCOL(f, task->state, &pad, cbuf, task->ppid); break; case P_PR: MKCOL(f, task->state, &pad, cbuf, task->priority); break; case P_RES: /* 'rss' vs 'resident' (which includes IO memory) ? -- we'll ensure that VIRT = SWAP + RES */ mkcol(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->resident), w, s)); break; case P_SHR: MKCOL(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->share), w, s)); break; case P_STA: MKCOL(f, task->state, &pad, cbuf, status(task)); break; case P_SWAP: MKCOL(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->size - task->resident), w, s)); break; case P_TIME: case P_TIME2: { TICS_t t; t = task->utime + task->stime; if (Show_ctimes) t += (task->cutime + task->cstime); mkcol(f, task->state, &pad, cbuf, scale_tics(t, w)); } break; case P_TTY: { char tmp[TNYBUFSIZ]; dev_to_tty(tmp, Fieldstab[f].width , task->tty, task->pid, ABBREV_DEV); mkcol(f, task->state, &pad, cbuf, tmp); } break; case P_UID: MKCOL(f, task->state, &pad, cbuf, task->euid); break; case P_USER: mkcol(f, task->state, &pad, cbuf, task->euser); break; case P_VIRT: MKCOL(f, task->state, &pad, cbuf , scale_num(PAGES_2K(task->size), w, s)); break; case P_WCHAN: if (No_ksyms) #ifdef UPCASE_HEXES MKCOL(f, task->state, &pad, cbuf , fmtmk("x%08lX", (long)task->wchan)); #else MKCOL(f, task->state, &pad, cbuf , fmtmk("x%08lx", (long)task->wchan)); #endif else MKCOL(f, task->state, &pad, cbuf, wchan(task->wchan)); break; } /* end: switch 'PFlags[i]' */ #ifdef TRAK_MAXBUFS MAXBUFS(show_a_task, cbuf); #endif strcat(rbuf, cbuf); } /* end: for 'NumFields' */ #ifdef TRAK_MAXBUFS MAXBUFS(show_a_task, rbuf); #endif /* This row buffer could be stuffed with parameterized strings. We are thus advised to always use tputs/putp, but it works just fine with good ol' printf... */ printf("\n%s%.*s%s%s" , (Show_hirows && 'R' == task->state) ? Row_color_high : Row_color_norm , Screen_cols + pad , rbuf , Caps_off , Cap_clr_eol); #ifdef TRAK_MAXCAPS if (pad > Max_pads) Max_pads = pad; if (pad < Min_pads) Min_pads = pad; /* now that we have TRAK_MAXBUFS, the next two duplicate some effort */ if ((int)strlen(rbuf) > Max_rbuf) Max_rbuf = (int)strlen(rbuf); if ((int)strlen(rbuf) < Min_rbuf) Min_rbuf = (int)strlen(rbuf); #endif #undef MKCOL } /* * Read all process info and display it. */ static void show_everything (void) { static proc_t **p_table = NULL; int ntask, nline; #ifdef UGH_ITS_4_RH unsigned mempgs; #endif if (!p_table) { p_table = readprocs(p_table); frame_states(p_table, 0); sleep(1); } else putp(Batch ? "\n" : Cap_home); /* ** Display Load averages */ if (HSum_loadav) show_special(fmtmk(LOADAV_line, Myname, sprint_uptime())); /* ** Display System stats (also calc 'pcpu' and sort p_table) */ p_table = readprocs(p_table); frame_states(p_table, HSum_states); /* ** Display Memory and Swap space usage */ #ifdef UGH_ITS_4_RH mempgs = frame_memory(); #else frame_memory(); #endif /* ** Display Headings for columns */ printf("%s%s%s%s%s" , tg2(0, MSG_line + 1) , Hdr_color , ColHeadings , Caps_off , Cap_clr_eol); /* Finally! Loop through to find each task, and display it ... ... lather, rinse, repeat */ ntask = nline = 0; while (-1 != p_table[ntask]->pid && nline < Max_lines) { if ((Show_idleps || ('S' != p_table[ntask]->state && 'Z' != p_table[ntask]->state)) && ((!ColUsername[0]) || (!strcmp(ColUsername, p_table[ntask]->euser)) ) ) { /* ** Display a process Row */ #ifdef UGH_ITS_4_RH show_a_task(p_table[ntask], mempgs); #else show_a_task(p_table[ntask]); #endif if (!Batch) ++nline; } ++ntask; } printf("%s%s%s", Cap_clr_eos, tg2(0, MSG_line), Cap_clr_eol); fflush(stdout); } /*###### Entry point ###################################################*/ int main (int dont_care_argc, char **argv) { char not_really_tmp[OURPATHSZ]; int i; /* setup our program name(s)... */ Myname = strrchr(argv[0], '/'); if (Myname) ++Myname; else Myname = argv[0]; Myrealname = Myname; memset(not_really_tmp, '\0', sizeof(not_really_tmp)); /* proper permissions should deny symlinks to /usr/bin for ordinary users, but root may have employed them -- Myrealname will be used in constructing the global rc filename ... */ if (-1 != readlink(argv[0], not_really_tmp, sizeof(not_really_tmp) - 1)) { Myrealname = strrchr(not_really_tmp, '/'); if (Myrealname) ++Myrealname; else Myrealname = not_really_tmp; #ifdef TRAK_MAXBUFS MAXBUFS(main, not_really_tmp); #endif } /* setup some important system stuff... */ Page_size = getpagesize(); Cpu_tot = sysconf(_SC_NPROCESSORS_ONLN); if (1 > Cpu_tot) Cpu_tot = 1; Cpu_map = alloc_r(NULL, sizeof(int) * Cpu_tot); for (i = 0; i < Cpu_tot; i++) Cpu_map[i] = i; rcfiles_read(); parse_argvs(argv); terminal_set(); window_resize(0); /* set up signal handlers */ signal(SIGALRM, stop); signal(SIGHUP, stop); signal(SIGINT, stop); signal(SIGPIPE, stop); signal(SIGQUIT, stop); signal(SIGTERM, stop); signal(SIGTSTP, suspend); signal(SIGTTIN, suspend); signal(SIGTTOU, suspend); signal(SIGCONT, window_resize); signal(SIGWINCH, window_resize); /* loop, collecting process info and sleeping */ do { struct timeval tv; fd_set fs; char c; show_everything(); if (Msg_awaiting) show_msg(Msg_delayed); if (0 < Loops) --Loops; if (!Loops) stop(0); if (Batch) sleep((unsigned)Delay_time); else { /* Linux reports time not slept, so we must reinit every time */ tv.tv_sec = Delay_time; tv.tv_usec = (Delay_time - (int)Delay_time) * 1000000; FD_ZERO(&fs); FD_SET(STDIN_FILENO, &fs); if (0 < select(STDIN_FILENO+1, &fs, NULL, NULL, &tv) && 0 < chin(0, &c, 1)) do_key((unsigned)c); } } while (1); return 0; }