top: utilize getopt and introduce long cmdline options

For quite some time now, top has stood out like a sore
thumb regarding the approach to cmdline options & help
text.  Only short options were used and that same help
text was displayed for '-h' (help) plus 'v' (version).

[ also, top 'rolled his own' when it came to parsing ]
[ options while avoiding that getopt implementation. ]

Well, with this commit all of that has changed and top
now has added a long form of his options. Additionally
he employs getopt_long() for the bulk of that parsing.

[ however, top will still avoid separate fputs calls ]
[ characteristic of other procps-ng programs when it ]
[ comes to help. rather all such text is one string. ]

Along the way, the following major getopt deficiencies
were addressed, assuming the absence of a new #define:

* an equals sign ('=') is allowed on both option forms

* whitespace is allowed before & after the equals sign

* optional arguments needn't abut their related option
for short form nor is an '=' required with either form

Signed-off-by: Jim Warner <james.warner@comcast.net>
This commit is contained in:
Jim Warner 2021-09-04 00:00:00 -05:00 committed by Craig Small
parent b8118b9fee
commit c91b371485
4 changed files with 187 additions and 184 deletions

322
top/top.c
View File

@ -21,6 +21,7 @@
#include <errno.h>
#include <fcntl.h>
#include <float.h>
#include <getopt.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
@ -3789,177 +3790,159 @@ default_or_error:
* and our job is to see if any of those options are to be
* overridden -- we'll force some on and negate others in our
* best effort to honor the loser's (oops, user's) wishes... */
static void parse_args (char **args) {
/* differences between us and the former top:
-C (separate CPU states for SMP) is left to an rcfile
-u (user monitoring) added to compliment interactive 'u'
-p (pid monitoring) allows a comma delimited list
-q (zero delay) eliminated as redundant, incomplete and inappropriate
use: "nice -n-10 top -d0" to achieve what was only claimed
. most switches act as toggles (not 'on' sw) for more user flexibility
. no deprecated/illegal use of 'breakargv:' with goto
. bunched args are actually handled properly and none are ignored
. we tolerate NO whitespace and NO switches -- maybe too tolerant? */
static const char numbs_str[] = "+,-.0123456789";
static void parse_args (int argc, char **argv) {
static const char sopts[] = "bcd:E:e:Hhin:Oo:p:SsU:u:Vw::1";
static const struct option lopts[] = {
{ "batch-mode", no_argument, NULL, 'b' },
{ "cmdline-toggle", no_argument, NULL, 'c' },
{ "delay", required_argument, NULL, 'd' },
{ "scale-summary-mem", required_argument, NULL, 'E' },
{ "scale-task-mem", required_argument, NULL, 'e' },
{ "threads-show", no_argument, NULL, 'H' },
{ "help", no_argument, NULL, 'h' },
{ "idle-toggle", no_argument, NULL, 'i' },
{ "iterations", required_argument, NULL, 'n' },
{ "list-fields", no_argument, NULL, 'O' },
{ "sort-override", required_argument, NULL, 'o' },
{ "pid", required_argument, NULL, 'p' },
{ "accum-time-toggle", no_argument, NULL, 'S' },
{ "secure-mode", no_argument, NULL, 's' },
{ "filter-any-user", required_argument, NULL, 'U' },
{ "filter-only-euser", required_argument, NULL, 'u' },
{ "version", no_argument, NULL, 'V' },
{ "width", optional_argument, NULL, 'w' },
{ "single-cpu-toggle", no_argument, NULL, '1' },
{ NULL, 0, NULL, 0 }
};
float tmp_delay = FLT_MAX;
int i;
int ch;
while (*args) {
const char *cp = *(args++);
while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) {
int i;
float tmp;
char *cp = optarg;
while (*cp) {
char ch;
float tmp;
#ifndef GETOPTFIX_NO
/* first, let's plug some awful gaps in the getopt implementation,
especially relating to short options with (optional) arguments! */
if (!cp && optind < argc && argv[optind][0] != '-')
cp = argv[optind++];
if (cp) {
if (*cp == '=') ++cp;
if (*cp == '\0' && optind < argc) cp = argv[optind++];
if (!cp || *cp == '\0') error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
}
#endif
switch (ch) {
case '1': // ensure behavior identical to run-time toggle
if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM);
else TOGw(Curwin, View_CPUSUM);
OFFw(Curwin, View_CPUNOD);
SETw(Curwin, View_STATES);
break;
case 'b':
Batch = 1;
break;
case 'c':
TOGw(Curwin, Show_CMDLIN);
break;
case 'd':
if (!mkfloat(cp, &tmp_delay, 0))
error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp));
if (0 > tmp_delay)
error_exit(N_txt(DELAY_badarg_txt));
continue;
case 'E':
{ const char *get = "kmgtpe", *got;
if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
Rc.summ_mscale = (int)(got - get);
} continue;
case 'e':
{ const char *get = "kmgtp", *got;
if (!(got = strchr(get, tolower(*cp))) || strlen(cp) > 1)
error_exit(fmtmk(N_fmt(BAD_memscale_fmt), cp));
Rc.task_mscale = (int)(got - get);
} continue;
case 'H':
Thread_mode = 1;
break;
case 'h':
puts(fmtmk(N_fmt(HELP_cmdline_fmt), Myname));
bye_bye(NULL);
case 'i':
TOGw(Curwin, Show_IDLEPS);
Curwin->rc.maxtasks = 0;
break;
case 'n':
if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp)
error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp));
Loops = (int)tmp;
continue;
case 'O':
for (i = 0; i < EU_MAXPFLGS; i++)
puts(N_col(i));
bye_bye(NULL);
case 'o':
if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; }
else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; }
for (i = 0; i < EU_MAXPFLGS; i++)
if (!STRCMP(cp, N_col(i))) break;
if (i == EU_MAXPFLGS)
error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp));
OFFw(Curwin, Show_FOREST);
Curwin->rc.sortindx = i;
continue;
case 'p':
{ int pid; char *p;
if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
do {
if (Monpidsidx >= MONPIDMAX)
error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX));
if (1 != sscanf(cp, "%d", &pid)
|| strpbrk(cp, "+-."))
error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp));
if (!pid) pid = getpid();
for (i = 0; i < Monpidsidx; i++)
if (Monpids[i] == pid) goto next_pid;
Monpids[Monpidsidx++] = pid;
next_pid:
if (!(p = strchr(cp, ','))) break;
cp = p + 1;
} while (*cp);
} continue;
case 'S':
TOGw(Curwin, Show_CTIMES);
break;
case 's':
Secure_mode = 1;
break;
case 'U':
case 'u':
{ const char *errmsg;
if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg);
} continue;
case 'V':
puts(fmtmk(N_fmt(VERSION_opts_fmt), Myname, PACKAGE_STRING));
bye_bye(NULL);
case 'w':
tmp = -1;
if (cp && (!mkfloat(cp, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX))
error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), cp));
Width_mode = (int)tmp;
continue;
default:
// we'll rely on getopt for any error message ...
bye_bye(NULL);
} // end: switch (ch)
#ifndef GETOPTFIX_NO
if (cp) error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), cp));
#endif
} // end: while getopt_long
switch ((ch = *cp)) {
case '\0':
break;
case '-':
if (cp[1]) ++cp;
else if (*args) cp = *args++;
if (strspn(cp, "+,-."))
error_exit(fmtmk(N_fmt(WRONG_switch_fmt)
, cp, Myname, N_txt(USAGE_abbrev_txt)));
continue;
case '1': // ensure behavior identical to run-time toggle
if (CHKw(Curwin, View_CPUNOD)) OFFw(Curwin, View_CPUSUM);
else TOGw(Curwin, View_CPUSUM);
OFFw(Curwin, View_CPUNOD);
SETw(Curwin, View_STATES);
goto bump_cp;
case 'b':
Batch = 1;
goto bump_cp;
case 'c':
TOGw(Curwin, Show_CMDLIN);
goto bump_cp;
case 'd':
if (cp[1]) ++cp;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (!mkfloat(cp, &tmp_delay, 0))
error_exit(fmtmk(N_fmt(BAD_delayint_fmt), cp));
if (0 > tmp_delay)
error_exit(N_txt(DELAY_badarg_txt));
break;
case 'e':
{ const char *get = "kmgtp", *got;
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (!(got = strchr(get, tolower(*cp))))
error_exit(fmtmk(N_fmt(BAD_memscale_fmt), *cp));
Rc.task_mscale = (int)(got - get);
} goto bump_cp;
case 'E':
{ const char *get = "kmgtpe", *got;
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (!(got = strchr(get, tolower(*cp))))
error_exit(fmtmk(N_fmt(BAD_memscale_fmt), *cp));
Rc.summ_mscale = (int)(got - get);
} goto bump_cp;
case 'H':
Thread_mode = 1;
goto bump_cp;
case 'h':
case 'v':
puts(fmtmk(N_fmt(HELP_cmdline_fmt)
, PACKAGE_STRING, Myname, N_txt(USAGE_abbrev_txt)));
bye_bye(NULL);
case 'i':
TOGw(Curwin, Show_IDLEPS);
Curwin->rc.maxtasks = 0;
goto bump_cp;
case 'n':
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (!mkfloat(cp, &tmp, 1) || 1.0 > tmp)
error_exit(fmtmk(N_fmt(BAD_niterate_fmt), cp));
Loops = (int)tmp;
break;
case 'o':
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (*cp == '+') { SETw(Curwin, Qsrt_NORMAL); ++cp; }
else if (*cp == '-') { OFFw(Curwin, Qsrt_NORMAL); ++cp; }
for (i = 0; i < EU_MAXPFLGS; i++)
if (!STRCMP(cp, N_col(i))) break;
if (i == EU_MAXPFLGS)
error_exit(fmtmk(N_fmt(XTRA_badflds_fmt), cp));
OFFw(Curwin, Show_FOREST);
Curwin->rc.sortindx = i;
cp += strlen(cp);
break;
case 'O':
for (i = 0; i < EU_MAXPFLGS; i++)
puts(N_col(i));
bye_bye(NULL);
case 'p':
{ int pid; char *p;
if (Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
do {
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if (Monpidsidx >= MONPIDMAX)
error_exit(fmtmk(N_fmt(LIMIT_exceed_fmt), MONPIDMAX));
if (1 != sscanf(cp, "%d", &pid)
|| strpbrk(cp, "+-."))
error_exit(fmtmk(N_fmt(BAD_mon_pids_fmt), cp));
if (!pid) pid = getpid();
for (i = 0; i < Monpidsidx; i++)
if (Monpids[i] == pid) goto next_pid;
Monpids[Monpidsidx++] = pid;
next_pid:
if (!(p = strchr(cp, ','))) break;
cp = p;
} while (*cp);
} break;
case 's':
Secure_mode = 1;
goto bump_cp;
case 'S':
TOGw(Curwin, Show_CTIMES);
goto bump_cp;
case 'u':
case 'U':
{ const char *errmsg;
if (Monpidsidx || Curwin->usrseltyp) error_exit(N_txt(SELECT_clash_txt));
if (cp[1]) cp++;
else if (*args) cp = *args++;
else error_exit(fmtmk(N_fmt(MISSING_args_fmt), ch));
if ((errmsg = user_certify(Curwin, cp, ch))) error_exit(errmsg);
cp += strlen(cp);
} break;
case 'w':
{ const char *pn = NULL;
int ai = 0, ci = 0;
tmp = -1;
if (cp[1]) pn = &cp[1];
else if (*args) { pn = *args; ai = 1; }
if (pn && !(ci = strspn(pn, numbs_str))) { ai = 0; pn = NULL; }
if (pn && (!mkfloat(pn, &tmp, 1) || tmp < W_MIN_COL || tmp > SCREENMAX))
error_exit(fmtmk(N_fmt(BAD_widtharg_fmt), pn));
Width_mode = (int)tmp;
cp++;
args += ai;
if (pn) cp = pn + ci;
} continue;
default :
error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt)
, *cp, Myname, N_txt(USAGE_abbrev_txt)));
} // end: switch (*cp)
// advance cp and jump over any numerical args used above
if (*cp) cp += strspn(&cp[1], numbs_str);
bump_cp:
if (*cp) ++cp;
} // end: while (*cp)
} // end: while (*args)
if (optind < argc)
error_exit(fmtmk(N_fmt(UNKNOWN_opts_fmt), argv[optind]));
// fixup delay time, maybe...
if (FLT_MAX > tmp_delay) {
@ -6320,13 +6303,12 @@ static void frame_make (void) {
/*
* duh... */
int main (int dont_care_argc, char **argv) {
(void)dont_care_argc;
int main (int argc, char *argv[]) {
before(*argv);
// +-------------+
wins_stage_1(); // top (sic) slice
configs_reads(); // > spread etc, <
parse_args(&argv[1]); // > lean stuff, <
parse_args(argc, argv); // > lean stuff, <
whack_terminal(); // > onions etc. <
wins_stage_2(); // as bottom slice
// +-------------+

View File

@ -29,6 +29,7 @@
//#define CASEUP_HEXES /* show any hex values in upper case */
//#define CASEUP_SUFIX /* show time/mem/cnts suffix in upper case */
//#define EQUCOLHDRYES /* yes, do equalize column header lengths */
//#define GETOPTFIX_NO /* do not address getopt_long deficiencies */
//#define INSP_JUSTNOT /* don't smooth unprintable right margins */
//#define INSP_OFFDEMO /* disable demo screens, issue msg instead */
//#define INSP_SAVEBUF /* preserve 'Insp_buf' contents in a file */
@ -656,7 +657,7 @@ typedef struct WIN_t {
//atic const char *configs_file (FILE *fp, const char *name, float *delay);
//atic int configs_path (const char *const fmts, ...);
//atic void configs_reads (void);
//atic void parse_args (char **args);
//atic void parse_args (int argc, char **argv);
//atic void whack_terminal (void);
/*------ Windows/Field Groups support ----------------------------------*/
//atic void win_names (WIN_t *q, const char *name);
@ -699,7 +700,7 @@ typedef struct WIN_t {
/*------ Entry point plus two ------------------------------------------*/
//atic void frame_hlp (int wix, int max);
//atic void frame_make (void);
// int main (int dont_care_argc, char **argv);
// int main (int argc, char *argv[]);
#endif /* _Itop */

View File

@ -375,25 +375,45 @@ static void build_norm_nlstab (void) {
Norm_nlstab[WRONG_switch_fmt] = _(""
"inappropriate '%s'\n"
"Usage:\n %s%s");
Norm_nlstab[HELP_cmdline_fmt] = _(""
" %s\n"
"Usage:\n %s%s");
Norm_nlstab[HELP_cmdline_fmt] = _("\n"
"Usage:\n"
" %s [options]\n"
"\n"
"Options:\n"
" -b, --batch-mode run in non-iteractive batch mode\n"
" -c, --cmdline-toggle reverse last remembered 'c' state\n"
" -d, --delay=SECONDS delay time between updates\n"
" -E, --scale-summary-mem=SCALE scale memory as: k,m,g,t,p or e\n"
" -e, --scale-task-mem=SCALE scale memory as: k,m,g,t or p\n"
" -H, --threads-show display individual threads\n"
" -i, --idle-toggle reverse last remembered 'i' state\n"
" -n, --iterations=NUMBER maximum number of iterations\n"
" -O, --list-fields output field names and exit\n"
" -o, --sort-override=FIELD force sorting on FIELD name\n"
" -p, --pid=PIDLIST monitor only specified process IDs\n"
" -S, --accum-time-toggle reverse last remembered 'S' state\n"
" -s, --secure-mode force secure mode operation\n"
" -U, --filter-any-user=USER show only processes owned by USER\n"
" -u, --filter-only-euser=USER show only processes owned by USER\n"
" -w, --width [=COLUMNS] override terminal width\n"
" -1, --single-cpu-toggle reverse last remembered '1' state\n"
"\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
"\n"
"For more details see top(1).");
Norm_nlstab[BAD_delayint_fmt] = _("bad delay interval '%s'");
Norm_nlstab[BAD_niterate_fmt] = _("bad iterations argument '%s'");
Norm_nlstab[LIMIT_exceed_fmt] = _("pid limit (%d) exceeded");
Norm_nlstab[BAD_mon_pids_fmt] = _("bad pid '%s'");
Norm_nlstab[MISSING_args_fmt] = _("-%c requires argument");
Norm_nlstab[MISSING_args_fmt] = _("-%c argument missing");
Norm_nlstab[BAD_widtharg_fmt] = _("bad width arg '%s'");
Norm_nlstab[UNKNOWN_opts_fmt] = _(""
"unknown option '%c'\n"
"Usage:\n %s%s");
Norm_nlstab[UNKNOWN_opts_fmt] = _("unknown option '%s'");
Norm_nlstab[DELAY_secure_txt] = _("-d disallowed in \"secure\" mode");
Norm_nlstab[DELAY_badarg_txt] = _("-d requires positive argument");
Norm_nlstab[ON_word_only_txt] = _("On");
Norm_nlstab[OFF_one_word_txt] = _("Off");
/* Translation Hint: Only the following words should be translated
. secs (seconds), max (maximum), user, field, cols (columns)*/
Norm_nlstab[USAGE_abbrev_txt] = _(" -hv | -bcEeHiOSs1 -d secs -n max -u|U user -p pid(s) -o field -w [cols]");
Norm_nlstab[VERSION_opts_fmt] = _("%s from %s");
Norm_nlstab[FOREST_modes_fmt] = _("Forest mode %s");
Norm_nlstab[FAIL_tty_get_txt] = _("failed tty get");
Norm_nlstab[FAIL_tty_set_fmt] = _("failed tty set: %s");
@ -544,7 +564,7 @@ static void build_norm_nlstab (void) {
Norm_nlstab[LIB_errormem_fmt] = _("library failed memory statistics, at %d: %s");
Norm_nlstab[LIB_errorcpu_fmt] = _("library failed cpu statistics, at %d: %s");
Norm_nlstab[LIB_errorpid_fmt] = _("library failed pids statistics, at %d: %s");
Norm_nlstab[BAD_memscale_fmt] = _("bad memory scaling arg '%c'");
Norm_nlstab[BAD_memscale_fmt] = _("bad memory scaling arg '%s'");
Norm_nlstab[XTRA_vforest_fmt] = _("PID to collapse/expand [default pid = %d]");
Norm_nlstab[XTRA_size2up_txt] = _("terminal is not wide enough");
Norm_nlstab[XTRA_modebad_txt] = _("wrong mode, command inactive");

View File

@ -78,7 +78,7 @@ enum norm_nls {
OSEL_casenot_txt, OSEL_caseyes_txt, OSEL_errdelm_fmt, OSEL_errdups_txt,
OSEL_errvalu_fmt, OSEL_prompts_fmt, OSEL_statlin_fmt, RC_bad_entry_fmt,
RC_bad_files_fmt, SCROLL_coord_fmt, SELECT_clash_txt, THREADS_show_fmt,
TIME_accumed_fmt, UNKNOWN_cmds_txt, UNKNOWN_opts_fmt, USAGE_abbrev_txt,
TIME_accumed_fmt, UNKNOWN_cmds_txt, UNKNOWN_opts_fmt, VERSION_opts_fmt,
WORD_abv_mem_txt, WORD_abv_swp_txt, WORD_allcpus_txt, WORD_another_txt,
WORD_eachcpu_fmt, WORD_exclude_txt, WORD_include_txt, WORD_noneone_txt,
WORD_process_txt, WORD_threads_txt, WRITE_rcfile_fmt, WRONG_switch_fmt,