]>
git.proxmox.com Git - mirror_frr.git/blob - lib/command_match.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Input matching routines for CLI backend.
6 * Copyright (C) 2016 Cumulus Networks, Inc.
11 #include "command_match.h"
14 DEFINE_MTYPE_STATIC(LIB
, CMD_MATCHSTACK
, "Command Match Stack");
22 #define trace_matcher(...) \
25 fprintf(stderr, __VA_ARGS__); \
28 /* matcher helper prototypes */
29 static int add_nexthops(struct list
*, struct graph_node
*,
30 struct graph_node
**, size_t, bool);
32 static enum matcher_rv
command_match_r(struct graph_node
*, vector
,
33 unsigned int, struct graph_node
**,
36 static int score_precedence(enum cmd_token_type
);
38 static enum match_type
min_match_level(enum cmd_token_type
);
40 static void del_arglist(struct list
*);
42 static struct cmd_token
*disambiguate_tokens(struct cmd_token
*,
43 struct cmd_token
*, char *);
45 static struct list
*disambiguate(struct list
*, struct list
*, vector
,
48 int compare_completions(const void *, const void *);
50 /* token matcher prototypes */
51 static enum match_type
match_token(struct cmd_token
*, char *);
53 static enum match_type
match_ipv4(const char *);
55 static enum match_type
match_ipv4_prefix(const char *);
57 static enum match_type
match_ipv6_prefix(const char *, bool);
59 static enum match_type
match_range(struct cmd_token
*, const char *);
61 static enum match_type
match_word(struct cmd_token
*, const char *);
63 static enum match_type
match_variable(struct cmd_token
*, const char *);
65 static enum match_type
match_mac(const char *, bool);
67 static bool is_neg(vector vline
, size_t idx
)
69 if (idx
>= vector_active(vline
) || !vector_slot(vline
, idx
))
71 return !strcmp(vector_slot(vline
, idx
), "no");
74 enum matcher_rv
command_match(struct graph
*cmdgraph
, vector vline
,
75 struct list
**argv
, const struct cmd_element
**el
)
77 struct graph_node
*stack
[CMD_ARGC_MAX
];
78 enum matcher_rv status
;
81 // prepend a dummy token to match that pesky start node
82 vector vvline
= vector_init(vline
->alloced
+ 1);
83 vector_set_index(vvline
, 0, XSTRDUP(MTYPE_TMP
, "dummy"));
84 memcpy(vvline
->index
+ 1, vline
->index
,
85 sizeof(void *) * vline
->alloced
);
86 vvline
->active
= vline
->active
+ 1;
88 struct graph_node
*start
= vector_slot(cmdgraph
->nodes
, 0);
89 status
= command_match_r(start
, vvline
, 0, stack
, argv
);
90 if (status
== MATCHER_OK
) { // successful match
91 struct listnode
*head
= listhead(*argv
);
92 struct listnode
*tail
= listtail(*argv
);
97 // delete dummy start node
98 cmd_token_del((struct cmd_token
*)head
->data
);
99 list_delete_node(*argv
, head
);
101 // get cmd_element out of list tail
102 *el
= listgetdata(tail
);
103 list_delete_node(*argv
, tail
);
105 // now argv is an ordered list of cmd_token matching the user
106 // input, with each cmd_token->arg holding the corresponding
115 trace_matcher("No match\n");
117 trace_matcher("Matched command\n->string %s\n->desc %s\n",
118 (*el
)->string
, (*el
)->doc
);
121 // free the leader token we alloc'd
122 XFREE(MTYPE_TMP
, vector_slot(vvline
, 0));
130 * Builds an argument list given a DFA and a matching input line.
132 * First the function determines if the node it is passed matches the first
133 * token of input. If it does not, it returns NULL (MATCHER_NO_MATCH). If it
134 * does match, then it saves the input token as the head of an argument list.
136 * The next step is to see if there is further input in the input line. If
137 * there is not, the current node's children are searched to see if any of them
138 * are leaves (type END_TKN). If this is the case, then the bottom of the
139 * recursion stack has been reached, the leaf is pushed onto the argument list,
140 * the current node is pushed, and the resulting argument list is
141 * returned (MATCHER_OK). If it is not the case, NULL is returned, indicating
142 * that there is no match for the input along this path (MATCHER_INCOMPLETE).
144 * If there is further input, then the function recurses on each of the current
145 * node's children, passing them the input line minus the token that was just
146 * matched. For each child, the return value of the recursive call is
147 * inspected. If it is null, then there is no match for the input along the
148 * subgraph headed by that child. If it is not null, then there is at least one
149 * input match in that subgraph (more on this in a moment).
151 * If a recursive call on a child returns a non-null value, then it has matched
152 * the input given it on the subgraph that starts with that child. However, due
153 * to the flexibility of the grammar, it is sometimes the case that two or more
154 * child graphs match the same input (two or more of the recursive calls have
155 * non-NULL return values). This is not a valid state, since only one true
156 * match is possible. In order to resolve this conflict, the function keeps a
157 * reference to the child node that most specifically matches the input. This
158 * is done by assigning each node type a precedence. If a child is found to
159 * match the remaining input, then the precedence values of the current
160 * best-matching child and this new match are compared. The node with higher
161 * precedence is kept, and the other match is discarded. Due to the recursive
162 * nature of this function, it is only necessary to compare the precedence of
163 * immediate children, since all subsequent children will already have been
164 * disambiguated in this way.
166 * In the event that two children are found to match with the same precedence,
167 * then the input is ambiguous for the passed cmd_element and NULL is returned.
169 * @param[in] start the start node.
170 * @param[in] vline the vectorized input line.
171 * @param[in] n the index of the first input token.
172 * @return A linked list of n elements. The first n-1 elements are pointers to
173 * struct cmd_token and represent the sequence of tokens matched by the input.
174 * The ->arg field of each token points to a copy of the input matched on it.
175 * The final nth element is a pointer to struct cmd_element, which is the
176 * command that was matched.
178 * If no match was found, the return value is NULL.
180 static enum matcher_rv
command_match_r(struct graph_node
*start
, vector vline
,
182 struct graph_node
**stack
,
183 struct list
**currbest
)
185 assert(n
< vector_active(vline
));
187 enum matcher_rv status
= MATCHER_NO_MATCH
;
189 // get the minimum match level that can count as a full match
190 struct cmd_token
*copy
, *token
= start
->data
;
191 enum match_type minmatch
= min_match_level(token
->type
);
193 /* check history/stack of tokens
194 * this disallows matching the same one more than once if there is a
195 * circle in the graph (used for keyword arguments) */
196 if (n
== CMD_ARGC_MAX
)
197 return MATCHER_NO_MATCH
;
198 if (!token
->allowrepeat
)
199 for (size_t s
= 0; s
< n
; s
++)
200 if (stack
[s
] == start
)
201 return MATCHER_NO_MATCH
;
203 // get the current operating input token
204 char *input_token
= vector_slot(vline
, n
);
207 fprintf(stdout
, "\"%-20s\" matches \"%-30s\" ? ", input_token
,
209 enum match_type mt
= match_token(token
, input_token
);
210 fprintf(stdout
, "type: %d ", token
->type
);
211 fprintf(stdout
, "min: %d - ", minmatch
);
214 fprintf(stdout
, "trivial_match ");
217 fprintf(stdout
, "no_match ");
220 fprintf(stdout
, "partly_match ");
223 fprintf(stdout
, "exact_match ");
227 fprintf(stdout
, " MATCH");
228 fprintf(stdout
, "\n");
231 // if we don't match this node, die
232 if (match_token(token
, input_token
) < minmatch
)
233 return MATCHER_NO_MATCH
;
237 // pointers for iterating linklist
239 struct graph_node
*gn
;
241 // get all possible nexthops
242 struct list
*next
= list_new();
243 add_nexthops(next
, start
, NULL
, 0, is_neg(vline
, 1));
245 // determine the best match
246 for (ALL_LIST_ELEMENTS_RO(next
, ln
, gn
)) {
247 // if we've matched all input we're looking for END_TKN
248 if (n
+ 1 == vector_active(vline
)) {
249 struct cmd_token
*tok
= gn
->data
;
250 if (tok
->type
== END_TKN
) {
251 // if more than one END_TKN in the follow set
253 status
= MATCHER_AMBIGUOUS
;
258 *currbest
= list_new();
259 // node should have one child node with the
261 struct graph_node
*leaf
=
262 vector_slot(gn
->to
, 0);
263 // last node in the list will hold the
264 // cmd_element; this is important because
265 // list_delete() expects that all nodes have
266 // the same data type, so when deleting this
267 // list the last node must be manually deleted
268 struct cmd_element
*el
= leaf
->data
;
269 listnode_add(*currbest
, el
);
271 (void (*)(void *)) & cmd_token_del
;
272 // do not break immediately; continue walking
273 // through the follow set to ensure that there
274 // is exactly one END_TKN
279 // else recurse on candidate child node
280 struct list
*result
= NULL
;
281 enum matcher_rv rstat
=
282 command_match_r(gn
, vline
, n
+ 1, stack
, &result
);
284 // save the best match
285 if (result
&& *currbest
) {
286 // pick the best of two matches
287 struct list
*newbest
=
288 disambiguate(*currbest
, result
, vline
, n
+ 1);
290 // current best and result are ambiguous
292 status
= MATCHER_AMBIGUOUS
;
293 // current best is still the best, but ambiguous
294 else if (newbest
== *currbest
295 && status
== MATCHER_AMBIGUOUS
)
296 status
= MATCHER_AMBIGUOUS
;
297 // result is better, but also ambiguous
298 else if (newbest
== result
299 && rstat
== MATCHER_AMBIGUOUS
)
300 status
= MATCHER_AMBIGUOUS
;
301 // one or the other is superior and not ambiguous
305 // delete the unnecessary result
306 struct list
*todelete
=
307 ((newbest
&& newbest
== result
) ? *currbest
309 del_arglist(todelete
);
311 *currbest
= newbest
? newbest
: *currbest
;
315 } else if (!*currbest
) {
316 status
= MAX(rstat
, status
);
320 // copy token, set arg and prepend to currbest
322 copy
= cmd_token_dup(token
);
323 copy
->arg
= XSTRDUP(MTYPE_CMD_ARG
, input_token
);
324 listnode_add_before(*currbest
, (*currbest
)->head
, copy
);
325 } else if (n
+ 1 == vector_active(vline
) && status
== MATCHER_NO_MATCH
)
326 status
= MATCHER_INCOMPLETE
;
334 static void stack_del(void *val
)
336 XFREE(MTYPE_CMD_MATCHSTACK
, val
);
339 enum matcher_rv
command_complete(struct graph
*graph
, vector vline
,
340 struct list
**completions
)
342 // pointer to next input token to match
344 bool neg
= is_neg(vline
, 0);
348 list_new(), // current nodes to match input token against
349 *next
= list_new(); // possible next hops after current input
351 current
->del
= next
->del
= stack_del
;
353 // pointers used for iterating lists
354 struct graph_node
**gstack
, **newstack
;
355 struct listnode
*node
;
357 // add all children of start node to list
358 struct graph_node
*start
= vector_slot(graph
->nodes
, 0);
359 add_nexthops(next
, start
, &start
, 0, neg
);
362 for (idx
= 0; idx
< vector_active(vline
) && next
->count
> 0; idx
++) {
363 list_delete(¤t
);
366 next
->del
= stack_del
;
368 input_token
= vector_slot(vline
, idx
);
370 int exact_match_exists
= 0;
371 for (ALL_LIST_ELEMENTS_RO(current
, node
, gstack
))
372 if (!exact_match_exists
)
374 (match_token(gstack
[0]->data
,
380 for (ALL_LIST_ELEMENTS_RO(current
, node
, gstack
)) {
381 struct cmd_token
*token
= gstack
[0]->data
;
383 if (token
->attr
& CMD_ATTR_HIDDEN
)
386 enum match_type minmatch
= min_match_level(token
->type
);
387 trace_matcher("\"%s\" matches \"%s\" (%d) ? ",
388 input_token
, token
->text
, token
->type
);
390 unsigned int last_token
=
391 (vector_active(vline
) - 1 == idx
);
392 enum match_type matchtype
=
393 match_token(token
, input_token
);
395 // occurs when last token is whitespace
397 trace_matcher("trivial_match\n");
399 newstack
= XMALLOC(MTYPE_CMD_MATCHSTACK
,
400 sizeof(struct graph_node
*));
401 /* we're not recursing here, just the first
403 newstack
[0] = gstack
[0];
404 listnode_add(next
, newstack
);
407 trace_matcher("trivial_match\n");
408 if (exact_match_exists
&& !last_token
)
412 trace_matcher("exact_match\n");
415 MTYPE_CMD_MATCHSTACK
,
416 sizeof(struct graph_node
*));
417 /* same as above, not recursing on this
419 newstack
[0] = gstack
[0];
420 listnode_add(next
, newstack
);
421 } else if (matchtype
>= minmatch
)
422 add_nexthops(next
, gstack
[0], gstack
,
426 trace_matcher("no_match\n");
433 * -----------------------------------------------------------------
434 * token = last input token processed
435 * idx = index in `command` of last token processed
436 * current = set of all transitions from the previous input token
437 * next = set of all nodes reachable from all nodes in `matched`
440 enum matcher_rv mrv
= idx
== vector_active(vline
) && next
->count
445 if (!MATCHER_ERROR(mrv
)) {
446 // extract cmd_token into list
447 *completions
= list_new();
448 for (ALL_LIST_ELEMENTS_RO(next
, node
, gstack
)) {
449 listnode_add(*completions
, gstack
[0]->data
);
453 list_delete(¤t
);
460 * Adds all children that are reachable by one parser hop to the given list.
461 * special tokens except END_TKN are treated as transparent.
463 * @param[in] list to add the nexthops to
464 * @param[in] node to start calculating nexthops from
465 * @param[in] stack listing previously visited nodes, if non-NULL.
466 * @param[in] stackpos how many valid entries are in stack
467 * @return the number of children added to the list
469 * NB: non-null "stack" means that new stacks will be added to "list" as
470 * output, instead of direct node pointers!
472 static int add_nexthops(struct list
*list
, struct graph_node
*node
,
473 struct graph_node
**stack
, size_t stackpos
, bool neg
)
476 struct graph_node
*child
;
477 struct graph_node
**nextstack
;
478 for (unsigned int i
= 0; i
< vector_active(node
->to
); i
++) {
479 child
= vector_slot(node
->to
, i
);
481 struct cmd_token
*token
= child
->data
;
482 if (!token
->allowrepeat
&& stack
) {
483 for (j
= 0; j
< stackpos
; j
++)
484 if (child
== stack
[j
])
490 if (token
->type
== NEG_ONLY_TKN
&& !neg
)
493 if (token
->type
>= SPECIAL_TKN
&& token
->type
!= END_TKN
) {
495 add_nexthops(list
, child
, stack
, stackpos
, neg
);
499 MTYPE_CMD_MATCHSTACK
,
501 * sizeof(struct graph_node
*));
502 nextstack
[0] = child
;
503 memcpy(nextstack
+ 1, stack
,
504 stackpos
* sizeof(struct graph_node
*));
506 listnode_add(list
, nextstack
);
508 listnode_add(list
, child
);
517 * Determines the node types for which a partial match may count as a full
518 * match. Enables command abbrevations.
520 * @param[in] type node type
521 * @return minimum match level needed to for a token to fully match
523 static enum match_type
min_match_level(enum cmd_token_type type
)
526 // anything matches a start node, for the sake of recursion
529 // allowing words to partly match enables command abbreviation
534 case IPV4_PREFIX_TKN
:
536 case IPV6_PREFIX_TKN
:
547 assert(!"Reached end of function we should never hit");
551 * Assigns precedence scores to node types.
553 * @param[in] type node type to score
554 * @return precedence score
556 static int score_precedence(enum cmd_token_type type
)
559 // some of these are mutually exclusive, so they share
560 // the same precedence value
562 case IPV4_PREFIX_TKN
:
564 case IPV6_PREFIX_TKN
:
581 assert(!"Reached end of function we should never hit");
585 * Picks the better of two possible matches for a token.
587 * @param[in] first candidate node matching token
588 * @param[in] second candidate node matching token
589 * @param[in] token the token being matched
590 * @return the best-matching node, or NULL if the two are entirely ambiguous
592 static struct cmd_token
*disambiguate_tokens(struct cmd_token
*first
,
593 struct cmd_token
*second
,
596 // if the types are different, simply go off of type precedence
597 if (first
->type
!= second
->type
) {
598 int firstprec
= score_precedence(first
->type
);
599 int secndprec
= score_precedence(second
->type
);
600 if (firstprec
!= secndprec
)
601 return firstprec
< secndprec
? first
: second
;
606 // if they're the same, return the more exact match
607 enum match_type fmtype
= match_token(first
, input_token
);
608 enum match_type smtype
= match_token(second
, input_token
);
609 if (fmtype
!= smtype
)
610 return fmtype
> smtype
? first
: second
;
616 * Picks the better of two possible matches for an input line.
618 * @param[in] first candidate list of cmd_token matching vline
619 * @param[in] second candidate list of cmd_token matching vline
620 * @param[in] vline the input line being matched
621 * @param[in] n index into vline to start comparing at
622 * @return the best-matching list, or NULL if the two are entirely ambiguous
624 static struct list
*disambiguate(struct list
*first
, struct list
*second
,
625 vector vline
, unsigned int n
)
627 assert(first
!= NULL
);
628 assert(second
!= NULL
);
629 // doesn't make sense for these to be inequal length
630 assert(first
->count
== second
->count
);
631 assert(first
->count
== vector_active(vline
) - n
+ 1);
633 struct listnode
*fnode
= listhead_unchecked(first
),
634 *snode
= listhead_unchecked(second
);
635 struct cmd_token
*ftok
= listgetdata(fnode
), *stok
= listgetdata(snode
),
638 // compare each token, if one matches better use that one
639 for (unsigned int i
= n
; i
< vector_active(vline
); i
++) {
640 char *token
= vector_slot(vline
, i
);
641 if ((best
= disambiguate_tokens(ftok
, stok
, token
)))
642 return best
== ftok
? first
: second
;
643 fnode
= listnextnode(fnode
);
644 snode
= listnextnode(snode
);
645 ftok
= listgetdata(fnode
);
646 stok
= listgetdata(snode
);
653 * Deletion function for arglist.
655 * Since list->del for arglists expects all listnode->data to hold cmd_token,
656 * but arglists have cmd_element as the data for the tail, this function
657 * manually deletes the tail before deleting the rest of the list as usual.
659 * The cmd_element at the end is *not* a copy. It is the one and only.
661 * @param list the arglist to delete
663 static void del_arglist(struct list
*list
)
665 // manually delete last node
666 struct listnode
*tail
= listtail(list
);
668 list_delete_node(list
, tail
);
670 // delete the rest of the list as usual
674 /*---------- token level matching functions ----------*/
676 static enum match_type
match_token(struct cmd_token
*token
, char *input_token
)
678 // nothing trivially matches everything
680 return trivial_match
;
682 switch (token
->type
) {
684 return match_word(token
, input_token
);
686 return match_ipv4(input_token
);
687 case IPV4_PREFIX_TKN
:
688 return match_ipv4_prefix(input_token
);
690 return match_ipv6_prefix(input_token
, false);
691 case IPV6_PREFIX_TKN
:
692 return match_ipv6_prefix(input_token
, true);
694 return match_range(token
, input_token
);
696 return match_variable(token
, input_token
);
698 return match_mac(input_token
, false);
700 return match_mac(input_token
, true);
709 assert(!"Reached end of function we should never hit");
712 #define IPV4_ADDR_STR "0123456789."
713 #define IPV4_PREFIX_STR "0123456789./"
715 static enum match_type
match_ipv4(const char *str
)
718 int dots
= 0, nums
= 0;
722 memset(buf
, 0, sizeof(buf
));
724 while (*str
!= '\0') {
729 if (*(str
+ 1) == '.')
732 if (*(str
+ 1) == '\0')
738 if (!isdigit((unsigned char)*str
))
747 memcpy(buf
, sp
, str
- sp
);
753 if (v
> 0 && buf
[0] == '0')
770 static enum match_type
match_ipv4_prefix(const char *str
)
777 memset(buf
, 0, sizeof(buf
));
779 while (*str
!= '\0' && *str
!= '/') {
784 if (*(str
+ 1) == '.' || *(str
+ 1) == '/')
787 if (*(str
+ 1) == '\0')
794 if (!isdigit((unsigned char)*str
))
803 memcpy(buf
, sp
, str
- sp
);
809 if (v
> 0 && buf
[0] == '0')
814 if (*(str
+ 1) == '\0')
819 } else if (*str
== '\0')
830 while (*str
!= '\0') {
831 if (!isdigit((unsigned char)*str
))
837 if (atoi(sp
) > IPV4_MAX_BITLEN
)
844 #define IPV6_ADDR_STR "0123456789abcdefABCDEF:."
845 #define IPV6_PREFIX_STR "0123456789abcdefABCDEF:./"
846 #define STATE_START 1
847 #define STATE_COLON 2
848 #define STATE_DOUBLE 3
851 #define STATE_SLASH 6
854 static enum match_type
match_ipv6_prefix(const char *str
, bool prefix
)
856 int state
= STATE_START
;
857 int colons
= 0, nums
= 0, double_colon
= 0;
859 const char *sp
= NULL
, *start
= str
;
865 if (strspn(str
, prefix
? IPV6_PREFIX_STR
: IPV6_ADDR_STR
)
869 while (*str
!= '\0' && state
!= STATE_MASK
) {
873 if (*(str
+ 1) != ':' && *(str
+ 1) != '\0')
885 if (*(str
+ 1) == '/')
887 else if (*(str
+ 1) == ':')
888 state
= STATE_DOUBLE
;
898 if (*(str
+ 1) == ':')
901 if (*(str
+ 1) != '\0' && *(str
+ 1) != '/')
905 if (*(str
+ 1) == '/')
915 if (*(str
+ 1) == ':' || *(str
+ 1) == '.'
916 || *(str
+ 1) == '\0' || *(str
+ 1) == '/') {
920 for (; sp
<= str
; sp
++)
926 if (*(str
+ 1) == ':')
928 else if (*(str
+ 1) == '.') {
929 if (colons
|| double_colon
)
933 } else if (*(str
+ 1) == '/')
941 if (*(str
+ 1) == '\0')
960 struct sockaddr_in6 sin6_dummy
;
961 int ret
= inet_pton(AF_INET6
, start
, &sin6_dummy
.sin6_addr
);
962 return ret
== 1 ? exact_match
: partly_match
;
965 if (state
< STATE_MASK
)
968 mask
= strtol(str
, &endptr
, 10);
972 if (mask
< 0 || mask
> IPV6_MAX_BITLEN
)
978 static enum match_type
match_range(struct cmd_token
*token
, const char *str
)
980 assert(token
->type
== RANGE_TKN
);
985 val
= strtoll(str
, &endptr
, 10);
989 if (val
< token
->min
|| val
> token
->max
)
995 static enum match_type
match_word(struct cmd_token
*token
, const char *word
)
997 assert(token
->type
== WORD_TKN
);
999 // if the passed token is 0 length, partly match
1001 return partly_match
;
1003 // if the passed token is strictly a prefix of the full word, partly
1005 if (strlen(word
) < strlen(token
->text
))
1006 return !strncmp(token
->text
, word
, strlen(word
)) ? partly_match
1009 // if they are the same length and exactly equal, exact match
1010 else if (strlen(word
) == strlen(token
->text
))
1011 return !strncmp(token
->text
, word
, strlen(word
)) ? exact_match
1017 static enum match_type
match_variable(struct cmd_token
*token
, const char *word
)
1019 assert(token
->type
== VARIABLE_TKN
);
1023 #define MAC_CHARS "ABCDEFabcdef0123456789:"
1025 static enum match_type
match_mac(const char *word
, bool prefix
)
1027 /* 6 2-digit hex numbers separated by 5 colons */
1028 size_t mac_explen
= 6 * 2 + 5;
1029 /* '/' + 2-digit integer */
1030 size_t mask_len
= 1 + 2;
1033 unsigned int maskval
;
1036 if (strlen(word
) > mac_explen
+ (prefix
? mask_len
: 0))
1040 for (i
= 0; i
< mac_explen
; i
++) {
1041 if (word
[i
] == '\0' || !strchr(MAC_CHARS
, word
[i
]))
1043 if (((i
+ 1) % 3 == 0) != (word
[i
] == ':'))
1047 /* incomplete address */
1048 if (i
< mac_explen
&& word
[i
] == '\0')
1049 return partly_match
;
1050 else if (i
< mac_explen
)
1054 if (prefix
&& word
[i
] == '/') {
1055 if (word
[++i
] == '\0')
1056 return partly_match
;
1058 maskval
= strtoul(&word
[i
], &eptr
, 10);
1059 if (*eptr
!= '\0' || maskval
> 48)
1061 } else if (prefix
&& word
[i
] == '\0') {
1062 return partly_match
;
1063 } else if (prefix
) {