880 lines
20 KiB
C
880 lines
20 KiB
C
/* $OpenBSD: display.c,v 1.67 2022/09/10 16:58:51 cheloha Exp $ */
|
|
|
|
/*
|
|
* Top users/processes display for Unix
|
|
* Version 3
|
|
*
|
|
* Copyright (c) 1984, 1989, William LeFebvre, Rice University
|
|
* Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR OR HIS EMPLOYER 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.
|
|
*/
|
|
|
|
/*
|
|
* This file contains the routines that display information on the screen.
|
|
* Each section of the screen has two routines: one for initially writing
|
|
* all constant and dynamic text, and one for only updating the text that
|
|
* changes. The prefix "i_" is used on all the "initial" routines and the
|
|
* prefix "u_" is used for all the "updating" routines.
|
|
*
|
|
* ASSUMPTIONS:
|
|
* None of the "i_" routines use any of the termcap capabilities.
|
|
* In this way, those routines can be safely used on terminals that
|
|
* have minimal (or nonexistent) terminal capabilities.
|
|
*
|
|
* The routines are called in this order: *_loadave, i_timeofday,
|
|
* *_procstates, *_cpustates, *_memory, *_message, *_header,
|
|
* *_process, u_endscreen.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/sched.h>
|
|
#include <curses.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "screen.h" /* interface to screen package */
|
|
#include "layout.h" /* defines for screen position layout */
|
|
#include "display.h"
|
|
#include "top.h"
|
|
#include "machine.h" /* we should eliminate this!!! */
|
|
#include "utils.h"
|
|
|
|
#ifdef DEBUG
|
|
FILE *debug;
|
|
#endif
|
|
|
|
static int display_width = MAX_COLS;
|
|
|
|
static char *cpustates_tag(int);
|
|
static int string_count(char **);
|
|
static void summary_format(char *, size_t, int *, char **);
|
|
static int readlinedumb(char *, int);
|
|
|
|
#define lineindex(l) ((l)*display_width)
|
|
|
|
/* things initialized by display_init and used throughout */
|
|
|
|
/* buffer of proc information lines for display updating */
|
|
char *screenbuf = NULL;
|
|
|
|
static char **procstate_names;
|
|
static char **cpustate_names;
|
|
static char **memory_names;
|
|
|
|
static int num_cpustates;
|
|
|
|
static int *cpustate_columns;
|
|
static int cpustate_total_length;
|
|
|
|
/* display ips */
|
|
int y_mem;
|
|
int y_message;
|
|
int y_header;
|
|
int y_idlecursor;
|
|
int y_procs;
|
|
extern int ncpu;
|
|
extern int ncpuonline;
|
|
extern int combine_cpus;
|
|
extern struct process_select ps;
|
|
|
|
int header_status = true;
|
|
|
|
static int
|
|
empty(void)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
myfputs(const char *s)
|
|
{
|
|
return fputs(s, stdout);
|
|
}
|
|
|
|
static int (*addstrp)(const char *);
|
|
static int (*printwp)(const char *, ...);
|
|
static int (*standoutp)(void);
|
|
static int (*standendp)(void);
|
|
|
|
int
|
|
display_resize(void)
|
|
{
|
|
int cpu_lines, display_lines;
|
|
|
|
ncpuonline = getncpuonline();
|
|
cpu_lines = (combine_cpus ? 1 : ncpuonline);
|
|
y_mem = 2 + cpu_lines;
|
|
y_header = 4 + cpu_lines;
|
|
y_procs = 5 + cpu_lines;
|
|
|
|
/* calculate the current dimensions */
|
|
/* if operating in "dumb" mode, we only need one line */
|
|
display_lines = smart_terminal ? screen_length - y_procs : 1;
|
|
|
|
y_idlecursor = y_message = 3 + (combine_cpus ? 1 : ncpuonline);
|
|
if (screen_length <= y_message)
|
|
y_idlecursor = y_message = screen_length - 1;
|
|
|
|
/*
|
|
* we don't want more than MAX_COLS columns, since the
|
|
* machine-dependent modules make static allocations based on
|
|
* MAX_COLS and we don't want to run off the end of their buffers
|
|
*/
|
|
display_width = screen_width;
|
|
if (display_width >= MAX_COLS)
|
|
display_width = MAX_COLS - 1;
|
|
|
|
if (display_lines < 0)
|
|
display_lines = 0;
|
|
|
|
/* return number of lines available */
|
|
/* for dumb terminals, pretend like we can show any amount */
|
|
return (smart_terminal ? display_lines : Largest);
|
|
}
|
|
|
|
int
|
|
display_init(struct statics * statics)
|
|
{
|
|
int display_lines, *ip, i;
|
|
char **pp;
|
|
|
|
if (smart_terminal) {
|
|
addstrp = addstr;
|
|
printwp = printw;
|
|
standoutp = standout;
|
|
standendp = standend;
|
|
} else {
|
|
addstrp = myfputs;
|
|
printwp = printf;
|
|
standoutp = empty;
|
|
standendp = empty;
|
|
}
|
|
|
|
/* call resize to do the dirty work */
|
|
display_lines = display_resize();
|
|
|
|
/* only do the rest if we need to */
|
|
/* save pointers and allocate space for names */
|
|
procstate_names = statics->procstate_names;
|
|
|
|
cpustate_names = statics->cpustate_names;
|
|
num_cpustates = string_count(cpustate_names);
|
|
|
|
cpustate_columns = calloc(num_cpustates, sizeof(int));
|
|
if (cpustate_columns == NULL)
|
|
err(1, NULL);
|
|
|
|
memory_names = statics->memory_names;
|
|
|
|
/* calculate starting columns where needed */
|
|
cpustate_total_length = 0;
|
|
pp = cpustate_names;
|
|
ip = cpustate_columns;
|
|
while (*pp != NULL) {
|
|
if ((i = strlen(*pp++)) > 0) {
|
|
*ip++ = cpustate_total_length;
|
|
cpustate_total_length += i + 8;
|
|
}
|
|
}
|
|
|
|
/* return number of lines available */
|
|
return (display_lines);
|
|
}
|
|
|
|
/*
|
|
* Print the time elapsed since the system booted.
|
|
*/
|
|
static void
|
|
format_uptime(char *buf, size_t buflen)
|
|
{
|
|
struct timespec boottime;
|
|
time_t uptime;
|
|
unsigned int days, hrs, mins, secs;
|
|
|
|
if (clock_gettime(CLOCK_BOOTTIME, &boottime) == -1)
|
|
err(1, "clock_gettime");
|
|
|
|
uptime = boottime.tv_sec;
|
|
days = uptime / (3600 * 24);
|
|
uptime %= (3600 * 24);
|
|
hrs = uptime / 3600;
|
|
uptime %= 3600;
|
|
mins = uptime / 60;
|
|
secs = uptime % 60;
|
|
snprintf(buf, buflen, "up %u days %02u:%02u:%02u",
|
|
days, hrs, mins, secs);
|
|
}
|
|
|
|
|
|
void
|
|
i_loadave(double *avenrun)
|
|
{
|
|
if (screen_length > 1 || !smart_terminal) {
|
|
int i;
|
|
|
|
move(0, 0);
|
|
clrtoeol();
|
|
|
|
addstrp("load averages");
|
|
for (i = 0; i < 3; i++)
|
|
printwp("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Display the current time.
|
|
* "ctime" always returns a string that looks like this:
|
|
*
|
|
* Sun Sep 16 01:03:52 1973
|
|
* 012345678901234567890123
|
|
* 1 2
|
|
*
|
|
* We want indices 11 thru 18 (length 8).
|
|
*/
|
|
|
|
void
|
|
i_timeofday(time_t * tod)
|
|
{
|
|
static char buf[30];
|
|
|
|
if (buf[0] == '\0')
|
|
gethostname(buf, sizeof(buf));
|
|
|
|
if (screen_length > 1 || !smart_terminal) {
|
|
if (smart_terminal) {
|
|
move(0, screen_width - 8 - strlen(buf) - 1);
|
|
} else {
|
|
if (fputs(" ", stdout) == EOF)
|
|
exit(1);
|
|
}
|
|
#ifdef DEBUG
|
|
{
|
|
char *foo;
|
|
foo = ctime(tod);
|
|
addstrp(foo);
|
|
}
|
|
#endif
|
|
printwp("%s %-8.8s", buf, &(ctime(tod)[11]));
|
|
putn();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_procstates(total, states, threads) - print the process/thread summary line
|
|
*
|
|
* Assumptions: cursor is at the beginning of the line on entry
|
|
*/
|
|
void
|
|
i_procstates(int total, int *states, int threads)
|
|
{
|
|
if (screen_length > 2 || !smart_terminal) {
|
|
char procstates_buffer[MAX_COLS];
|
|
char uptime[40];
|
|
|
|
move(1, 0);
|
|
clrtoeol();
|
|
/* write current number of procs and remember the value */
|
|
printwp("%d %s: ", total, threads ? "threads" : "processes");
|
|
|
|
/* format and print the process state summary */
|
|
summary_format(procstates_buffer, sizeof(procstates_buffer),
|
|
states, procstate_names);
|
|
|
|
addstrp(procstates_buffer);
|
|
|
|
format_uptime(uptime, sizeof(uptime));
|
|
if (smart_terminal)
|
|
move(1, screen_width - strlen(uptime));
|
|
else
|
|
printwp(" ");
|
|
printwp("%s", uptime);
|
|
putn();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_cpustates(states) - print the cpu state percentages
|
|
*
|
|
* Assumptions: cursor is on the PREVIOUS line
|
|
*/
|
|
|
|
/* cpustates_tag() calculates the correct tag to use to label the line */
|
|
|
|
static char *
|
|
cpustates_tag(int cpu)
|
|
{
|
|
if (screen_length > 3 || !smart_terminal) {
|
|
static char *tag;
|
|
static int cpulen, old_width;
|
|
int i;
|
|
|
|
if (cpulen == 0 && ncpu > 1) {
|
|
/* compute length of the cpu string */
|
|
for (i = ncpu; i > 0; cpulen++, i /= 10)
|
|
continue;
|
|
}
|
|
|
|
if (old_width == screen_width) {
|
|
if (ncpu > 1) {
|
|
/* just store the cpu number in the tag */
|
|
i = tag[3 + cpulen];
|
|
snprintf(tag + 3, cpulen + 1, "%.*d", cpulen, cpu);
|
|
tag[3 + cpulen] = i;
|
|
}
|
|
} else {
|
|
/*
|
|
* use a long tag if it will fit, otherwise use short one.
|
|
*/
|
|
free(tag);
|
|
if (cpustate_total_length + 10 + cpulen >= screen_width)
|
|
i = asprintf(&tag, "CPU%.*d: ", cpulen, cpu);
|
|
else
|
|
i = asprintf(&tag, "CPU%.*d states: ", cpulen, cpu);
|
|
if (i == -1)
|
|
tag = NULL;
|
|
else
|
|
old_width = screen_width;
|
|
}
|
|
return (tag);
|
|
} else
|
|
return ("\0");
|
|
}
|
|
|
|
void
|
|
i_cpustates(int64_t *ostates, int *online)
|
|
{
|
|
int i, first, cpu, cpu_line;
|
|
double value;
|
|
int64_t *states;
|
|
char **names, *thisname;
|
|
|
|
if (combine_cpus) {
|
|
static double *values;
|
|
if (!values) {
|
|
values = calloc(num_cpustates, sizeof(*values));
|
|
if (!values)
|
|
err(1, NULL);
|
|
}
|
|
memset(values, 0, num_cpustates * sizeof(*values));
|
|
for (cpu = 0; cpu < ncpu; cpu++) {
|
|
if (!online[cpu])
|
|
continue;
|
|
names = cpustate_names;
|
|
states = ostates + (CPUSTATES * cpu);
|
|
i = 0;
|
|
while ((thisname = *names++) != NULL) {
|
|
if (*thisname != '\0') {
|
|
/* retrieve the value and remember it */
|
|
values[i++] += *states++;
|
|
}
|
|
}
|
|
}
|
|
if (screen_length > 2 || !smart_terminal) {
|
|
names = cpustate_names;
|
|
i = 0;
|
|
first = 0;
|
|
move(2, 0);
|
|
clrtoeol();
|
|
printwp("%-3d CPUs: ", ncpuonline);
|
|
|
|
while ((thisname = *names++) != NULL) {
|
|
if (*thisname != '\0') {
|
|
value = values[i++] / ncpuonline;
|
|
/* if percentage is >= 1000, print it as 100% */
|
|
printwp((value >= 1000 ? "%s%4.0f%% %s" :
|
|
"%s%4.1f%% %s"), first++ == 0 ? "" : ", ",
|
|
value / 10., thisname);
|
|
}
|
|
}
|
|
putn();
|
|
}
|
|
return;
|
|
}
|
|
for (cpu = cpu_line = 0; cpu < ncpu; cpu++) {
|
|
/* skip if offline */
|
|
if (!online[cpu])
|
|
continue;
|
|
|
|
/* now walk thru the names and print the line */
|
|
names = cpustate_names;
|
|
first = 0;
|
|
states = ostates + (CPUSTATES * cpu);
|
|
|
|
if (screen_length > 2 + cpu_line || !smart_terminal) {
|
|
move(2 + cpu_line, 0);
|
|
clrtoeol();
|
|
addstrp(cpustates_tag(cpu));
|
|
|
|
while ((thisname = *names++) != NULL) {
|
|
if (*thisname != '\0') {
|
|
/* retrieve the value and remember it */
|
|
value = *states++;
|
|
|
|
/* if percentage is >= 1000, print it as 100% */
|
|
printwp((value >= 1000 ? "%s%4.0f%% %s" :
|
|
"%s%4.1f%% %s"), first++ == 0 ? "" : ", ",
|
|
value / 10., thisname);
|
|
}
|
|
}
|
|
putn();
|
|
cpu_line++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_memory(stats) - print "Memory: " followed by the memory summary string
|
|
*/
|
|
void
|
|
i_memory(int *stats)
|
|
{
|
|
if (screen_length > y_mem || !smart_terminal) {
|
|
char memory_buffer[MAX_COLS];
|
|
|
|
move(y_mem, 0);
|
|
clrtoeol();
|
|
addstrp("Memory: ");
|
|
|
|
/* format and print the memory summary */
|
|
summary_format(memory_buffer, sizeof(memory_buffer), stats,
|
|
memory_names);
|
|
addstrp(memory_buffer);
|
|
putn();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_message() - print the next pending message line, or erase the one
|
|
* that is there.
|
|
*/
|
|
|
|
/*
|
|
* i_message is funny because it gets its message asynchronously (with
|
|
* respect to screen updates).
|
|
*/
|
|
|
|
static char next_msg[MAX_COLS + 5];
|
|
static int msgon = 0;
|
|
|
|
void
|
|
i_message(void)
|
|
{
|
|
move(y_message, 0);
|
|
if (next_msg[0] != '\0') {
|
|
standoutp();
|
|
addstrp(next_msg);
|
|
standendp();
|
|
clrtoeol();
|
|
msgon = TRUE;
|
|
next_msg[0] = '\0';
|
|
} else if (msgon) {
|
|
clrtoeol();
|
|
msgon = FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_header(text) - print the header for the process area
|
|
*/
|
|
|
|
void
|
|
i_header(char *text)
|
|
{
|
|
if (header_status && (screen_length > y_header
|
|
|| !smart_terminal)) {
|
|
if (!smart_terminal) {
|
|
putn();
|
|
if (fputs(text, stdout) == EOF)
|
|
exit(1);
|
|
putn();
|
|
} else {
|
|
move(y_header, 0);
|
|
clrtoeol();
|
|
addstrp(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* *_process(line, thisline) - print one process line
|
|
*/
|
|
|
|
void
|
|
i_process(int line, char *thisline, int hl)
|
|
{
|
|
/* make sure we are on the correct line */
|
|
move(y_procs + line, 0);
|
|
|
|
/* truncate the line to conform to our current screen width */
|
|
thisline[display_width] = '\0';
|
|
|
|
/* write the line out */
|
|
if (hl && smart_terminal)
|
|
standoutp();
|
|
addstrp(thisline);
|
|
if (hl && smart_terminal)
|
|
standendp();
|
|
putn();
|
|
clrtoeol();
|
|
}
|
|
|
|
void
|
|
u_endscreen(void)
|
|
{
|
|
if (smart_terminal) {
|
|
clrtobot();
|
|
/* move the cursor to a pleasant place */
|
|
move(y_idlecursor, x_idlecursor);
|
|
} else {
|
|
/*
|
|
* separate this display from the next with some vertical
|
|
* room
|
|
*/
|
|
if (fputs("\n\n", stdout) == EOF)
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void
|
|
display_header(int status)
|
|
{
|
|
header_status = status;
|
|
}
|
|
|
|
void
|
|
new_message(int type, const char *msgfmt,...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, msgfmt);
|
|
/* first, format the message */
|
|
vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap);
|
|
va_end(ap);
|
|
|
|
if (next_msg[0] != '\0') {
|
|
/* message there already -- can we clear it? */
|
|
/* yes -- write it and clear to end */
|
|
if ((type & MT_delayed) == 0) {
|
|
move(y_message, 0);
|
|
if (type & MT_standout)
|
|
standoutp();
|
|
addstrp(next_msg);
|
|
if (type & MT_standout)
|
|
standendp();
|
|
clrtoeol();
|
|
msgon = TRUE;
|
|
next_msg[0] = '\0';
|
|
if (smart_terminal)
|
|
refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
clear_message(void)
|
|
{
|
|
move(y_message, 0);
|
|
clrtoeol();
|
|
}
|
|
|
|
|
|
static int
|
|
readlinedumb(char *buffer, int size)
|
|
{
|
|
char *ptr = buffer, ch, cnt = 0, maxcnt = 0;
|
|
extern volatile sig_atomic_t leaveflag;
|
|
ssize_t len;
|
|
|
|
/* allow room for null terminator */
|
|
size -= 1;
|
|
|
|
/* read loop */
|
|
while ((fflush(stdout), (len = read(STDIN_FILENO, ptr, 1)) > 0)) {
|
|
|
|
if (len == 0 || leaveflag) {
|
|
end_screen();
|
|
exit(0);
|
|
}
|
|
|
|
/* newline means we are done */
|
|
if ((ch = *ptr) == '\n')
|
|
break;
|
|
|
|
/* handle special editing characters */
|
|
if (ch == ch_kill) {
|
|
/* return null string */
|
|
*buffer = '\0';
|
|
putr();
|
|
return (-1);
|
|
} else if (ch == ch_erase) {
|
|
/* erase previous character */
|
|
if (cnt <= 0) {
|
|
/* none to erase! */
|
|
if (putchar('\7') == EOF)
|
|
exit(1);
|
|
} else {
|
|
if (fputs("\b \b", stdout) == EOF)
|
|
exit(1);
|
|
ptr--;
|
|
cnt--;
|
|
}
|
|
}
|
|
/* check for character validity and buffer overflow */
|
|
else if (cnt == size || !isprint((unsigned char)ch)) {
|
|
/* not legal */
|
|
if (putchar('\7') == EOF)
|
|
exit(1);
|
|
} else {
|
|
/* echo it and store it in the buffer */
|
|
if (putchar(ch) == EOF)
|
|
exit(1);
|
|
ptr++;
|
|
cnt++;
|
|
if (cnt > maxcnt)
|
|
maxcnt = cnt;
|
|
}
|
|
}
|
|
|
|
/* all done -- null terminate the string */
|
|
*ptr = '\0';
|
|
|
|
/* return either inputted number or string length */
|
|
putr();
|
|
return (cnt == 0 ? -1 : cnt);
|
|
}
|
|
|
|
int
|
|
readline(char *buffer, int size)
|
|
{
|
|
size_t cnt;
|
|
|
|
/* allow room for null terminator */
|
|
size -= 1;
|
|
|
|
if (smart_terminal) {
|
|
int y, x;
|
|
getyx(stdscr, y, x);
|
|
while (getnstr(buffer, size) == KEY_RESIZE)
|
|
move(y, x);
|
|
} else
|
|
return readlinedumb(buffer, size);
|
|
|
|
cnt = strlen(buffer);
|
|
if (cnt > 0 && buffer[cnt - 1] == '\n')
|
|
buffer[cnt - 1] = '\0';
|
|
return (cnt == 0 ? -1 : cnt);
|
|
}
|
|
|
|
/* internal support routines */
|
|
static int
|
|
string_count(char **pp)
|
|
{
|
|
int cnt;
|
|
|
|
cnt = 0;
|
|
while (*pp++ != NULL)
|
|
cnt++;
|
|
return (cnt);
|
|
}
|
|
|
|
#define COPYLEFT(to, from) \
|
|
do { \
|
|
len = strlcpy((to), (from), left); \
|
|
if (len >= left) \
|
|
return; \
|
|
p += len; \
|
|
left -= len; \
|
|
} while (0)
|
|
|
|
static void
|
|
summary_format(char *buf, size_t left, int *numbers, char **names)
|
|
{
|
|
char *p, *thisname;
|
|
int len;
|
|
int num;
|
|
|
|
/* format each number followed by its string */
|
|
p = buf;
|
|
while ((thisname = *names++) != NULL) {
|
|
/* get the number to format */
|
|
num = *numbers++;
|
|
|
|
if (num >= 0) {
|
|
/* is this number in kilobytes? */
|
|
if (thisname[0] == 'K') {
|
|
/* yes: format it as a memory value */
|
|
COPYLEFT(p, format_k(num));
|
|
|
|
/*
|
|
* skip over the K, since it was included by
|
|
* format_k
|
|
*/
|
|
COPYLEFT(p, thisname + 1);
|
|
} else if (num > 0) {
|
|
len = snprintf(p, left, "%d%s", num, thisname);
|
|
if (len < 0 || len >= left)
|
|
return;
|
|
p += len;
|
|
left -= len;
|
|
}
|
|
} else {
|
|
/*
|
|
* Ignore negative numbers, but display corresponding
|
|
* string.
|
|
*/
|
|
COPYLEFT(p, thisname);
|
|
}
|
|
}
|
|
|
|
/* if the last two characters in the string are ", ", delete them */
|
|
p -= 2;
|
|
if (p >= buf && p[0] == ',' && p[1] == ' ')
|
|
*p = '\0';
|
|
}
|
|
|
|
/*
|
|
* printable(str) - make the string pointed to by "str" into one that is
|
|
* printable (i.e.: all ascii), by converting all non-printable
|
|
* characters into '?'. Replacements are done in place and a pointer
|
|
* to the original buffer is returned.
|
|
*/
|
|
char *
|
|
printable(char *str)
|
|
{
|
|
char *ptr, ch;
|
|
|
|
ptr = str;
|
|
while ((ch = *ptr) != '\0') {
|
|
if (!isprint((unsigned char)ch))
|
|
*ptr = '?';
|
|
ptr++;
|
|
}
|
|
return (str);
|
|
}
|
|
|
|
|
|
/*
|
|
* show_help() - display the help screen; invoked in response to
|
|
* either 'h' or '?'.
|
|
*/
|
|
void
|
|
show_help(void)
|
|
{
|
|
if (smart_terminal) {
|
|
clear();
|
|
nl();
|
|
}
|
|
printwp("These single-character commands are available:\n"
|
|
"\n"
|
|
"^L - redraw screen\n"
|
|
"<space> - update screen\n"
|
|
"+ - reset any P highlight, g, p, or u filters\n"
|
|
"1 - display CPU statistics on a single line\n"
|
|
"9 | 0 - scroll up/down the process list by one line\n"
|
|
"( | ) - scroll up/down the process list by screen half\n"
|
|
"C - toggle the display of command line arguments\n"
|
|
"d count - show `count' displays, then exit\n"
|
|
"e - list errors generated by last \"kill\" or \"renice\" command\n"
|
|
"g|/ string - filter on command name (g+ or /+ selects all commands)\n"
|
|
"h | ? - help; show this text\n"
|
|
"H - toggle the display of threads\n"
|
|
"I | i - toggle the display of idle processes\n"
|
|
"k [-sig] pid - send signal `-sig' (TERM by default) to process `pid'\n"
|
|
"n|# count - show `count' processes\n"
|
|
"o [-]field - specify sort order (size, res, cpu, time, pri, pid, command)\n"
|
|
" (o -field sorts in reverse)\n"
|
|
"P pid - highlight process `pid' (P+ switches highlighting off)\n"
|
|
"p pid - display process by `pid' (p+ selects all processes)\n"
|
|
"q - quit\n"
|
|
"r count pid - renice process `pid' to nice value `count'\n"
|
|
"S - toggle the display of system processes\n"
|
|
"s time - change delay between displays to `time' seconds\n"
|
|
"T [-]rtable - show processes associated with routing table `rtable'\n"
|
|
" (T+ shows all, T -rtable hides rtable)\n"
|
|
"t - toggle the display of routing tables\n"
|
|
"u [-]user - show processes for `user' (u+ shows all, u -user hides user)\n"
|
|
"\n");
|
|
|
|
if (smart_terminal) {
|
|
nonl();
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* show_errors() - display on stdout the current log of errors.
|
|
*/
|
|
void
|
|
show_errors(void)
|
|
{
|
|
struct errs *errp = errs;
|
|
int cnt = 0;
|
|
|
|
if (smart_terminal) {
|
|
clear();
|
|
nl();
|
|
}
|
|
printwp("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
|
|
while (cnt++ < errcnt) {
|
|
printwp("%5s: %s\n", errp->arg,
|
|
errp->err == 0 ? "Not a number" : strerror(errp->err));
|
|
errp++;
|
|
}
|
|
printwp("\n");
|
|
if (smart_terminal) {
|
|
nonl();
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
void
|
|
anykey(void)
|
|
{
|
|
int ch;
|
|
ssize_t len;
|
|
|
|
standoutp();
|
|
addstrp("Hit any key to continue: ");
|
|
standendp();
|
|
if (smart_terminal)
|
|
refresh();
|
|
else
|
|
fflush(stdout);
|
|
while (1) {
|
|
len = read(STDIN_FILENO, &ch, 1);
|
|
if (len == -1 && errno == EINTR)
|
|
continue;
|
|
if (len == 0)
|
|
exit(1);
|
|
break;
|
|
}
|
|
}
|