/*! 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; }
/*! Copy from one database to another, eg running->startup * @param[in] argv a string: "<db1> <db2>" Copy from db1 to db2 */ int db_copy(clicon_handle h, cvec *cvv, cvec *argv) { char *db1; char *db2; db1 = cv_string_get(cvec_i(argv, 0)); db2 = cv_string_get(cvec_i(argv, 1)); return clicon_rpc_copy_config(h, db1, db2); }
/*! 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; }
/*! Change cligen tree */ int changetree(cligen_handle h, cvec *vars, cg_var *arg) { char *treename = cv_string_get(arg); return cligen_tree_active_set(h, treename); }
/*! 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; }
/* * Command without assigned callback */ int unknown(cligen_handle h, cvec *vars, cg_var *arg) { cg_var *cv = cvec_i(vars, 0); printf("The command has no assigned callback: %s\n", cv_string_get(cv)); return 0; }
/*! 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; }
/*! 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; }
/* * This callback changes the prompt to the variable setting */ int secret(cligen_handle h, cvec *vars, cg_var *arg) { printf("This is a hidden command: %s\n", cv_string_get(arg)); return 0; }
/* * This callback just prints the function argument */ int hello(cligen_handle h, cvec *vars, cg_var *arg) { printf("%s\n", cv_string_get(arg)); return 0; }
#include "ios.h" int vlan_add_interface(void *handle, cvec *vars, cg_var *arg) { char *dot; cg_var *cv; cg_var *ifname; char *str; if ((ifname = cvec_find_var(vars, "name")) == NULL) { clicon_err(OE_CFG, ENOENT, "Could not find 'name' in variable vector"); goto catch; } dot = strrchr(cv_string_get(ifname), '.'); *dot = '\0'; if (if_nametoindex(cv_string_get(ifname)) == 0) { clicon_err(OE_CFG, ENODEV, "Parent interface '%s'", ifname); goto catch; } *dot = '.'; /* range check vlan id */ if (atoi(dot+1) > MAX_VID || atoi(dot+1) < MIN_VID) { clicon_err(OE_CFG, ERANGE, "VLAN ID: %s", dot+1); goto catch; } str = chunk_sprintf(__FUNCTION__,
/*! 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; }