/** @file load.c * @brief Common part for XML parsing. * * Different XML parsing engines can provide a functions in a form of * standardized API. This code uses this API and parses XML to kscheme. */ #include #include #include #include #include #include #include #include #include #include #include #include #define TAG "XML" #ifdef DEBUG #define KXML_DEBUG DEBUG #endif typedef bool_t (kxml_process_fn)(const kxml_node_t *element, void *parent, faux_error_t *error); static kxml_process_fn process_action, process_param, process_command, process_view, process_ptype, process_plugin, process_nspace, process_klish, process_entry; // Different TAGs types typedef enum { KTAG_NONE, KTAG_ACTION, KTAG_PARAM, KTAG_SWITCH, // PARAM alias KTAG_SUBCOMMAND, // PARAM alias KTAG_MULTI, // PARAM alias KTAG_COMMAND, KTAG_VIEW, KTAG_PTYPE, KTAG_PLUGIN, KTAG_NSPACE, KTAG_KLISH, KTAG_ENTRY, KTAG_MAX, } ktags_e; static const char * const kxml_tags[] = { NULL, "ACTION", "PARAM", "SWITCH", "SUBCOMMAND", "MULTI", "COMMAND", "VIEW", "PTYPE", "PLUGIN", "NSPACE", "KLISH", "ENTRY", }; static kxml_process_fn *kxml_handlers[] = { NULL, process_action, process_param, process_param, process_param, process_param, process_command, process_view, process_ptype, process_plugin, process_nspace, process_klish, process_entry, }; static const char *kxml_tag_name(ktags_e tag) { if ((KTAG_NONE == tag) || (tag >= KTAG_MAX)) return "NONE"; return kxml_tags[tag]; } static ktags_e kxml_node_tag(const kxml_node_t *node) { ktags_e tag = KTAG_NONE; char *name = NULL; if (!node) return KTAG_NONE; if (kxml_node_type(node) != KXML_NODE_ELM) return KTAG_NONE; name = kxml_node_name(node); if (!name) return KTAG_NONE; // Strange case for (tag = (KTAG_NONE + 1); tag < KTAG_MAX; tag++) { if (faux_str_casecmp(name, kxml_tags[tag]) == 0) break; } kxml_node_name_free(name); if (tag >= KTAG_MAX) return KTAG_NONE; return tag; } static kxml_process_fn *kxml_node_handler(const kxml_node_t *node) { return kxml_handlers[kxml_node_tag(node)]; } /** @brief Reads an element from the XML stream and processes it. */ static bool_t process_node(const kxml_node_t *node, void *parent, faux_error_t *error) { kxml_process_fn *handler = NULL; // Process only KXML_NODE_ELM. Don't process other types like: // KXML_NODE_DOC, // KXML_NODE_TEXT, // KXML_NODE_ATTR, // KXML_NODE_COMMENT, // KXML_NODE_PI, // KXML_NODE_DECL, // KXML_NODE_UNKNOWN, if (kxml_node_type(node) != KXML_NODE_ELM) return BOOL_TRUE; handler = kxml_node_handler(node); if (!handler) { // Unknown element faux_error_sprintf(error, TAG": Unknown tag \"%s\"", kxml_node_name(node)); return BOOL_FALSE; } #ifdef KXML_DEBUG printf("kxml: Tag \"%s\"\n", kxml_node_name(node)); #endif return handler(node, parent, error); } static bool_t kxml_load_file(kscheme_t *scheme, const char *filename, faux_error_t *error) { kxml_doc_t *doc = NULL; kxml_node_t *root = NULL; bool_t r = BOOL_FALSE; if (!scheme) return BOOL_FALSE; if (!filename) return BOOL_FALSE; #ifdef KXML_DEBUG printf("kxml: Processing XML file \"%s\"\n", filename); #endif doc = kxml_doc_read(filename); if (!kxml_doc_is_valid(doc)) { /* int errcaps = kxml_doc_error_caps(doc); printf("Unable to open file '%s'", filename); if ((errcaps & kxml_ERR_LINE) == kxml_ERR_LINE) printf(", at line %d", kxml_doc_err_line(doc)); if ((errcaps & kxml_ERR_COL) == kxml_ERR_COL) printf(", at column %d", kxml_doc_err_col(doc)); if ((errcaps & kxml_ERR_DESC) == kxml_ERR_DESC) printf(", message is %s", kxml_doc_err_msg(doc)); printf("\n"); */ kxml_doc_release(doc); return BOOL_FALSE; } root = kxml_doc_root(doc); r = process_node(root, scheme, error); kxml_doc_release(doc); if (!r) { faux_error_sprintf(error, TAG": Illegal file %s", filename); return BOOL_FALSE; } return BOOL_TRUE; } /** @brief Default path to get XML files from. */ static const char *default_path = "/etc/klish;~/.klish"; static const char *path_separators = ":;"; bool_t kxml_load_scheme(kscheme_t *scheme, const char *xml_path, faux_error_t *error) { char *path = NULL; char *fn = NULL; char *saveptr = NULL; bool_t ret = BOOL_TRUE; assert(scheme); if (!scheme) return BOOL_FALSE; // Use the default path if xml path is not specified. // Dup is needed because sring will be tokenized but // the xml_path is must be const. if (!xml_path) path = faux_str_dup(default_path); else path = faux_str_dup(xml_path); #ifdef KXML_DEBUG printf("kxml: Loading scheme \"%s\"\n", path); #endif // Loop through each directory for (fn = strtok_r(path, path_separators, &saveptr); fn; fn = strtok_r(NULL, path_separators, &saveptr)) { DIR *dir = NULL; struct dirent *entry = NULL; char *realpath = NULL; // Expand tilde. Tilde must be the first symbol. realpath = faux_expand_tilde(fn); // Regular file if (faux_isfile(realpath)) { if (!kxml_load_file(scheme, realpath, error)) ret = BOOL_FALSE; faux_str_free(realpath); continue; } // Search this directory for any XML files #ifdef KXML_DEBUG printf("kxml: Processing XML dir \"%s\"\n", realpath); #endif dir = opendir(realpath); if (!dir) { faux_str_free(realpath); continue; } for (entry = readdir(dir); entry; entry = readdir(dir)) { const char *extension = strrchr(entry->d_name, '.'); char *filename = NULL; // Check the filename if (!extension || strcmp(".xml", extension)) continue; filename = faux_str_sprintf("%s/%s", realpath, entry->d_name); if (!kxml_load_file(scheme, filename, error)) ret = BOOL_FALSE; faux_str_free(filename); } closedir(dir); faux_str_free(realpath); } faux_str_free(path); return ret; } /** @brief Iterate through element's children. */ static bool_t process_children(const kxml_node_t *element, void *parent, faux_error_t *error) { const kxml_node_t *node = NULL; while ((node = kxml_node_next_child(element, node)) != NULL) { bool_t res = BOOL_FALSE; res = process_node(node, parent, error); if (!res) return res; } return BOOL_TRUE; } static bool_t process_klish(const kxml_node_t *element, void *parent, faux_error_t *error) { return process_children(element, parent, error); } static bool_t process_view(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kscheme_t *scheme = (kscheme_t *)parent; // Mandatory VIEW name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": VIEW without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = "true"; ientry.mode = "switch"; ientry.min = "1"; ientry.max = "1"; ientry.ptype = NULL; ientry.ref = NULL; ientry.value = NULL; ientry.restore = "false"; ientry.order = "true"; // Parent must be a KLISH tag if (parent_tag != KTAG_KLISH) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain VIEW tag", kxml_tag_name(parent_tag)); goto err; } if (!scheme) { faux_error_sprintf(error, TAG": Broken parent object for VIEW \"%s\"", ientry.name); goto err; } // Does VIEW already exist entry = kscheme_find_entry(scheme, ientry.name); if (entry) { if (!ientry_parse(&ientry, entry, error)) goto err; } else { // New VIEW object entry = ientry_load(&ientry, error); if (!entry) goto err; if (!kscheme_add_entrys(scheme, entry)) { faux_error_sprintf(error, TAG": Can't add VIEW \"%s\". " "Probably duplication", kentry_name(entry)); kentry_free(entry); goto err; } } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); return res; } static bool_t process_ptype(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kscheme_t *scheme = (kscheme_t *)parent; // Mandatory PTYPE name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": PTYPE without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = "true"; ientry.mode = "sequence"; ientry.min = "1"; ientry.max = "1"; ientry.ptype = NULL; ientry.ref = NULL; ientry.value = kxml_node_attr(element, "value"); ientry.restore = "false"; ientry.order = "true"; // Parent must be a KLISH tag if (parent_tag != KTAG_KLISH) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain PTYPE tag", kxml_tag_name(parent_tag)); goto err; } if (!scheme) { faux_error_sprintf(error, TAG": Broken parent object for PTYPE \"%s\"", ientry.name); goto err; } // Create and add object entry = ientry_load(&ientry, error); if (!entry) goto err; if (!kscheme_add_entrys(scheme, entry)) { faux_error_sprintf(error, TAG": Can't add PTYPE \"%s\". " "Probably duplication", kentry_name(entry)); kentry_free(entry); goto err; } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); kxml_node_attr_free(ientry.value); return res; } static bool_t process_plugin(const kxml_node_t *element, void *parent, faux_error_t *error) { iplugin_t iplugin = {}; kplugin_t *plugin = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); if (parent_tag != KTAG_KLISH) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain PLUGIN tag", kxml_tag_name(parent_tag)); return BOOL_FALSE; } iplugin.name = kxml_node_attr(element, "name"); iplugin.id = kxml_node_attr(element, "id"); iplugin.file = kxml_node_attr(element, "file"); iplugin.conf = kxml_node_content(element); plugin = iplugin_load(&iplugin, error); if (!plugin) goto err; if (!kscheme_add_plugins((kscheme_t *)parent, plugin)) { faux_error_sprintf(error, TAG": Can't add PLUGIN \"%s\". " "Probably duplication", kplugin_name(plugin)); kplugin_free(plugin); goto err; } if (!process_children(element, plugin, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(iplugin.name); kxml_node_attr_free(iplugin.id); kxml_node_attr_free(iplugin.file); kxml_node_content_free(iplugin.conf); return res; } static bool_t process_param(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kentry_t *parent_entry = (kentry_t *)parent; kentry_t *entry_add_to = parent_entry; // Mandatory PARAM name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": PARAM without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = kxml_node_attr(element, "container"); ientry.mode = kxml_node_attr(element, "mode"); ientry.min = kxml_node_attr(element, "min"); ientry.max = kxml_node_attr(element, "max"); ientry.ptype = kxml_node_attr(element, "ptype"); ientry.ref = kxml_node_attr(element, "ref"); ientry.value = kxml_node_attr(element, "value"); ientry.restore = "false"; ientry.order = kxml_node_attr(element, "order"); entry = ientry_load(&ientry, error); if (!entry) goto err; if ((KTAG_COMMAND != parent_tag) && (KTAG_PARAM != parent_tag) && (KTAG_ENTRY != parent_tag)) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain PARAM tag", kxml_tag_name(parent_tag)); kentry_free(entry); goto err; } // Add newly created entry to special container in 'sequence' mode if // parent entry can has 'switch' mode. if (kentry_mode(parent_entry) == KENTRY_MODE_SWITCH) { const char *seq_entry_name = "__sequence"; kentry_t *seq_entry = kentry_find_entry(parent_entry, seq_entry_name); if (!seq_entry) { seq_entry = kentry_new(seq_entry_name); assert(seq_entry); kentry_set_container(seq_entry, BOOL_TRUE); kentry_set_mode(seq_entry, KENTRY_MODE_SEQUENCE); kentry_add_entrys(parent_entry, seq_entry); } entry_add_to = seq_entry; } if (!kentry_add_entrys(entry_add_to, entry)) { faux_error_sprintf(error, TAG": Can't add PARAM \"%s\" to ENTRY \"%s\". " "Probably duplication", kentry_name(entry_add_to), kentry_name(entry_add_to)); kentry_free(entry); goto err; } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); kxml_node_attr_free(ientry.container); kxml_node_attr_free(ientry.mode); kxml_node_attr_free(ientry.min); kxml_node_attr_free(ientry.max); kxml_node_attr_free(ientry.ptype); kxml_node_attr_free(ientry.ref); kxml_node_attr_free(ientry.value); kxml_node_attr_free(ientry.order); return res; } static bool_t process_command(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kentry_t *parent_entry = (kentry_t *)parent; // Mandatory COMMAND name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": COMMAND without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = "false"; ientry.mode = "switch"; ientry.min = "1"; ientry.max = "1"; ientry.ptype = kxml_node_attr(element, "ptype"); ientry.ref = kxml_node_attr(element, "ref"); ientry.value = kxml_node_attr(element, "value"); ientry.restore = kxml_node_attr(element, "restore"); ientry.order = kxml_node_attr(element, "order"); entry = ientry_load(&ientry, error); if (!entry) goto err; if ((KTAG_COMMAND != parent_tag) && (KTAG_VIEW != parent_tag) && (KTAG_ENTRY != parent_tag)) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain COMMAND tag", kxml_tag_name(parent_tag)); kentry_free(entry); goto err; } if (!kentry_add_entrys(parent_entry, entry)) { faux_error_sprintf(error, TAG": Can't add PARAM \"%s\" to ENTRY \"%s\". " "Probably duplication", kentry_name(entry), kentry_name(parent_entry)); kentry_free(entry); goto err; } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); kxml_node_attr_free(ientry.ptype); kxml_node_attr_free(ientry.ref); kxml_node_attr_free(ientry.value); kxml_node_attr_free(ientry.restore); kxml_node_attr_free(ientry.order); return res; } static bool_t process_action(const kxml_node_t *element, void *parent, faux_error_t *error) { iaction_t iaction = {}; kaction_t *action = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kentry_t *parent_entry = (kentry_t *)parent; iaction.sym = kxml_node_attr(element, "sym"); iaction.lock = kxml_node_attr(element, "lock"); iaction.interrupt = kxml_node_attr(element, "interrupt"); iaction.interactive = kxml_node_attr(element, "interactive"); iaction.exec_on = kxml_node_attr(element, "exec_on"); iaction.update_retcode = kxml_node_attr(element, "update_retcode"); iaction.script = kxml_node_content(element); action = iaction_load(&iaction, error); if (!action) goto err; if ((parent_tag != KTAG_ENTRY) && (parent_tag != KTAG_COMMAND) && (parent_tag != KTAG_PTYPE)) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain ACTION tag", kxml_tag_name(parent_tag)); kaction_free(action); goto err; } if (!kentry_add_actions(parent_entry, action)) { faux_error_sprintf(error, TAG": Can't add ACTION #%d to ENTRY \"%s\". " "Probably duplication", kentry_actions_len(parent_entry) + 1, kentry_name(parent_entry)); kaction_free(action); goto err; } if (!process_children(element, action, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(iaction.sym); kxml_node_attr_free(iaction.lock); kxml_node_attr_free(iaction.interrupt); kxml_node_attr_free(iaction.interactive); kxml_node_attr_free(iaction.exec_on); kxml_node_attr_free(iaction.update_retcode); kxml_node_content_free(iaction.script); return res; } static bool_t process_nspace(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kentry_t *parent_entry = (kentry_t *)parent; // Mandatory NSPACE name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": NSPACE without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = kxml_node_attr(element, "container"); ientry.mode = kxml_node_attr(element, "mode");; ientry.min = kxml_node_attr(element, "min"); ientry.max = kxml_node_attr(element, "max"); ientry.ptype = kxml_node_attr(element, "ptype"); ientry.ref = kxml_node_attr(element, "ref"); ientry.value = kxml_node_attr(element, "value"); ientry.restore = kxml_node_attr(element, "restore"); ientry.order = kxml_node_attr(element, "order"); entry = ientry_load(&ientry, error); if (!entry) goto err; if ((KTAG_COMMAND != parent_tag) && (KTAG_VIEW != parent_tag) && (KTAG_PARAM != parent_tag) && (KTAG_ENTRY != parent_tag)) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain NSPACE tag", kxml_tag_name(parent_tag)); kentry_free(entry); goto err; } if (!kentry_add_entrys(parent_entry, entry)) { faux_error_sprintf(error, TAG": Can't add NSPACE \"%s\" to ENTRY \"%s\". " "Probably duplication", kentry_name(entry), kentry_name(parent_entry)); kentry_free(entry); goto err; } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); kxml_node_attr_free(ientry.container); kxml_node_attr_free(ientry.mode); kxml_node_attr_free(ientry.min); kxml_node_attr_free(ientry.max); kxml_node_attr_free(ientry.ptype); kxml_node_attr_free(ientry.ref); kxml_node_attr_free(ientry.value); kxml_node_attr_free(ientry.restore); kxml_node_attr_free(ientry.order); return res; } static bool_t process_entry(const kxml_node_t *element, void *parent, faux_error_t *error) { ientry_t ientry = {}; kentry_t *entry = NULL; bool_t res = BOOL_FALSE; ktags_e parent_tag = kxml_node_tag(kxml_node_parent(element)); kentry_t *parent_entry = (kentry_t *)parent; // Mandatory entry name ientry.name = kxml_node_attr(element, "name"); if (!ientry.name) { faux_error_sprintf(error, TAG": entry without name"); return BOOL_FALSE; } ientry.help = kxml_node_attr(element, "help"); ientry.container = kxml_node_attr(element, "container"); ientry.mode = kxml_node_attr(element, "mode"); ientry.min = kxml_node_attr(element, "min"); ientry.max = kxml_node_attr(element, "max"); ientry.ptype = kxml_node_attr(element, "ptype"); ientry.ref = kxml_node_attr(element, "ref"); ientry.value = kxml_node_attr(element, "value"); ientry.restore = kxml_node_attr(element, "restore"); ientry.order = kxml_node_attr(element, "order"); // Parent must be a KLISH or ENTRY tag if ((parent_tag != KTAG_KLISH) && (parent_tag != KTAG_ENTRY)) { faux_error_sprintf(error, TAG": Tag \"%s\" can't contain ENTRY tag", kxml_tag_name(parent_tag)); goto err; } if (!parent_entry) { faux_error_sprintf(error, TAG": Broken parent object for ENTRY \"%s\"", ientry.name); goto err; } if (KTAG_KLISH == parent_tag) { // High level ENTRY // Does such ENTRY already exist entry = kscheme_find_entry((kscheme_t *)parent, ientry.name); if (entry) { if (!ientry_parse(&ientry, entry, error)) goto err; } else { // New entry object entry = ientry_load(&ientry, error); if (!entry) goto err; if (!kscheme_add_entrys((kscheme_t *)parent, entry)) { faux_error_sprintf(error, TAG": Can't add entry \"%s\". " "Probably duplication", kentry_name(entry)); kentry_free(entry); goto err; } } } else { // ENTRY within ENTRY // Does such ENTRY already exist entry = kentry_find_entry(parent_entry, ientry.name); if (entry) { if (!ientry_parse(&ientry, entry, error)) goto err; } else { // New entry object entry = ientry_load(&ientry, error); if (!entry) goto err; kentry_set_parent(entry, parent_entry); if (!kentry_add_entrys(parent_entry, entry)) { faux_error_sprintf(error, TAG": Can't add entry \"%s\". " "Probably duplication", kentry_name(entry)); kentry_free(entry); goto err; } } } if (!process_children(element, entry, error)) goto err; res = BOOL_TRUE; err: kxml_node_attr_free(ientry.name); kxml_node_attr_free(ientry.help); kxml_node_attr_free(ientry.container); kxml_node_attr_free(ientry.mode); kxml_node_attr_free(ientry.min); kxml_node_attr_free(ientry.max); kxml_node_attr_free(ientry.ptype); kxml_node_attr_free(ientry.ref); kxml_node_attr_free(ientry.value); kxml_node_attr_free(ientry.restore); kxml_node_attr_free(ientry.order); return res; }