From nobody Mon Feb 9 06:50:24 2026 Delivered-To: importer@patchew.org Received-SPF: none (zohomail.com: 8.43.85.245 is neither permitted nor denied by domain of lists.libvirt.org) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; spf=none (zohomail.com: 8.43.85.245 is neither permitted nor denied by domain of lists.libvirt.org) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=redhat.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1713533141116396.23436766200484; Fri, 19 Apr 2024 06:25:41 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 06D771E1D; Fri, 19 Apr 2024 09:25:40 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id C3D671E37; Fri, 19 Apr 2024 09:07:00 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 9A1CF1D24; Fri, 19 Apr 2024 09:06:52 -0400 (EDT) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id DF7B11DCF for ; Fri, 19 Apr 2024 09:05:47 -0400 (EDT) Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-533-uflwIMDJN5iYUgCz3YVsBQ-1; Fri, 19 Apr 2024 09:05:46 -0400 Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.rdu2.redhat.com [10.11.54.6]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CB85F1C3F106 for ; Fri, 19 Apr 2024 13:05:45 +0000 (UTC) Received: from speedmetal.lan (unknown [10.45.242.11]) by smtp.corp.redhat.com (Postfix) with ESMTP id 42FC82166B34 for ; Fri, 19 Apr 2024 13:05:45 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.7 required=5.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 X-MC-Unique: uflwIMDJN5iYUgCz3YVsBQ-1 From: Peter Krempa To: devel@lists.libvirt.org Subject: [PATCH 13/13] vsh: Refactor logic in vshCommandParse Date: Fri, 19 Apr 2024 15:05:31 +0200 Message-ID: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.6 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Message-ID-Hash: ONBTURCNWCPMFNULLJKYIN2YLUITV7LQ X-Message-ID-Hash: ONBTURCNWCPMFNULLJKYIN2YLUITV7LQ X-MailFrom: pkrempa@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1713533143092100001 Refactor the existing logic using two nested loops with a jump into the middle of both with 3 separate places fetching next token to a single loop using a state machine with one centralized place to fetch next tokens and add explanation comments. Signed-off-by: Peter Krempa --- tools/vsh.c | 336 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 210 insertions(+), 126 deletions(-) diff --git a/tools/vsh.c b/tools/vsh.c index b04b4f5cd0..e0a04593b0 100644 --- a/tools/vsh.c +++ b/tools/vsh.c @@ -1476,171 +1476,255 @@ vshCmdNew(vshControl *ctl, } -static bool -vshCommandParse(vshControl *ctl, vshCommandParser *parser, vshCmd **partia= l) +static int +vshCmdOptAssignPositional(vshControl *ctl, + vshCmd *cmd, + const char *val, + bool report) { - g_autoptr(vshCmd) cmd =3D NULL; - char *tkdata =3D NULL; - vshCmd *clast =3D NULL; + vshCmdOpt *opt; - if (!partial) { + if (!(opt =3D vshCmdGetNextPositionalOpt(cmd))) { + /* ignore spurious arguments for 'help' command */ + if (STREQ(cmd->def->name, "help")) + return 0; + + if (report) + vshError(ctl, _("unexpected data '%1$s'"), val); + + return -1; + } + + vshCmdOptAssign(ctl, cmd, opt, val, report); + return 0; +} + + +typedef enum { + VSH_CMD_PARSER_STATE_START, + VSH_CMD_PARSER_STATE_COMMENT, + VSH_CMD_PARSER_STATE_COMMAND, + VSH_CMD_PARSER_STATE_ASSIGN_OPT, + VSH_CMD_PARSER_STATE_POSITIONAL_ONLY, +} vshCommandParserState; + +static bool +vshCommandParse(vshControl *ctl, + vshCommandParser *parser, + vshCmd **partial) +{ + g_autoptr(vshCmd) cmds =3D NULL; /* linked list of all parsed commands= in this session */ + vshCmd *cmds_last =3D NULL; + g_autoptr(vshCmd) cmd =3D NULL; /* currently parsed command */ + vshCommandParserState state =3D VSH_CMD_PARSER_STATE_START; + vshCmdOpt *opt =3D NULL; + g_autofree char *optionvalue =3D NULL; + bool report =3D !partial; + bool ret =3D false; + + if (partial) { + g_clear_pointer(partial, vshCommandFree); + } else { g_clear_pointer(&ctl->cmd, vshCommandFree); } while (1) { - vshCommandToken tk; - bool data_only =3D false; + /* previous iteration might have already gotten a value. Store it = as the + * token in this iteration */ + g_autofree char *tkdata =3D g_steal_pointer(&optionvalue); - if (partial) { - g_clear_pointer(partial, vshCommandFree); - } - - while (1) { - vshCmdOpt *opt =3D NULL; + /* If we have a value already or the option to fill is a boolean we + * don't want to fetch a new token */ + if (!(tkdata || + (opt && opt->def->type =3D=3D VSH_OT_BOOL))) { + vshCommandToken tk; - tkdata =3D NULL; - tk =3D parser->getNextArg(ctl, parser, &tkdata, true); + tk =3D parser->getNextArg(ctl, parser, &tkdata, report); - if (tk =3D=3D VSH_TK_ERROR) - goto syntaxError; - if (tk !=3D VSH_TK_ARG) { - VIR_FREE(tkdata); + switch (tk) { + case VSH_TK_ARG: + /* will be handled below */ break; - } - if (cmd =3D=3D NULL) { - /* first token must be command name or comment */ - if (*tkdata =3D=3D '#') { - do { - VIR_FREE(tkdata); - tk =3D parser->getNextArg(ctl, parser, &tkdata, fa= lse); - } while (tk =3D=3D VSH_TK_ARG); - VIR_FREE(tkdata); - break; + case VSH_TK_ERROR: + goto out; + + case VSH_TK_END: + case VSH_TK_SUBCMD_END: + /* The last argument name expects a value, but it's missin= g */ + if (opt) { + if (partial) { + /* for completion to work we need to also store the + * last token into the last 'opt' */ + vshCmdOptAssign(ctl, cmd, opt, tkdata, report); + } else { + if (opt->def->type =3D=3D VSH_OT_INT) + vshError(ctl, _("expected syntax: --%1$s "), + opt->def->name); + else + vshError(ctl, _("expected syntax: --%1$s "), + opt->def->name); + + goto out; + } } - if (!(cmd =3D vshCmdNew(ctl, tkdata, !partial))) - goto syntaxError; - - VIR_FREE(tkdata); - } else if (data_only) { - goto get_data; - } else if (tkdata[0] =3D=3D '-' && tkdata[1] =3D=3D '-' && - g_ascii_isalnum(tkdata[2])) { - char *optstr =3D strchr(tkdata + 2, '=3D'); - - if (optstr) { - *optstr =3D '\0'; /* convert the '=3D' to '\0' */ - optstr =3D g_strdup(optstr + 1); - } + /* command parsed -- allocate new struct for the command */ + if (cmd) { + /* if we encountered --help, replace parsed command wi= th 'help ' */ + if (cmd->helpOptionSeen) { + vshCmd *helpcmd =3D vshCmdNewHelp(cmd->def->name); - /* Special case 'help' to ignore all spurious options */ - if (!(opt =3D vshCmdGetOption(ctl, cmd, tkdata + 2, - &optstr, partial =3D=3D NULL))= ) { - VIR_FREE(optstr); - if (STREQ(cmd->def->name, "help")) - continue; - goto syntaxError; - } - VIR_FREE(tkdata); - - if (opt->def->type !=3D VSH_OT_BOOL) { - /* option data */ - if (optstr) - tkdata =3D optstr; - else - tk =3D parser->getNextArg(ctl, parser, &tkdata, pa= rtial =3D=3D NULL); - if (tk =3D=3D VSH_TK_ERROR) - goto syntaxError; - if (tk !=3D VSH_TK_ARG) { - if (partial) { - vshCmdOptAssign(ctl, cmd, opt, tkdata, !partia= l); - VIR_FREE(tkdata); - } else { - vshError(ctl, - _("expected syntax: --%1$s <%2$s>"), - opt->def->name, - opt->def->type =3D=3D - VSH_OT_INT ? _("number") : _("string"= )); - } - goto syntaxError; - } - } else { - tkdata =3D NULL; - if (optstr) { - if (!partial) - vshError(ctl, _("invalid '=3D' after option --= %1$s"), - opt->def->name); - VIR_FREE(optstr); - goto syntaxError; + vshCommandFree(cmd); + cmd =3D helpcmd; } + + if (!partial && + vshCommandCheckOpts(ctl, cmd) < 0) + goto out; + + if (!cmds) + cmds =3D cmd; + if (cmds_last) + cmds_last->next =3D cmd; + cmds_last =3D g_steal_pointer(&cmd); } - } else if (tkdata[0] =3D=3D '-' && tkdata[1] =3D=3D '-' && - tkdata[2] =3D=3D '\0') { - VIR_FREE(tkdata); - data_only =3D true; - continue; - } else { - get_data: - /* Special case 'help' to ignore spurious data */ - if (!(opt =3D vshCmdGetNextPositionalOpt(cmd)) && - STRNEQ(cmd->def->name, "help")) { - if (!partial) - vshError(ctl, _("unexpected data '%1$s'"), tkdata); - goto syntaxError; + + + /* everything parsed */ + if (tk =3D=3D VSH_TK_END) { + ret =3D true; + goto out; } - } - if (opt) { - vshCmdOptAssign(ctl, cmd, opt, tkdata, !partial); - VIR_FREE(tkdata); + /* after processing the command we need to start over agai= n to + * fetch another token */ + state =3D VSH_CMD_PARSER_STATE_START; + continue; } } - /* command parsed -- allocate new struct for the command */ - if (cmd) { - /* if we encountered --help, replace parsed command with 'help= ' */ - if (cmd->helpOptionSeen) { - vshCmd *helpcmd =3D vshCmdNewHelp(cmd->def->name); + /* at this point we know that @tkdata is an argument */ + switch (state) { + case VSH_CMD_PARSER_STATE_START: + if (*tkdata =3D=3D '#') { + state =3D VSH_CMD_PARSER_STATE_COMMENT; + } else { + state =3D VSH_CMD_PARSER_STATE_COMMAND; + + if (!(cmd =3D vshCmdNew(ctl, tkdata, !partial))) + goto out; + } + + break; + + case VSH_CMD_PARSER_STATE_COMMENT: + /* continue eating tokens until end of line or end of input */ + state =3D VSH_CMD_PARSER_STATE_COMMENT; + break; + + case VSH_CMD_PARSER_STATE_COMMAND: { + /* parsing individual options for the command. There are follo= wing options: + * --option + * --option value + * --option=3Dvalue + * --aliasoptionwithvalue (value is part of the alias defini= tion) + * value + * -- (terminate accepting '--option', fill only positional = args) + */ + const char *optionname =3D tkdata + 2; + char *sep; - vshCommandFree(cmd); - cmd =3D helpcmd; + if (!STRPREFIX(tkdata, "--")) { + if (vshCmdOptAssignPositional(ctl, cmd, tkdata, report) < = 0) + goto out; + break; } - if (!partial && - vshCommandCheckOpts(ctl, cmd) < 0) { - vshCommandFree(cmd); - goto syntaxError; + if (STREQ(tkdata, "--")) { + state =3D VSH_CMD_PARSER_STATE_POSITIONAL_ONLY; + break; + } + + if ((sep =3D strchr(optionname, '=3D'))) { + *(sep++) =3D '\0'; + + /* 'optionvalue' has lifetime until next iteration */ + optionvalue =3D g_strdup(sep); } - if (partial) { - vshCommandFree(*partial); - *partial =3D g_steal_pointer(&cmd); + /* lookup the option. Note that vshCmdGetOption also resolves = aliases + * and thus the value possibly contained in the alias */ + if (!(opt =3D vshCmdGetOption(ctl, cmd, optionname, &optionval= ue, report))) { + if (STRNEQ(cmd->def->name, "help")) + goto out; + + /* ignore spurious arguments for 'help' command */ + g_clear_pointer(&optionvalue, g_free); + state =3D VSH_CMD_PARSER_STATE_COMMAND; } else { - if (!ctl->cmd) - ctl->cmd =3D cmd; - if (clast) - clast->next =3D cmd; - clast =3D g_steal_pointer(&cmd); + state =3D VSH_CMD_PARSER_STATE_ASSIGN_OPT; } } + break; + + case VSH_CMD_PARSER_STATE_ASSIGN_OPT: + /* Parameter for a boolean was passed via --boolopt=3Dval */ + if (tkdata && opt->def->type =3D=3D VSH_OT_BOOL) { + if (report) + vshError(ctl, _("invalid '=3D' after option --%1$s"), + opt->def->name); + goto out; + } - if (tk =3D=3D VSH_TK_END) + vshCmdOptAssign(ctl, cmd, opt, tkdata, report); + opt =3D NULL; + state =3D VSH_CMD_PARSER_STATE_COMMAND; break; + + case VSH_CMD_PARSER_STATE_POSITIONAL_ONLY: + state =3D VSH_CMD_PARSER_STATE_POSITIONAL_ONLY; + + if (vshCmdOptAssignPositional(ctl, cmd, tkdata, report) < 0) + goto out; + break; + } } - return true; + out: - syntaxError: if (partial) { - *partial =3D g_steal_pointer(&cmd); + /* When parsing a command for command completion, the last process= ed + * command or the one being currently parsed */ + if (cmd) { + *partial =3D g_steal_pointer(&cmd); + } else if (cmds =3D=3D cmds_last) { + *partial =3D g_steal_pointer(&cmds); + } else { + /* break the last command out of the linked list and let the r= est be freed */ + vshCmd *nc; + + for (nc =3D cmds; nc; nc =3D nc->next) { + if (nc->next =3D=3D cmds_last) { + nc->next =3D NULL; + break; + } + } + + *partial =3D cmds_last; + } } else { - g_clear_pointer(&ctl->cmd, vshCommandFree); + /* for normal command parsing use the whole parsed command list, b= ut + * only on success */ + if (ret =3D=3D true) { + ctl->cmd =3D g_steal_pointer(&cmds); + } } - VIR_FREE(tkdata); - return false; + + return ret; } + /* -------------------- * Command argv parsing * -------------------- --=20 2.44.0 _______________________________________________ Devel mailing list -- devel@lists.libvirt.org To unsubscribe send an email to devel-leave@lists.libvirt.org