6.15. Command Line Processing
This document describes the command-line parser in
src/util/pmix_cmd_line.h and src/util/pmix_cmd_line.c: how a tool
declares the options it accepts, the syntax a user may write them in, how
the parsed result is read back, and the helpers for options whose values
are a small language of their own (--map-by package:span:pe=2).
The parser is used by every PMIx tool (pattrs, pquery, pctrl,
palloc, …) and by PRRTE, which builds its tools’ option tables from
the same macros. The header is installed so that projects layered on PMIx
can use it, which is why generic command-line machinery belongs here rather
than in any one consumer.
For contributors, src/util/AGENTS.md records the parser’s special cases
and the defects that produced them; the unit tests are in
test/unit/util/util_cmd_line.c.
6.15.1. Overview
The parser is a wrapper around getopt_long(3). A tool supplies
an array of
struct optionnaming every long option it accepts,a
getopt-style string of the short options it accepts, andthe name of its help file,
and pmix_cmd_line_parse() walks argv and files every occurrence of
every option into a pmix_cli_result_t. Everything from the first token
that is not an option onward - normally the executable a launcher is to
start, and that executable’s own arguments - is returned untouched as the
result’s tail.
argv itself is never modified: getopt reorders the array it is
given, so the parser works on a copy.
6.15.2. Declaring the options
6.15.2.1. The option table
Options are declared as an array of struct option terminated by
PMIX_OPTION_END. Two macros fill in an entry:
PMIX_OPTION_DEFINE(name, arg)A long option with no single-character equivalent.
PMIX_OPTION_SHORT_DEFINE(name, arg, c)A long option that may also be given as
-c. The same character must appear in the short-option string (below), orgetoptwill not recognize it.
arg says whether the option takes a value:
PMIX_ARG_NONETakes none. Its presence is the whole of what it says, and it is filed with no value.
PMIX_ARG_REQDRequires one, given as the next token or attached with
=.PMIX_ARG_OPTIONALMay take one, which must then be attached:
--name=valueor-cvalue. A value given as the next token is not taken - it is read as the next argument, and usually as the start of the tail. The one exception is--help, which the parser treats specially (see below).
The option names are #define s in pmix_cmd_line.h -
PMIX_CLI_HELP, PMIX_CLI_PMIXMCA, PMIX_CLI_NAMESPACE and so on -
each with a comment giving the argument it takes. Use them rather than
string literals: a misspelled macro is a compile error, a misspelled string
is an option nobody can reach. A project layered on PMIx defines its own
names the same way (PRRTE’s are the PRTE_CLI_* macros in
src/util/prte_cmd_line.h).
static struct option myoptions[] = {
/* basic options */
PMIX_OPTION_SHORT_DEFINE(PMIX_CLI_HELP, PMIX_ARG_OPTIONAL, 'h'),
PMIX_OPTION_SHORT_DEFINE(PMIX_CLI_VERSION, PMIX_ARG_NONE, 'V'),
PMIX_OPTION_SHORT_DEFINE(PMIX_CLI_VERBOSE, PMIX_ARG_NONE, 'v'),
PMIX_OPTION_DEFINE(PMIX_CLI_PMIXMCA, PMIX_ARG_REQD),
/* what this tool does */
PMIX_OPTION_DEFINE(PMIX_CLI_PID, PMIX_ARG_REQD),
PMIX_OPTION_DEFINE(PMIX_CLI_NAMESPACE, PMIX_ARG_REQD),
PMIX_OPTION_DEFINE(PMIX_CLI_SYSTEM_SERVER, PMIX_ARG_NONE),
PMIX_OPTION_END
};
6.15.2.2. Options that take two values
A few options are declared PMIX_ARG_REQD - one value, as far as
getopt knows - and then take a second token of the parser’s own
accord:
any option whose name ends in
mca(--pmixmca, and PRRTE’s--prtemca):--pmixmca name valueis filed as the single valuename=value;--prepend-envand--append-env:--prepend-env VAR valueis filed as two values,VARthenvalue;--show-version, which takes up to two trailing tokens and none at all when the next token is another option.
The second token must be present and must not name a registered option;
otherwise the command line is refused with a message naming the option.
Whether a token names an option is decided by asking the option table (a
long option or an abbreviation of one, or a cluster of registered short
options), not by looking for a dash, because a value may legitimately begin
with one (--pmixmca foo -1, negative MCA values being real) or contain
one (0-1).
6.15.2.3. The short-option string
The short options are given as a getopt(3) option string:
c-ctakes no value.c:-crequires a value:-c valueor-cvalue.c::-cmay take a value, which must be attached:-cvalue. Written-c value, the value is not taken.
Every character in the string must also be the c of a
PMIX_OPTION_SHORT_DEFINE entry; a short option with no long equivalent
is refused when it is used. Pass NULL for a tool with no short options.
Do not prefix the string with + or - - the parser prefixes it with
+ itself (see “Where the options end”, below). The usual string for a
tool whose only short options are the basic ones is:
static char *myshorts = "h::vV";
Three short options are handled by the parser itself rather than filed:
-h/--helpWith no value, prints the
usagetopic of the tool’s help file. With a value ---help topic,--help=topicor-htopic, any leading dashes stripped - prints that topic, which by convention is named after the option it describes, so--help pidexplains--pid.help,versionandverboseare answered from PMIx’s ownhelp-cli.txt.-V/--versionPrints the
versiontopic of the tool’s help file.-v/--verboseFiled with a value that counts the
vs:-vvvis3, while--verboseand-vare each1. Repeated, it is filed once per occurrence, and the caller adds them up.
For both help and version the parse ends there and returns
PMIX_OPERATION_SUCCEEDED.
Finally, -np N is accepted as a spelling of --np N when the tool
registers np with a short n: getopt reads -np as -n
with the value p, and the parser recognizes that and takes the next
token as the count.
6.15.3. Parsing
int pmix_cmd_line_parse(char **argv, char *shorts,
struct option myoptions[],
pmix_cmd_line_store_fn_t storefn,
pmix_cli_result_t *results,
char *helpfile);
results must already be constructed (PMIX_CONSTRUCT or
PMIX_CLI_RESULT_STATIC_INIT). It may already hold values the caller
put there; those are kept, and are recorded as having no position on the
command line.
storefn is NULL for the default, which files each occurrence under
the option’s name, appending its value (if any) to that option’s list. A
tool may supply its own to file values differently - under another key, or
several - and the positions described below are still recorded correctly,
because they are worked out from what the result holds afterwards rather
than from what the store function says it did.
The return value says what the caller should do next:
PMIX_SUCCESSThe command line was parsed. Carry on.
PMIX_OPERATION_SUCCEEDEDThe parse is over and the tool has already said its piece (
--help,--version). Print nothing more and exit successfully.PMIX_ERR_SILENTThe command line was refused and the reason has already been printed. Exit without adding a message of your own.
- anything else
A failure nothing has reported yet - say so, for instance with
PMIx_Error_string().
pmix_cli_result_t results;
int rc;
PMIX_CONSTRUCT(&results, pmix_cli_result_t);
rc = pmix_cmd_line_parse(argv, myshorts, myoptions, NULL,
&results, "help-mytool.txt");
if (PMIX_SUCCESS != rc) {
if (PMIX_ERR_SILENT != rc && PMIX_OPERATION_SUCCEEDED != rc) {
fprintf(stderr, "%s: command line error (%s)\n",
argv[0], PMIx_Error_string(rc));
}
exit((PMIX_OPERATION_SUCCEEDED == rc) ? 0 : 1);
}
The parser keeps its state in getopt’s process-global optind,
optarg, opterr and optopt, which it resets on entry. Two parses
at once corrupt each other, whatever results they are filling: parse once,
at startup, on the tool’s main thread.
6.15.4. The command line a user writes
These are the rules a user of any tool built on this parser can rely on.
6.15.4.1. Long options
An option that takes a value may be given it as the next token or attached with
=:--namespace fooand--namespace=fooare the same. An option whose value is optional takes it only when attached.A long option’s name may be abbreviated to any prefix that names only that option -
getopt_longaccepts--namesfor--namespacein a tool with no other option beginning that way. A prefix that fits two options is refused.Each long option is written with two dashes.
-npis the one single-dash spelling of a long option the parser accepts.
6.15.4.2. Short options
Short options that take no value may be clustered:
-vvV.A required value may be attached or be the next token; an optional one must be attached (
-hpid).
6.15.4.3. Values
A value may begin with a dash. It is read as a value unless it names a registered option - a long option or an abbreviation of one, or a cluster of registered short options - so
--pmixmca foo -1works.An option given more than once is filed once per occurrence, in the order given. Whether a repeat adds to the earlier ones or contradicts them is the tool’s decision, not the parser’s.
6.15.4.4. Where the options end
Options end at the first token that is not one. That token and everything after it are the tail - for a launcher, the executable and its own arguments, which must not be read as the launcher’s. So in
mytool --verbose app --verbosethe second--verbosebelongs toapp.--ends the options explicitly; everything after it is the tail, even tokens that look like options.A lone
-is not an option. It conventionally names standard input, and it begins the tail.An
&where the tail would begin - the command being put into the background - leaves no tail rather than a tail of&.
6.15.5. Reading the result
A pmix_cli_result_t holds a list of pmix_cli_item_t, one per option
given, and the tail:
typedef struct {
pmix_list_item_t super;
char *key; // the option's name, as declared
char **values; // every value given, in order; NULL if it takes none
int *seq; // command-line position of each value
int nseq;
int firstseq; // position at which the option first appeared
} pmix_cli_item_t;
typedef struct {
pmix_object_t super;
pmix_list_t instances; // pmix_cli_item_t's
char **tail; // everything after the options, or NULL
int nseq;
} pmix_cli_result_t;
The accessors, all keyed by the option’s declared name:
pmix_cmd_line_get_param(results, key)The option’s item, or
NULLif it was not given.pmix_cmd_line_is_taken(results, key)Whether it was given at all - how an option that takes no value is read.
pmix_cmd_line_get_ninsts(results, key)How many values it was given.
pmix_cmd_line_get_nth_instance(results, key, n)Its
nth value, orNULLif there is none.pmix_cmd_line_get_nth_seq(results, key, n)andpmix_cmd_line_get_first_seq(results, key)Where on the command line its
nth value, or its first appearance, was - a number meaningful only compared with another from the same parse, and-1for a value the caller put there itself.pmix_cmd_line_get_ordered(results, &occurrences, &nocc)Every occurrence of every option, sorted by position. The grouped view cannot say whether one
--set-envcame before or after a--prepend-env; this one can. The array points into the result’s strings and is released withfree().
pmix_cli_item_t *opt;
if (NULL != (opt = pmix_cmd_line_get_param(&results, PMIX_CLI_PID))) {
pid = strtol(opt->values[0], NULL, 10);
}
if (pmix_cmd_line_is_taken(&results, PMIX_CLI_SYSTEM_SERVER)) {
/* ... */
}
/* the executable and its arguments */
app_argv = results.tail;
6.15.6. Options whose values are directives
Some options take a value that is a small language of its own. In PRRTE,
--map-by package:span:pe=2 is a directive (package) followed by
qualifiers (span, pe=2), each of which may carry a value after
an =, and --output tag,timestamp is a list of directives. The words
come from a fixed vocabulary and may be abbreviated. getopt has
nothing to say about any of this; these helpers do.
The separators are the consumer’s to define, but the convention - which PRRTE follows - is:
,separates directives,:separates a directive from its qualifiers, and one qualifier from the next,=separates a word from its value.
So a qualifier written after a , is not a qualifier at all:
device=gpu,ndev=2 is a device named gpu,ndev=2, and the ndev
qualifier is spelled device=gpu:ndev=2.
6.15.6.1. Matching one word: pmix_check_cli_option()
bool pmix_check_cli_option(char *input, char *name);
#define PMIX_CHECK_CLI_OPTION(input, name) ...
True if input names name: spells it out, or abbreviates it.
Comparison is case-insensitive.
Both strings are cut at their first
=, so the value never takes part, and a name may be declared with its=("pe=").An abbreviation is a leading part of the name: never empty and never longer than the name.
packagefoois notpackage, andgpu,ndev=2is notgpu.A hyphenated name is abbreviated segment by segment:
sys-servissystem-server, butsystemserveris not, and a word with more segments than the name never matches it.
This answers for one name at a time, so it cannot tell whether an
abbreviation also fits some other word the caller accepts - a chain of
if (PMIX_CHECK_CLI_OPTION(...)) tests takes the first that fits, and
the order the chain happens to be written in then decides what an
ambiguous word means. Where the input could be one of several words, use
pmix_cli_match().
6.15.6.2. Matching against a vocabulary: pmix_cli_match()
A vocabulary is a pmix_cli_choice_t table ended by
PMIX_CLI_CHOICE_END:
PMIX_CLI_CHOICE(name, tag, value)
nameThe word in full. Anything from an
=on is ignored, so"pe="can be used as it stands.tagWhat the match reports. Entries that share a tag are spellings of one thing (
parseableandparsable), and an input matching several of them is not ambiguous. Switch on the tag, never on a position in the table.valuePMIX_CLI_VALUE_NONE- takes no value (span);PMIX_CLI_VALUE_OPTIONAL- may take one (sharedorshared=false);PMIX_CLI_VALUE_REQUIRED- must have one (pe=2).
pmix_cli_match_t pmix_cli_match(const char *input,
const pmix_cli_choice_t *choices,
int *tag);
applies these rules in order:
an input that spells a word out in full is that word, whatever else it also abbreviates -
peispe, notpe-list;otherwise the input must abbreviate exactly one word, or words that all share a tag;
the matched word’s value rule is then applied. An
=with nothing after it promises a value and does not give one, so it counts as missing wherever a value is allowed.
and returns one of
PMIX_CLI_MATCH_FOUND*tagis the word’s tag.PMIX_CLI_MATCH_NONEMatches no word (including an empty or
NULLinput).*tagis-1.PMIX_CLI_MATCH_AMBIGUOUSAbbreviates words with different tags.
*tagis-1.PMIX_CLI_MATCH_UNEXPECTED_VALUEA value was given to a word that takes none -
span=false, which a comparison that stops at the=would have read asspan.*tagis the word’s tag, so the caller can name it.PMIX_CLI_MATCH_MISSING_VALUEA word that needs a value was given none.
*tagis the word’s tag.
The value itself is read with pmix_cli_qualifier_value().
char *pmix_cli_match_list(const char *input,
const pmix_cli_choice_t *choices,
char sep);
returns the names of the words input matches, joined by sep and
without any = - the list to show the user after an ambiguous input.
Given a NULL input it lists every word, which is the list of valid
spellings to show after an input that matched nothing. Returns NULL
if nothing matches; the caller frees the result.
An example: the policy word of a --bind-to-style option, where n
fits both none and numa:
enum { BIND_NONE, BIND_CORE, BIND_NUMA, BIND_PACKAGE };
static const pmix_cli_choice_t binders[] = {
PMIX_CLI_CHOICE("none", BIND_NONE, PMIX_CLI_VALUE_NONE),
PMIX_CLI_CHOICE("core", BIND_CORE, PMIX_CLI_VALUE_NONE),
PMIX_CLI_CHOICE("numa", BIND_NUMA, PMIX_CLI_VALUE_NONE),
PMIX_CLI_CHOICE("package", BIND_PACKAGE, PMIX_CLI_VALUE_NONE),
PMIX_CLI_CHOICE("socket", BIND_PACKAGE, PMIX_CLI_VALUE_NONE), // older spelling
PMIX_CLI_CHOICE_END
};
int tag;
char *list;
switch (pmix_cli_match(word, binders, &tag)) {
case PMIX_CLI_MATCH_FOUND:
break;
case PMIX_CLI_MATCH_AMBIGUOUS:
list = pmix_cli_match_list(word, binders, ',');
fprintf(stderr, "\"%s\" could be any of: %s\n", word, list);
free(list);
return ERROR;
case PMIX_CLI_MATCH_NONE:
list = pmix_cli_match_list(NULL, binders, ',');
fprintf(stderr, "\"%s\" is not one of: %s\n", word, list);
free(list);
return ERROR;
default:
fprintf(stderr, "\"%s\": wrong value for %s\n",
word, binders[tag].name); // tags here are indices
return ERROR;
}
With that table, c, co and core are all BIND_CORE;
n is ambiguous; nu is BIND_NUMA; s is BIND_PACKAGE;
cores and core=2 are refused.
pmix_cli_match() is available when PMIX_CAP_CLI_MATCH is defined
in pmix_version.h.
6.15.6.3. Reading a value: pmix_cli_qualifier_value()
char *pmix_cli_qualifier_value(char *qual);
#define PMIX_CLI_QUALIFIER_VALUE(q) ...
returns the text after a word’s =, or NULL if there is none or it
is empty. The result points into qual.
Always read a value this way, never at an offset fixed to the word’s full
spelling: the user may have abbreviated it, so p=2 is pe=2 and the
value is not three characters in.
6.15.6.4. Reading a time: pmix_convert_string_to_time()
unsigned int pmix_convert_string_to_time(const char *t);
#define PMIX_CONVERT_TIME(t) ...
converts [[[days:]hours:]minutes:]seconds to a number of seconds:
90 and 1:30 are both ninety seconds, 1:0:0 an hour. An empty
string, or one with no fields, is zero.
Because a time contains :, an option whose values may be times cannot
also use : to separate qualifiers - PRRTE’s --rtos has no
qualifiers for exactly that reason.