From c91b371485b7ffaea3a7c669b080328d141dfb6b Mon Sep 17 00:00:00 2001 From: Jim Warner Date: Sat, 4 Sep 2021 00:00:00 -0500 Subject: [PATCH] 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 --- top/top.c | 326 ++++++++++++++++++++++++-------------------------- top/top.h | 5 +- top/top_nls.c | 42 +++++-- top/top_nls.h | 2 +- 4 files changed, 189 insertions(+), 186 deletions(-) diff --git a/top/top.c b/top/top.c index f1ea0004..4e2ab70b 100644 --- a/top/top.c +++ b/top/top.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -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; - - while (*args) { - const char *cp = *(args++); - - while (*cp) { - char ch; - float tmp; + int ch; + + while (-1 != (ch = getopt_long(argc, argv, sopts, lopts, NULL))) { + int i; + float tmp; + char *cp = optarg; + +#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 // +-------------+ diff --git a/top/top.h b/top/top.h index edfbfbf6..53348e18 100644 --- a/top/top.h +++ b/top/top.h @@ -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 */ diff --git a/top/top_nls.c b/top/top_nls.c index db54dc21..259eec27 100644 --- a/top/top_nls.c +++ b/top/top_nls.c @@ -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"); diff --git a/top/top_nls.h b/top/top_nls.h index fb868117..df38c778 100644 --- a/top/top_nls.h +++ b/top/top_nls.h @@ -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, -- 2.40.0