1/* top.c - Source file:         show Linux processes */
2/*
3 * Copyright (c) 2002, by:      James C. Warner
4 *    All rights reserved.      8921 Hilloway Road
5 *                              Eden Prairie, Minnesota 55347 USA
6 *                             <warnerjc@worldnet.att.net>
7 *
8 * This file may be used subject to the terms and conditions of the
9 * GNU Library General Public License Version 2, or any later version
10 * at your option, as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU Library General Public License for more details.
15 *
16 * For their contributions to this program, the author wishes to thank:
17 *    Albert D. Cahalan, <albert@users.sf.net>
18 *    Craig Small, <csmall@small.dropbear.id.au>
19 *
20 * Changes by Albert Cahalan, 2002.
21 */
22#include <sys/ioctl.h>
23#include <sys/resource.h>
24#include <sys/time.h>
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <ctype.h>
28#include <curses.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <signal.h>
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <term.h>
37#include <termios.h>
38#include <time.h>
39#include <unistd.h>
40#include <values.h>
41
42#include "proc/devname.h"
43#include "proc/wchan.h"
44#include "proc/procps.h"
45#include "proc/readproc.h"
46#include "proc/escape.h"
47#include "proc/sig.h"
48#ifdef USE_LIB_STA3
49#include "proc/status.h"
50#endif
51#include "proc/sysinfo.h"
52#include "proc/version.h"
53#include "proc/whattime.h"
54
55#include "top.h"
56
57/*######  Miscellaneous global stuff  ####################################*/
58	/* Used for recording data to and reading data from a file. */
59static FILE *outfile;
60static FILE *datafile;
61static int o_flag;
62	/* The original and new terminal attributes */
63static struct termios Savedtty, Rawtty;
64static int Ttychanged = 0;
65
66	/* Program name used in error messages and local 'rc' file name */
67static char *Myname;
68
69	/* Name of user config file (dynamically constructed) and our
70	   'Current' rcfile contents, initialized with defaults but may be
71	   overridden with the local rcfile (old or new-style) values */
72static char Rc_name[OURPATHSZ];
73static RCF_t Rc = DEF_RCFILE;
74
75	/* The run-time acquired page size */
76static int Page_size;
77
78	/* SMP, Irix/Solaris mode, Linux 2.5.xx support */
79static int Cpu_tot, *Cpu_map;
80	/* assume no IO-wait stats, overridden if linux 2.5.41 */
81static const char *States_fmts = STATES_line2x4;
82
83	/* Specific process id monitoring support */
84static pid_t Monpids[MONPIDMAX] = { 0 };
85
86static int Monpidsidx = 0;
87
88	/* A postponed error message */
89static char Msg_delayed[SMLBUFSIZ];
90static int Msg_awaiting = 0;
91
92	/* Configurable Display support ################################## */
93
94	/* Current screen dimensions.
95	   note: the number of processes displayed is tracked on a per window
96	   basis (see the WIN_t).  Max_lines is the total number of
97	   screen rows after deducting summary information overhead. */
98	/* Current terminal screen size. */
99static int Screen_cols, Screen_rows, Max_lines;
100
101	/* This is really the number of lines needed to display the summary
102	   information (0 - nn), but is used as the relative row where we
103	   stick the cursor between frames. */
104static int Msg_row;
105
106	/* Global/Non-windows mode stuff that is NOT persistent */
107static int No_ksyms = -1,	// set to '0' if ksym avail, '1' otherwise
108    PSDBopen = 0,		// set to '1' if psdb opened (now postponed)
109    Batch = 0,			// batch mode, collect no input, dumb output
110    Loops = -1,			// number of iterations, -1 loops forever
111    Secure_mode = 0;		// set if some functionality restricted
112
113	/* Some cap's stuff to reduce runtime calls --
114	   to accomodate 'Batch' mode, they begin life as empty strings */
115static char Cap_clr_eol[CAPBUFSIZ] = "",
116    Cap_clr_eos[CAPBUFSIZ] = "",
117    Cap_clr_scr[CAPBUFSIZ] = "",
118    Cap_curs_norm[CAPBUFSIZ] = "",
119    Cap_curs_huge[CAPBUFSIZ] = "",
120    Cap_home[CAPBUFSIZ] = "",
121    Cap_norm[CAPBUFSIZ] = "",
122    Cap_reverse[CAPBUFSIZ] = "", Caps_off[CAPBUFSIZ] = "";
123static int Cap_can_goto = 0;
124
125	/* Some optimization stuff, to reduce output demands...
126	   The Pseudo_ guys are managed by wins_resize and frame_make.  They
127	   are exploited in a macro and represent 90% of our optimization.
128	   The Stdout_buf is transparent to our code and regardless of whose
129	   buffer is used, stdout is flushed at frame end or if interactive. */
130static char *Pseudo_scrn;
131static int Pseudo_row, Pseudo_cols, Pseudo_size;
132#ifndef STDOUT_IOLBF
133	// less than stdout's normal buffer but with luck mostly '\n' anyway
134static char Stdout_buf[2048];
135#endif
136
137	/* ////////////////////////////////////////////////////////////// */
138	/* Special Section: multiple windows/field groups  --------------- */
139
140	/* The pointers to our four WIN_t's, and which of those is considered
141	   the 'current' window (ie. which window is associated with any summ
142	   info displayed and to which window commands are directed) */
143static WIN_t Winstk[GROUPSMAX], *Curwin;
144
145	/* Frame oriented stuff that can't remain local to any 1 function
146	   and/or that would be too cumbersome managed as parms,
147	   and/or that are simply more efficiently handled as globals
148	   (first 2 persist beyond a single frame, changed infrequently) */
149static int Frames_libflags;	// PROC_FILLxxx flags (0 = need new)
150//atic int       Frames_maxcmdln;       // the largest from the 4 windows
151static unsigned Frame_maxtask;	// last known number of active tasks
152					// ie. current 'size' of proc table
153static unsigned Frame_running,	// state categories for this frame
154 Frame_sleepin, Frame_stopped, Frame_zombied;
155static float Frame_tscale;	// so we can '*' vs. '/' WHEN 'pcpu'
156static int Frame_srtflg,	// the subject window's sort direction
157 Frame_ctimes,			// the subject window's ctimes flag
158 Frame_cmdlin;			// the subject window's cmdlin flag
159	/* ////////////////////////////////////////////////////////////// */
160
161/*######  Sort callbacks  ################################################*/
162
163	/*
164	 * These happen to be coded in the same order as the enum 'pflag'
165	 * values.  Note that 2 of these routines serve double duty --
166	 * 2 columns each.
167	 */
168
169SCB_NUMx(P_PID, pid)
170    SCB_NUMx(P_PPD, ppid)
171    SCB_STRx(P_URR, ruser)
172    SCB_NUMx(P_UID, euid)
173    SCB_STRx(P_URE, euser)
174    SCB_STRx(P_GRP, egroup)
175    SCB_NUMx(P_TTY, tty)
176    SCB_NUMx(P_PRI, priority)
177    SCB_NUMx(P_NCE, nice)
178    SCB_NUMx(P_CPN, processor)
179    SCB_NUM1(P_CPU, pcpu)
180    // also serves P_TM2 !
181static int sort_P_TME(const proc_t ** P, const proc_t ** Q)
182{
183	if (Frame_ctimes) {
184		if (((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime)
185		    < ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime))
186			return SORT_lt;
187		if (((*P)->cutime + (*P)->cstime + (*P)->utime + (*P)->stime)
188		    > ((*Q)->cutime + (*Q)->cstime + (*Q)->utime + (*Q)->stime))
189			return SORT_gt;
190	} else {
191		if (((*P)->utime + (*P)->stime) < ((*Q)->utime + (*Q)->stime))
192			return SORT_lt;
193		if (((*P)->utime + (*P)->stime) > ((*Q)->utime + (*Q)->stime))
194			return SORT_gt;
195	}
196	return SORT_eq;
197}
198
199SCB_NUM1(P_VRT, size)
200    SCB_NUM2(P_SWP, size, resident)
201    SCB_NUM1(P_RES, resident)	// also serves P_MEM !
202    SCB_NUM1(P_COD, trs)
203    SCB_NUM1(P_DAT, drs)
204    SCB_NUM1(P_SHR, share)
205    SCB_NUM1(P_FLT, maj_flt)
206    SCB_NUM1(P_DRT, dt)
207    SCB_NUMx(P_STA, state)
208
209static int sort_P_CMD(const proc_t ** P, const proc_t ** Q)
210{
211	/* if a process doesn't have a cmdline, we'll consider it a kernel thread
212	   -- since displayed tasks are given special treatment, we must too */
213	if (Frame_cmdlin && ((*P)->cmdline || (*Q)->cmdline)) {
214		if (!(*Q)->cmdline)
215			return Frame_srtflg * -1;
216		if (!(*P)->cmdline)
217			return Frame_srtflg;
218		return Frame_srtflg *
219		    strncmp((*Q)->cmdline[0], (*P)->cmdline[0],
220			    (unsigned)Curwin->maxcmdln);
221	}
222	// this part also handles the compare if both are kernel threads
223	return Frame_srtflg * strcmp((*Q)->cmd, (*P)->cmd);
224}
225
226SCB_NUM1(P_WCH, wchan)
227    SCB_NUM1(P_FLG, flags)
228
229    /* ///////////////////////////////// special sort for prochlp() ! */
230static int sort_HST_t(const HST_t * P, const HST_t * Q)
231{
232	return P->pid - Q->pid;
233}
234
235/*######  Tiny useful routine(s)  ########################################*/
236
237	/*
238	 * This routine isolates ALL user INPUT and ensures that we
239	 * wont be mixing I/O from stdio and low-level read() requests */
240static int chin(int ech, char *buf, unsigned cnt)
241{
242	int rc;
243
244	fflush(stdout);
245	if (!ech)
246		rc = read(STDIN_FILENO, buf, cnt);
247	else {
248		tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty);
249		rc = read(STDIN_FILENO, buf, cnt);
250		tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty);
251	}
252	/* may be the beginning of a lengthy escape sequence  */
253	tcflush(STDIN_FILENO, TCIFLUSH);
254	return rc;		/* note: we do NOT produce a vaid 'string' */
255}
256
257	/*
258	 * This routine simply formats whatever the caller wants and
259	 * returns a pointer to the resulting 'const char' string... */
260static const char *fmtmk(const char *fmts, ...)
261{
262	static char buf[BIGBUFSIZ];	/* with help stuff, our buffer */
263	va_list va;		/* requirements exceed 1k */
264
265	va_start(va, fmts);
266	vsnprintf(buf, sizeof(buf), fmts, va);
267	va_end(va);
268	return (const char *)buf;
269}
270
271	/*
272	 * This guy is just our way of avoiding the overhead of the standard
273	 * strcat function (should the caller choose to participate) */
274static inline char *scat(char *restrict dst, const char *restrict src)
275{
276	while (*dst)
277		dst++;
278	while ((*(dst++) = *(src++))) ;
279	return --dst;
280}
281
282// Trim the rc file lines and any 'open_psdb_message' result which arrives
283// with an inappropriate newline (thanks to 'sysmap_mmap')
284static char *strim_0(char *str)
285{
286	static const char ws[] = "\b\e\f\n\r\t\v\x9b";	// 0x9b is an escape
287	char *p;
288
289	if ((p = strpbrk(str, ws)))
290		*p = 0;
291	return str;
292}
293
294	/*
295	 * This guy just facilitates Batch and protects against dumb ttys
296	 * -- we'd 'inline' him but he's only called twice per frame,
297	 * yet used in many other locations. */
298static const char *tg2(int x, int y)
299{
300	return Cap_can_goto ? tgoto(cursor_address, x, y) : "";
301}
302
303/*######  Exit/Interrput routines  #######################################*/
304
305	/*
306	 * The usual program end --
307	 * called only by functions in this section. */
308static void bye_bye(int eno, const char *str) NORETURN;
309static void bye_bye(int eno, const char *str)
310{
311	if (!Batch)
312		tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty);
313	putp(tg2(0, Screen_rows));
314	putp(Cap_curs_norm);
315	putp("\n");
316	fflush(stdout);
317
318#ifdef ATEOJ_REPORT
319	fprintf(stderr,
320		"\nbye_bye's Summary report:"
321		"\n\tProgram"
322		"\n\t   Linux version = %u.%u.%u, %s"
323		"\n\t   Hertz = %u (%u bytes, %u-bit time)"
324		"\n\t   Page_size = %d, Cpu_tot = %d, sizeof(proc_t) = %u"
325		"\n\t   sizeof(CPU_t) = %u, sizeof(HST_t) = %u (%u HST_t's/Page)"
326		"\n\t   Crufty?  %s"
327		"\n\tTerminal: %s"
328		"\n\t   device = %s, ncurses = v%s"
329		"\n\t   max_colors = %d, max_pairs = %d"
330		"\n\t   Cap_can_goto = %s"
331		"\n\t   Screen_cols = %d, Screen_rows = %d"
332		"\n\t   Max_lines = %d, most recent Pseudo_size = %d"
333#ifndef STDOUT_IOLBF
334		"\n\t   Stdout_buf = %d, BUFSIZ = %u"
335#endif
336		"\n\tWindows and Curwin->"
337		"\n\t   sizeof(WIN_t) = %u, GROUPSMAX = %d"
338		"\n\t   rc.winname = %s, grpname = %s"
339#ifdef CASEUP_HEXES
340		"\n\t   rc.winflags = %08X, maxpflgs = %d"
341#else
342		"\n\t   rc.winflags = %08x, maxpflgs = %d"
343#endif
344		"\n\t   rc.fieldscur = %s"
345		"\n\t   winlines  = %d, rc.maxtasks = %d, maxcmdln = %d"
346		"\n\t   rc.sortindx  = %d"
347		"\n", LINUX_VERSION_MAJOR(linux_version_code)
348		, LINUX_VERSION_MINOR(linux_version_code)
349		, LINUX_VERSION_PATCH(linux_version_code)
350		, procps_version, (unsigned)Hertz, sizeof(Hertz),
351		sizeof(Hertz) * 8, Page_size, Cpu_tot, sizeof(proc_t)
352		, sizeof(CPU_t), sizeof(HST_t), Page_size / sizeof(HST_t)
353#ifdef PRETENDNOCAP
354		, "dumb"
355#else
356		, termname()
357#endif
358		, ttyname(STDOUT_FILENO), NCURSES_VERSION, max_colors,
359		max_pairs, Cap_can_goto ? "yes" : "No!", Screen_cols,
360		Screen_rows, Max_lines, Pseudo_size
361#ifndef STDOUT_IOLBF
362		, sizeof(Stdout_buf), (unsigned)BUFSIZ
363#endif
364		, sizeof(WIN_t), GROUPSMAX, Curwin->rc.winname, Curwin->grpname,
365		Curwin->rc.winflags, Curwin->maxpflgs, Curwin->rc.fieldscur,
366		Curwin->winlines, Curwin->rc.maxtasks, Curwin->maxcmdln,
367		Curwin->rc.sortindx);
368#endif
369
370	if (str) {
371		if (eno)
372			perror(str);
373		else {
374			fputs(str, stderr);
375			eno = 1;
376		}
377	}
378	exit(eno);
379}
380
381	/*
382	 * Normal end of execution.
383	 * catches:
384	 *    SIGALRM, SIGHUP, SIGINT, SIGPIPE, SIGQUIT and SIGTERM */
385static void end_pgm(int dont_care_sig) NORETURN;
386static void end_pgm(int dont_care_sig)
387{
388	(void)dont_care_sig;
389	bye_bye(0, NULL);
390}
391
392	/*
393	 * Standard error handler to normalize the look of all err o/p */
394static void std_err(const char *str) NORETURN;
395static void std_err(const char *str)
396{
397	static char buf[SMLBUFSIZ];
398
399	fflush(stdout);
400	/* we'll use our own buffer so callers can still use fmtmk() and, yes the
401	   leading tab is not the standard convention, but the standard is wrong
402	   -- OUR msg won't get lost in screen clutter, like so many others! */
403	snprintf(buf, sizeof(buf), "\t%s: %s\n", Myname, str);
404	if (!Ttychanged) {
405		fprintf(stderr, "%s\n", buf);
406		exit(1);
407	}
408	/* not to worry, he'll change our exit code to 1 due to 'buf' */
409	bye_bye(0, buf);
410}
411
412	/*
413	 * Suspend ourself.
414	 * catches:
415	 *    SIGTSTP, SIGTTIN and SIGTTOU */
416static void suspend(int dont_care_sig)
417{
418	(void)dont_care_sig;
419	/* reset terminal */
420	tcsetattr(STDIN_FILENO, TCSAFLUSH, &Savedtty);
421	putp(tg2(0, Screen_rows));
422	putp(Cap_curs_norm);
423	fflush(stdout);
424	raise(SIGSTOP);
425	/* later, after SIGCONT... */
426	if (!Batch)
427		tcsetattr(STDIN_FILENO, TCSAFLUSH, &Rawtty);
428}
429
430/*######  Misc Color/Display support  ####################################*/
431
432	/*
433	 * Make the appropriate caps/color strings and set some
434	 * lengths which are used to distinguish twix the displayed
435	 * columns and an actual printed row!
436	 * note: we avoid the use of background color so as to maximize
437	 *       compatibility with the user's xterm settings */
438static void capsmk(WIN_t * q)
439{
440	/* macro to test if a basic (non-color) capability is valid
441	   thanks: Floyd Davidson <floyd@ptialaska.net> */
442#define tIF(s)  s ? s : ""
443	static int capsdone = 0;
444
445	// we must NOT disturb our 'empty' terminfo strings!
446	if (Batch)
447		return;
448
449	// these are the unchangeable puppies, so we only do 'em once
450	if (!capsdone) {
451		strcpy(Cap_clr_eol, tIF(clr_eol));
452		strcpy(Cap_clr_eos, tIF(clr_eos));
453		strcpy(Cap_clr_scr, tIF(clear_screen));
454		strcpy(Cap_curs_huge, tIF(cursor_visible));
455		strcpy(Cap_curs_norm, tIF(cursor_normal));
456		strcpy(Cap_home, tIF(cursor_home));
457		strcpy(Cap_norm, tIF(exit_attribute_mode));
458		strcpy(Cap_reverse, tIF(enter_reverse_mode));
459		snprintf(Caps_off, sizeof(Caps_off), "%s%s", Cap_norm,
460			 tIF(orig_pair));
461		if (tgoto(cursor_address, 1, 1))
462			Cap_can_goto = 1;
463		capsdone = 1;
464	}
465	/* the key to NO run-time costs for configurable colors -- we spend a
466	   little time with the user now setting up our terminfo strings, and
467	   the job's done until he/she/it has a change-of-heart */
468	strcpy(q->cap_bold,
469	       CHKw(q, View_NOBOLD) ? Cap_norm : tIF(enter_bold_mode));
470	if (CHKw(q, Show_COLORS) && max_colors > 0) {
471		strcpy(q->capclr_sum, tparm(set_a_foreground, q->rc.summclr));
472		snprintf(q->capclr_msg, sizeof(q->capclr_msg), "%s%s",
473			 tparm(set_a_foreground, q->rc.msgsclr), Cap_reverse);
474		snprintf(q->capclr_pmt, sizeof(q->capclr_pmt), "%s%s",
475			 tparm(set_a_foreground, q->rc.msgsclr), q->cap_bold);
476		snprintf(q->capclr_hdr, sizeof(q->capclr_hdr), "%s%s",
477			 tparm(set_a_foreground, q->rc.headclr), Cap_reverse);
478		snprintf(q->capclr_rownorm, sizeof(q->capclr_rownorm), "%s%s",
479			 Caps_off, tparm(set_a_foreground, q->rc.taskclr));
480	} else {
481		q->capclr_sum[0] = '\0';
482		strcpy(q->capclr_msg, Cap_reverse);
483		strcpy(q->capclr_pmt, q->cap_bold);
484		strcpy(q->capclr_hdr, Cap_reverse);
485		strcpy(q->capclr_rownorm, Cap_norm);
486	}
487	// composite(s), so we do 'em outside and after the if
488	snprintf(q->capclr_rowhigh, sizeof(q->capclr_rowhigh), "%s%s",
489		 q->capclr_rownorm, CHKw(q,
490					 Show_HIBOLD) ? q->
491		 cap_bold : Cap_reverse);
492	q->len_rownorm = strlen(q->capclr_rownorm);
493	q->len_rowhigh = strlen(q->capclr_rowhigh);
494
495#undef tIF
496}
497
498	/*
499	 * Show an error, but not right now.
500	 * Due to the postponed opening of ksym, using open_psdb_message,
501	 * if P_WCH had been selected and the program is restarted, the
502	 * message would otherwise be displayed prematurely.
503	 * (old top handles that situation with typical inelegance) */
504static void msg_save(const char *fmts, ...)
505{
506	char tmp[SMLBUFSIZ];
507	va_list va;
508
509	va_start(va, fmts);
510	vsnprintf(tmp, sizeof(tmp), fmts, va);
511	va_end(va);
512	/* we'll add some extra attention grabbers to whatever this is */
513	snprintf(Msg_delayed, sizeof(Msg_delayed), "\a***  %s  ***",
514		 strim_0(tmp));
515	Msg_awaiting = 1;
516}
517
518	/*
519	 * Show an error message (caller may include a '\a' for sound) */
520static void show_msg(const char *str)
521{
522	PUTT("%s%s %s %s%s", tg2(0, Msg_row)
523	     , Curwin->capclr_msg, str, Caps_off, Cap_clr_eol);
524	fflush(stdout);
525	sleep(MSG_SLEEP);
526	Msg_awaiting = 0;
527}
528
529	/*
530	 * Show an input prompt + larger cursor */
531static void show_pmt(const char *str)
532{
533	PUTT("%s%s%s: %s%s", tg2(0, Msg_row)
534	     , Curwin->capclr_pmt, str, Cap_curs_huge, Caps_off);
535	fflush(stdout);
536}
537
538	/*
539	 * Show lines with specially formatted elements, but only output
540	 * what will fit within the current screen width.
541	 *    Our special formatting consists of:
542	 *       "some text <_delimiter_> some more text <_delimiter_>...\n"
543	 *    Where <_delimiter_> is a single byte in the range of:
544	 *       \01 through \10  (in decimalizee, 1 - 8)
545	 *    and is used to select an 'attribute' from a capabilities table
546	 *    which is then applied to the *preceding* substring.
547	 * Once recognized, the delimiter is replaced with a null character
548	 * and viola, we've got a substring ready to output!  Strings or
549	 * substrings without delimiters will receive the Cap_norm attribute.
550	 *
551	 * Caution:
552	 *    This routine treats all non-delimiter bytes as displayable
553	 *    data subject to our screen width marching orders.  If callers
554	 *    embed non-display data like tabs or terminfo strings in our
555	 *    glob, a line will truncate incorrectly at best.  Worse case
556	 *    would be truncation of an embedded tty escape sequence.
557	 *
558	 *    Tabs must always be avoided or our efforts are wasted and
559	 *    lines will wrap.  To lessen but not eliminate the risk of
560	 *    terminfo string truncation, such non-display stuff should
561	 *    be placed at the beginning of a "short" line.
562	 *    (and as for tabs, gimme 1 more color then no worries, mate) */
563static void show_special(int interact, const char *glob)
564{				/* note: the following is for documentation only,
565				   the real captab is now found in a group's WIN_t !
566				   +------------------------------------------------------+
567				   | char *captab[] = {                 :   Cap's/Delim's |
568				   |   Cap_norm, Cap_norm, Cap_bold,    =   \00, \01, \02 |
569				   |   Sum_color,                       =   \03           |
570				   |   Msg_color, Pmt_color,            =   \04, \05      |
571				   |   Hdr_color,                       =   \06           |
572				   |   Row_color_high,                  =   \07           |
573				   |   Row_color_norm  };               =   \10 [octal!]  |
574				   +------------------------------------------------------+ */
575	char lin[BIGBUFSIZ], row[ROWBUFSIZ], tmp[ROWBUFSIZ]
576	, *rp, *cap, *lin_end, *sub_beg, *sub_end;
577	int room;
578
579	/* handle multiple lines passed in a bunch */
580	while ((lin_end = strchr(glob, '\n'))) {
581
582		/* create a local copy we can extend and otherwise abuse */
583		memcpy(lin, glob, (unsigned)(lin_end - glob));
584		/* zero terminate this part and prepare to parse substrings */
585		lin[lin_end - glob] = '\0';
586		room = Screen_cols;
587		sub_beg = sub_end = lin;
588		*(rp = row) = '\0';
589
590		while (*sub_beg) {
591			switch (*sub_end) {
592			case 0:	/* no end delim, captab makes normal */
593				*(sub_end + 1) = '\0';	/* extend str end, then fall through */
594			case 1 ... 8:
595				cap = Curwin->captab[(int)*sub_end];
596				*sub_end = '\0';
597				snprintf(tmp, sizeof(tmp), "%s%.*s%s", cap,
598					 room, sub_beg, Caps_off);
599				rp = scat(rp, tmp);
600				room -= (sub_end - sub_beg);
601				sub_beg = ++sub_end;
602				break;
603			default:	/* nothin' special, just text */
604				++sub_end;
605			}
606			if (unlikely(0 >= room))
607				break;	/* skip substrings that won't fit */
608		}
609
610		if (interact)
611			PUTT("%s%s\n", row, Cap_clr_eol);
612		else
613			PUFF("%s%s\n", row, Cap_clr_eol);
614		glob = ++lin_end;	/* point to next line (maybe) */
615	}			/* end: while 'lines' */
616
617	/* If there's anything left in the glob (by virtue of no trailing '\n'),
618	   it probably means caller wants to retain cursor position on this final
619	   line.  That, in turn, means we're interactive and so we'll just do our
620	   'fit-to-screen' thingy... */
621	if (*glob)
622		PUTT("%.*s", Screen_cols, glob);
623}
624
625/*######  Small Utility routines  ########################################*/
626
627	/*
628	 * Get a string from the user */
629static char *ask4str(const char *prompt)
630{
631	static char buf[GETBUFSIZ];
632
633	show_pmt(prompt);
634	memset(buf, '\0', sizeof(buf));
635	chin(1, buf, sizeof(buf) - 1);
636	putp(Cap_curs_norm);
637	return strim_0(buf);
638}
639
640	/*
641	 * Get a float from the user */
642static float get_float(const char *prompt)
643{
644	char *line;
645	float f;
646
647	if (!(*(line = ask4str(prompt))))
648		return -1;
649	// note: we're not allowing negative floats
650	if (strcspn(line, ",.1234567890")) {
651		show_msg("\aNot valid");
652		return -1;
653	}
654	sscanf(line, "%f", &f);
655	return f;
656}
657
658	/*
659	 * Get an integer from the user */
660static int get_int(const char *prompt)
661{
662	char *line;
663	int n;
664
665	if (!(*(line = ask4str(prompt))))
666		return -1;
667	// note: we've got to allow negative ints (renice)
668	if (strcspn(line, "-1234567890")) {
669		show_msg("\aNot valid");
670		return -1;
671	}
672	sscanf(line, "%d", &n);
673	return n;
674}
675
676	/*
677	 * Do some scaling stuff.
678	 * We'll interpret 'num' as one of the following types and
679	 * try to format it to fit 'width'.
680	 *    SK_no (0) it's a byte count
681	 *    SK_Kb (1) it's kilobytes
682	 *    SK_Mb (2) it's megabytes
683	 *    SK_Gb (3) it's gigabytes  */
684static const char *scale_num(unsigned num, const int width, const unsigned type)
685{
686	/* kilobytes, megabytes, gigabytes, duh! */
687	static float scale[] = { 1024, 1024 * 1024, 1024 * 1024 * 1024, 0 };
688	/* kilo, mega, giga, none */
689#ifdef CASEUP_SCALE
690	static char nextup[] = { 'K', 'M', 'G', 0 };
691#else
692	static char nextup[] = { 'k', 'm', 'g', 0 };
693#endif
694	static char buf[TNYBUFSIZ];
695	float *dp;
696	char *up;
697
698	/* try an unscaled version first... */
699	if (width >= snprintf(buf, sizeof(buf), "%u", num))
700		return buf;
701
702	/* now try successively higher types until it fits */
703	for (up = nextup + type, dp = scale; *dp; ++dp, ++up) {
704		/* the most accurate version */
705		if (width >=
706		    snprintf(buf, sizeof(buf), "%.1f%c", num / *dp, *up))
707			return buf;
708		/* the integer version */
709		if (width >=
710		    snprintf(buf, sizeof(buf), "%d%c", (int)(num / *dp), *up))
711			return buf;
712	}
713	/* well shoot, this outta' fit... */
714	return "?";
715}
716
717	/*
718	 * Do some scaling stuff.
719	 * format 'tics' to fit 'width'. */
720static const char *scale_tics(TIC_t tics, const int width)
721{
722#ifdef CASEUP_SCALE
723#define HH "%uH"
724#define DD "%uD"
725#define WW "%uW"
726#else
727#define HH "%uh"
728#define DD "%ud"
729#define WW "%uw"
730#endif
731	static char buf[TNYBUFSIZ];
732	unsigned long nt;	// narrow time, for speed on 32-bit
733	unsigned cc;		// centiseconds
734	unsigned nn;		// multi-purpose whatever
735
736	nt = (tics * 100ull) / Hertz;
737	cc = nt % 100;		// centiseconds past second
738	nt /= 100;		// total seconds
739	nn = nt % 60;		// seconds past the minute
740	nt /= 60;		// total minutes
741	if (width >= snprintf(buf, sizeof(buf), "%lu:%02u.%02u", nt, nn, cc))
742		return buf;
743	if (width >= snprintf(buf, sizeof buf, "%lu:%02u", nt, nn))
744		return buf;
745	nn = nt % 60;		// minutes past the hour
746	nt /= 60;		// total hours
747	if (width >= snprintf(buf, sizeof buf, "%lu,%02u", nt, nn))
748		return buf;
749	nn = nt;		// now also hours
750	if (width >= snprintf(buf, sizeof buf, HH, nn))
751		return buf;
752	nn /= 24;		// now days
753	if (width >= snprintf(buf, sizeof buf, DD, nn))
754		return buf;
755	nn /= 7;		// now weeks
756	if (width >= snprintf(buf, sizeof buf, WW, nn))
757		return buf;
758	// well shoot, this outta' fit...
759	return "?";
760
761#undef HH
762#undef DD
763#undef WW
764}
765
766#include <pwd.h>
767
768static int selection_type;
769static uid_t selection_uid;
770
771// FIXME: this is "temporary" code we hope
772static int good_uid(const proc_t * restrict const pp)
773{
774	switch (selection_type) {
775	case 'p':
776		return 1;
777	case 0:
778		return 1;
779	case 'U':
780		if (pp->ruid == selection_uid)
781			return 1;
782		if (pp->suid == selection_uid)
783			return 1;
784		if (pp->fuid == selection_uid)
785			return 1;
786		// FALLTHROUGH
787	case 'u':
788		if (pp->euid == selection_uid)
789			return 1;
790		// FALLTHROUGH
791	default:
792		;		// don't know what it is; find bugs fast
793	}
794	return 0;
795}
796
797// swiped from ps, and ought to be in libproc
798static const char *parse_uid(const char *restrict const str,
799			     uid_t * restrict const ret)
800{
801	struct passwd *passwd_data;
802	char *endp;
803	unsigned long num;
804	static const char uidrange[] = "User ID out of range.";
805	static const char uidexist[] = "User name does not exist.";
806	num = strtoul(str, &endp, 0);
807	if (*endp != '\0') {	/* hmmm, try as login name */
808		passwd_data = getpwnam(str);
809		if (!passwd_data)
810			return uidexist;
811		num = passwd_data->pw_uid;
812	}
813	if (num > 0xfffffffeUL)
814		return uidrange;
815	*ret = num;
816	return 0;
817}
818
819/*######  Library Alternatives  ##########################################*/
820
821	/*
822	 * Handle our own memory stuff without the risk of leaving the
823	 * user's terminal in an ugly state should things go sour. */
824
825static void *alloc_c(unsigned numb) MALLOC;
826static void *alloc_c(unsigned numb)
827{
828	void *p;
829
830	if (!numb)
831		++numb;
832	if (!(p = calloc(1, numb)))
833		std_err("failed memory allocate");
834	return p;
835}
836
837static void *alloc_r(void *q, unsigned numb) MALLOC;
838static void *alloc_r(void *q, unsigned numb)
839{
840	void *p;
841
842	if (!numb)
843		++numb;
844	if (!(p = realloc(q, numb)))
845		std_err("failed memory allocate");
846	return p;
847}
848
849	/*
850	 * This guy's modeled on libproc's 'five_cpu_numbers' function except
851	 * we preserve all cpu data in our CPU_t array which is organized
852	 * as follows:
853	 *    cpus[0] thru cpus[n] == tics for each separate cpu
854	 *    cpus[Cpu_tot]        == tics from the 1st /proc/stat line */
855static CPU_t *cpus_refresh(CPU_t * cpus)
856{
857	static FILE *fp = NULL;
858	int i;
859	// enough for a /proc/stat CPU line (not the intr line)
860	char buf[SMLBUFSIZ];
861
862	/* by opening this file once, we'll avoid the hit on minor page faults
863	   (sorry Linux, but you'll have to close it for us) */
864	if (!fp) {
865		if (!(fp = fopen("/proc/stat", "r")))
866			std_err(fmtmk
867				("Failed /proc/stat open: %s",
868				 strerror(errno)));
869		/* note: we allocate one more CPU_t than Cpu_tot so that the last slot
870		   can hold tics representing the /proc/stat cpu summary (the first
871		   line read) -- that slot supports our View_CPUSUM toggle */
872		cpus = alloc_c((1 + Cpu_tot) * sizeof(CPU_t));
873	}
874	rewind(fp);
875	fflush(fp);
876
877	// first value the last slot with the cpu summary line
878	if (!fgets(buf, sizeof(buf), fp))
879		std_err("failed /proc/stat read");
880	if (4 >
881	    sscanf(buf, CPU_FMTS_JUST1, &cpus[Cpu_tot].u, &cpus[Cpu_tot].n,
882		   &cpus[Cpu_tot].s, &cpus[Cpu_tot].i, &cpus[Cpu_tot].w))
883		std_err("failed /proc/stat read");
884	// and just in case we're 2.2.xx compiled without SMP support...
885	if (1 == Cpu_tot)
886		memcpy(cpus, &cpus[1], sizeof(CPU_t));
887
888	// now value each separate cpu's tics
889	for (i = 0; 1 < Cpu_tot && i < Cpu_tot; i++) {
890#ifdef PRETEND4CPUS
891		rewind(fp);
892#endif
893		if (!fgets(buf, sizeof(buf), fp))
894			std_err("failed /proc/stat read");
895		if (4 >
896		    sscanf(buf, CPU_FMTS_MULTI, &cpus[i].u, &cpus[i].n,
897			   &cpus[i].s, &cpus[i].i, &cpus[i].w))
898			std_err("failed /proc/stat read");
899	}
900	return cpus;
901}
902
903	/*
904	 * Refresh procs *Helper* function to eliminate yet one more need
905	 * to loop through our darn proc_t table.  He's responsible for:
906	 *    1) calculating the elapsed time since the previous frame
907	 *    2) counting the number of tasks in each state (run, sleep, etc)
908	 *    3) maintaining the HST_t's and priming the proc_t pcpu field
909	 *    4) establishing the total number tasks for this frame */
910static void prochlp(proc_t * this)
911{
912	static HST_t *hist_sav = NULL;
913	static HST_t *hist_new = NULL;
914	static unsigned hist_siz = 0;	// number of structs
915	static unsigned maxt_sav;	// prior frame's max tasks
916	TIC_t tics;
917
918	if (unlikely(!this)) {
919		static struct timeval oldtimev;
920		struct timeval timev;
921		struct timezone timez;
922		HST_t *hist_tmp;
923		float et;
924
925		gettimeofday(&timev, &timez);
926		et = (timev.tv_sec - oldtimev.tv_sec)
927		    + (float)(timev.tv_usec - oldtimev.tv_usec) / 1000000.0;
928		oldtimev.tv_sec = timev.tv_sec;
929		oldtimev.tv_usec = timev.tv_usec;
930
931		// if in Solaris mode, adjust our scaling for all cpus
932		Frame_tscale =
933		    100.0f / ((float)Hertz * (float)et *
934			      (Rc.mode_irixps ? 1 : Cpu_tot));
935		maxt_sav = Frame_maxtask;
936		Frame_maxtask = Frame_running = Frame_sleepin = Frame_stopped =
937		    Frame_zombied = 0;
938
939		// reuse memory each time around
940		hist_tmp = hist_sav;
941		hist_sav = hist_new;
942		hist_new = hist_tmp;
943		// prep for our binary search by sorting the last frame's HST_t's
944		qsort(hist_sav, maxt_sav, sizeof(HST_t), (QFP_t) sort_HST_t);
945		return;
946	}
947
948	switch (this->state) {
949	case 'R':
950		Frame_running++;
951		break;
952	case 'S':
953	case 'D':
954		Frame_sleepin++;
955		break;
956	case 'T':
957		Frame_stopped++;
958		break;
959	case 'Z':
960		Frame_zombied++;
961		break;
962	}
963
964	if (unlikely(Frame_maxtask + 1 >= hist_siz)) {
965		hist_siz = hist_siz * 5 / 4 + 100;	// grow by at least 25%
966		hist_sav = alloc_r(hist_sav, sizeof(HST_t) * hist_siz);
967		hist_new = alloc_r(hist_new, sizeof(HST_t) * hist_siz);
968	}
969	/* calculate time in this process; the sum of user time (utime) and
970	   system time (stime) -- but PLEASE dont waste time and effort on
971	   calcs and saves that go unused, like the old top! */
972	hist_new[Frame_maxtask].pid = this->pid;
973	hist_new[Frame_maxtask].tics = tics = (this->utime + this->stime);
974
975#if 0
976	{
977		int i;
978		int lo = 0;
979		int hi = maxt_sav - 1;
980
981		// find matching entry from previous frame and make ticks elapsed
982		while (lo <= hi) {
983			i = (lo + hi) / 2;
984			if (this->pid < hist_sav[i].pid)
985				hi = i - 1;
986			else if (likely(this->pid > hist_sav[i].pid))
987				lo = i + 1;
988			else {
989				tics -= hist_sav[i].tics;
990				break;
991			}
992		}
993	}
994#else
995	{
996		HST_t tmp;
997		const HST_t *ptr;
998		tmp.pid = this->pid;
999		ptr = bsearch(&tmp, hist_sav, maxt_sav, sizeof tmp, sort_HST_t);
1000		if (ptr)
1001			tics -= ptr->tics;
1002	}
1003#endif
1004
1005	// we're just saving elapsed tics, to be converted into %cpu if
1006	// this task wins it's displayable screen row lottery... */
1007	this->pcpu = tics;
1008// if (Frames_maxcmdln) { }
1009	// shout this to the world with the final call (or us the next time in)
1010	Frame_maxtask++;
1011}
1012
1013	/*
1014	 * This guy's modeled on libproc's 'readproctab' function except
1015	 * we reuse and extend any prior proc_t's.  He's been customized
1016	 * for our specific needs and to avoid the use of <stdarg.h> */
1017static proc_t **procs_refresh(proc_t ** table, int flags)
1018{
1019#define PTRsz  sizeof(proc_t *)
1020#define ENTsz  sizeof(proc_t)
1021	static unsigned savmax = 0;	// first time, Bypass: (i)
1022	proc_t *ptsk = (proc_t *) - 1;	// first time, Force: (ii)
1023	unsigned curmax = 0;	// every time  (jeeze)
1024	PROCTAB *PT;
1025
1026	prochlp(NULL);		// prep for a new frame
1027	if (Monpidsidx)
1028		PT = openproc(PROC_FILLBUG | PROC_PID, Monpids);
1029	else
1030		PT = openproc(flags);
1031
1032	// i) Allocated Chunks:  *Existing* table;  refresh + reuse
1033	while (curmax < savmax) {
1034		if (table[curmax]->cmdline) {
1035			free(*table[curmax]->cmdline);
1036			table[curmax]->cmdline = NULL;
1037		}
1038		if (unlikely(!(ptsk = readproc(PT, table[curmax]))))
1039			break;
1040		prochlp(ptsk);	// tally & complete this proc_t
1041		++curmax;
1042	}
1043
1044	// ii) Unallocated Chunks:  *New* or *Existing* table;  extend + fill
1045	while (ptsk) {
1046		// realloc as we go, keeping 'table' ahead of 'currmax++'
1047		table = alloc_r(table, (curmax + 1) * PTRsz);
1048		// here, readproc will allocate the underlying proc_t stg
1049		if (likely(ptsk = readproc(PT, NULL))) {
1050			prochlp(ptsk);	// tally & complete this proc_t
1051			table[curmax++] = ptsk;
1052		}
1053	}
1054	closeproc(PT);
1055
1056	// iii) Chunkless:  make 'eot' entry, after ensuring proc_t exists
1057	if (curmax >= savmax) {
1058		table = alloc_r(table, (curmax + 1) * PTRsz);
1059		// here, we must allocate the underlying proc_t stg ourselves
1060		table[curmax] = alloc_c(ENTsz);
1061		savmax = curmax + 1;
1062	}
1063	// this frame's end, but not necessarily end of allocated space
1064	table[curmax]->pid = -1;
1065	return table;
1066
1067#undef PTRsz
1068#undef ENTsz
1069}
1070
1071/*######  Field Table/RCfile compatability support  ######################*/
1072
1073	/* These are the Fieldstab.lflg values used here and in reframewins.
1074	   (own identifiers as documentation and protection against changes) */
1075#define L_stat     PROC_FILLSTAT
1076#define L_statm    PROC_FILLMEM
1077#define L_status   PROC_FILLSTATUS
1078#define L_CMDLINE  L_stat   | PROC_FILLARG
1079#define L_EUSER    PROC_FILLUSR
1080#define L_RUSER    L_status | PROC_FILLUSR
1081#define L_GROUP    L_status | PROC_FILLGRP
1082#define L_NONE     0
1083   // from either 'stat' or 'status' (preferred), via bits not otherwise used
1084#define L_EITHER   PROC_SPARE_1
1085   // for reframewins and summary_show 1st pass
1086#define L_DEFAULT  PROC_FILLSTAT
1087
1088   // a temporary macro, soon to be undef'd...
1089#define SF(f) (QFP_t)sort_P_ ## f
1090
1091	/* These are our gosh darn 'Fields' !
1092	   They MUST be kept in sync with pflags !!
1093	   note: for integer data, the length modifiers found in .fmts may
1094	   NOT reflect the true field type found in proc_t -- this plus
1095	   a cast when/if displayed provides minimal width protection. */
1096static FLD_t Fieldstab[] = {
1097/* .lflg anomolies:
1098      P_UID, L_NONE  - natural outgrowth of 'stat()' in readproc        (euid)
1099      P_CPU, L_stat  - never filled by libproc, but requires times      (pcpu)
1100      P_CMD, L_stat  - may yet require L_CMDLINE in reframewins  (cmd/cmdline)
1101      L_EITHER       - must L_status, else 64-bit math, __udivdi3 on 32-bit !
1102      keys   head           fmts     width   scale  sort   desc                     lflg
1103     ------  -----------    -------  ------  -----  -----  ----------------------   -------- */
1104	{"AaAa", "  PID ", "%5u ", -1, -1, SF(PID), "Process Id", L_NONE},
1105	{"BbBb", " PPID ", "%5u ", -1, -1, SF(PPD), "Parent Process Pid",
1106	 L_EITHER},
1107	{"CcQq", "RUSER    ", "%-8.8s ", -1, -1, SF(URR), "Real user name",
1108	 L_RUSER},
1109	{"DdCc", " UID ", "%4u ", -1, -1, SF(UID), "User Id", L_NONE},
1110	{"EeDd", "USER     ", "%-8.8s ", -1, -1, SF(URE), "User Name", L_EUSER},
1111	{"FfNn", "GROUP    ", "%-8.8s ", -1, -1, SF(GRP), "Group Name",
1112	 L_GROUP},
1113	{"GgGg", "TTY      ", "%-8.8s ", 8, -1, SF(TTY), "Controlling Tty",
1114	 L_stat},
1115	{"HhHh", " PR ", "%3d ", -1, -1, SF(PRI), "Priority", L_stat},
1116	{"IiIi", " NI ", "%3d ", -1, -1, SF(NCE), "Nice value", L_stat},
1117	{"JjYy", "#C ", "%2u ", -1, -1, SF(CPN), "Last used cpu (SMP)", L_stat},
1118	{"KkEe", "%CPU ", "%#4.1f ", -1, -1, SF(CPU), "CPU usage", L_stat},
1119	{"LlWw", "  TIME ", "%6.6s ", 6, -1, SF(TME), "CPU Time", L_stat},
1120	{"MmRr", "   TIME+  ", "%9.9s ", 9, -1, SF(TME), "CPU Time, hundredths",
1121	 L_stat},
1122	{"NnFf", "%MEM ", "%#4.1f ", -1, -1, SF(RES), "Memory usage (RES)",
1123	 L_statm},
1124	{"OoMm", " VIRT ", "%5.5s ", 5, SK_Kb, SF(VRT), "Virtual Image (kb)",
1125	 L_statm},
1126	{"PpOo", "SWAP ", "%4.4s ", 4, SK_Kb, SF(SWP), "Swapped size (kb)",
1127	 L_statm},
1128	{"QqTt", " RES ", "%4.4s ", 4, SK_Kb, SF(RES), "Resident size (kb)",
1129	 L_statm},
1130	{"RrKk", "CODE ", "%4.4s ", 4, SK_Kb, SF(COD), "Code size (kb)",
1131	 L_statm},
1132	{"SsLl", "DATA ", "%4.4s ", 4, SK_Kb, SF(DAT), "Data+Stack size (kb)",
1133	 L_statm},
1134	{"TtPp", " SHR ", "%4.4s ", 4, SK_Kb, SF(SHR), "Shared Mem size (kb)",
1135	 L_statm},
1136	{"UuJj", "nFLT ", "%4.4s ", 4, SK_no, SF(FLT), "Page Fault count",
1137	 L_stat},
1138	{"VvSs", "nDRT ", "%4.4s ", 4, SK_no, SF(DRT), "Dirty Pages count",
1139	 L_statm},
1140#ifdef USE_LIB_STA3
1141	{"WwVv", "STA ", "%3.3s ", -1, -1, SF(STA), "Process Status", L_stat},
1142#else
1143	{"WwVv", "S ", "%c ", -1, -1, SF(STA), "Process Status", L_EITHER},
1144#endif
1145	// next entry's special: '.head' will be formatted using table entry's own
1146	//                       '.fmts' plus runtime supplied conversion args!
1147	{"XxXx", "Command ", "%-*.*s ", -1, -1, SF(CMD), "Command name/line",
1148	 L_EITHER},
1149	{"YyUu", "WCHAN     ", "%-9.9s ", -1, -1, SF(WCH),
1150	 "Sleeping in Function", L_stat},
1151	// next entry's special: the 0's will be replaced with '.'!
1152#ifdef CASEUP_HEXES
1153	{"ZzZz", "Flags    ", "%08lX ", -1, -1, SF(FLG), "Task Flags <sched.h>",
1154	 L_stat},
1155#else
1156	{"ZzZz", "Flags    ", "%08lx ", -1, -1, SF(FLG), "Task Flags <sched.h>",
1157	 L_stat},
1158#endif
1159#if 0
1160	{"..Qq", "  A ", "%4.4s ", 4, SK_no, SF(PID), "Accessed Page count",
1161	 L_stat},
1162	{"..Nn", " TRS ", "%4.4s ", 4, SK_Kb, SF(PID), "Code in memory (kb)",
1163	 L_stat},
1164	{"..Rr", " WP ", "%4.4s ", 4, SK_no, SF(PID), "Unwritable Pages",
1165	 L_stat},
1166	{"Jj[{", "#C ", "%2u ", -1, -1, SF(CPN), "Last used cpu (SMP)", L_stat},
1167	{"..\\|", "Bad ", "%2u ", -1, -1, SF(CPN), "-- must ignore | --", 0},
1168	{"..]}", "Bad ", "%2u ", -1, -1, SF(CPN), "-- not used --", 0},
1169	{"..^~", "Bad ", "%2u ", -1, -1, SF(CPN), "-- not used --", 0},
1170#endif
1171};
1172
1173#undef SF
1174
1175	/* All right, those-that-follow -- Listen Up!
1176	 * For the above table keys and the following present/future rc file
1177	 * compatibility support, you have Mr. Albert D. Cahalan to thank.
1178	 * He must have been in a 'Christmas spirit'.  Were it left to me,
1179	 * this top would never have gotten that close to the former top's
1180	 * crufty rcfile.  Not only is it illogical, it's odoriferous !
1181	 */
1182
1183	// used as 'to' and/or 'from' args in the ft_xxx utilities...
1184#define FT_NEW_fmt 0
1185#define FT_OLD_fmt 2
1186
1187#if 0
1188	// convert, or 0 for failure
1189static int ft_cvt_char(const int fr, const int to, int c)
1190{
1191	int j = -1;
1192
1193	while (++j < MAXTBL(Fieldstab)) {
1194		if (c == Fieldstab[j].keys[fr])
1195			return Fieldstab[j].keys[to];
1196		if (c == Fieldstab[j].keys[fr + 1])
1197			return Fieldstab[j].keys[to + 1];
1198	}
1199	return 0;
1200}
1201#endif
1202
1203	// convert
1204static inline int ft_get_char(const int fr, int i)
1205{
1206	int c;
1207	if (i < 0)
1208		return 0;
1209	if (i >= MAXTBL(Fieldstab))
1210		return 0;
1211	c = Fieldstab[i].keys[fr];
1212	if (c == '.')
1213		c = 0;		// '.' marks a bad entry
1214	return c;
1215}
1216
1217#if 0
1218	// convert, or -1 for failure
1219static int ft_get_idx(const int fr, int c)
1220{
1221	int j = -1;
1222
1223	while (++j < MAXTBL(Fieldstab)) {
1224		if (c == Fieldstab[j].keys[fr])
1225			return j;
1226		if (c == Fieldstab[j].keys[fr + 1])
1227			return j;
1228	}
1229	return -1;
1230}
1231#endif
1232
1233	// convert, or NULL for failure
1234static const FLD_t *ft_get_ptr(const int fr, int c)
1235{
1236	int j = -1;
1237
1238	while (++j < MAXTBL(Fieldstab)) {
1239		if (c == Fieldstab[j].keys[fr])
1240			return Fieldstab + j;
1241		if (c == Fieldstab[j].keys[fr + 1])
1242			return Fieldstab + j;
1243	}
1244	return NULL;
1245}
1246
1247#if 0
1248	// convert, or NULL for failure
1249static const FLD_t *ft_idx_to_ptr(const int i)
1250{
1251	if (i < 0)
1252		return NULL;
1253	if (i >= MAXTBL(Fieldstab))
1254		return NULL;
1255	return Fieldstab + i;
1256}
1257
1258	// convert, or -1 for failure
1259static int ft_ptr_to_idx(const FLD_t * p)
1260{
1261	int i;
1262	if (p < Fieldstab)
1263		return -1;
1264	i = p - Fieldstab;
1265	if (i >= MAXTBL(Fieldstab))
1266		return -1;
1267	return i;
1268}
1269#endif
1270
1271#if 0
1272static void rc_bugless(const RCF_t * const rc)
1273{
1274	const RCW_t *w;
1275	int i = 0;
1276
1277	fprintf(stderr, "\n%d %d %f %d\n", rc->mode_altscr, rc->mode_irixps,
1278		rc->delay_time, rc->win_index);
1279	while (i < 4) {
1280		w = &rc->win[i++];
1281		fprintf(stderr, "<%s> <%s> %d %08x %d %d %d %d %d\n",
1282			w->winname, w->fieldscur, w->sortindx, w->winflags,
1283			w->maxtasks, w->summclr, w->msgsclr, w->headclr,
1284			w->taskclr);
1285	}
1286}
1287#endif
1288
1289	/*
1290	 * '$HOME/Rc_name' contains multiple lines - 2 global + 3 per window.
1291	 *   line 1: an eyecatcher, with a shameless advertisement
1292	 *   line 2: an id, Mode_altcsr, Mode_irixps, Delay_time and Curwin.
1293	 * For each of the 4 windows:
1294	 *   line a: contains winname, fieldscur
1295	 *   line b: contains winflags, sortindx, maxtasks
1296	 *   line c: contains summclr, msgsclr, headclr, taskclr
1297	 *   line d: if present, would crash procps-3.1.1
1298	 */
1299static int rc_read_new(const char *const buf, RCF_t * rc)
1300{
1301	int i;
1302	int cnt;
1303	const char *cp;
1304
1305	cp = strstr(buf, "\n\n" RCF_EYECATCHER);
1306	if (!cp)
1307		return -1;
1308	cp = strchr(cp + 2, '\n');
1309	if (!cp++)
1310		return -2;
1311
1312	cnt =
1313	    sscanf(cp,
1314		   "Id:a, Mode_altscr=%d, Mode_irixps=%d, Delay_time=%f, Curwin=%d\n",
1315		   &rc->mode_altscr, &rc->mode_irixps, &rc->delay_time,
1316		   &rc->win_index);
1317	if (cnt != 4)
1318		return -3;
1319	cp = strchr(cp, '\n');
1320	if (!cp++)
1321		return -4;
1322
1323	for (i = 0; i < GROUPSMAX; i++) {
1324		RCW_t *ptr = &rc->win[i];
1325		cnt =
1326		    sscanf(cp, "%3s\tfieldscur=%31s\n", ptr->winname,
1327			   ptr->fieldscur);
1328		if (cnt != 2)
1329			return 5 + 100 * i;	// OK to have less than 4 windows
1330		if (WINNAMSIZ <= strlen(ptr->winname))
1331			return -6;
1332		if (strlen(DEF_FIELDS) != strlen(ptr->fieldscur))
1333			return -7;
1334		cp = strchr(cp, '\n');
1335		if (!cp++)
1336			return -(8 + 100 * i);
1337
1338		cnt =
1339		    sscanf(cp, "\twinflags=%d, sortindx=%u, maxtasks=%d \n",
1340			   &ptr->winflags, &ptr->sortindx, &ptr->maxtasks);
1341		if (cnt != 3)
1342			return -(9 + 100 * i);
1343		cp = strchr(cp, '\n');
1344		if (!cp++)
1345			return -(10 + 100 * i);
1346
1347		cnt =
1348		    sscanf(cp,
1349			   "\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d \n",
1350			   &ptr->summclr, &ptr->msgsclr, &ptr->headclr,
1351			   &ptr->taskclr);
1352		if (cnt != 4)
1353			return -(11 + 100 * i);
1354		cp = strchr(cp, '\n');
1355		if (!cp++)
1356			return -(12 + 100 * i);
1357		while (*cp == '\t') {	// skip unknown per-window settings
1358			cp = strchr(cp, '\n');
1359			if (!cp++)
1360				return -(13 + 100 * i);
1361		}
1362	}
1363	return 13;
1364}
1365
1366static int rc_read_old(const char *const buf, RCF_t * rc)
1367{
1368	const char std[] =
1369	    "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzJj......";
1370	const char old[] =
1371	    "AaBb..CcDd..GgHhIiYyEeWw..FfMmOoTtKkLlPpJjSsVvXxUuZz[{QqNnRr";
1372	unsigned u;
1373	const char *cp;
1374	unsigned c_show = 0;
1375	int badchar = 0;	// allow a limited number of duplicates and junk
1376
1377	char scoreboard[256];
1378	memset(scoreboard, '\0', sizeof scoreboard);
1379
1380	cp = buf + 2;		// skip the "\n\n" we stuck at the beginning
1381	u = 0;
1382	for (;;) {
1383		const char *tmp;
1384		int c = *cp++;
1385		if (u + 1 >= sizeof rc->win[0].fieldscur)
1386			return -1;
1387		if (c == '\0')
1388			return -2;
1389		if (c == '\n')
1390			break;
1391		if (c & ~0x7f)
1392			return -3;
1393		if (~c & 0x20)
1394			c_show |= 1 << (c & 0x1f);	// 0x20 means lowercase means hidden
1395		if (scoreboard[c | 0xe0u])
1396			badchar++;	// duplicates not allowed
1397		scoreboard[c | 0xe0u]++;
1398		tmp = strchr(old, c);
1399		if (!tmp)
1400			continue;
1401		c = *((tmp - old) + std);
1402		if (c == '.')
1403			continue;
1404		if (scoreboard[c & 0x1fu])
1405			badchar++;	// duplicates not allowed
1406		scoreboard[c & 0x1fu]++;
1407		rc->win[0].fieldscur[u++] = c;
1408	}
1409	rc->win[0].fieldscur[u++] = '\0';
1410	if (u < 21)
1411		return -6;	// catch junk, not good files (had 23 chars in one)
1412	if (u > 33)
1413		return -7;	// catch junk, not good files (had 29 chars in one)
1414// fprintf(stderr, "badchar: %d\n", badchar); sleep(2);
1415	if (badchar > 8)
1416		return -8;	// too much junk
1417	if (!c_show)
1418		return -9;	// nothing was shown
1419
1420	// rest of file is optional, but better look right if it exists
1421	if (!*cp)
1422		return 12;
1423	if (*cp < '2' || *cp > '9')
1424		return -13;	// stupid, and why isn't '1' valid?
1425	rc->delay_time = *cp - '0';
1426
1427	memset(scoreboard, '\0', sizeof(scoreboard));
1428	for (;;) {
1429		int c = *++cp & 0xffu;	// protect scoreboard[] from negative char
1430		if (!c)
1431			return -14;	// not OK to hit EOL w/o '\n'
1432		if (c == '\n')
1433			break;
1434		switch (c) {
1435		case ' ':
1436		case '.':
1437		case '0' ... '9':
1438			return -15;	// not supposed to have digits here
1439
1440//       case 's':                      // mostly for global rcfile
1441//          rc->mode_secure = 1;
1442//          break;
1443		case 'S':
1444			rc->win[0].winflags |= Show_CTIMES;
1445			break;
1446		case 'c':
1447			rc->win[0].winflags |= Show_CMDLIN;
1448			break;
1449		case 'i':
1450			rc->win[0].winflags &= ~Show_IDLEPS;
1451			break;
1452		case 'H':	// 'H' = show threads (yea, sure)
1453			//rc->win[0].winflags |= ;
1454			break;
1455		case 'm':
1456			rc->win[0].winflags &= ~View_MEMORY;
1457			break;
1458		case 'l':
1459			rc->win[0].winflags &= ~View_LOADAV;
1460			break;
1461		case 't':
1462			rc->win[0].winflags &= ~View_STATES;
1463			break;
1464		case 'I':
1465			rc->mode_irixps = 0;
1466			break;
1467
1468		case 'M':
1469			c = 0;	// for scoreboard
1470			rc->win[0].sortindx = P_MEM;
1471			break;
1472		case 'P':
1473			c = 0;	// for scoreboard
1474			rc->win[0].sortindx = P_CPU;
1475			break;
1476		case 'A':	// supposed to be start_time
1477			c = 0;	// for scoreboard
1478			rc->win[0].sortindx = P_PID;
1479			break;
1480		case 'T':
1481			c = 0;	// for scoreboard
1482			rc->win[0].sortindx = P_TM2;
1483			break;
1484		case 'N':
1485			c = 0;	// for scoreboard
1486			rc->win[0].sortindx = P_PID;
1487			break;
1488
1489		default:
1490			// just ignore it, except for the scoreboard of course
1491			break;
1492		}
1493		if (scoreboard[c])
1494			return -16;	// duplicates not allowed
1495		scoreboard[c] = 1;
1496	}
1497	return 17;
1498}
1499
1500static void rc_write_new(FILE * fp)
1501{
1502	int i;
1503
1504	fprintf(fp,
1505		RCF_EYECATCHER "\"%s with windows\"\t\t# shameless braggin'\n",
1506		Myname);
1507	fprintf(fp,
1508		RCF_DEPRECATED
1509		"Mode_altscr=%d, Mode_irixps=%d, Delay_time=%.3f, Curwin=%d\n",
1510		Rc.mode_altscr, Rc.mode_irixps, Rc.delay_time, Curwin - Winstk);
1511	for (i = 0; i < GROUPSMAX; i++) {
1512		char buf[40];
1513		char *cp = Winstk[i].rc.fieldscur;
1514		int j = 0;
1515
1516		while (j < 36) {
1517			int c = *cp++ & 0xff;
1518			switch (c) {
1519			case '.':
1520			case 1 ... ' ':
1521			case 0x7f ... 0xff:
1522				continue;	// throw away junk (some of it)
1523			default:
1524				buf[j++] = c;	// gets the '\0' too
1525			}
1526			if (!c)
1527				break;
1528		}
1529		fprintf(fp, "%s\tfieldscur=%s\n", Winstk[i].rc.winname, buf);
1530		fprintf(fp, "\twinflags=%d, sortindx=%d, maxtasks=%d\n",
1531			Winstk[i].rc.winflags, Winstk[i].rc.sortindx,
1532			Winstk[i].rc.maxtasks);
1533		fprintf(fp,
1534			"\tsummclr=%d, msgsclr=%d, headclr=%d, taskclr=%d\n",
1535			Winstk[i].rc.summclr, Winstk[i].rc.msgsclr,
1536			Winstk[i].rc.headclr, Winstk[i].rc.taskclr);
1537	}
1538}
1539
1540#if 0
1541static void rc_write_old(FILE * fp)
1542{
1543	char buf[SMLBUFSIZ];
1544	char *cp = Curwin->rc.fieldscur;
1545	int j = 0;
1546	int tmp;
1547
1548	while (j < 36) {
1549		int c = *cp++ & 0xff;
1550		if (c == 'M')
1551			c = 'L';
1552		if (c == 'm')
1553			c = 'l';
1554		switch (c) {
1555		case '.':
1556		case 'x':	// try not to crash Rik's top (move COMMAND)
1557		case 'X':	// try not to crash Rik's top (move COMMAND)
1558		case 1 ... ' ':
1559		case 0x7f ... 0xff:
1560			continue;	// throw away junk (some of it)
1561		default:
1562			c = ft_cvt_char(FT_NEW_fmt, FT_OLD_fmt, c);
1563			if (!c)
1564				continue;	// skip one we can't represent
1565			break;
1566		case '\0':
1567			buf[j++] = 'X';	// try not to crash Rik's top (move COMMAND)
1568			break;
1569		}
1570		buf[j++] = c;
1571		if (!c)
1572			break;
1573	}
1574
1575	fprintf(fp, "%s\n", buf);
1576	cp = buf;
1577
1578	tmp = (int)(Rc.delay_time + 0.5);
1579	if (tmp < 2)
1580		tmp = 2;
1581	if (tmp > 9)
1582		tmp = 9;
1583	*cp++ = tmp + '0';
1584
1585	tmp = Curwin->rc.winflags;
1586// if (Secure_mode)       *cp++ = 's';     // stupid to have in local rcfile
1587	if (tmp & Show_CTIMES)
1588		*cp++ = 'S';
1589	if (tmp & Show_CMDLIN)
1590		*cp++ = 'c';
1591	if (~tmp & Show_IDLEPS)
1592		*cp++ = 'i';
1593// if () *cp++ = 'H';     // 'H' = show threads (yea, sure)
1594	if (~tmp & View_MEMORY)
1595		*cp++ = 'm';
1596	if (~tmp & View_LOADAV)
1597		*cp++ = 'l';
1598	if (~tmp & View_STATES)
1599		*cp++ = 't';
1600	if (!Rc.mode_irixps)
1601		*cp++ = 'I';
1602
1603	switch (Curwin->rc.sortindx) {
1604	case P_MEM:
1605		*cp++ = 'M';
1606		break;
1607	case P_CPU:
1608		*cp++ = 'P';
1609		break;
1610//    case P_???:                           // was by start_time (non-display)
1611//       *cp++ = 'A';
1612//       break;
1613	case P_TM2:
1614		*cp++ = 'T';
1615		break;
1616	case P_PID:
1617		*cp++ = 'N';
1618		break;
1619	}
1620	*cp++ = '\0';
1621	fprintf(fp, "%s\n\n\n", buf);	// important "\n\n" separator!
1622}
1623#endif
1624
1625static const char *rc_write_whatever(void)
1626{
1627	FILE *fp = fopen(Rc_name, "w");
1628
1629	if (!fp)
1630		return strerror(errno);
1631//   if (Crufty_rcf) rc_write_old(fp);
1632	rc_write_new(fp);
1633	fclose(fp);
1634	return NULL;
1635}
1636
1637/*######  Startup routines  ##############################################*/
1638
1639#ifdef PRETEND4CPUS
1640#define smp_num_cpus 4
1641#endif
1642
1643	/*
1644	 * No mater what *they* say, we handle the really really BIG and
1645	 * IMPORTANT stuff upon which all those lessor functions depend! */
1646static void before(char *me)
1647{
1648	int i;
1649
1650	/* setup our program name -- big! */
1651	Myname = strrchr(me, '/');
1652	if (Myname)
1653		++Myname;
1654	else
1655		Myname = me;
1656
1657	/* establish cpu particulars -- even bigger! */
1658	Cpu_tot = smp_num_cpus;
1659	Cpu_map = alloc_r(NULL, sizeof(int) * Cpu_tot);
1660	for (i = 0; i < Cpu_tot; i++)
1661		Cpu_map[i] = i;
1662	if (linux_version_code > LINUX_VERSION(2, 5, 41))
1663		States_fmts = STATES_line2x5;
1664
1665	/* get virtual page size -- nearing huge! */
1666	Page_size = getpagesize();
1667}
1668
1669       /*
1670        * Config file read *helper* function.
1671        * Anything missing won't show as a choice in the field editor,
1672        * so make sure there is exactly one of each letter.
1673        *
1674        * Due to Rik blindly accepting damem's broken patches, procps-2.0.1x
1675        * has 3 ("three"!!!) instances of "#C", "LC", or "CPU". Fix that too.
1676        * Some people are maintainers, and others are human patchbots.
1677        * (thanks, Albert) */
1678static void confighlp(char *fields)
1679{
1680	unsigned upper[PFLAGSSIZ];
1681	unsigned lower[PFLAGSSIZ];
1682	char c;
1683	char *cp;
1684
1685	memset(upper, '\0', sizeof upper);
1686	memset(lower, '\0', sizeof lower);
1687
1688	cp = fields;
1689	for (;;) {
1690		c = *cp++;
1691		if (!c)
1692			break;
1693		if (isupper(c))
1694			upper[c & 0x1f]++;
1695		else
1696			lower[c & 0x1f]++;
1697	}
1698
1699	c = 'a';
1700	while (c <= 'z') {
1701		if (upper[c & 0x1f] && lower[c & 0x1f]) {
1702			lower[c & 0x1f] = 0;	// got both, so wipe out unseen column
1703			for (;;) {
1704				cp = strchr(fields, c);
1705				if (cp)
1706					memmove(cp, cp + 1, strlen(cp));
1707				else
1708					break;
1709			}
1710		}
1711		while (lower[c & 0x1f] > 1) {	// got too many a..z
1712			lower[c & 0x1f]--;
1713			cp = strchr(fields, c);
1714			memmove(cp, cp + 1, strlen(cp));
1715		}
1716		while (upper[c & 0x1f] > 1) {	// got too many A..Z
1717			upper[c & 0x1f]--;
1718			cp = strchr(fields, toupper(c));
1719			memmove(cp, cp + 1, strlen(cp));
1720		}
1721		if (!upper[c & 0x1f] && !lower[c & 0x1f]) {	// both missing
1722			lower[c & 0x1f]++;
1723			memmove(fields + 1, fields, strlen(fields) + 1);
1724			fields[0] = c;
1725		}
1726		c++;
1727	}
1728}
1729
1730	/*
1731	 * First attempt to read the /etc/rcfile which contains two lines
1732	 * consisting of the secure mode switch and an update interval.
1733	 * It's presence limits what ordinary users are allowed to do.
1734	 * (it's actually an old-style config file)
1735	 *
1736	 * Then build the local rcfile name and try to read a crufty old-top
1737	 * rcfile (whew, odoriferous), which may contain an embedded new-style
1738	 * rcfile.   Whether embedded or standalone, new-style rcfile values
1739	 * will always override that crufty stuff!
1740	 * note: If running in secure mode via the /etc/rcfile,
1741	 *       Delay_time will be ignored except for root. */
1742static void configs_read(void)
1743{
1744	const RCF_t def_rcf = DEF_RCFILE;
1745	char fbuf[MEDBUFSIZ];
1746	int i, fd;
1747	RCF_t rcf;
1748	float delay = Rc.delay_time;
1749
1750	// read part of an old-style config in /etc/toprc
1751	fd = open(SYS_RCFILESPEC, O_RDONLY);
1752	if (fd > 0) {
1753		ssize_t num;
1754		num = read(fd, fbuf, sizeof(fbuf) - 1);
1755		if (num > 0) {
1756			const char *sec = strchr(fbuf, 's');
1757			const char *eol = strchr(fbuf, '\n');
1758			if (eol) {
1759				const char *two = eol + 1;	// line two
1760				if (sec < eol)
1761					Secure_mode = ! !sec;
1762				eol = strchr(two, '\n');
1763				if (eol && eol > two && isdigit(*two))
1764					Rc.delay_time = atof(two);
1765			}
1766		}
1767		close(fd);
1768	}
1769
1770	snprintf(Rc_name, sizeof(Rc_name), ".%src", Myname);	// eeew...
1771	if (getenv("HOME"))
1772		snprintf(Rc_name, sizeof(Rc_name), "%s/.%src", getenv("HOME"),
1773			 Myname);
1774
1775	rcf = def_rcf;
1776	fd = open(Rc_name, O_RDONLY);
1777	if (fd > 0) {
1778		ssize_t num;
1779		num = read(fd, fbuf + 2, sizeof(fbuf) - 3);
1780		if (num > 0) {
1781			fbuf[0] = '\n';
1782			fbuf[1] = '\n';
1783			fbuf[num + 2] = '\0';
1784//fprintf(stderr,"rc_read_old returns %d\n",rc_read_old(fbuf, &rcf));
1785//sleep(2);
1786			if (rc_read_new(fbuf, &rcf) < 0) {
1787				rcf = def_rcf;	// on failure, maybe mangled
1788				if (rc_read_old(fbuf, &rcf) < 0)
1789					rcf = def_rcf;
1790			}
1791			delay = rcf.delay_time;
1792		}
1793		close(fd);
1794	}
1795	// update Rc defaults, establish a Curwin and fix up the window stack
1796	Rc.mode_altscr = rcf.mode_altscr;
1797	Rc.mode_irixps = rcf.mode_irixps;
1798	if (rcf.win_index >= GROUPSMAX)
1799		rcf.win_index = 0;
1800	Curwin = &Winstk[rcf.win_index];
1801	for (i = 0; i < GROUPSMAX; i++) {
1802		memcpy(&Winstk[i].rc, &rcf.win[i], sizeof rcf.win[i]);
1803		confighlp(Winstk[i].rc.fieldscur);
1804	}
1805
1806	// lastly, establish the true runtime secure mode and delay time
1807	if (!getuid())
1808		Secure_mode = 0;
1809	if (!Secure_mode)
1810		Rc.delay_time = delay;
1811}
1812
1813	/*
1814	 * Parse command line arguments.
1815	 * Note: it's assumed that the rc file(s) have already been read
1816	 *       and our job is to see if any of those options are to be
1817	 *       overridden -- we'll force some on and negate others in our
1818	 *       best effort to honor the loser's (oops, user's) wishes... */
1819#define BUFF_SIZE 256
1820static void parse_args(char **args)
1821{
1822	/* differences between us and the former top:
1823	   -o filename to output data to at each measurement interval
1824	   -f filename to read output data from to calculate averages
1825	   -C (separate CPU states for SMP) is left to an rcfile
1826	   -p (pid monitoring) allows a comma delimited list
1827	   -q (zero delay) eliminated as redundant, incomplete and inappropriate
1828	   use: "nice -n-10 top -d0" to achieve what was only claimed
1829	   -c,i,S act as toggles (not 'on' switches) for enhanced user flexibility
1830	   .  no deprecated/illegal use of 'breakargv:' with goto
1831	   .  bunched args are actually handled properly and none are ignored
1832	   .  we tolerate NO whitespace and NO switches -- maybe too tolerant? */
1833	static const char usage[] =
1834	    " -hv | -bcisS -d delay -n iterations [-u user | -U user] -o filename -p pid [,pid ...] | -f filename";
1835	float tmp_delay = MAXFLOAT;
1836	char *p;
1837	char buff[BUFF_SIZE];
1838	int retcode;
1839	int loopcntr = 0;
1840
1841	int Task1, Task2, Task3, Task4, Task5;
1842	float AvgMaxTasks = 0.0, AvgRunningTasks = 0.0, AvgSleepingTasks =
1843	    0.0, AvgStoppedTasks = 0.0, AvgZombieTasks = 0.0;
1844
1845	float CPU1, CPU2, CPU3, CPU4, CPU5;
1846	float AvgCPUuser = 0.0, AvgCPUsys = 0.0, AvgCPUnice = 0.0, AvgCPUidle =
1847	    0.0, AvgCPUiowait = 0.0;
1848
1849	int Mem1, Mem2, Mem3, Mem4;
1850	int AvgMem1 = 0;
1851	long double AvgMem2 = 0.0, AvgMem3 = 0.0, AvgMem4 =
1852	    0.0, UsedMemPercentage = 0.0;
1853	int AvgSwap1 = 0;
1854	long double AvgSwap2 = 0.0, AvgSwap3 = 0.0, AvgSwap4 =
1855	    0.0, UsedSwapPercentage = 0.0;
1856
1857	while (*args) {
1858		const char *cp = *(args++);
1859
1860		while (*cp) {
1861			switch (*cp) {
1862			case '\0':
1863			case '-':
1864				break;
1865			case 'b':
1866				Batch = 1;
1867				break;
1868			case 'c':
1869				TOGw(Curwin, Show_CMDLIN);
1870				break;
1871			case 'd':
1872				if (cp[1])
1873					++cp;
1874				else if (*args)
1875					cp = *args++;
1876				else
1877					std_err("-d requires argument");
1878				/* a negative delay will be dealt with shortly... */
1879				if (1 != sscanf(cp, "%f", &tmp_delay))
1880					std_err(fmtmk("bad delay '%s'", cp));
1881				break;
1882			case 'f':
1883				if (cp[1])
1884					cp++;
1885				else if (*args)
1886					cp = *args++;
1887				else
1888					std_err("-f requires argument");
1889				if ((datafile = fopen(cp, "r")) == NULL)
1890					std_err(fmtmk
1891						("bad file arg; failed to fopen '%s' for reading",
1892						 cp));
1893				retcode =
1894				    fscanf(datafile,
1895					   " MaxTasks:%d RunningTasks:%d SleepingTasks:%d StoppedTasks:%d ZombieTasks:%d",
1896					   &Task1, &Task2, &Task3, &Task4,
1897					   &Task5);
1898				while (retcode == 5) {
1899					loopcntr++;
1900					fgets(buff, BUFF_SIZE, datafile);
1901					AvgMaxTasks += Task1;
1902					AvgRunningTasks += Task2;
1903					AvgSleepingTasks += Task3;
1904					AvgStoppedTasks += Task4;
1905					AvgZombieTasks += Task5;
1906					fscanf(datafile,
1907					       " Cpu(s): User:%f\tSystem:%f\tNice:%f\t\tIdle:%f\tIO-wait:%f",
1908					       &CPU1, &CPU2, &CPU3, &CPU4,
1909					       &CPU5);
1910					AvgCPUuser += CPU1;
1911					AvgCPUsys += CPU2;
1912					AvgCPUnice += CPU3;
1913					AvgCPUidle += CPU4;
1914					AvgCPUiowait += CPU5;
1915					fscanf(datafile,
1916					       " TotalMem:%dk\tUsedMem:%dk\tFreeMem:%dk\t\tBuffers:%dk",
1917					       &Mem1, &Mem2, &Mem3, &Mem4);
1918					fgets(buff, BUFF_SIZE, datafile);
1919					AvgMem1 = Mem1 / 1024;	//this data should not change
1920					AvgMem2 += Mem2 / 1024;
1921					AvgMem3 += Mem3 / 1024;
1922					AvgMem4 += Mem4 / 1024;
1923					fscanf(datafile,
1924					       " TotalSwap:%dk\tUsedSwap:%dk\tFreeSwap:%dk\tCached:%dk",
1925					       &Mem1, &Mem2, &Mem3, &Mem4);
1926					fgets(buff, BUFF_SIZE, datafile);
1927					AvgSwap1 = Mem1 / 1024;	//this data should not change
1928					AvgSwap2 += Mem2 / 1024;
1929					AvgSwap3 += Mem3 / 1024;
1930					AvgSwap4 += Mem4 / 1024;
1931					fgets(buff, BUFF_SIZE, datafile);
1932					retcode =
1933					    fscanf(datafile,
1934						   " MaxTasks:%d RunningTasks:%d SleepingTasks:%d StoppedTasks:%d ZombieTasks:%d",
1935						   &Task1, &Task2, &Task3,
1936						   &Task4, &Task5);
1937				}
1938				fclose(datafile);
1939				AvgMaxTasks = AvgMaxTasks / loopcntr;
1940				AvgRunningTasks = AvgRunningTasks / loopcntr;
1941				AvgSleepingTasks = AvgSleepingTasks / loopcntr;
1942				AvgStoppedTasks = AvgStoppedTasks / loopcntr;
1943				AvgZombieTasks = AvgZombieTasks / loopcntr;
1944				AvgCPUuser = AvgCPUuser / loopcntr;
1945				AvgCPUsys = AvgCPUsys / loopcntr;
1946				AvgCPUnice = AvgCPUnice / loopcntr;
1947				AvgCPUidle = AvgCPUidle / loopcntr;
1948				AvgCPUiowait = AvgCPUiowait / loopcntr;
1949				AvgMem1 = AvgMem1;
1950				AvgMem2 = AvgMem2 / loopcntr;
1951				AvgMem3 = AvgMem3 / loopcntr;
1952				AvgMem4 = AvgMem4 / loopcntr;
1953				AvgSwap1 = AvgSwap1;
1954				AvgSwap2 = AvgSwap2 / loopcntr;
1955				AvgSwap3 = AvgSwap3 / loopcntr;
1956				AvgSwap4 = AvgSwap4 / loopcntr;
1957				UsedMemPercentage = AvgMem2 / AvgMem1 * 100;
1958				UsedSwapPercentage = AvgSwap2 / AvgSwap1 * 100;
1959
1960				printf("\nAverage data from %s\n", cp);
1961				printf("================================\n");
1962				printf
1963				    (" MaxTasks:%.0f\t\tRunningTasks:%.0f\tSleepingTasks:%.0f\tStoppedTasks:%.0f\tZombieTasks:%.0f\n\n",
1964				     AvgMaxTasks, AvgRunningTasks,
1965				     AvgSleepingTasks, AvgStoppedTasks,
1966				     AvgZombieTasks);
1967				printf
1968				    (" Cpu(s) : User:%.2f\%\tSystem:%.2f\%\t\tNice:%.2f\%\t\tIdle:%.2f\%\t\tIO-wait:%.2f\%\n\n",
1969				     AvgCPUuser, AvgCPUsys, AvgCPUnice,
1970				     AvgCPUidle, AvgCPUiowait);
1971				printf
1972				    (" TotalMem:%dMb\t\tUsedMem:%.0LfMb\t\tFreeMem:%.0LfMb\t\tBuffers:%.0LfMb\n",
1973				     AvgMem1, AvgMem2, AvgMem3, AvgMem4);
1974				printf
1975				    (" TotalSwap:%dMb\tUsedSwap:%.0LfMb\t\tFreeSwap:%.0LfMb\t\tCached:%.0LfMb\n",
1976				     AvgSwap1, AvgSwap2, AvgSwap3, AvgSwap4);
1977				printf
1978				    ("\n UsedMem Percentage:%.2Lf\%\tUsedSwap Percentage:%.2Lf\%\n\n",
1979				     UsedMemPercentage, UsedSwapPercentage);
1980				printf
1981				    ("A total of [%d] entries processed from %s.\n\n",
1982				     loopcntr, cp);
1983				exit(0);
1984				break;
1985			case 'h':
1986			case 'H':
1987			case 'v':
1988			case 'V':
1989				std_err(fmtmk
1990					("%s\nusage:\t%s%s", procps_version,
1991					 Myname, usage));
1992			case 'i':
1993				TOGw(Curwin, Show_IDLEPS);
1994				Curwin->rc.maxtasks = 0;
1995				break;
1996			case 'n':
1997				if (cp[1])
1998					cp++;
1999				else if (*args)
2000					cp = *args++;
2001				else
2002					std_err("-n requires argument");
2003				if (1 != sscanf(cp, "%d", &Loops) || 1 > Loops)
2004					std_err(fmtmk
2005						("bad iterations arg '%s'",
2006						 cp));
2007				break;
2008			case 'o':
2009				o_flag = 0;
2010				if (cp[1])
2011					cp++;
2012				else if (*args)
2013					cp = *args++;
2014				else
2015					std_err("-o requires argument");
2016				if ((outfile = fopen(cp, "a")) == NULL)
2017					std_err(fmtmk
2018						("bad file arg; failed to fopen '%s' for write",
2019						 cp));
2020				else
2021					o_flag = 1;
2022				cp = cp + strlen(cp);
2023				break;
2024			case 'p':
2025				do {
2026					if (selection_type)
2027						std_err
2028						    ("conflicting process selection");
2029					selection_type = 'p';
2030					if (cp[1])
2031						cp++;
2032					else if (*args)
2033						cp = *args++;
2034					else
2035						std_err("-p argument missing");
2036					if (Monpidsidx >= MONPIDMAX)
2037						std_err(fmtmk
2038							("pid limit (%d) exceeded",
2039							 MONPIDMAX));
2040					if (1 !=
2041					    sscanf(cp, "%d",
2042						   &Monpids[Monpidsidx])
2043					    || 0 > Monpids[Monpidsidx])
2044						std_err(fmtmk
2045							("bad pid '%s'", cp));
2046					if (!Monpids[Monpidsidx])
2047						Monpids[Monpidsidx] = getpid();
2048					Monpidsidx++;
2049					if (!(p = strchr(cp, ',')))
2050						break;
2051					cp = p;
2052				} while (*cp);
2053				break;
2054			case 's':
2055				Secure_mode = 1;
2056				break;
2057			case 'S':
2058				TOGw(Curwin, Show_CTIMES);
2059				break;
2060			case 'u':
2061				do {
2062					const char *errmsg;
2063					if (selection_type)
2064						std_err
2065						    ("conflicting process selection");
2066					if (cp[1])
2067						cp++;
2068					else if (*args)
2069						cp = *args++;
2070					else
2071						std_err("-u missing name");
2072					errmsg = parse_uid(cp, &selection_uid);
2073					if (errmsg)
2074						std_err(errmsg);
2075					selection_type = 'u';
2076					cp += snprintf(Curwin->colusrnam, USRNAMSIZ - 1, "%s", cp);	// FIXME: junk
2077				} while (0);
2078				break;
2079			case 'U':
2080				do {
2081					const char *errmsg;
2082					if (selection_type)
2083						std_err
2084						    ("conflicting process selection");
2085					if (cp[1])
2086						cp++;
2087					else if (*args)
2088						cp = *args++;
2089					else
2090						std_err("-u missing name");
2091					errmsg = parse_uid(cp, &selection_uid);
2092					if (errmsg)
2093						std_err(errmsg);
2094					selection_type = 'U';
2095					cp += snprintf(Curwin->colusrnam, USRNAMSIZ - 1, "%s", cp);	// FIXME: junk
2096				} while (0);
2097				break;
2098			default:
2099				std_err(fmtmk
2100					("unknown argument '%c'\nusage:\t%s%s",
2101					 *cp, Myname, usage));
2102
2103			}	/* end: switch (*cp) */
2104
2105			/* advance cp and jump over any numerical args used above */
2106			if (*cp)
2107				cp += strspn(&cp[1], "- ,.1234567890") + 1;
2108		}		/* end: while (*cp) */
2109	}			/* end: while (*args) */
2110
2111	/* fixup delay time, maybe... */
2112	if (MAXFLOAT != tmp_delay) {
2113		if (Secure_mode || 0 > tmp_delay)
2114			msg_save("Delay time Not changed");
2115		else
2116			Rc.delay_time = tmp_delay;
2117	}
2118}
2119
2120	/*
2121	 * Set up the terminal attributes */
2122static void whack_terminal(void)
2123{
2124	struct termios newtty;
2125
2126	// the curses part...
2127#ifdef PRETENDNOCAP
2128	setupterm("dumb", STDOUT_FILENO, NULL);
2129#else
2130	setupterm(NULL, STDOUT_FILENO, NULL);
2131#endif
2132	// our part...
2133	if (!Batch) {
2134		if (-1 == tcgetattr(STDIN_FILENO, &Savedtty))
2135			std_err("failed tty get");
2136		newtty = Savedtty;
2137		newtty.c_lflag &= ~(ICANON | ECHO);
2138		newtty.c_oflag &= ~(TAB3);
2139		newtty.c_cc[VMIN] = 1;
2140		newtty.c_cc[VTIME] = 0;
2141
2142		Ttychanged = 1;
2143		if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtty)) {
2144			putp(Cap_clr_scr);
2145			std_err(fmtmk("failed tty set: %s", strerror(errno)));
2146		}
2147		tcgetattr(STDIN_FILENO, &Rawtty);
2148#ifndef STDOUT_IOLBF
2149		// thanks anyway stdio, but we'll manage buffering at the frame level...
2150		setbuffer(stdout, Stdout_buf, sizeof(Stdout_buf));
2151#endif
2152		putp(Cap_clr_scr);
2153		fflush(stdout);
2154	}
2155}
2156
2157/*######  Field Selection/Ordering routines  #############################*/
2158
2159	/*
2160	 * Display each field represented in the Fields Table along with its
2161	 * description and mark (with a leading asterisk) fields associated
2162	 * with upper case letter(s) in the passed 'fields string'.
2163	 *
2164	 * After all fields have been displayed, some extra explanatory
2165	 * text may also be output */
2166static void display_fields(const char *fields, const char *xtra)
2167{
2168#define yRSVD 3
2169	const char *p;
2170	int i, cmax = Screen_cols / 2, rmax = Screen_rows - yRSVD;
2171
2172	/* we're relying on callers to first clear the screen and thus avoid screen
2173	   flicker if they're too lazy to handle their own asterisk (*) logic */
2174	putp(Curwin->cap_bold);
2175	for (i = 0; fields[i]; ++i) {
2176		const FLD_t *f = ft_get_ptr(FT_NEW_fmt, fields[i]);
2177		int b = isupper(fields[i]);
2178
2179		if (!f)
2180			continue;	// hey, should be std_err!
2181		for (p = f->head; ' ' == *p; ++p)	// advance past any leading spaces
2182			;
2183		PUTT("%s%s%c %c: %-10s = %s",
2184		     tg2((i / rmax) * cmax, (i % rmax) + yRSVD)
2185		     , b ? Curwin->cap_bold : Cap_norm, b ? '*' : ' ', fields[i]
2186		     , p, f->desc);
2187	}
2188	if (xtra) {
2189		putp(Curwin->capclr_rownorm);
2190		while ((p = strchr(xtra, '\n'))) {
2191			++i;
2192			PUTT("%s%.*s",
2193			     tg2((i / rmax) * cmax, (i % rmax) + yRSVD)
2194			     , (int)(p - xtra)
2195			     , xtra);
2196			xtra = ++p;
2197		}
2198	}
2199	putp(Caps_off);
2200
2201#undef yRSVD
2202}
2203
2204	/*
2205	 * Change order of displayed fields. */
2206static void fields_reorder(void)
2207{
2208	static const char prompt[] =
2209	    "Upper case letter moves field left, lower case right";
2210	char c, *p;
2211	int i;
2212
2213	putp(Cap_clr_scr);
2214	putp(Cap_curs_huge);
2215	for (;;) {
2216		display_fields(Curwin->rc.fieldscur, FIELDS_xtra);
2217		show_special(1,
2218			     fmtmk(FIELDS_current, Cap_home,
2219				   Curwin->rc.fieldscur, Curwin->grpname,
2220				   prompt));
2221		chin(0, &c, 1);
2222		if (!ft_get_ptr(FT_NEW_fmt, c))
2223			break;
2224		i = toupper(c) - 'A';
2225		if (((p = strchr(Curwin->rc.fieldscur, i + 'A')))
2226		    || ((p = strchr(Curwin->rc.fieldscur, i + 'a')))) {
2227			if (isupper(c))
2228				p--;
2229			if (('\0' != p[1]) && (p >= Curwin->rc.fieldscur)) {
2230				c = p[0];
2231				p[0] = p[1];
2232				p[1] = c;
2233			}
2234		}
2235	}
2236	putp(Cap_curs_norm);
2237}
2238
2239	/*
2240	 * Select sort field. */
2241static void fields_sort(void)
2242{
2243	static const char prompt[] =
2244	    "Select sort field via field letter, type any other key to return";
2245	char phoney[PFLAGSSIZ];
2246	char c, *p;
2247	int i, x;
2248
2249	strcpy(phoney, NUL_FIELDS);
2250	x = i = Curwin->rc.sortindx;
2251	putp(Cap_clr_scr);
2252	putp(Cap_curs_huge);
2253	for (;;) {
2254		p = phoney + i;
2255		*p = toupper(*p);
2256		display_fields(phoney, SORT_xtra);
2257		show_special(1,
2258			     fmtmk(SORT_fields, Cap_home, *p, Curwin->grpname,
2259				   prompt));
2260		chin(0, &c, 1);
2261		if (!ft_get_ptr(FT_NEW_fmt, c))
2262			break;
2263		i = toupper(c) - 'A';
2264		*p = tolower(*p);
2265		x = i;
2266	}
2267	if ((p = strchr(Curwin->rc.fieldscur, x + 'a')))
2268		*p = x + 'A';
2269	Curwin->rc.sortindx = x;
2270	putp(Cap_curs_norm);
2271}
2272
2273	/*
2274	 * Toggle displayed fields. */
2275static void fields_toggle(void)
2276{
2277	static const char prompt[] =
2278	    "Toggle fields via field letter, type any other key to return";
2279	char c, *p;
2280	int i;
2281
2282	putp(Cap_clr_scr);
2283	putp(Cap_curs_huge);
2284	for (;;) {
2285		display_fields(Curwin->rc.fieldscur, FIELDS_xtra);
2286		show_special(1,
2287			     fmtmk(FIELDS_current, Cap_home,
2288				   Curwin->rc.fieldscur, Curwin->grpname,
2289				   prompt));
2290		chin(0, &c, 1);
2291		if (!ft_get_ptr(FT_NEW_fmt, c))
2292			break;
2293		i = toupper(c) - 'A';
2294		if ((p = strchr(Curwin->rc.fieldscur, i + 'A')))
2295			*p = i + 'a';
2296		else if ((p = strchr(Curwin->rc.fieldscur, i + 'a')))
2297			*p = i + 'A';
2298	}
2299	putp(Cap_curs_norm);
2300}
2301
2302/*######  Windows/Field Groups support  #################################*/
2303
2304	/*
2305	 * For each of the four windows:
2306	 *    1) Set the number of fields/columns to display
2307	 *    2) Create the field columns heading
2308	 *    3) Set maximum cmdline length, if command lines are in use
2309	 * In the process, the required PROC_FILLxxx flags will be rebuilt! */
2310static void reframewins(void)
2311{
2312	WIN_t *w;
2313	char *s;
2314	const char *h;
2315	int i, needpsdb = 0;
2316
2317// Frames_libflags = 0;  // should be called only when it's zero
2318// Frames_maxcmdln = 0;  // to become largest from up to 4 windows, if visible
2319	w = Curwin;
2320	do {
2321		if (!Rc.mode_altscr || CHKw(w, VISIBLE_tsk)) {
2322			// build window's procflags array and establish a tentative maxpflgs
2323			for (i = 0, w->maxpflgs = 0; w->rc.fieldscur[i]; i++) {
2324				if (isupper(w->rc.fieldscur[i]))
2325					w->procflags[w->maxpflgs++] =
2326					    w->rc.fieldscur[i] - 'A';
2327			}
2328
2329			/* build a preliminary columns header not to exceed screen width
2330			   while accounting for a possible leading window number */
2331			*(s = w->columnhdr) = '\0';
2332			if (Rc.mode_altscr)
2333				s = scat(s, " ");
2334			for (i = 0; i < w->maxpflgs; i++) {
2335				h = Fieldstab[w->procflags[i]].head;
2336				// oops, won't fit -- we're outta here...
2337				if (Screen_cols <
2338				    (int)((s - w->columnhdr) + strlen(h)))
2339					break;
2340				s = scat(s, h);
2341			}
2342
2343			/* establish the final maxpflgs and prepare to grow the command column
2344			   heading via maxcmdln - it may be a fib if P_CMD wasn't encountered,
2345			   but that's ok because it won't be displayed anyway */
2346			w->maxpflgs = i;
2347			w->maxcmdln = Screen_cols
2348			    - (strlen(w->columnhdr) -
2349			       strlen(Fieldstab[P_CMD].head)) - 1;
2350
2351			/* finally, we can build the true run-time columns header, format the
2352			   command column heading, if P_CMD is really being displayed, and
2353			   rebuild the all-important PROC_FILLxxx flags that will be used
2354			   until/if we're we're called again */
2355			*(s = w->columnhdr) = '\0';
2356			if (Rc.mode_altscr)
2357				s = scat(s, fmtmk("%d", w->winnum));
2358			for (i = 0; i < w->maxpflgs; i++) {
2359				h = Fieldstab[w->procflags[i]].head;
2360				if (P_WCH == w->procflags[i])
2361					needpsdb = 1;
2362				if (P_CMD == w->procflags[i]) {
2363					s = scat(s,
2364						 fmtmk(Fieldstab[P_CMD].fmts,
2365						       w->maxcmdln, w->maxcmdln,
2366						       h));
2367					if (CHKw(w, Show_CMDLIN)) {
2368						Frames_libflags |= L_CMDLINE;
2369//                if (w->maxcmdln > Frames_maxcmdln) Frames_maxcmdln = w->maxcmdln;
2370					}
2371				} else
2372					s = scat(s, h);
2373				Frames_libflags |=
2374				    Fieldstab[w->procflags[i]].lflg;
2375			}
2376		}
2377		if (Rc.mode_altscr)
2378			w = w->next;
2379	} while (w != Curwin);
2380
2381	// do we need the kernel symbol table (and is it already open?)
2382	if (needpsdb) {
2383		if (-1 == No_ksyms) {
2384			No_ksyms = 0;
2385			if (open_psdb_message(NULL, msg_save))
2386				No_ksyms = 1;
2387			else
2388				PSDBopen = 1;
2389		}
2390	}
2391
2392	if (selection_type == 'U')
2393		Frames_libflags |= L_status;
2394
2395	if (Frames_libflags & L_EITHER) {
2396		Frames_libflags &= ~L_EITHER;
2397		if (!(Frames_libflags & L_stat))
2398			Frames_libflags |= L_status;
2399	}
2400	if (!Frames_libflags)
2401		Frames_libflags = L_DEFAULT;
2402}
2403
2404	/*
2405	 * Value a window's name and make the associated group name. */
2406static void win_names(WIN_t * q, const char *name)
2407{
2408	sprintf(q->rc.winname, "%.*s", WINNAMSIZ - 1, name);
2409	sprintf(q->grpname, "%d:%.*s", q->winnum, WINNAMSIZ - 1, name);
2410}
2411
2412	/*
2413	 * Display a window/field group (ie. make it "current"). */
2414static void win_select(char ch)
2415{
2416	static const char prompt[] = "Choose field group (1 - 4)";
2417
2418	/* if there's no ch, it means we're supporting the external interface,
2419	   so we must try to get our own darn ch by begging the user... */
2420	if (!ch) {
2421		show_pmt(prompt);
2422		chin(0, (char *)&ch, 1);
2423	}
2424	switch (ch) {
2425	case 'a':		/* we don't carry 'a' / 'w' in our */
2426		Curwin = Curwin->next;	/* pmt - they're here for a good   */
2427		break;		/* friend of ours -- wins_colors.  */
2428	case 'w':		/* (however those letters work via */
2429		Curwin = Curwin->prev;	/* the pmt too but gee, end-loser  */
2430		break;		/* should just press the darn key) */
2431	case '1':
2432	case '2':
2433	case '3':
2434	case '4':
2435		Curwin = &Winstk[ch - '1'];
2436		break;
2437	}
2438}
2439
2440	/*
2441	 * Just warn the user when a command can't be honored. */
2442static int win_warn(void)
2443{
2444	show_msg(fmtmk
2445		 ("\aCommand disabled, activate %s with '-' or '_'",
2446		  Curwin->grpname));
2447	/* we gotta' return false 'cause we're somewhat well known within
2448	   macro society, by way of that sassy little tertiary operator... */
2449	return 0;
2450}
2451
2452	/*
2453	 * Change colors *Helper* function to save/restore settings;
2454	 * ensure colors will show; and rebuild the terminfo strings. */
2455static void winsclrhlp(WIN_t * q, int save)
2456{
2457	static int flgssav, summsav, msgssav, headsav, tasksav;
2458
2459	if (save) {
2460		flgssav = q->rc.winflags;
2461		summsav = q->rc.summclr;
2462		msgssav = q->rc.msgsclr;
2463		headsav = q->rc.headclr;
2464		tasksav = q->rc.taskclr;
2465		SETw(q, Show_COLORS);
2466	} else {
2467		q->rc.winflags = flgssav;
2468		q->rc.summclr = summsav;
2469		q->rc.msgsclr = msgssav;
2470		q->rc.headclr = headsav;
2471		q->rc.taskclr = tasksav;
2472	}
2473	capsmk(q);
2474}
2475
2476	/*
2477	 * Change colors used in display */
2478static void wins_colors(void)
2479{
2480#define kbdABORT  'q'
2481#define kbdAPPLY  '\n'
2482	int clr = Curwin->rc.taskclr, *pclr = &Curwin->rc.taskclr;
2483	char ch, tgt = 'T';
2484
2485	if (0 >= max_colors) {
2486		show_msg("\aNo colors to map!");
2487		return;
2488	}
2489	winsclrhlp(Curwin, 1);
2490	putp(Cap_clr_scr);
2491	putp(Cap_curs_huge);
2492
2493	do {
2494		putp(Cap_home);
2495		/* this string is well above ISO C89's minimum requirements! */
2496		show_special(1,
2497			     fmtmk(COLOR_help, procps_version, Curwin->grpname,
2498				   CHKw(Curwin, View_NOBOLD) ? "On" : "Off",
2499				   CHKw(Curwin, Show_COLORS) ? "On" : "Off",
2500				   CHKw(Curwin, Show_HIBOLD) ? "On" : "Off",
2501				   tgt, clr, Curwin->grpname));
2502		chin(0, &ch, 1);
2503		switch (ch) {
2504		case 'S':
2505			pclr = &Curwin->rc.summclr;
2506			clr = *pclr;
2507			tgt = ch;
2508			break;
2509		case 'M':
2510			pclr = &Curwin->rc.msgsclr;
2511			clr = *pclr;
2512			tgt = ch;
2513			break;
2514		case 'H':
2515			pclr = &Curwin->rc.headclr;
2516			clr = *pclr;
2517			tgt = ch;
2518			break;
2519		case 'T':
2520			pclr = &Curwin->rc.taskclr;
2521			clr = *pclr;
2522			tgt = ch;
2523			break;
2524		case '0' ... '7':
2525			clr = ch - '0';
2526			*pclr = clr;
2527			break;
2528		case 'B':
2529			TOGw(Curwin, View_NOBOLD);
2530			break;
2531		case 'b':
2532			TOGw(Curwin, Show_HIBOLD);
2533			break;
2534		case 'z':
2535			TOGw(Curwin, Show_COLORS);
2536			break;
2537		case 'a':
2538		case 'w':
2539			win_select(ch);
2540			winsclrhlp(Curwin, 1);
2541			clr = Curwin->rc.taskclr, pclr = &Curwin->rc.taskclr;
2542			tgt = 'T';
2543			break;
2544		}
2545		capsmk(Curwin);
2546	} while (kbdAPPLY != ch && kbdABORT != ch);
2547
2548	if (kbdABORT == ch)
2549		winsclrhlp(Curwin, 0);
2550	putp(Cap_curs_norm);
2551
2552#undef kbdABORT
2553#undef kbdAPPLY
2554}
2555
2556	/*
2557	 * Manipulate flag(s) for all our windows. */
2558static void wins_reflag(int what, int flg)
2559{
2560	WIN_t *w;
2561
2562	w = Curwin;
2563	do {
2564		switch (what) {
2565		case Flags_TOG:
2566			TOGw(w, flg);
2567			break;
2568		case Flags_SET:	/* Ummmm, i can't find anybody */
2569			SETw(w, flg);	/* who uses Flags_set ...      */
2570			break;
2571		case Flags_OFF:
2572			OFFw(w, flg);
2573			break;
2574		}
2575		/* a flag with special significance -- user wants to rebalance
2576		   display so we gotta' 'off' one number then force on two flags... */
2577		if (EQUWINS_cwo == flg) {
2578			w->rc.maxtasks = 0;
2579			SETw(w, Show_IDLEPS | VISIBLE_tsk);
2580		}
2581		w = w->next;
2582	} while (w != Curwin);
2583}
2584
2585	/*
2586	 * Set the screen dimensions and arrange for the real workhorse.
2587	 * (also) catches:
2588	 *    SIGWINCH and SIGCONT */
2589static void wins_resize(int dont_care_sig)
2590{
2591	struct winsize wz;
2592
2593	(void)dont_care_sig;
2594	Screen_cols = columns;
2595	Screen_rows = lines;
2596	if (-1 != (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wz))) {
2597		Screen_cols = wz.ws_col;
2598		Screen_rows = wz.ws_row;
2599	}
2600	if (Batch)
2601		Screen_rows = MAXINT;
2602
2603	// we might disappoint some folks (but they'll deserve it)
2604	if (SCREENMAX < Screen_cols)
2605		Screen_cols = SCREENMAX;
2606
2607	/* keep our support for output optimization in sync with current reality
2608	   note: when we're in Batch mode, we don't really need a Pseudo_scrn and
2609	   when not Batch, our buffer will contain 1 extra 'line' since
2610	   Msg_row is never represented -- but it's nice to have some space
2611	   between us and the great-beyond... */
2612	Pseudo_cols = Screen_cols + CLRBUFSIZ + 1;
2613	if (Batch)
2614		Pseudo_size = ROWBUFSIZ + 1;
2615	else
2616		Pseudo_size = Pseudo_cols * Screen_rows;
2617	Pseudo_scrn = alloc_r(Pseudo_scrn, Pseudo_size);
2618
2619	// force rebuild of column headers AND libproc/readproc requirements
2620	Frames_libflags = 0;
2621}
2622
2623	/*
2624	 * Set up the raw/incomplete field group windows --
2625	 * they'll be finished off after startup completes.
2626	 * [ and very likely that will override most/all of our efforts ]
2627	 * [               --- life-is-NOT-fair ---                     ] */
2628static void windows_stage1(void)
2629{
2630	WIN_t *w;
2631	int i;
2632
2633	for (i = 0; i < GROUPSMAX; i++) {
2634		w = &Winstk[i];
2635		w->winnum = i + 1;
2636		w->rc = Rc.win[i];
2637		w->captab[0] = Cap_norm;
2638		w->captab[1] = Cap_norm;
2639		w->captab[2] = w->cap_bold;
2640		w->captab[3] = w->capclr_sum;
2641		w->captab[4] = w->capclr_msg;
2642		w->captab[5] = w->capclr_pmt;
2643		w->captab[6] = w->capclr_hdr;
2644		w->captab[7] = w->capclr_rowhigh;
2645		w->captab[8] = w->capclr_rownorm;
2646		w->next = w + 1;
2647		w->prev = w - 1;
2648		++w;
2649	}
2650	/* fixup the circular chains... */
2651	Winstk[3].next = &Winstk[0];
2652	Winstk[0].prev = &Winstk[3];
2653	Curwin = Winstk;
2654}
2655
2656	/*
2657	 * This guy just completes the field group windows after the
2658	 * rcfiles have been read and command line arguments parsed */
2659static void windows_stage2(void)
2660{
2661	int i;
2662
2663	for (i = 0; i < GROUPSMAX; i++) {
2664		win_names(&Winstk[i], Winstk[i].rc.winname);
2665		capsmk(&Winstk[i]);
2666	}
2667	// rely on this next guy to force a call (eventually) to reframewins
2668	wins_resize(0);
2669}
2670
2671/*######  Main Screen routines  ##########################################*/
2672
2673	/*
2674	 * Process keyboard input during the main loop */
2675static void do_key(unsigned c)
2676{
2677	// standardized 'secure mode' errors
2678	static const char err_secure[] = "\aUnavailable in secure mode";
2679#ifdef WARN_NOT_SMP
2680	// standardized 'smp' errors
2681	static const char err_smp[] = "\aSorry, only 1 cpu detected";
2682#endif
2683
2684	switch (c) {
2685	case '1':
2686#ifdef WARN_NOT_SMP
2687		if (Cpu_tot > 1)
2688			TOGw(Curwin, View_CPUSUM);
2689		else
2690			show_msg(err_smp);
2691#else
2692		TOGw(Curwin, View_CPUSUM);
2693#endif
2694		break;
2695
2696	case 'a':
2697		if (Rc.mode_altscr)
2698			Curwin = Curwin->next;
2699		break;
2700
2701	case 'A':
2702		Rc.mode_altscr = !Rc.mode_altscr;
2703		wins_resize(0);
2704		break;
2705
2706	case 'b':
2707		if (VIZCHKc) {
2708			if (!CHKw(Curwin, Show_HICOLS | Show_HIROWS))
2709				show_msg("\aNothing to highlight!");
2710			else {
2711				TOGw(Curwin, Show_HIBOLD);
2712				capsmk(Curwin);
2713			}
2714		}
2715		break;
2716
2717	case 'B':
2718		TOGw(Curwin, View_NOBOLD);
2719		capsmk(Curwin);
2720		break;
2721
2722	case 'c':
2723		VIZTOGc(Show_CMDLIN);
2724		break;
2725
2726	case 'd':
2727	case 's':
2728		if (Secure_mode)
2729			show_msg(err_secure);
2730		else {
2731			float tmp =
2732			    get_float(fmtmk
2733				      ("Change delay from %.1f to",
2734				       Rc.delay_time));
2735			if (-1 < tmp)
2736				Rc.delay_time = tmp;
2737		}
2738		break;
2739
2740	case 'f':
2741		if (VIZCHKc)
2742			fields_toggle();
2743		break;
2744
2745	case 'F':
2746	case 'O':
2747		if (VIZCHKc)
2748			fields_sort();
2749		break;
2750
2751	case 'g':
2752		if (Rc.mode_altscr) {
2753			char tmp[GETBUFSIZ];
2754			strcpy(tmp,
2755			       ask4str(fmtmk
2756				       ("Rename window '%s' to (1-3 chars)",
2757					Curwin->rc.winname)));
2758			if (tmp[0])
2759				win_names(Curwin, tmp);
2760		}
2761		break;
2762
2763	case 'G':
2764		win_select(0);
2765		break;
2766
2767	case 'h':
2768	case '?':
2769		{
2770			char ch;
2771			putp(Cap_clr_scr);
2772			putp(Cap_curs_huge);
2773			/* this string is well above ISO C89's minimum requirements! */
2774			show_special(1,
2775				     fmtmk(KEYS_help, procps_version,
2776					   Curwin->grpname, CHKw(Curwin,
2777								 Show_CTIMES) ?
2778					   "On" : "Off", Rc.delay_time,
2779					   Secure_mode ? "On" : "Off",
2780					   Secure_mode ? "" :
2781					   KEYS_help_unsecured));
2782			chin(0, &ch, 1);
2783			if ('?' == ch || 'h' == ch) {
2784				do {
2785					putp(Cap_clr_scr);
2786					show_special(1,
2787						     fmtmk(WINDOWS_help,
2788							   Curwin->grpname,
2789							   Winstk[0].rc.winname,
2790							   Winstk[1].rc.winname,
2791							   Winstk[2].rc.winname,
2792							   Winstk[3].rc.
2793							   winname));
2794					chin(0, &ch, 1);
2795					win_select(ch);
2796				} while ('\n' != ch);
2797			}
2798			putp(Cap_curs_norm);
2799		}
2800		break;
2801
2802	case 'i':
2803		VIZTOGc(Show_IDLEPS);
2804		break;
2805
2806	case 'I':
2807#ifdef WARN_NOT_SMP
2808		if (Cpu_tot > 1) {
2809			Rc.mode_irixps = !Rc.mode_irixps;
2810			show_msg(fmtmk
2811				 ("Irix mode %s",
2812				  Rc.mode_irixps ? "On" : "Off"));
2813		} else
2814			show_msg(err_smp);
2815#else
2816		Rc.mode_irixps = !Rc.mode_irixps;
2817		show_msg(fmtmk("Irix mode %s", Rc.mode_irixps ? "On" : "Off"));
2818#endif
2819		break;
2820
2821	case 'k':
2822		if (Secure_mode) {
2823			show_msg(err_secure);
2824		} else {
2825			int sig, pid = get_int("PID to kill");
2826			if (0 < pid) {
2827				sig =
2828				    signal_name_to_number(ask4str
2829							  (fmtmk
2830							   ("Kill PID %d with signal [%i]",
2831							    pid, DEF_SIGNAL)));
2832				if (-1 == sig)
2833					sig = DEF_SIGNAL;
2834				if (sig && kill(pid, sig))
2835					show_msg(fmtmk
2836						 ("\aKill of PID '%d' with '%d' failed: %s",
2837						  pid, sig, strerror(errno)));
2838			}
2839		}
2840		break;
2841
2842	case 'l':
2843		TOGw(Curwin, View_LOADAV);
2844		break;
2845
2846	case 'm':
2847		TOGw(Curwin, View_MEMORY);
2848		break;
2849
2850	case 'n':
2851	case '#':
2852		if (VIZCHKc) {
2853			int num =
2854			    get_int(fmtmk
2855				    ("Maximum tasks = %d, change to (0 is unlimited)",
2856				     Curwin->rc.maxtasks));
2857			if (-1 < num)
2858				Curwin->rc.maxtasks = num;
2859		}
2860		break;
2861
2862	case 'o':
2863		if (VIZCHKc)
2864			fields_reorder();
2865		break;
2866
2867	case 'q':
2868		end_pgm(0);
2869
2870	case 'r':
2871		if (Secure_mode)
2872			show_msg(err_secure);
2873		else {
2874			int val, pid = get_int("PID to renice");
2875			if (0 < pid) {
2876				val =
2877				    get_int(fmtmk
2878					    ("Renice PID %d to value", pid));
2879				if (setpriority
2880				    (PRIO_PROCESS, (unsigned)pid, val))
2881					show_msg(fmtmk
2882						 ("\aRenice of PID %d to %d failed: %s",
2883						  pid, val, strerror(errno)));
2884			}
2885		}
2886		break;
2887
2888	case 'R':
2889		VIZTOGc(Qsrt_NORMAL);
2890		break;
2891
2892	case 'S':
2893		if (VIZCHKc) {
2894			TOGw(Curwin, Show_CTIMES);
2895			show_msg(fmtmk
2896				 ("Cumulative time %s",
2897				  CHKw(Curwin, Show_CTIMES) ? "On" : "Off"));
2898		}
2899		break;
2900
2901	case 't':
2902		TOGw(Curwin, View_STATES);
2903		break;
2904
2905//    case 'u':
2906//       if (VIZCHKc)
2907//          strcpy(Curwin->colusrnam, ask4str("Which user (blank for all)"));
2908//       break;
2909
2910	case 'u':
2911//       if (!VIZCHKc) break;
2912		do {
2913			const char *errmsg;
2914			const char *answer;
2915			answer = ask4str("Which user (blank for all)");
2916			// FIXME: do this better:
2917			if (!answer || *answer == '\0' || *answer == '\n'
2918			    || *answer == '\r' || *answer == '\t'
2919			    || *answer == ' ') {
2920				selection_type = 0;
2921				selection_uid = -1;
2922				break;
2923			}
2924			errmsg = parse_uid(answer, &selection_uid);
2925			if (errmsg) {
2926				show_msg(errmsg);
2927				// Change settings here? I guess not.
2928				break;
2929			}
2930			selection_type = 'u';
2931		} while (0);
2932		break;
2933
2934	case 'U':
2935//       if (!VIZCHKc) break;
2936		do {
2937			const char *errmsg;
2938			const char *answer;
2939			answer = ask4str("Which user (blank for all)");
2940			// FIXME: do this better:
2941			if (!answer || *answer == '\0' || *answer == '\n'
2942			    || *answer == '\r' || *answer == '\t'
2943			    || *answer == ' ') {
2944				selection_type = 0;
2945				selection_uid = -1;
2946				break;
2947			}
2948			errmsg = parse_uid(answer, &selection_uid);
2949			if (errmsg) {
2950				show_msg(errmsg);
2951				// Change settings here? I guess not.
2952				break;
2953			}
2954			selection_type = 'U';
2955		} while (0);
2956		break;
2957
2958	case 'w':
2959		if (Rc.mode_altscr)
2960			Curwin = Curwin->prev;
2961		break;
2962
2963	case 'W':
2964		{
2965			const char *err = rc_write_whatever();
2966			if (err)
2967				show_msg(fmtmk
2968					 ("\aFailed '%s' open: %s", Rc_name,
2969					  err));
2970			else
2971				show_msg(fmtmk
2972					 ("Wrote configuration to '%s'",
2973					  Rc_name));
2974		}
2975		break;
2976
2977	case 'x':
2978		if (VIZCHKc) {
2979			TOGw(Curwin, Show_HICOLS);
2980			capsmk(Curwin);
2981		}
2982		break;
2983
2984	case 'y':
2985		if (VIZCHKc) {
2986			TOGw(Curwin, Show_HIROWS);
2987			capsmk(Curwin);
2988		}
2989		break;
2990
2991	case 'z':
2992		if (VIZCHKc) {
2993			TOGw(Curwin, Show_COLORS);
2994			capsmk(Curwin);
2995		}
2996		break;
2997
2998	case 'Z':
2999		wins_colors();
3000		break;
3001
3002	case '-':
3003		if (Rc.mode_altscr)
3004			TOGw(Curwin, VISIBLE_tsk);
3005		break;
3006
3007	case '_':
3008		if (Rc.mode_altscr)
3009			wins_reflag(Flags_TOG, VISIBLE_tsk);
3010		break;
3011
3012	case '=':
3013		Curwin->rc.maxtasks = 0;
3014		SETw(Curwin, Show_IDLEPS | VISIBLE_tsk);
3015		Monpidsidx = 0;
3016		break;
3017
3018	case '+':
3019		if (Rc.mode_altscr)
3020			SETw(Curwin, EQUWINS_cwo);
3021		break;
3022
3023	case '<':
3024		if (VIZCHKc) {
3025			FLG_t *p = Curwin->procflags + Curwin->maxpflgs - 1;
3026			while (*p != Curwin->rc.sortindx)
3027				--p;
3028			if (--p >= Curwin->procflags)
3029				Curwin->rc.sortindx = *p;
3030		}
3031		break;
3032
3033	case '>':
3034		if (VIZCHKc) {
3035			FLG_t *p = Curwin->procflags;
3036			while (*p != Curwin->rc.sortindx)
3037				++p;
3038			if (++p < Curwin->procflags + Curwin->maxpflgs)
3039				Curwin->rc.sortindx = *p;
3040		}
3041		break;
3042
3043	case 'M':		// these keys represent old-top compatability
3044	case 'N':		// -- grouped here so that if users could ever
3045	case 'P':		// be weaned, we would just whack this part...
3046	case 'T':
3047		{
3048			static struct {
3049				const unsigned xkey;
3050				const char *xmsg;
3051				const FLG_t sort;
3052			} xtab[] = {
3053				{
3054				'M', "Memory", P_MEM,}, {
3055				'N', "Numerical", P_PID,}, {
3056				'P', "CPU", P_CPU,}, {
3057			'T', "Time", P_TM2},};
3058			int i;
3059			for (i = 0; i < MAXTBL(xtab); ++i)
3060				if (c == xtab[i].xkey) {
3061					Curwin->rc.sortindx = xtab[i].sort;
3062					show_msg(fmtmk
3063						 ("%s sort compatibility key honored",
3064						  xtab[i].xmsg));
3065					break;
3066				}
3067		}
3068		break;
3069
3070	case '\n':		// just ignore these, they'll have the effect
3071	case ' ':		// of refreshing display after waking us up !
3072		break;
3073
3074	default:
3075		show_msg("\aUnknown command - try 'h' for help");
3076	}
3077	/* The following assignment will force a rebuild of all column headers and
3078	   the PROC_FILLxxx flags.  It's NOT simply lazy programming.  Here are
3079	   some keys that COULD require new column headers and/or libproc flags:
3080	   'A' - likely
3081	   'c' - likely when !Mode_altscr, maybe when Mode_altscr
3082	   'F' - maybe, if new field forced on
3083	   'f' - likely
3084	   'G' - likely
3085	   'O' - maybe, if new field forced on
3086	   'o' - maybe, if new field brought into view
3087	   'Z' - likely, if 'Curwin' changed when !Mode_altscr
3088	   '-' - likely (restricted to Mode_altscr)
3089	   '_' - likely (restricted to Mode_altscr)
3090	   '=' - maybe, but only when Mode_altscr
3091	   '+' - likely (restricted to Mode_altscr)
3092	   ( At this point we have a human being involved and so have all the time )
3093	   ( in the world.  We can afford a few extra cpu cycles every now & then! )
3094	 */
3095	Frames_libflags = 0;
3096}
3097
3098	/*
3099	 * State display *Helper* function to calc and display the state
3100	 * percentages for a single cpu.  In this way, we can support
3101	 * the following environments without the usual code bloat.
3102	 *    1) single cpu machines
3103	 *    2) modest smp boxes with room for each cpu's percentages
3104	 *    3) massive smp guys leaving little or no room for process
3105	 *       display and thus requiring the cpu summary toggle */
3106static void summaryhlp(CPU_t * cpu, const char *pfx)
3107{
3108	/* we'll trim to zero if we get negative time ticks,
3109	   which has happened with some SMP kernels (pre-2.4?) */
3110#define TRIMz(x)  ((tz = (SIC_t)(x)) < 0 ? 0 : tz)
3111	SIC_t u_frme, s_frme, n_frme, i_frme, w_frme, tot_frme, tz;
3112	float scale;
3113
3114	u_frme = cpu->u - cpu->u_sav;
3115	s_frme = cpu->s - cpu->s_sav;
3116	n_frme = cpu->n - cpu->n_sav;
3117	i_frme = TRIMz(cpu->i - cpu->i_sav);
3118	w_frme = cpu->w - cpu->w_sav;
3119	tot_frme = u_frme + s_frme + n_frme + i_frme + w_frme;
3120	if (1 > tot_frme)
3121		tot_frme = 1;
3122	scale = 100.0 / (float)tot_frme;
3123
3124	/* display some kinda' cpu state percentages
3125	   (who or what is explained by the passed prefix) */
3126	show_special(0,
3127		     fmtmk(States_fmts, pfx, (float)u_frme * scale,
3128			   (float)s_frme * scale, (float)n_frme * scale,
3129			   (float)i_frme * scale, (float)w_frme * scale));
3130	if (o_flag)
3131		fprintf(outfile,
3132			" %s User:%.2f\tSystem:%.2f\tNice:%.2f\t\tIdle:%.2f\tIO-wait:%.2f\n",
3133			pfx, (float)u_frme * scale, (float)s_frme * scale,
3134			(float)n_frme * scale, (float)i_frme * scale,
3135			(float)w_frme * scale);
3136	Msg_row += 1;
3137
3138	// remember for next time around
3139	cpu->u_sav = cpu->u;
3140	cpu->s_sav = cpu->s;
3141	cpu->n_sav = cpu->n;
3142	cpu->i_sav = cpu->i;
3143	cpu->w_sav = cpu->w;
3144
3145#undef TRIMz
3146}
3147
3148	/*
3149	 * Begin a new frame by:
3150	 *    1) Refreshing the all important proc table
3151	 *    2) Displaying uptime and load average (maybe)
3152	 *    3) Displaying task/cpu states (maybe)
3153	 *    4) Displaying memory & swap usage (maybe)
3154	 * and then, returning a pointer to the pointers to the proc_t's! */
3155static proc_t **summary_show(void)
3156{
3157	static proc_t **p_table = NULL;
3158	static CPU_t *smpcpu = NULL;
3159
3160	// whoa first time, gotta' prime the pump...
3161	if (!p_table) {
3162		p_table = procs_refresh(NULL, L_DEFAULT);
3163		putp(Cap_clr_scr);
3164		sleep(1);
3165	} else
3166		putp(Batch ? "\n\n" : Cap_home);
3167	p_table = procs_refresh(p_table, Frames_libflags);
3168
3169	/*
3170	 ** Display Uptime and Loadavg */
3171	if (CHKw(Curwin, View_LOADAV)) {
3172		if (!Rc.mode_altscr)
3173			show_special(0,
3174				     fmtmk(LOADAV_line, Myname,
3175					   sprint_uptime()));
3176		else
3177			show_special(0, fmtmk(CHKw(Curwin, VISIBLE_tsk)
3178					      ? LOADAV_line_alt
3179					      : LOADAV_line, Curwin->grpname,
3180					      sprint_uptime()));
3181		Msg_row += 1;
3182	}
3183
3184	/*
3185	 ** Display Task and Cpu(s) States */
3186
3187	if (CHKw(Curwin, View_STATES)) {
3188		show_special(0,
3189			     fmtmk(STATES_line1, Frame_maxtask, Frame_running,
3190				   Frame_sleepin, Frame_stopped,
3191				   Frame_zombied));
3192		if (o_flag)
3193			fprintf(outfile,
3194				" MaxTasks:%d\t\tRunningTasks:%d\tSleepingTasks:%d\tStoppedTasks:%d\tZombieTasks:%d\n\n",
3195				Frame_maxtask, Frame_running, Frame_sleepin,
3196				Frame_stopped, Frame_zombied);
3197		Msg_row += 1;
3198
3199		smpcpu = cpus_refresh(smpcpu);
3200
3201		if (CHKw(Curwin, View_CPUSUM)) {
3202			// display just the 1st /proc/stat line
3203			summaryhlp(&smpcpu[Cpu_tot], "Cpu(s):");
3204			if (o_flag)
3205				fprintf(outfile, "\n");
3206		} else {
3207			int i;
3208			char tmp[SMLBUFSIZ];
3209			// display each cpu's states separately
3210			for (i = 0; i < Cpu_tot; i++) {
3211				snprintf(tmp, sizeof(tmp), " Cpu%-2d:",
3212					 Rc.mode_irixps ? i : Cpu_map[i]);
3213				summaryhlp(&smpcpu[i], tmp);
3214			}
3215		}
3216	}
3217
3218	/*
3219	 ** Display Memory and Swap stats */
3220	meminfo();
3221	if (CHKw(Curwin, View_MEMORY)) {
3222		show_special(0,
3223			     fmtmk(MEMORY_line1, kb_main_total, kb_main_used,
3224				   kb_main_free, kb_main_buffers));
3225		if (o_flag)
3226			fprintf(outfile,
3227				" TotalMem:%dk\tUsedMem:%dk\tFreeMem:%dk\t\tBuffers:%dk\n",
3228				kb_main_total, kb_main_used, kb_main_free,
3229				kb_main_buffers);
3230		show_special(0,
3231			     fmtmk(MEMORY_line2, kb_swap_total, kb_swap_used,
3232				   kb_swap_free, kb_main_cached));
3233		if (o_flag)
3234			fprintf(outfile,
3235				" TotalSwap:%dk\tUsedSwap:%dk\tFreeSwap:%dk\tCached:%dk\n",
3236				kb_swap_total, kb_swap_used, kb_swap_free,
3237				kb_main_cached);
3238		Msg_row += 2;
3239	}
3240	if (o_flag)
3241		fprintf(outfile, "============\n");
3242	SETw(Curwin, NEWFRAM_cwo);
3243	return p_table;
3244}
3245
3246	/*
3247	 * Display information for a single task row. */
3248static void task_show(const WIN_t * q, const proc_t * p)
3249{
3250	/* the following macro is our means to 'inline' emitting a column -- next to
3251	   procs_refresh, that's the most frequent and costly part of top's job ! */
3252#define MKCOL(va...) do { \
3253   if (likely(!(CHKw(q, Show_HICOLS) && q->rc.sortindx == i))) \
3254      snprintf(cbuf, sizeof(cbuf), f, ## va); \
3255   else { \
3256      snprintf(_z, sizeof(_z), f, ## va); \
3257      snprintf(cbuf, sizeof(cbuf), "%s%s%s", q->capclr_rowhigh, _z \
3258         , !(CHKw(q, Show_HIROWS) && 'R' == p->state) ? q->capclr_rownorm : ""); \
3259      pad += q->len_rowhigh; \
3260      if (!(CHKw(q, Show_HIROWS) && 'R' == p->state)) pad += q->len_rownorm; \
3261   } } while (0)
3262	char rbuf[ROWBUFSIZ], *rp;
3263	int j, x, pad;
3264
3265	// we must begin a row with a possible window number in mind...
3266	*(rp = rbuf) = '\0';
3267	if ((pad = Rc.mode_altscr))
3268		rp = scat(rp, " ");
3269
3270	for (x = 0; x < q->maxpflgs; x++) {
3271		char cbuf[ROWBUFSIZ], _z[ROWBUFSIZ];
3272		FLG_t i = q->procflags[x];	// support for our field/column
3273		const char *f = Fieldstab[i].fmts;	// macro AND sometimes the fmt
3274		unsigned s = Fieldstab[i].scale;	// string must be altered !
3275		unsigned w = Fieldstab[i].width;
3276
3277		switch (i) {
3278		case P_CMD:
3279			{
3280				char tmp[ROWBUFSIZ];
3281				unsigned flags;
3282				if (CHKw(q, Show_CMDLIN))
3283					flags =
3284					    ESC_DEFUNCT | ESC_BRACKETS |
3285					    ESC_ARGS;
3286				else
3287					flags = ESC_DEFUNCT;
3288				escape_command(tmp, p, sizeof tmp, q->maxcmdln,
3289					       flags);
3290				MKCOL(q->maxcmdln, q->maxcmdln, tmp);
3291			}
3292			break;
3293		case P_COD:
3294			MKCOL(scale_num(PAGES_2K(p->trs), w, s));
3295			break;
3296		case P_CPN:
3297			MKCOL((unsigned)p->processor);
3298			break;
3299		case P_CPU:
3300			{
3301				float u = (float)p->pcpu * Frame_tscale;
3302				if (99.9 < u)
3303					u = 99.9;
3304				MKCOL(u);
3305			}
3306			break;
3307		case P_DAT:
3308			MKCOL(scale_num(PAGES_2K(p->drs), w, s));
3309			break;
3310		case P_DRT:
3311			MKCOL(scale_num((unsigned)p->dt, w, s));
3312			break;
3313		case P_FLG:
3314			{
3315				char tmp[TNYBUFSIZ];
3316				snprintf(tmp, sizeof(tmp), f, (long)p->flags);
3317				for (j = 0; tmp[j]; j++)
3318					if ('0' == tmp[j])
3319						tmp[j] = '.';
3320				f = tmp;
3321				MKCOL("");
3322			}
3323			break;
3324		case P_FLT:
3325			MKCOL(scale_num(p->maj_flt, w, s));
3326			break;
3327		case P_GRP:
3328			MKCOL(p->egroup);
3329			break;
3330		case P_MEM:
3331			MKCOL((float)PAGES_2K(p->resident) * 100 /
3332			      kb_main_total);
3333			break;
3334		case P_NCE:
3335			MKCOL((int)p->nice);
3336			break;
3337		case P_PID:
3338			MKCOL((unsigned)p->pid);
3339			break;
3340		case P_PPD:
3341			MKCOL((unsigned)p->ppid);
3342			break;
3343		case P_PRI:
3344			if (unlikely(-99 > p->priority)
3345			    || unlikely(999 < p->priority)) {
3346				f = " RT ";
3347				MKCOL("");
3348			} else
3349				MKCOL((int)p->priority);
3350			break;
3351		case P_RES:
3352			MKCOL(scale_num(PAGES_2K(p->resident), w, s));
3353			break;
3354		case P_SHR:
3355			MKCOL(scale_num(PAGES_2K(p->share), w, s));
3356			break;
3357		case P_STA:
3358#ifdef USE_LIB_STA3
3359			MKCOL(status(p));
3360#else
3361			MKCOL(p->state);
3362#endif
3363			break;
3364		case P_SWP:
3365			MKCOL(scale_num(PAGES_2K(p->size - p->resident), w, s));
3366			break;
3367		case P_TME:
3368		case P_TM2:
3369			{
3370				TIC_t t = p->utime + p->stime;
3371				if (CHKw(q, Show_CTIMES))
3372					t += (p->cutime + p->cstime);
3373				MKCOL(scale_tics(t, w));
3374			}
3375			break;
3376		case P_TTY:
3377			{
3378				char tmp[TNYBUFSIZ];
3379				dev_to_tty(tmp, (int)w, p->tty, p->pid,
3380					   ABBREV_DEV);
3381				MKCOL(tmp);
3382			}
3383			break;
3384		case P_UID:
3385			MKCOL((unsigned)p->euid);
3386			break;
3387		case P_URE:
3388			MKCOL(p->euser);
3389			break;
3390		case P_URR:
3391			MKCOL(p->ruser);
3392			break;
3393		case P_VRT:
3394			MKCOL(scale_num(PAGES_2K(p->size), w, s));
3395			break;
3396		case P_WCH:
3397			if (No_ksyms) {
3398#ifdef CASEUP_HEXES
3399				f = "%08lX  ";
3400#else
3401				f = "%08lx  ";
3402#endif
3403				MKCOL((long)p->wchan);
3404			} else {
3405				MKCOL(wchan(p->wchan, p->pid));
3406			}
3407			break;
3408
3409		}		/* end: switch 'procflag' */
3410
3411		rp = scat(rp, cbuf);
3412	}			/* end: for 'maxpflgs' */
3413
3414	PUFF("\n%s%.*s%s%s", (CHKw(q, Show_HIROWS) && 'R' == p->state)
3415	     ? q->capclr_rowhigh : q->capclr_rownorm, Screen_cols + pad, rbuf,
3416	     Caps_off, Cap_clr_eol);
3417
3418#undef MKCOL
3419}
3420
3421	/*
3422	 * Squeeze as many tasks as we can into a single window,
3423	 * after sorting the passed proc table. */
3424static void window_show(proc_t ** ppt, WIN_t * q, int *lscr)
3425{
3426#ifdef SORT_SUPRESS
3427	// the 1 flag that DOES and 2 flags that MAY impact our proc table qsort
3428#define srtMASK  ~( Qsrt_NORMAL | Show_CMDLIN | Show_CTIMES )
3429	static FLG_t sav_indx = 0;
3430	static int sav_flgs = -1;
3431#endif
3432	int i, lwin;
3433
3434	/*
3435	 ** Display Column Headings -- and distract 'em while we sort (maybe) */
3436	PUFF("\n%s%s%s%s", q->capclr_hdr, q->columnhdr, Caps_off, Cap_clr_eol);
3437
3438#ifdef SORT_SUPRESS
3439	if (CHKw(Curwin, NEWFRAM_cwo)
3440	    || sav_indx != q->rc.sortindx
3441	    || sav_flgs != (q->rc.winflags & srtMASK)) {
3442		sav_indx = q->rc.sortindx;
3443		sav_flgs = (q->rc.winflags & srtMASK);
3444#endif
3445		if (CHKw(q, Qsrt_NORMAL))
3446			Frame_srtflg = 1;	// this one's always needed!
3447		else
3448			Frame_srtflg = -1;
3449		Frame_ctimes = CHKw(q, Show_CTIMES);	// this and next, only maybe
3450		Frame_cmdlin = CHKw(q, Show_CMDLIN);
3451		qsort(ppt, Frame_maxtask, sizeof(proc_t *),
3452		      Fieldstab[q->rc.sortindx].sort);
3453#ifdef SORT_SUPRESS
3454	}
3455#endif
3456	// account for column headings
3457	(*lscr)++;
3458	lwin = 1;
3459	i = 0;
3460
3461	while (-1 != ppt[i]->pid && *lscr < Max_lines
3462	       && (!q->winlines || (lwin <= q->winlines))) {
3463		if ((CHKw(q, Show_IDLEPS)
3464		     || ('S' != ppt[i]->state && 'Z' != ppt[i]->state))
3465		    && good_uid(ppt[i])) {
3466			/*
3467			 ** Display a process Row */
3468			task_show(q, ppt[i]);
3469			(*lscr)++;
3470			++lwin;
3471		}
3472		++i;
3473	}
3474	// for this frame that window's toast, cleanup for next time
3475	q->winlines = 0;
3476	OFFw(Curwin, FLGSOFF_cwo);
3477
3478#ifdef SORT_SUPRESS
3479#undef srtMASK
3480#endif
3481}
3482
3483/*######  Entry point plus two  ##########################################*/
3484
3485	/*
3486	 * This guy's just a *Helper* function who apportions the
3487	 * remaining amount of screen real estate under multiple windows */
3488static void framehlp(int wix, int max)
3489{
3490	int i, rsvd, size, wins;
3491
3492	// calc remaining number of visible windows + total 'user' lines
3493	for (i = wix, rsvd = 0, wins = 0; i < GROUPSMAX; i++) {
3494		if (CHKw(&Winstk[i], VISIBLE_tsk)) {
3495			rsvd += Winstk[i].rc.maxtasks;
3496			++wins;
3497			if (max <= rsvd)
3498				break;
3499		}
3500	}
3501	if (!wins)
3502		wins = 1;
3503	// set aside 'rsvd' & deduct 1 line/window for the columns heading
3504	size = (max - wins) - rsvd;
3505	if (0 <= size)
3506		size = max;
3507	size = (max - wins) / wins;
3508
3509	/* for remaining windows, set WIN_t winlines to either the user's
3510	   maxtask (1st choice) or our 'foxized' size calculation
3511	   (foxized  adj. -  'fair and balanced') */
3512	for (i = wix; i < GROUPSMAX; i++) {
3513		if (CHKw(&Winstk[i], VISIBLE_tsk)) {
3514			Winstk[i].winlines =
3515			    Winstk[i].rc.maxtasks ? Winstk[i].rc.
3516			    maxtasks : size;
3517		}
3518	}
3519}
3520
3521	/*
3522	 * Initiate the Frame Display Update cycle at someone's whim!
3523	 * This routine doesn't do much, mostly he just calls others.
3524	 *
3525	 * (Whoa, wait a minute, we DO caretake those row guys, plus)
3526	 * (we CALCULATE that IMPORTANT Max_lines thingy so that the)
3527	 * (*subordinate* functions invoked know WHEN the user's had)
3528	 * (ENOUGH already.  And at Frame End, it SHOULD be apparent)
3529	 * (WE am d'MAN -- clearing UNUSED screen LINES and ensuring)
3530	 * (the CURSOR is STUCK in just the RIGHT place, know what I)
3531	 * (mean?  Huh, "doesn't DO MUCH"!  Never, EVER think or say)
3532	 * (THAT about THIS function again, Ok?  Good that's better.)
3533	 *
3534	 * (ps. we ARE the UNEQUALED justification KING of COMMENTS!)
3535	 * (No, I don't mean significance/relevance, only alignment.)
3536	 *
3537	 * (What's that?  Are you sure?  Old main's REALLY GOOD too?)
3538	 * (You say he even JUSTIFIES comments in his FUNCTION BODY?)
3539	 * (Jeeze, how COULD I have known?  That sob's NOT IN SCOPE!)
3540	 */
3541static void frame_make(void)
3542{
3543	proc_t **ppt;
3544	int i, scrlins;
3545
3546	/* note: except for PROC_PID, all libproc flags are managed by
3547	   reframewins(), who also builds each window's column headers */
3548	if (!Frames_libflags) {
3549		reframewins();
3550		memset(Pseudo_scrn, '\0', Pseudo_size);
3551	}
3552	Pseudo_row = Msg_row = scrlins = 0;
3553	ppt = summary_show();
3554	Max_lines = (Screen_rows - Msg_row) - 1;
3555
3556	if (CHKw(Curwin, EQUWINS_cwo))
3557		wins_reflag(Flags_OFF, EQUWINS_cwo);
3558
3559	// sure hope each window's columns header begins with a newline...
3560	putp(tg2(0, Msg_row));
3561
3562	if (!Rc.mode_altscr) {
3563		// only 1 window to show so, piece o' cake
3564		Curwin->winlines = Curwin->rc.maxtasks;
3565		window_show(ppt, Curwin, &scrlins);
3566	} else {
3567		// maybe NO window is visible but assume, pieces o' cakes
3568		for (i = 0; i < GROUPSMAX; i++) {
3569			if (CHKw(&Winstk[i], VISIBLE_tsk)) {
3570				framehlp(i, Max_lines - scrlins);
3571				window_show(ppt, &Winstk[i], &scrlins);
3572			}
3573			if (Max_lines <= scrlins)
3574				break;
3575		}
3576	}
3577	/* clear to end-of-screen (critical if last window is 'idleps off'),
3578	   then put the cursor in-its-place, and rid us of any prior frame's msg
3579	   (main loop must iterate such that we're always called before sleep) */
3580	PUTT("%s%s%s%s", scrlins < Max_lines ? "\n" : "",
3581	     scrlins < Max_lines ? Cap_clr_eos : "", tg2(0, Msg_row)
3582	     , Cap_clr_eol);
3583	fflush(stdout);
3584}
3585
3586	/*
3587	 * Darling, you DO look simply MARVELOUS -- have you been dieting?
3588	 * Or maybe it's because YOU and WINDOWS seem such a HAPPY couple.
3589	 *
3590	 * Of course NO.  Not THOSE deathly BLUE WINDOWS!  I mean your 'A'
3591	 * mode (alt display) windows.  Yes, yes those completely OPTIONAL
3592	 * ones.  We ALL know you'd NEVER FORCE that interface on ANY user
3593	 * - unlike You-Know-Who!  Well I've got to run.  But you're doing
3594	 * it just SPLENDIDLY!  You go right on doing it EXACTLY the SAME!
3595	 */
3596int main(int dont_care_argc, char **argv)
3597{
3598	(void)dont_care_argc;
3599	before(*argv);
3600	/*
3601	   Ok, she's gone now.  Don't you mind her, she means well but yes, she is
3602	   a bit of a busy-body.  Always playing the matchmaker role, trying to do
3603	   away with unmarried windows and bachelors.  So, back to business buddy!
3604
3605	   You're hungry, you said?  How'd you like a sandwich?  No, no, no -- not
3606	   the usual slopped together, hacked up illogic.  I'm talkin' a carefully
3607	   reasoned, artfully crafted, extremely capable, well behaved executable!
3608
3609	   Well then, here, try THIS sandwich:  */
3610	//                 +-------------+
3611	windows_stage1();	//                 top (sic) slice
3612	configs_read();		//                 > spread etc, <
3613	parse_args(&argv[1]);	//                 > lean stuff, <
3614	whack_terminal();	//                 > onions etc. <
3615	windows_stage2();	//                 as bottom slice
3616	//                 +-------------+
3617	signal(SIGALRM, end_pgm);
3618	signal(SIGHUP, end_pgm);
3619	signal(SIGINT, end_pgm);
3620	signal(SIGPIPE, end_pgm);
3621	signal(SIGQUIT, end_pgm);
3622	signal(SIGTERM, end_pgm);
3623	signal(SIGTSTP, suspend);
3624	signal(SIGTTIN, suspend);
3625	signal(SIGTTOU, suspend);
3626	signal(SIGCONT, wins_resize);
3627	signal(SIGWINCH, wins_resize);
3628
3629	for (;;) {
3630		struct timeval tv;
3631		fd_set fs;
3632		char c;
3633		//                     This is it?
3634		frame_make();	//                     Impossible!
3635
3636		if (Msg_awaiting)
3637			show_msg(Msg_delayed);
3638		if (0 < Loops)
3639			--Loops;
3640		if (!Loops)
3641			end_pgm(0);
3642
3643		if (Batch)
3644			sleep((unsigned)Rc.delay_time);
3645		else {		//   Linux reports time not slept,
3646			tv.tv_sec = Rc.delay_time;	//   so we must reinit every time.
3647			tv.tv_usec =
3648			    (Rc.delay_time - (int)Rc.delay_time) * 1000000;
3649			FD_ZERO(&fs);
3650			FD_SET(STDIN_FILENO, &fs);
3651			if (0 < select(STDIN_FILENO + 1, &fs, NULL, NULL, &tv)
3652			    && 0 < chin(0, &c, 1))
3653				do_key((unsigned)c);
3654		}
3655	}
3656
3657	/*
3658	   (listen before we return, aren't you sort of sad for old 'frame_make'?)
3659	   (so, uh, why don't we just move this main guy to near the beginning of)
3660	   (the C source file.  then that poor old function would be sure to have)
3661	   (at least a chance at scopin' us out, ya know what i mean?  so what do)
3662	   (ya think?  all things considered, would that be a proper thing to do?)
3663
3664	   Now there's an EXCELLENT idea!  After all, SOME programmers DO code the
3665	   main function ANY OLD PLACE they feel like.  And in doing THAT, they're
3666	   helping to keep those that FOLLOW out of mischief, busy HUNTING for the
3667	   <bleepin'> thing.  Further, by moving it we can contribute to PROTOTYPE
3668	   PROLIFERATION for every function main calls.  Don't you KNOW that those
3669	   declarations OFTEN LINGER, usually long AFTER the REAL definitions have
3670	   DISAPPEARED, since programs do evolve?  Yep that's a GREAT idea you got
3671	   there, NICE GOING!  But, here's an opposing view: ANYONE who'd put main
3672	   ANYWHERE such that its LOCATION cannot be REACHED with JUST 1 KEYSTROKE
3673	   better turn in their Coding_Badge and Pocket_Protector then -- BE GONE!
3674	   The main function has only ONE proper HOME: always the LAST function in
3675	   that C Listing.  End-of-Story, No-More-Discussion, so BE QUIET already!
3676
3677	   \---------------------------------------------------------------------/
3678	   Sheeesh, didn't that doofus know the return statement can't be executed
3679	   or that we end via a caught signal?  Oh Lordy, I is DROWNING in morons!
3680	   They done REACHED clear up to my OUTER braces!  We's surely DOOMED now!
3681	   /---------------------------------------------------------------------\
3682	 */
3683	return 0;
3684}
3685