1/*
2 * Copyright (c) 1984 through 2008, William LeFebvre
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *
16 *     * Neither the name of William LeFebvre nor the names of other
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33/*
34 *  Top users/processes display for Unix
35 *  Version 3
36 */
37
38/*
39 *  This file contains the routines that implement some of the interactive
40 *  mode commands.  Note that some of the commands are implemented in-line
41 *  in "main".  This is necessary because they change the global state of
42 *  "top" (i.e.:  changing the number of processes to display).
43 */
44
45#include "os.h"
46#include <ctype.h>
47#include <signal.h>
48#include <stdarg.h>
49#include <unistd.h>
50#include <color.h>
51#include <errno.h>
52#ifdef HAVE_SYS_RESOURCE_H
53#include <sys/resource.h>
54#endif
55
56#if defined(HAVE_DECL_SYS_SIGLIST) & defined(HAVE_STRCASECMP)
57#define USE_SYS_SIGLIST
58#endif
59
60#ifdef USE_SYS_SIGLIST
61extern const char * const sys_siglist[];
62extern const char * const sys_signame[];
63#else
64#include "sigdesc.h"		/* generated automatically */
65#endif
66#include "top.h"
67#include "machine.h"
68#include "globalstate.h"
69#include "boolean.h"
70#include "color.h"
71#include "commands.h"
72#include "display.h"
73#include "screen.h"
74#include "username.h"
75#include "utils.h"
76#include "version.h"
77
78extern char *copyright;
79
80typedef struct command {
81    int ch;
82    int (*cmd_func)(globalstate *);
83    const char *help;
84} command;
85
86/*
87 *  Some of the commands make system calls that could generate errors.
88 *  These errors are collected up in an array of structures for later
89 *  contemplation and display.  Such routines return a string containing an
90 *  error message, or NULL if no errors occurred.  We need an upper limit on
91 *  the number of errors, so we arbitrarily choose 20.
92 */
93
94#define ERRMAX 20
95
96struct errs		/* structure for a system-call error */
97{
98    int  errnum;	/* value of errno (that is, the actual error) */
99    char *arg;		/* argument that caused the error */
100};
101
102static struct errs errs[ERRMAX];
103static int errcnt;
104
105/* These macros get used to reset and log the errors */
106#define ERR_RESET   errcnt = 0
107#define ERROR(p, e) if (errcnt < ERRMAX) \
108		    { \
109			errs[errcnt].arg = (p); \
110			errs[errcnt++].errnum = (e); \
111		    }
112
113/*
114 *  err_compar(p1, p2) - comparison routine used by "qsort"
115 *	for sorting errors.
116 */
117
118static int
119err_compar(const void *p1, const void *p2)
120
121{
122    register int result;
123
124    if ((result = ((const struct errs *)p1)->errnum -
125	 ((const struct errs *)p2)->errnum) == 0)
126    {
127	return(strcmp(((const struct errs *)p1)->arg,
128		      ((const struct errs *)p2)->arg));
129    }
130    return(result);
131}
132
133/*
134 *  str_adderr(str, len, err) - add an explanation of error "err" to
135 *	the string "str" without overflowing length "len".  return
136 *      number of characters remaining in str, or 0 if overflowed.
137 */
138
139static int
140str_adderr(char *str, int len, int err)
141
142{
143    register const char *msg;
144    register int  msglen;
145
146    msg = err == 0 ? "Not a number" : errmsg(err);
147    msglen = strlen(msg) + 2;
148    if (len <= msglen)
149    {
150	return(0);
151    }
152    (void) strcat(str, ": ");
153    (void) strcat(str, msg);
154    return(len - msglen);
155}
156
157/*
158 *  str_addarg(str, len, arg, first) - add the string argument "arg" to
159 *	the string "str" without overflowing length "len".  This is the
160 *      first in the group when "first" is set (indicating that a comma
161 *      should NOT be added to the front).  Return number of characters
162 *      remaining in str, or 0 if overflowed.
163 */
164
165static int
166str_addarg(char *str, int len, char *arg, int first)
167
168{
169    register int arglen;
170
171    arglen = strlen(arg);
172    if (!first)
173    {
174	arglen += 2;
175    }
176    if (len <= arglen)
177    {
178	return(0);
179    }
180    if (!first)
181    {
182	(void) strcat(str, ", ");
183    }
184    (void) strcat(str, arg);
185    return(len - arglen);
186}
187
188/*
189 * void err_string()
190 *
191 * Use message_error to log errors in the errs array.  This function
192 * will combine identical errors to make the message short, but if
193 * there is more than one type of error it will call message_error
194 * for each one.
195 */
196
197#define STRMAX 80
198
199static void
200err_string(void)
201
202{
203    register struct errs *errp;
204    register int  cnt = 0;
205    register int  first = Yes;
206    register int  currerr = -1;
207    int stringlen = 0;		/* characters still available in "string" */
208    char string[STRMAX];
209
210    /* if there are no errors, our job is easy */
211    if (errcnt == 0)
212    {
213	return;
214    }
215
216    /* sort the errors */
217    qsort((char *)errs, errcnt, sizeof(struct errs), err_compar);
218
219    /* initialize the buffer (probably not necessary) */
220    string[0] = '\0';
221    stringlen = STRMAX - 1;
222
223    /* loop thru the sorted list, logging errors */
224    while (cnt < errcnt)
225    {
226	/* point to the current error */
227	errp = &(errs[cnt++]);
228
229	/* note that on overflow "stringlen" will become 0 and all
230	   subsequent calls to str_addarg or str_adderr will return 0 */
231
232	/* if the error number is different then add the error string */
233	if (errp->errnum != currerr)
234	{
235	    if (currerr != -1)
236	    {
237		/* add error string and log the error */
238		stringlen = str_adderr(string, stringlen, currerr);
239		message_error(" %s", string);
240
241	    }
242	    /* reset the buffer */
243	    string[0] = '\0';
244	    stringlen = STRMAX - 1;
245
246	    /* move to next error num */
247	    currerr = errp->errnum;
248	    first = Yes;
249	}
250
251	/* add this arg */
252	stringlen = str_addarg(string, stringlen, errp->arg, first);
253	first = No;
254    }
255
256    /* add final message */
257    stringlen = str_adderr(string, stringlen, currerr);
258
259    /* write the error string */
260    message_error(" %s", string);
261}
262
263/*
264 *  Utility routines that help with some of the commands.
265 */
266
267static char *
268next_field(char *str)
269
270
271{
272    if ((str = strchr(str, ' ')) == NULL)
273    {
274	return(NULL);
275    }
276    *str = '\0';
277    while (*++str == ' ') /* loop */;
278
279    /* if there is nothing left of the string, return NULL */
280    /* This fix is dedicated to Greg Earle */
281    return(*str == '\0' ? NULL : str);
282}
283
284static int
285scanint(char *str, int *intp)
286
287{
288    register int val = 0;
289    register int ch;
290
291    /* if there is nothing left of the string, flag it as an error */
292    /* This fix is dedicated to Greg Earle */
293    if (*str == '\0')
294    {
295	return(-1);
296    }
297
298    while ((ch = *str++) != '\0')
299    {
300	if (isdigit(ch))
301	{
302	    val = val * 10 + (ch - '0');
303	}
304	else if (isspace(ch))
305	{
306	    break;
307	}
308	else
309	{
310	    return(-1);
311	}
312    }
313    *intp = val;
314    return(0);
315}
316
317#ifdef notdef
318/*
319 *  error_count() - return the number of errors currently logged.
320 */
321
322static int
323error_count(void)
324
325{
326    return(errcnt);
327}
328
329/*
330 *  show_errors() - display on stdout the current log of errors.
331 */
332
333static void
334show_errors(void)
335
336{
337    register int cnt = 0;
338    register struct errs *errp = errs;
339
340    printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
341    while (cnt++ < errcnt)
342    {
343	printf("%5s: %s\n", errp->arg,
344	    errp->errnum == 0 ? "Not a number" : errmsg(errp->errnum));
345	errp++;
346    }
347}
348#endif
349
350/*
351 *  kill_procs(str) - send signals to processes, much like the "kill"
352 *		command does; invoked in response to 'k'.
353 */
354
355static void
356kill_procs(char *str)
357
358{
359    register char *nptr;
360    int signum = SIGTERM;	/* default */
361    int procnum;
362    int uid;
363    int owner;
364#ifndef USE_SYS_SIGLIST
365    struct sigdesc *sigp;
366#endif
367
368    /* reset error array */
369    ERR_RESET;
370
371    /* remember our uid */
372    uid = getuid();
373
374    /* skip over leading white space */
375    while (isspace((int)*str)) str++;
376
377    if (str[0] == '-')
378    {
379	/* explicit signal specified */
380	if ((nptr = next_field(str)) == NULL)
381	{
382	    message_error(" kill: no processes specified");
383	    return;
384	}
385
386	str++;
387	if (isdigit((int)str[0]))
388	{
389	    (void) scanint(str, &signum);
390	    if (signum <= 0 || signum >= NSIG)
391	    {
392		message_error(" kill: invalid signal number");
393		return;
394	    }
395	}
396	else
397	{
398	    /* translate the name into a number */
399#ifdef USE_SYS_SIGLIST
400	    for (signum = 1; signum < NSIG; signum++)
401	    {
402		if (strcasecmp(sys_signame[signum], str) == 0)
403		{
404		    break;
405		}
406	    }
407	    if (signum == NSIG)
408	    {
409		message_error(" kill: bad signal name");
410		return;
411	    }
412#else
413	    for (sigp = sigdesc; sigp->name != NULL; sigp++)
414	    {
415#ifdef HAVE_STRCASECMP
416		if (strcasecmp(sigp->name, str) == 0)
417#else
418		if (strcmp(sigp->name, str) == 0)
419#endif
420		{
421		    signum = sigp->number;
422		    break;
423		}
424	    }
425
426	    /* was it ever found */
427	    if (sigp->name == NULL)
428	    {
429		message_error(" kill: bad signal name");
430		return;
431	    }
432#endif
433	}
434	/* put the new pointer in place */
435	str = nptr;
436    }
437
438    /* loop thru the string, killing processes */
439    do
440    {
441	if (scanint(str, &procnum) == -1)
442	{
443	    ERROR(str, 0);
444	}
445	else
446	{
447	    /* check process owner if we're not root */
448	    owner = proc_owner(procnum);
449	    if (uid && (uid != owner))
450	    {
451		ERROR(str, owner == -1 ? ESRCH : EACCES);
452	    }
453	    /* go in for the kill */
454	    else if (kill(procnum, signum) == -1)
455	    {
456		/* chalk up an error */
457		ERROR(str, errno);
458	    }
459	}
460    } while ((str = next_field(str)) != NULL);
461
462    /* process errors */
463    err_string();
464}
465
466/*
467 *  renice_procs(str) - change the "nice" of processes, much like the
468 *		"renice" command does; invoked in response to 'r'.
469 */
470
471static void
472renice_procs(char *str)
473
474{
475    register char negate;
476    int prio;
477    int procnum;
478    int uid;
479
480    ERR_RESET;
481    uid = getuid();
482
483    /* allow for negative priority values */
484    if ((negate = (*str == '-')) != 0)
485    {
486	/* move past the minus sign */
487	str++;
488    }
489
490    /* use procnum as a temporary holding place and get the number */
491    procnum = scanint(str, &prio);
492
493    /* negate if necessary */
494    if (negate)
495    {
496	prio = -prio;
497    }
498
499#if defined(PRIO_MIN) && defined(PRIO_MAX)
500    /* check for validity */
501    if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
502    {
503	message_error(" renice: bad priority value");
504	return;
505    }
506#endif
507
508    /* move to the first process number */
509    if ((str = next_field(str)) == NULL)
510    {
511	message_error(" remice: no processes specified");
512	return;
513    }
514
515#ifdef HAVE_SETPRIORITY
516    /* loop thru the process numbers, renicing each one */
517    do
518    {
519	if (scanint(str, &procnum) == -1)
520	{
521	    ERROR(str, 0);
522	}
523
524	/* check process owner if we're not root */
525	else if (uid && (uid != proc_owner(procnum)))
526	{
527	    ERROR(str, EACCES);
528	}
529	else if (setpriority(PRIO_PROCESS, procnum, prio) == -1)
530	{
531	    ERROR(str, errno);
532	}
533    } while ((str = next_field(str)) != NULL);
534    err_string();
535#else
536    message_error(" renice operation not supported");
537#endif
538}
539
540/* COMMAND ROUTINES */
541
542/*
543 * Each command routine is called by command_process and is passed a
544 * pointer to the current global state.  Command routines are free
545 * to change anything in the global state, although changes to the
546 * statics structure are discouraged.  Whatever a command routine
547 * returns will be returned by command_process.
548 */
549
550static void
551cmd_quit(globalstate *gstate)
552
553{
554    quit(EX_OK);
555    /*NOTREACHED*/
556}
557
558static int
559cmd_update(globalstate *gstate)
560
561{
562    /* go home for visual feedback */
563    screen_home();
564    fflush(stdout);
565    message_expire();
566    return CMD_REFRESH;
567}
568
569static int
570cmd_redraw(globalstate *gstate)
571
572{
573    gstate->fulldraw = Yes;
574    return CMD_REFRESH;
575}
576
577static int
578cmd_color(globalstate *gstate)
579
580{
581    gstate->use_color = color_activate(-1);
582    gstate->fulldraw = Yes;
583    return CMD_REFRESH;
584}
585
586static int
587cmd_number(globalstate *gstate)
588
589{
590    int newval;
591    char tmpbuf[20];
592
593    message_prompt("Number of processes to show: ");
594    newval = readline(tmpbuf, 8, Yes);
595    if (newval > -1)
596    {
597	if (newval > gstate->max_topn)
598	{
599	    message_error(" This terminal can only display %d processes",
600			  gstate->max_topn);
601	}
602
603	if (newval == 0)
604	{
605	    /* inhibit the header */
606	    display_header(No);
607	}
608
609	else if (gstate->topn == 0)
610	{
611	    display_header(Yes);
612	}
613
614	gstate->topn = newval;
615    }
616    return CMD_REFRESH;
617}
618
619static int
620cmd_delay(globalstate *gstate)
621
622{
623    double newval;
624    char tmpbuf[20];
625
626    message_prompt("Seconds to delay: ");
627    if (readline(tmpbuf, 8, No) > 0)
628    {
629	newval = atof(tmpbuf);
630	if (newval == 0 && getuid() != 0)
631	{
632	    gstate->delay = 1;
633	}
634	else
635	{
636	    gstate->delay = newval;
637	}
638    }
639    return CMD_REFRESH;
640}
641
642static int
643cmd_idle(globalstate *gstate)
644
645{
646    gstate->pselect.idle = !gstate->pselect.idle;
647    message_error(" %sisplaying idle processes.",
648		  gstate->pselect.idle ? "D" : "Not d");
649    return CMD_REFRESH;
650}
651
652static int
653cmd_displays(globalstate *gstate)
654
655{
656    int i;
657    char tmpbuf[20];
658
659    message_prompt("Displays to show (currently %s): ",
660		   gstate->displays == -1 ? "infinite" :
661		   itoa(gstate->displays));
662
663    if ((i = readline(tmpbuf, 10, Yes)) > 0)
664    {
665	gstate->displays = i;
666    }
667    else if (i == 0)
668    {
669	quit(EX_OK);
670	/*NOTREACHED*/
671    }
672    return CMD_OK;
673}
674
675static int
676cmd_cmdline(globalstate *gstate)
677
678{
679    if (gstate->statics->flags.fullcmds)
680    {
681	gstate->pselect.fullcmd = !gstate->pselect.fullcmd;
682	message_error(" %sisplaying full command lines.",
683		      gstate->pselect.fullcmd ? "D" : "Not d");
684	return CMD_REFRESH;
685    }
686    message_error(" Full command display not supported.");
687    return CMD_OK;
688}
689
690static int
691cmd_order(globalstate *gstate)
692
693{
694    char tmpbuf[MAX_COLS];
695    int i;
696
697    if (gstate->statics->order_names != NULL)
698    {
699	message_prompt("Column to sort: ");
700	if (readline(tmpbuf, sizeof(tmpbuf), No) > 0)
701	{
702	    if ((i = string_index(tmpbuf, gstate->statics->order_names)) == -1)
703	    {
704		message_error(" Sort order \"%s\" not recognized", tmpbuf);
705	    }
706	    else
707	    {
708		gstate->order_index = i;
709		return CMD_REFRESH;
710	    }
711	}
712    }
713    return CMD_OK;
714}
715
716static int
717cmd_order_x(globalstate *gstate, const char *name, ...)
718
719{
720    va_list ap;
721    char *p;
722    const char **names;
723    int i;
724
725    names = gstate->statics->order_names;
726    if (names != NULL)
727    {
728	if ((i = string_index(name, names)) == -1)
729	{
730	    /* check the alternate list */
731	    va_start(ap, name);
732	    p = va_arg(ap, char *);
733	    while (p != NULL)
734	    {
735		if ((i = string_index(p, names)) != -1)
736		{
737		    gstate->order_index = i;
738		    return CMD_REFRESH;
739		}
740		p = va_arg(ap, char *);
741	    }
742	    message_error(" Sort order not recognized");
743	}
744	else
745	{
746	    gstate->order_index = i;
747	    return CMD_REFRESH;
748	}
749    }
750    return CMD_OK;
751}
752
753static int
754cmd_order_cpu(globalstate *gstate)
755
756{
757    return cmd_order_x(gstate, "cpu", NULL);
758}
759
760static int
761cmd_order_pid(globalstate *gstate)
762
763{
764    return cmd_order_x(gstate, "pid", NULL);
765}
766
767static int
768cmd_order_mem(globalstate *gstate)
769
770{
771    return cmd_order_x(gstate, "mem", "size", NULL);
772}
773
774static int
775cmd_order_time(globalstate *gstate)
776
777{
778    return cmd_order_x(gstate, "time");
779}
780
781#ifdef ENABLE_KILL
782
783static int
784cmd_kill(globalstate *gstate)
785
786{
787    char tmpbuf[MAX_COLS];
788
789    message_prompt_plain("kill ");
790    if (readline(tmpbuf, sizeof(tmpbuf), No) > 0)
791    {
792	kill_procs(tmpbuf);
793    }
794    return CMD_OK;
795}
796
797static int
798cmd_renice(globalstate *gstate)
799
800{
801    char tmpbuf[MAX_COLS];
802
803    message_prompt_plain("renice ");
804    if (readline(tmpbuf, sizeof(tmpbuf), No) > 0)
805    {
806	renice_procs(tmpbuf);
807    }
808    return CMD_OK;
809}
810
811#endif
812
813static int
814cmd_pid(globalstate *gstate)
815
816{
817    char tmpbuf[MAX_COLS];
818
819    message_prompt_plain("select pid ");
820    gstate->pselect.pid = -1;
821    if (readline(tmpbuf, sizeof(tmpbuf), No) > 0)
822    {
823	int pid;
824	if (scanint(tmpbuf, &pid) == 0)
825	    gstate->pselect.pid = pid;
826    }
827    return CMD_OK;
828}
829
830static int
831cmd_user(globalstate *gstate)
832
833{
834    char linebuf[MAX_COLS];
835    int i;
836    int ret = CMD_OK;
837
838    message_prompt("Username to show: ");
839    if (readline(linebuf, sizeof(linebuf), No) > 0)
840    {
841	if (linebuf[0] == '+' &&
842	    linebuf[1] == '\0')
843	{
844	    gstate->pselect.uid = -1;
845	    ret = CMD_REFRESH;
846	}
847	else if ((i = userid(linebuf)) == -1)
848	{
849	    message_error(" %s: unknown user", linebuf);
850	}
851	else
852	{
853	    gstate->pselect.uid = i;
854	    ret = CMD_REFRESH;
855	}
856    }
857    return ret;
858}
859
860static int
861cmd_command(globalstate *gstate)
862
863{
864    char linebuf[MAX_COLS];
865
866    if (gstate->pselect.command != NULL)
867    {
868	free(gstate->pselect.command);
869	gstate->pselect.command = NULL;
870    }
871
872    message_prompt("Command to show: ");
873    if (readline(linebuf, sizeof(linebuf), No) > 0)
874    {
875	if (linebuf[0] != '\0')
876	{
877	    gstate->pselect.command = estrdup(linebuf);
878	}
879    }
880    return CMD_REFRESH;
881}
882
883static int
884cmd_useruid(globalstate *gstate)
885
886{
887    gstate->pselect.usernames = !gstate->pselect.usernames;
888    display_header(2);
889    return CMD_REFRESH;
890}
891
892static int
893cmd_mode(globalstate *gstate)
894
895{
896    if (gstate->statics->modemax <= 1)
897    {
898	return CMD_NA;
899    }
900    gstate->pselect.mode = (gstate->pselect.mode + 1) % gstate->statics->modemax;
901    display_header(2);
902    return CMD_REFRESH;
903}
904
905static int
906cmd_system(globalstate *gstate)
907
908{
909    gstate->pselect.system = !gstate->pselect.system;
910    display_header(2);
911    return CMD_REFRESH;
912}
913
914static int
915cmd_threads(globalstate *gstate)
916
917{
918    if (gstate->statics->flags.threads)
919    {
920	gstate->pselect.threads = !gstate->pselect.threads;
921	display_header(2);
922	return CMD_REFRESH;
923    }
924    return CMD_NA;
925}
926
927static int
928cmd_percpustates(globalstate *gstate)
929{
930	gstate->percpustates = !gstate->percpustates;
931	gstate->fulldraw = Yes;
932	gstate->max_topn += display_setmulti(gstate->percpustates);
933	return CMD_REFRESH;
934}
935
936
937/* forward reference for cmd_help, as it needs to see the command_table */
938int cmd_help(globalstate *gstate);
939
940/* command table */
941command command_table[] = {
942    { '\014', cmd_redraw, "redraw screen" },
943    { ' ', cmd_update, "update screen" },
944    { '?', cmd_help, "help; show this text" },
945    { 'h', cmd_help, NULL },
946    { '1', cmd_percpustates, "toggle the detail per cpu of cpustates" },
947    { 'C', cmd_color, "toggle the use of color" },
948    { 'H', cmd_threads, "toggle the display of individual threads" },
949    { 't', cmd_threads, NULL },
950    { 'M', cmd_order_mem, "sort by memory usage" },
951    { 'N', cmd_order_pid, "sort by process id" },
952    { 'P', cmd_order_cpu, "sort by CPU usage" },
953    { 'S', cmd_system, "toggle the display of system processes" },
954    { 'T', cmd_order_time, "sort by CPU time" },
955    { 'U', cmd_useruid, "toggle the display of usernames or uids" },
956    { 'c', cmd_command, "display processes by command name" },
957    { 'd', cmd_displays, "change number of displays to show" },
958    { 'f', cmd_cmdline, "toggle the display of full command paths" },
959    { 'i', cmd_idle, "toggle the displaying of idle processes" },
960    { 'I', cmd_idle, NULL },
961#ifdef ENABLE_KILL
962    { 'k', cmd_kill, "kill processes; send a signal to a list of processes" },
963#endif
964    { 'm', cmd_mode, "toggle between display modes" },
965    { 'n', cmd_number, "change number of processes to display" },
966    { '#', cmd_number, NULL },
967    { 'o', cmd_order, "specify sort order (see below)" },
968    { 'p', cmd_pid, "select a single pid" },
969    { 'q', (int (*)(globalstate *))cmd_quit, "quit" },
970#ifdef ENABLE_KILL
971    { 'r', cmd_renice, "renice a process" },
972#endif
973    { 's', cmd_delay, "change number of seconds to delay between updates" },
974    { 'u', cmd_user, "display processes for only one user (+ selects all users)" },
975    { '\0', NULL, NULL },
976};
977
978int
979cmd_help(globalstate *gstate)
980
981{
982    command *c;
983    char buf[12];
984    char *p;
985    const char *help;
986
987    display_pagerstart();
988
989    display_pager("Top version %s, %s\n", version_string(), copyright);
990    display_pager("Platform module: %s\n\n", MODULE);
991    display_pager("A top users display for Unix\n\n");
992    display_pager("These single-character commands are available:\n\n");
993
994    c = command_table;
995    while (c->cmd_func != NULL)
996    {
997	/* skip null help strings */
998	if ((help = c->help) == NULL)
999	{
1000	    continue;
1001	}
1002
1003	/* translate character in to something readable */
1004	if (c->ch < ' ')
1005	{
1006	    buf[0] = '^';
1007	    buf[1] = c->ch + '@';
1008	    buf[2] = '\0';
1009	}
1010	else if (c->ch == ' ')
1011	{
1012	    strcpy(buf, "<sp>");
1013	}
1014	else
1015	{
1016	    buf[0] = c->ch;
1017	    buf[1] = '\0';
1018	}
1019
1020	/* if the next command is the same, fold them onto one line */
1021	if ((c+1)->cmd_func == c->cmd_func)
1022	{
1023	    strcat(buf, " or ");
1024	    p = buf + strlen(buf);
1025	    *p++ = (c+1)->ch;
1026	    *p = '\0';
1027	    c++;
1028	}
1029
1030	display_pager("%-7s - %s\n", buf, help);
1031	c++;
1032    }
1033
1034    display_pager("\nNot all commands are available on all systems.\n\n");
1035    display_pager("Available sort orders: %s\n", gstate->order_namelist);
1036    display_pagerend();
1037    gstate->fulldraw = Yes;
1038    return CMD_REFRESH;
1039}
1040
1041/*
1042 * int command_process(globalstate *gstate, int cmd)
1043 *
1044 * Process the single-character command "cmd".  The global state may
1045 * be modified by the command to alter the output.  Returns CMD_ERROR
1046 * if there was a serious error that requires an immediate exit, CMD_OK
1047 * to indicate success, CMD_REFRESH to indicate that the screen needs
1048 * to be refreshed immediately, CMD_UNKNOWN when the command is not known,
1049 * and CMD_NA when the command is not available.  Error messages for
1050 * CMD_NA and CMD_UNKNOWN must be handled by the caller.
1051 */
1052
1053int
1054command_process(globalstate *gstate, int cmd)
1055
1056{
1057    command *c;
1058
1059    c = command_table;
1060    while (c->cmd_func != NULL)
1061    {
1062	if (c->ch == cmd)
1063	{
1064	    return (c->cmd_func)(gstate);
1065	}
1066	c++;
1067    }
1068
1069    return CMD_UNKNOWN;
1070}
1071