/*! Make a notify subscription to backend and un/register callback for return messages. * * @param[in] h Clicon handle * @param[in] cvv Not used * @param[in] arg A string with <log stream name> <stream status> [<format>] * where <status> is "0" or "1" * and <format> is XXX * Example code: Start logging of mystream and show logs as xml * @code * cmd("comment"), cli_notify("mystream","1","xml"); * @endcode * XXX: format is a memory leak */ int cli_notify(clicon_handle h, cvec *cvv, cvec *argv) { char *stream = NULL; int retval = -1; int status; char *formatstr = NULL; enum format_enum format = FORMAT_TEXT; if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ clicon_err(OE_PLUGIN, 0, "Requires arguments: <logstream> <status> [<format>]"); goto done; } stream = cv_string_get(cvec_i(argv, 0)); status = atoi(cv_string_get(cvec_i(argv, 1))); if (cvec_len(argv) > 2){ formatstr = cv_string_get(cvec_i(argv, 2)); format = format_str2int(formatstr); } if (cli_notification_register(h, stream, format, "", status, cli_notification_cb, (void*)format) < 0) goto done; retval = 0; done: return retval; }
/*! Delete a cv variable from a cvec. Note: cv is not reset & cv may be stale! * * @param[in] cvv Cligen variable vector * @param[in] del variable to delete * * @note This is a dangerous command since the cv it deletes (such as created by * cvec_add) may have been modified with realloc (eg cvec_add/delete) and * therefore can not be used as a reference. Safer methods are to use * cvec_find/cvec_i to find a cv and then to immediately remove it. */ int cvec_del(cvec *cvv, cg_var *del) { int i; cg_var *cv; if (cvec_len(cvv) == 0) return 0; i = 0; cv = NULL; while ((cv = cvec_each(cvv, cv)) != NULL) { if (cv == del) break; i++; } if (i >= cvec_len(cvv)) /* Not found !?! */ return cvec_len(cvv); if (i != cvec_len(cvv)-1) /* If not last entry, move the remaining cv's */ memmove(&cvv->vr_vec[i], &cvv->vr_vec[i+1], (cvv->vr_len-i-1) * sizeof(cvv->vr_vec[0])); cvv->vr_len--; cvv->vr_vec = realloc(cvv->vr_vec, cvv->vr_len*sizeof(cvv->vr_vec[0])); /* Shrink should not fail? */ return cvec_len(cvv); }
/*! Delete all elements in a database * Utility function used by cligen spec file */ int delete_all(clicon_handle h, cvec *cvv, cvec *argv) { char *dbstr; int retval = -1; if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires one element: dbname"); goto done; } dbstr = cv_string_get(cvec_i(argv, 0)); if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0 && strcmp(dbstr, "startup") != 0){ clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); goto done; } if (clicon_rpc_delete_config(h, dbstr) < 0) goto done; retval = 0; done: return retval; }
/*! Compare two dbs using XML. Write to file and run diff * @param[in] h Clicon handle * @param[in] cvv * @param[in] arg arg: 0 as xml, 1: as text */ int compare_dbs(clicon_handle h, cvec *cvv, cvec *argv) { cxobj *xc1 = NULL; /* running xml */ cxobj *xc2 = NULL; /* candidate xml */ cxobj *xerr; int retval = -1; int astext; if (cvec_len(argv) > 1){ clicon_err(OE_PLUGIN, 0, "Requires 0 or 1 element. If given: astext flag 0|1"); goto done; } if (cvec_len(argv)) astext = cv_int32_get(cvec_i(argv, 0)); else astext = 0; if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0) goto done; if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0) goto done; if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */ goto done; retval = 0; done: if (xc1) xml_free(xc1); if (xc2) xml_free(xc2); return retval; }
/*! Start bash from cli callback * XXX Application specific?? * XXX replace fprintf with clicon_err? */ int cli_start_shell(clicon_handle h, cvec *vars, cvec *argv) { char *cmd; struct passwd *pw; int retval = -1; char bcmd[128]; cg_var *cv1 = cvec_i(vars, 1); cmd = (cvec_len(vars)>1 ? cv_string_get(cv1) : NULL); if ((pw = getpwuid(getuid())) == NULL){ fprintf(stderr, "%s: getpwuid: %s\n", __FUNCTION__, strerror(errno)); goto done; } if (chdir(pw->pw_dir) < 0){ fprintf(stderr, "%s: chdir(%s): %s\n", __FUNCTION__, pw->pw_dir, strerror(errno)); endpwent(); goto done; } endpwent(); cli_signal_flush(h); cli_signal_unblock(h); if (cmd){ snprintf(bcmd, 128, "bash -l -c \"%s\"", cmd); if (system(bcmd) < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash -c): %s\n", __FUNCTION__, strerror(errno)); goto done; } } else if (system("bash -l") < 0){ cli_signal_block(h); fprintf(stderr, "%s: system(bash): %s\n", __FUNCTION__, strerror(errno)); goto done; } cli_signal_block(h); #if 0 /* Allow errcodes from bash */ if (retval != 0){ fprintf(stderr, "%s: system(%s) code=%d\n", __FUNCTION__, cmd, retval); goto done; } #endif retval = 0; done: return retval; }
/*! Call expand callback and insert expanded commands in place of variable * variable argument callback variant * @see pt_expand_fn */ static int pt_expand_fnv(cligen_handle h, cg_obj *co, cvec *cvv, parse_tree *ptn, cg_obj *parent) { int retval = -1; cvec *commands = cvec_new(0); cvec *helptexts = cvec_new(0); cg_var *cv = NULL; char *helpstr; cg_obj *con; int i; if ((*co->co_expandv_fn)( cligen_userhandle(h)?cligen_userhandle(h):h, co->co_expand_fn_str, cvv, co->co_expand_fn_vec, commands, helptexts) < 0) goto done; i = 0; while ((cv = cvec_each(commands, cv)) != NULL) { if (i < cvec_len(helptexts)){ helpstr = strdup(cv_string_get(cvec_i(helptexts, i))); } else helpstr = NULL; i++; pt_realloc(ptn); if (co_expand_sub(co, parent, &ptn->pt_vec[ptn->pt_len-1]) < 0) goto done; con = ptn->pt_vec[ptn->pt_len-1]; if (transform_var_to_cmd(con, strdup(cv_string_get(cv)), helpstr) < 0) goto done; } if (commands) cvec_free(commands); if (helptexts) cvec_free(helptexts); retval = 0; done: return retval; }
/*! Set syntax mode */ int cli_set_mode(clicon_handle h, cvec *vars, cvec *argv) { int retval = -1; char *str = NULL; if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires one element to be cli mode"); goto done; } str = cv_string_get(cvec_i(argv, 0)); cli_set_syntax_mode(h, str); retval = 0; done: return retval; }
/*! Unlock database * * @param[in] h Clicon handle * @param[in] cvv Not used * @param[in] arg A string with <database> * @code * lock("comment"), cli_lock("running"); * @endcode * XXX: format is a memory leak */ int cli_unlock(clicon_handle h, cvec *cvv, cvec *argv) { char *db; int retval = -1; if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires arguments: <db>"); goto done; } db = cv_string_get(cvec_i(argv, 0)); if (clicon_rpc_unlock(h, db) < 0) goto done; retval = 0; done: return retval; }
/*! Set debug level on backend daemon (not CLI) * @param[in] h Clicon handle * @param[in] vars If variable "level" exists, its integer value is used * @param[in] arg Else use the integer value of argument * @note The level is either what is specified in arg as int argument. * _or_ if a 'level' variable is present in vars use that value instead. */ int cli_debug_backend(clicon_handle h, cvec *vars, cvec *argv) { int retval = -1; cg_var *cv; int level; if ((cv = cvec_find(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1"); goto done; } cv = cvec_i(argv, 0); } level = cv_int32_get(cv); /* config daemon */ retval = clicon_rpc_debug(h, level); done: return retval; }
/*! Set debug level on CLI client (not backend daemon) * @param[in] h Clicon handle * @param[in] vars If variable "level" exists, its integer value is used * @param[in] arg Else use the integer value of argument * @note The level is either what is specified in arg as int argument. * _or_ if a 'level' variable is present in vars use that value instead. */ int cli_debug_cli(clicon_handle h, cvec *vars, cvec *argv) { int retval = -1; cg_var *cv; int level; if ((cv = cvec_find(vars, "level")) == NULL){ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires either label var or single arg: 0|1"); goto done; } cv = cvec_i(argv, 0); } level = cv_int32_get(cv); /* cli */ clicon_debug_init(level, NULL); /* 0: dont debug, 1:debug */ retval = 0; done: return retval; }
/*! Copy database to local file * Utility function used by cligen spec file * @param[in] h CLICON handle * @param[in] cvv variable vector (containing <varname>) * @param[in] argv a string: "<dbname> <varname>" * <dbname> is running, candidate, or startup * <varname> is name of cligen variable in the "cvv" vector containing file name * Note that "filename" is local on client filesystem not backend. * The function can run without a local database * @note The file is saved with dummy top-tag: clicon: <clicon></clicon> * @code * save file <name:string>, save_config_file("running name"); * @endcode * @see load_config_file */ int save_config_file(clicon_handle h, cvec *cvv, cvec *argv) { int retval = -1; char *filename = NULL; cg_var *cv; char *dbstr; char *varstr; cxobj *xt = NULL; cxobj *xerr; FILE *f = NULL; if (cvec_len(argv) != 2){ if (cvec_len(argv)==1) clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<dbname>,<varname>\"", cv_string_get(cvec_i(argv,0))); else clicon_err(OE_PLUGIN, 0, " Got %d arguments. Expected: <dbname>,<varname>", cvec_len(argv)); goto done; } dbstr = cv_string_get(cvec_i(argv, 0)); varstr = cv_string_get(cvec_i(argv, 1)); if (strcmp(dbstr, "running") != 0 && strcmp(dbstr, "candidate") != 0 && strcmp(dbstr, "startup") != 0) { clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); goto done; } if ((cv = cvec_find(cvv, varstr)) == NULL){ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); goto done; } filename = cv_string_get(cv); if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) goto done; if (xt == NULL){ clicon_err(OE_CFG, 0, "get config: empty tree"); /* Shouldnt happen */ goto done; } if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } /* get-config returns a <data> tree. Save as <config> tree so it can be used * as data-store. */ if (xml_name_set(xt, "config") < 0) goto done; if ((f = fopen(filename, "w")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); goto done; } if (clicon_xml2file(f, xt, 0, 1) < 0) goto done; retval = 0; /* Fall through */ done: if (xt) xml_free(xt); if (f != NULL) fclose(f); return retval; }
/*! Load a configuration file to candidate database * Utility function used by cligen spec file * @param[in] h CLICON handle * @param[in] cvv Vector of variables (where <varname> is found) * @param[in] argv A string: "<varname> (merge|replace)" * <varname> is name of a variable occuring in "cvv" containing filename * @note that "filename" is local on client filesystem not backend. * @note file is assumed to have a dummy top-tag, eg <clicon></clicon> * @code * # cligen spec * load file <name2:string>, load_config_file("name2","merge"); * @endcode * @see save_config_file */ int load_config_file(clicon_handle h, cvec *cvv, cvec *argv) { int ret = -1; struct stat st; char *filename = NULL; int replace; cg_var *cv; char *opstr; char *varstr; int fd = -1; cxobj *xt = NULL; cxobj *x; cbuf *cbxml; if (cvec_len(argv) != 2){ if (cvec_len(argv)==1) clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<varname>,<op>\"", cv_string_get(cvec_i(argv,0))); else clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <varname>,<op>", cvec_len(argv)); goto done; } varstr = cv_string_get(cvec_i(argv, 0)); opstr = cv_string_get(cvec_i(argv, 1)); if (strcmp(opstr, "merge") == 0) replace = 0; else if (strcmp(opstr, "replace") == 0) replace = 1; else{ clicon_err(OE_PLUGIN, 0, "No such op: %s, expected merge or replace", opstr); goto done; } if ((cv = cvec_find(cvv, varstr)) == NULL){ clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); goto done; } filename = cv_string_get(cv); if (stat(filename, &st) < 0){ clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s", filename, strerror(errno)); goto done; } /* Open and parse local file into xml */ if ((fd = open(filename, O_RDONLY)) < 0){ clicon_err(OE_UNIX, errno, "open(%s)", filename); goto done; } if (xml_parse_file(fd, "</clicon>", NULL, &xt) < 0) goto done; if (xt == NULL) goto done; if ((cbxml = cbuf_new()) == NULL) goto done; x = NULL; while ((x = xml_child_each(xt, x, -1)) != NULL) { /* Ensure top-level is "config", maybe this is too rough? */ xml_name_set(x, "config"); if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0) goto done; } if (clicon_rpc_edit_config(h, "candidate", replace?OP_REPLACE:OP_MERGE, cbuf_get(cbxml)) < 0) goto done; cbuf_free(cbxml); // } ret = 0; done: if (xt) xml_free(xt); if (fd != -1) close(fd); return ret; }
/*! Modify xml datastore from a callback using xml key format strings * @param[in] h Clicon handle * @param[in] cvv Vector of cli string and instantiated variables * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" * @param[in] op Operation to perform on database * Cvv will contain first the complete cli string, and then a set of optional * instantiated variables. * Example: * cvv[0] = "set interfaces interface eth0 type bgp" * cvv[1] = "eth0" * cvv[2] = "bgp" * argv[0] = "/interfaces/interface/%s/type" * op: OP_MERGE * @see cli_callback_generate where arg is generated */ static int cli_dbxml(clicon_handle h, cvec *cvv, cvec *argv, enum operation_type op) { int retval = -1; char *str = NULL; char *api_path_fmt; /* xml key format */ char *api_path = NULL; /* xml key */ cg_var *cval; int len; cg_var *arg; cbuf *cb = NULL; yang_stmt *yspec; cxobj *xbot = NULL; /* xpath, NULL if datastore */ yang_stmt *y = NULL; /* yang spec of xpath */ cxobj *xtop = NULL; /* xpath root */ cxobj *xa; /* attribute */ cxobj *xb; /* body */ if (cvec_len(argv) != 1){ clicon_err(OE_PLUGIN, 0, "Requires one element to be xml key format string"); goto done; } if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } arg = cvec_i(argv, 0); api_path_fmt = cv_string_get(arg); if (api_path_fmt2api_path(api_path_fmt, cvv, &api_path) < 0) goto done; /* Create config top-of-tree */ if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y) < 1) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) goto done; xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; if (yang_keyword_get(y) != Y_LIST && yang_keyword_get(y) != Y_LEAF_LIST){ len = cvec_len(cvv); if (len > 1){ cval = cvec_i(cvv, len-1); if ((str = cv2str_dup(cval)) == NULL){ clicon_err(OE_UNIX, errno, "cv2str_dup"); goto done; } if ((xb = xml_new("body", xbot, NULL)) == NULL) goto done; xml_type_set(xb, CX_BODY); if (xml_value_set(xb, str) < 0) goto done; } } if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; } if (clicon_xml2cbuf(cb, xtop, 0, 0) < 0) goto done; if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0) goto done; if (clicon_autocommit(h)) { if (clicon_rpc_commit(h) < 0) goto done; } retval = 0; done: if (cb) cbuf_free(cb); if (str) free(str); if (api_path) free(api_path); if (xtop) xml_free(xtop); return retval; }
/*! Copy one configuration object to antother * * Works for objects that are items ina yang list with a keyname, eg as: * list sender{ * key name; * leaf name{... * * @param[in] h CLICON handle * @param[in] cvv Vector of variables from CLIgen command-line * @param[in] argv Vector: <db>, <xpath>, <field>, <fromvar>, <tovar> * Explanation of argv fields: * db: Database name, eg candidate|tmp|startup * xpath: XPATH expression with exactly two %s pointing to field and from name * field: Name of list key, eg name * fromvar:Name of variable containing name of object to copy from (given by xpath) * tovar: Name of variable containing name of object to copy to. * @code * cli spec: * copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s='%s']", "from", "n1", "n2"); * cli command: * copy snd from to to * @endcode */ int cli_copy_config(clicon_handle h, cvec *cvv, cvec *argv) { int retval = -1; char *db; cxobj *x1 = NULL; cxobj *x2 = NULL; cxobj *x; char *xpath; int i; int j; cbuf *cb = NULL; char *keyname; char *fromvar; cg_var *fromcv; char *fromname = NULL; char *tovar; cg_var *tocv; char *toname; cxobj *xerr; if (cvec_len(argv) != 5){ clicon_err(OE_PLUGIN, 0, "Requires four elements: <db> <xpath> <keyname> <from> <to>"); goto done; } /* First argv argument: Database */ db = cv_string_get(cvec_i(argv, 0)); /* Second argv argument: xpath */ xpath = cv_string_get(cvec_i(argv, 1)); /* Third argv argument: name of keyname */ keyname = cv_string_get(cvec_i(argv, 2)); /* Fourth argv argument: from variable */ fromvar = cv_string_get(cvec_i(argv, 3)); /* Fifth argv argument: to variable */ tovar = cv_string_get(cvec_i(argv, 4)); /* Get from variable -> cv -> from name */ if ((fromcv = cvec_find(cvv, fromvar)) == NULL){ clicon_err(OE_PLUGIN, 0, "fromvar '%s' not found in cligen var list", fromvar); goto done; } /* Get from name from cv */ fromname = cv_string_get(fromcv); /* Create xpath */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_PLUGIN, errno, "cbuf_new"); goto done; } /* Sanity check that xpath contains exactly two %s, ie [%s='%s'] */ j = 0; for (i=0; i<strlen(xpath); i++){ if (xpath[i] == '%') j++; } if (j != 2){ clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'", xpath); goto done; } cprintf(cb, xpath, keyname, fromname); /* Get from object configuration and store in x1 */ if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0) goto done; if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){ clicon_rpc_generate_error("Get configuration", xerr); goto done; } /* Get to variable -> cv -> to name */ if ((tocv = cvec_find(cvv, tovar)) == NULL){ clicon_err(OE_PLUGIN, 0, "tovar '%s' not found in cligen var list", tovar); goto done; } toname = cv_string_get(tocv); /* Create copy xml tree x2 */ if ((x2 = xml_new("new", NULL, NULL)) == NULL) goto done; if (xml_copy(x1, x2) < 0) goto done; xml_name_set(x2, "config"); cprintf(cb, "/%s", keyname); if ((x = xpath_first(x2, "%s", cbuf_get(cb))) == NULL){ clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname); goto done; } x = xml_find(x, "body"); xml_value_set(x, toname); /* resuse cb */ cbuf_reset(cb); /* create xml copy tree and merge it with database configuration */ clicon_xml2cbuf(cb, x2, 0, 0); if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0) goto done; retval = 0; done: if (cb) cbuf_free(cb); if (x1 != NULL) xml_free(x1); if (x2 != NULL) xml_free(x2); return retval; }
/* * match_pattern_node * Non-terminal. Need to match exact. * INPUT: * h CLIgen handle * string0 Input string to match * pt Vector of commands (array of cligen object pointers (cg_obj) * pt_max Length of the pt array * level How many levels (words) into string0 * use_pref Set this flag value if you want to use the preferences between * matches. It is only when you want a final exact match (not * completion or show options) that you should set this. * RETURNS: * The number of matches (0-n) in pt or -1 on error. See matchlen below. * OUTPUT: * ptp Returns the vector at the place of matching * matchv A vector of integers containing which * matchlen Length of matchv. That is, # of matches and same as return * value (if 0-n) * cvec cligen variable vector containing vars/values pair for completion * reason0 If retval = 0, this may be malloced to indicate reason for not * matching variables, if given. Need to be free:d */ static int match_pattern_node(cligen_handle h, char *string0, parse_tree pt, int level, int use_pref, int hide, pt_vec *ptp, int *matchv[], int *matchlen, cvec *cvec, char **reason0 ) { char *string = NULL; int i; int match; int matches = 0; int perfect = 0; int retval = -1; cg_obj *co, *co_match; cg_obj *co_orig; int rest_match = -1; int cmd_levels; int p; int preference = 0; int exact; char *reason; int findreason; parse_tree ptn={0,}; /* Expanded */ cg_var *cv = NULL; co_match = NULL; if (level > command_levels(string0)){ fprintf(stderr, "%s: level > command_level in %s\n", __FUNCTION__, string0); return -1; } /* If there are only variables in the list, then keep track of variable match errors */ findreason = 0; if (reason0) for (i=0; i<pt.pt_len; i++){ if ((co = pt.pt_vec[i]) == NULL) continue; if (co->co_type != CO_VARIABLE){ findreason = 0; break; } findreason++; } extract_substring(string0, level, &string); for (i=0; i<pt.pt_len; i++){ if ((co = pt.pt_vec[i]) == NULL) continue; reason = NULL; if ((match = match_object(string, co, &exact, findreason?&reason:NULL)) < 0) goto error; if (match){ assert(reason==NULL); /* Special case to catch rest variable and space delimited arguments after it */ if (co->co_type == CO_VARIABLE && co->co_vtype == CGV_REST) rest_match = i; if (match_perfect(string, co)){ if (!perfect){ matches = 0; perfect = 1; } } else{ if (perfect) break; if (1 || use_pref){ p = co_pref(co, exact); if (p < preference) continue; /* ignore */ if (p > preference){ preference = p; matches = 0; /* Start again at this level */ } } } co_match = co; matches++; } /* match == 0, co type is variable and findreason, then reason is set this may not be the best preference, we just set the first */ if (reason){ if (*reason0 == NULL) *reason0 = reason; reason = NULL; findreason = 0; } } /* for */ if (matches != 0 && reason0 && *reason0){ free(*reason0); *reason0 = NULL; } if (matches != 1) { #ifdef notneeded if (matches == 0){ cligen_nomatch_set(h, "Unrecognized command"); } else cligen_nomatch_set(h, "Ambigious command"); #endif retval = 0; goto quit; } assert(co_match); if ((cmd_levels = command_levels(string0)) < 0) goto error; /* co_orig is original object in case of expansion */ co_orig = co_match->co_ref?co_match->co_ref: co_match; if (pt_expand_1(h, co_match, &co_match->co_pt) < 0) /* sub-tree expansion */ goto error; if (co_match->co_type == CO_VARIABLE){ if ((cv = add_cov_to_cvec(co_match, string, cvec)) == NULL) goto error; } else if (co_match->co_type == CO_COMMAND && co_orig->co_type == CO_VARIABLE) if ((cv = add_cov_to_cvec(co_orig, string, cvec)) == NULL) goto error; if (pt_expand_2(h, &co_match->co_pt, cvec, &ptn, hide) < 0) /* expand/choice variables */ goto error; if (level+1 == cmd_levels) retval = match_pattern_terminal(h, string0, ptn, level+1, use_pref, ptp, matchv, matchlen, reason0); else retval = match_pattern_node(h, string0, ptn, level+1, use_pref, hide, ptp, matchv, matchlen, cvec, reason0); if (pt_expand_add(co_orig, ptn) < 0) /* add expanded ptn to orig parsetree */ goto error; if (co_match->co_type == CO_COMMAND && co_orig->co_type == CO_VARIABLE) if (co_value_set(co_orig, co_match->co_command) < 0) goto error; /* Cleanup made on top-level */ /* * Special case: we have matched a REST variable (anything) and * there is more text have this word, then we can match REST */ if (retval == 0 && rest_match != -1){ retval = 1; if (*matchlen < 1){ *matchlen = 1; if ((*matchv = realloc(*matchv, (*matchlen)*sizeof(int))) == NULL){ fprintf(stderr, "%s: realloc: %s\n", __FUNCTION__, strerror(errno)); return -1; } } else *matchlen = 1; *ptp = pt.pt_vec; (*matchv)[0] = rest_match; } quit: if (cv){ /* cv may be stale */ cv = cvec_i(cvec, cvec_len(cvec)-1); cv_reset(cv); cvec_del(cvec, cv); } /* Only the last level may have multiple matches */ if (string) free(string); return retval; error: retval = -1; goto quit; }