/*
* Copyright (c) 1984 through 2008, William LeFebvre
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of William LeFebvre nor the names of other
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const char *copyright =
"Copyright (c) 1984 through 2008, William LeFebvre";
/*
* Changes to other files that we can do at the same time:
* screen.c:init_termcap: get rid of the "interactive" argument and have it
* pass back something meaningful (such as success/failure/error).
*/
/* determine which type of signal functions to use */
/* cant have sigaction without sigprocmask */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGPROCMASK)
#undef HAVE_SIGACTION
#endif
/* always use sigaction when it is available */
#ifdef HAVE_SIGACTION
#undef HAVE_SIGHOLD
#else
/* use sighold/sigrelse, otherwise use old fashioned BSD signals */
#if !defined(HAVE_SIGHOLD) || !defined(HAVE_SIGRELSE)
#define BSD_SIGNALS
#endif
#endif
/* if FD_SET and friends aren't present, then fake something up */
#ifndef FD_SET
typedef int fd_set;
#define FD_ZERO(x) (*(x) = 0)
#define FD_SET(f, x) (*(x) = 1<<f)
#endif
/*
* void do_arguments(globalstate *gstate, int ac, char **av)
*
* Arguments processing. gstate points to the global state,
* ac and av are the arguments to process. This can be called
* multiple times with different sets of arguments.
*/
static void
do_arguments(globalstate *gstate, int ac, char **av)
{
int i;
double f;
/* this appears to keep getopt happy */
optind = 1;
#ifdef HAVE_GETOPT_LONG
while ((i = getopt_long(ac, av, "1CDSITabcinp:qtuvs:d:U:o:m:", longopts, NULL)) != -1)
#else
while ((i = getopt(ac, av, "1CDSITabcinp:qtuvs:d:U:o:m:")) != EOF)
#endif
{
switch(i)
{
case '1':
gstate->percpustates = !gstate->percpustates;
break;
#ifdef ENABLE_COLOR
case 'C':
gstate->use_color = !gstate->use_color;
break;
#endif
case 'D':
debug_set(1);
break;
case 'v':
fprintf(stderr, "%s: version %s\n", myname, version_string());
exit(EX_OK);
break;
case 'b':
case 'n':
gstate->interactive = No;
break;
case 'a':
gstate->displays = Infinity;
gstate->topn = Infinity;
break;
case 'i':
gstate->interactive = Yes;
break;
case 'o':
gstate->order_name = optarg;
break;
case 'd':
i = atoiwi(optarg);
if (i == Invalid || i == 0)
{
message_error(" Bad display count");
}
else
{
gstate->displays = i;
}
break;
case 's':
f = atof(optarg);
if (f < 0 || (f == 0 && getuid() != 0))
{
message_error(" Bad seconds delay");
}
else
{
gstate->delay = f;
}
break;
case 'u':
gstate->show_usernames = !gstate->show_usernames;
break;
case 'U':
i = userid(optarg);
if (i == -1)
{
message_error(" Unknown user '%s'", optarg);
}
else
{
gstate->pselect.uid = i;
}
break;
case 'm':
i = atoi(optarg);
gstate->pselect.mode = i;
break;
case 'S':
gstate->pselect.system = !gstate->pselect.system;
break;
case 'I':
gstate->pselect.idle = !gstate->pselect.idle;
break;
#ifdef ENABLE_COLOR
case 'T':
gstate->show_tags = 1;
break;
#endif
case 'c':
gstate->pselect.fullcmd = !gstate->pselect.fullcmd;
break;
case 't':
gstate->pselect.threads = !gstate->pselect.threads;
break;
case 'p':
gstate->pselect.pid = atoi(optarg);
break;
case 'q': /* be quick about it */
/* only allow this if user is really root */
if (getuid() == 0)
{
/* be very un-nice! */
(void) nice(-20);
}
else
{
message_error(" Option -q can only be used by root");
}
break;
/* get count of top processes to display */
if (optind < ac && *av[optind])
{
if ((i = atoiwi(av[optind])) == Invalid)
{
message_error(" Process count not a number");
}
else
{
gstate->topn = i;
}
}
}
static void
do_display(globalstate *gstate)
{
int active_procs;
int i;
time_t curr_time;
caddr_t processes;
struct system_info system_info;
char *hdr;
/* get the time */
time_mark(&(gstate->now));
curr_time = (time_t)(gstate->now.tv_sec);
/* get the current stats */
get_system_info(&system_info);
/* get the current processes */
processes = get_process_info(&system_info, &(gstate->pselect), gstate->order_index);
/* determine number of processes to actually display */
if (gstate->topn > 0)
{
/* this number will be the smallest of: active processes,
number user requested, number current screen accomodates */
active_procs = system_info.P_ACTIVE;
if (active_procs > gstate->topn)
{
active_procs = gstate->topn;
}
if (active_procs > gstate->max_topn)
{
active_procs = gstate->max_topn;
}
}
else
{
/* dont show any */
active_procs = 0;
}
#ifdef HAVE_FORMAT_PROCESS_HEADER
/* get the process header to use */
hdr = format_process_header(&(gstate->pselect), processes, active_procs);
#else
hdr = gstate->header_text;
#endif
/* full screen or update? */
if (gstate->fulldraw)
{
display_clear();
i_loadave(system_info.last_pid, system_info.load_avg);
i_uptime(&(gstate->statics->boottime), &curr_time);
i_timeofday(&curr_time);
i_procstates(system_info.p_total, system_info.procstates, gstate->pselect.threads);
if (gstate->show_cpustates)
{
i_cpustates(system_info.cpustates);
}
else
{
if (smart_terminal)
{
z_cpustates();
}
gstate->show_cpustates = Yes;
}
i_kernel(system_info.kernel);
i_memory(system_info.memory);
i_swap(system_info.swap);
i_message(&(gstate->now));
i_header(hdr);
for (i = 0; i < active_procs; i++)
{
i_process(i, format_next_process(processes, gstate->get_userid));
}
i_endscreen();
if (gstate->smart_terminal)
{
gstate->fulldraw = No;
}
}
else
{
u_loadave(system_info.last_pid, system_info.load_avg);
u_uptime(&(gstate->statics->boottime), &curr_time);
i_timeofday(&curr_time);
u_procstates(system_info.p_total, system_info.procstates, gstate->pselect.threads);
u_cpustates(system_info.cpustates);
u_kernel(system_info.kernel);
u_memory(system_info.memory);
u_swap(system_info.swap);
u_message(&(gstate->now));
u_header(hdr);
for (i = 0; i < active_procs; i++)
{
u_process(i, format_next_process(processes, gstate->get_userid));
}
u_endscreen();
}
}
/* calculate new refresh time */
gstate->refresh = gstate->now;
double2tv(&now, gstate->delay);
timeradd(&now, &gstate->refresh, &gstate->refresh);
time_get(&now);
/* loop waiting for time to expire */
do {
/* calculate time to wait */
if (gstate->delay > 0)
{
wait = gstate->refresh;
wait.tv_usec -= now.tv_usec;
if (wait.tv_usec < 0)
{
wait.tv_usec += 1000000;
wait.tv_sec--;
}
wait.tv_sec -= now.tv_sec;
}
/* set up arguments for select on stdin (0) */
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
/* wait for something to read or time out */
if (select(32, &readfds, NULL, NULL, &wait) > 0)
{
/* read it */
if (read(STDIN_FILENO, &ch, 1) != 1)
{
/* read error */
message_error(" Read error on stdin");
quit(EX_DATAERR);
/*NOTREACHED*/
}
/* mark pending messages as old */
message_mark();
/* dispatch */
status = command_process(gstate, (int)ch);
switch(status)
{
case CMD_ERROR:
quit(EX_SOFTWARE);
/*NOTREACHED*/
case CMD_REFRESH:
return;
case CMD_UNKNOWN:
message_error(" Unknown command");
break;
case CMD_NA:
message_error(" Command not available");
}
}
/* get new time */
time_get(&now);
} while (timercmp(&now, &(gstate->refresh), < ));
}
static void
do_minidisplay(globalstate *gstate)
{
double real_delay;
struct system_info si;
/* save the real delay and substitute 1 second */
real_delay = gstate->delay;
gstate->delay = 1;
/* wait 1 second for a command */
time_mark(&(gstate->now));
do_command(gstate);
/* do a mini update that only updates the cpustates */
get_system_info(&si);
u_cpustates(si.cpustates);
/* restore the delay time */
gstate->delay = real_delay;
/* use a large buffer for stdout */
#ifdef HAVE_SETVBUF
setvbuf(stdout, stdoutbuf, _IOFBF, BUFFERSIZE);
#else
#ifdef HAVE_SETBUFFER
setbuffer(stdout, stdoutbuf, BUFFERSIZE);
#endif
#endif
/* get preset options from the environment */
if ((env_top = getenv("TOP")) != NULL)
{
preset_argv = argparse(env_top, &preset_argc);
preset_argv[0] = myname;
do_arguments(gstate, preset_argc, preset_argv);
}
/* process arguments */
do_arguments(gstate, argc, argv);
#ifdef ENABLE_COLOR
/* If colour has been turned on read in the settings. */
env_top = getenv("TOPCOLOURS");
if (!env_top)
{
env_top = getenv("TOPCOLORS");
}
/* must do something about error messages */
color_env_parse(env_top);
color_activate(gstate->use_color);
#endif
/* in order to support forward compatability, we have to ensure that
the entire statics structure is set to a known value before we call
machine_init. This way fields that a module does not know about
will retain their default values */
memzero((void *)&statics, sizeof(statics));
statics.boottime = -1;
/* call the platform-specific init */
if (machine_init(&statics) == -1)
{
exit(EX_SOFTWARE);
}
/* create a helper list of sort order names */
gstate->order_namelist = string_list(statics.order_names);
/* look up chosen sorting order */
if (gstate->order_name != NULL)
{
int i;
if (statics.order_names == NULL)
{
message_error(" This platform does not support arbitrary ordering");
}
else if ((i = string_index(gstate->order_name,
statics.order_names)) == -1)
{
message_error(" Sort order `%s' not recognized", gstate->order_name);
message_error(" Recognized sort orders: %s", gstate->order_namelist);
}
else
{
gstate->order_index = i;
}
}
/* determine interactive state */
if (gstate->interactive == Maybe)
{
gstate->interactive = smart_terminal;
}
/* if displays were not specified, choose an appropriate default */
if (gstate->displays == 0)
{
gstate->displays = gstate->smart_terminal ? Infinity: 1;
}
/* we don't need a mini display when delay is less than 2
seconds or when we are not on a smart terminal */
if (gstate->delay <= 1 || !smart_terminal)
{
need_mini = 0;
}
/* initialize display */
if ((gstate->max_topn = display_init(&statics, gstate->percpustates)) == -1)
{
fprintf(stderr, "%s: display too small\n", myname);
exit(EX_OSERR);
}
/* check for infinity and for overflowed screen */
if (gstate->topn == Infinity)
{
gstate->topn = INT_MAX;
}
else if (gstate->topn > gstate->max_topn)
{
message_error(" This terminal can only display %d processes",
gstate->max_topn);
}
#ifdef ENABLE_COLOR
/* producing a list of color tags is easy */
if (gstate->show_tags)
{
color_dump(stdout);
exit(EX_OK);
}
#endif
/* hold all signals while we initialize the screen */
mask = hold_signals();
screen_init();
/* set the signal handlers */
set_signals();
/* longjmp re-entry point */
/* set the jump buffer for long jumps out of signal handlers */
if (setjmp(jmp_int) != 0)
{
/* this is where we end up after processing sigwinch or sigtstp */
/* tell display to resize its buffers, and get the new length */
if ((gstate->max_topn = display_resize()) == -1)
{
/* thats bad */
quit(EX_OSERR);
/*NOTREACHED*/
}
/* set up for a full redraw, and get the current line count */
gstate->fulldraw = Yes;
/* safe to release the signals now */
release_signals(mask);
}
else
{
/* release the signals */
release_signals(mask);
/* some systems require a warmup */
/* always do a warmup for batch mode */
if (gstate->interactive == 0 || statics.flags.warmup)
{
struct system_info system_info;
struct timeval timeout;