NetBSD/external/bsd/top/dist/top.c

998 lines
22 KiB
C

/*
* 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).
*/
#include "os.h"
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
/* definitions */
#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#endif
/* 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
/* includes specific to top */
#include "top.h"
#include "machine.h"
#include "globalstate.h"
#include "commands.h"
#include "display.h"
#include "screen.h"
#include "boolean.h"
#include "username.h"
#include "utils.h"
#include "version.h"
#ifdef ENABLE_COLOR
#include "color.h"
#endif
/* definitions */
#define BUFFERSIZE 4096
#define JMP_RESUME 1
#define JMP_RESIZE 2
/* externs for getopt: */
extern int optind;
extern char *optarg;
/* statics */
static char stdoutbuf[BUFFERSIZE];
static jmp_buf jmp_int;
/* globals */
char *myname;
void
quit(int status)
{
screen_end();
chdir("/tmp");
exit(status);
/* NOTREACHED */
}
/*
* signal handlers
*/
static void
set_signal(int sig, RETSIGTYPE (*handler)(int))
{
#ifdef HAVE_SIGACTION
struct sigaction action;
sigemptyset(&action.sa_mask);
action.sa_handler = handler;
action.sa_flags = 0;
(void) sigaction(sig, &action, NULL);
#else
(void) signal(sig, handler);
#endif
}
static void
release_signal(int sig)
{
#ifdef HAVE_SIGACTION
sigset_t set;
sigemptyset(&set);
sigaddset(&set, sig);
sigprocmask(SIG_UNBLOCK, &set, NULL);
#endif
#ifdef HAVE_SIGHOLD
sigrelse(sig);
#endif
#ifdef BSD_SIGNALS
(void) sigsetmask(sigblock(0) & ~(sigmask(sig)));
#endif
}
static RETSIGTYPE
sig_leave(int i) /* exit under normal conditions -- INT handler */
{
screen_end();
exit(EX_OK);
}
static RETSIGTYPE
sig_tstop(int i) /* SIGTSTP handler */
{
/* move to the lower left */
screen_end();
fflush(stdout);
/* default the signal handler action */
set_signal(SIGTSTP, SIG_DFL);
/* unblock the TSTP signal */
release_signal(SIGTSTP);
/* send ourselves a TSTP to stop the process */
(void) kill(0, SIGTSTP);
/* reset the signal handler */
set_signal(SIGTSTP, sig_tstop);
/* reinit screen */
screen_reinit();
/* jump back to a known place in the main loop */
longjmp(jmp_int, JMP_RESUME);
/* NOTREACHED */
}
#ifdef SIGWINCH
static RETSIGTYPE
sig_winch(int i) /* SIGWINCH handler */
{
/* reascertain the screen dimensions */
screen_getsize();
/* jump back to a known place in the main loop */
longjmp(jmp_int, JMP_RESIZE);
}
#endif
#ifdef HAVE_SIGACTION
static sigset_t signalset;
#endif
static void *
hold_signals(void)
{
#ifdef HAVE_SIGACTION
sigemptyset(&signalset);
sigaddset(&signalset, SIGINT);
sigaddset(&signalset, SIGQUIT);
sigaddset(&signalset, SIGTSTP);
#ifdef SIGWINCH
sigaddset(&signalset, SIGWINCH);
#endif
sigprocmask(SIG_BLOCK, &signalset, NULL);
return (void *)(&signalset);
#endif
#ifdef HAVE_SIGHOLD
sighold(SIGINT);
sighold(SIGQUIT);
sighold(SIGTSTP);
#ifdef SIGWINCH
sighold(SIGWINCH);
return NULL;
#endif
#endif
#ifdef BSD_SIGNALS
int mask;
#ifdef SIGWINCH
mask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) |
sigmask(SIGTSTP) | sigmask(SIGWINCH));
#else
mask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT) | sigmask(SIGTSTP));
return (void *)mask;
#endif
#endif
}
static void
set_signals(void)
{
(void) set_signal(SIGINT, sig_leave);
(void) set_signal(SIGQUIT, sig_leave);
(void) set_signal(SIGTSTP, sig_tstop);
#ifdef SIGWINCH
(void) set_signal(SIGWINCH, sig_winch);
#endif
}
static void
release_signals(void *parm)
{
#ifdef HAVE_SIGACTION
sigprocmask(SIG_UNBLOCK, (sigset_t *)parm, NULL);
#endif
#ifdef HAVE_SIGHOLD
sigrelse(SIGINT);
sigrelse(SIGQUIT);
sigrelse(SIGTSTP);
#ifdef SIGWINCH
sigrelse(SIGWINCH);
#endif
#endif
#ifdef BSD_SIGNALS
(void) sigsetmask((int)parm);
#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.
*/
#ifdef HAVE_GETOPT_LONG
static struct option longopts[] = {
{ "percpustates", no_argument, NULL, '1' },
{ "color", no_argument, NULL, 'C' },
{ "debug", no_argument, NULL, 'D' },
{ "system-procs", no_argument, NULL, 'S' },
{ "idle-procs", no_argument, NULL, 'I' },
{ "tag-names", no_argument, NULL, 'T' },
{ "all", no_argument, NULL, 'a' },
{ "batch", no_argument, NULL, 'b' },
{ "full-commands", no_argument, NULL, 'c' },
{ "interactive", no_argument, NULL, 'i' },
{ "quick", no_argument, NULL, 'q' },
{ "threads", no_argument, NULL, 't' },
{ "uids", no_argument, NULL, 'u' },
{ "version", no_argument, NULL, 'v' },
{ "delay", required_argument, NULL, 's' },
{ "displays", required_argument, NULL, 'd' },
{ "user", required_argument, NULL, 'U' },
{ "sort-order", required_argument, NULL, 'o' },
{ "pid", required_argument, NULL, 'p' },
{ "display-mode", required_argument, NULL, 'm' },
{ NULL, 0, NULL, 0 },
};
#endif
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;
default:
fprintf(stderr, "\
Top version %s\n\
Usage: %s [-1CISTabcinqtuv] [-d count] [-m mode] [-o field] [-p pid]\n\
[-s time] [-U username] [number]\n",
version_string(), myname);
exit(EX_USAGE);
}
}
/* 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();
}
}
#ifdef DEBUG
void
timeval_xdprint(char *s, struct timeval tv)
{
xdprintf("%s %d.%06d\n", s, tv.tv_sec, tv.tv_usec);
}
#endif
static void
do_wait(globalstate *gstate)
{
struct timeval wait;
double2tv(&wait, gstate->delay);
select(0, NULL, NULL, NULL, &wait);
}
static void
do_command(globalstate *gstate)
{
int status;
struct timeval wait = {0, 0};
struct timeval now;
fd_set readfds;
unsigned char ch;
/* 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;
/* done */
i_endscreen();
}
int
main(int argc, char *argv[])
{
char *env_top;
char **preset_argv;
int preset_argc = 0;
void *mask;
volatile int need_mini = 1;
static char top[] = "top";
struct statics statics;
globalstate *gstate;
/* get our name */
if (argc > 0)
{
if ((myname = strrchr(argv[0], '/')) == 0)
{
myname = argv[0];
}
else
{
myname++;
}
} else
myname = top;
/* binary compatibility check */
#ifdef HAVE_UNAME
{
struct utsname uts;
if (uname(&uts) == 0)
{
if (strcmp(uts.machine, UNAME_HARDWARE) != 0)
{
fprintf(stderr, "%s: incompatible hardware platform\n",
myname);
exit(EX_UNAVAILABLE);
}
}
}
#endif
/* initialization */
gstate = ecalloc(1, sizeof(globalstate));
gstate->statics = &statics;
time_mark(NULL);
/* preset defaults for various options */
gstate->show_usernames = Yes;
gstate->topn = DEFAULT_TOPN;
gstate->delay = DEFAULT_DELAY;
gstate->fulldraw = Yes;
gstate->use_color = Yes;
gstate->interactive = Maybe;
gstate->percpustates = No;
/* preset defaults for process selection */
gstate->pselect.idle = Yes;
gstate->pselect.system = Yes;
gstate->pselect.fullcmd = No;
gstate->pselect.command = NULL;
gstate->pselect.uid = -1;
gstate->pselect.pid = -1;
gstate->pselect.mode = 0;
/* 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;
}
}
/* initialize extensions */
init_username();
/* initialize termcap */
gstate->smart_terminal = screen_readtermcap(gstate->interactive);
/* 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;
}
#ifndef HAVE_FORMAT_PROCESS_HEADER
/* set constants for username/uid display */
if (gstate->show_usernames)
{
gstate->header_text = format_header("USERNAME");
gstate->get_userid = username;
}
else
{
gstate->header_text = format_header(" UID ");
gstate->get_userid = itoa7;
}
#endif
gstate->pselect.usernames = gstate->show_usernames;
/* 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;
time_mark(&(gstate->now));
get_system_info(&system_info);
(void)get_process_info(&system_info, &gstate->pselect, 0);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
select(0, NULL, NULL, NULL, &timeout);
/* if we've warmed up, then we can show good states too */
gstate->show_cpustates = Yes;
need_mini = 0;
}
}
/* main loop */
while ((gstate->displays == -1) || (--gstate->displays > 0))
{
do_display(gstate);
if (gstate->interactive)
{
if (need_mini)
{
do_minidisplay(gstate);
need_mini = 0;
}
do_command(gstate);
}
else
{
do_wait(gstate);
}
}
/* do one last display */
do_display(gstate);
quit(EX_OK);
/* NOTREACHED */
return 1; /* Keep compiler quiet. */
}