/* LibRCC - module handling XML configuration Copyright (C) 2005-2018 Suren A. Chilingaryan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 or later as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #ifdef HAVE_STRINGS_H # include #endif /* HAVE_STRINGS_H */ #include "../config.h" #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ #ifdef HAVE_FCNTL_H # include #endif /* HAVE_FCNTL_H */ #ifdef HAVE_SYS_TYPES_H # include #endif /* HAVE_SYS_TYPES_H */ #ifdef HAVE_SYS_STAT_H # include #endif /* HAVE_SYS_STAT_H */ #ifdef HAVE_SYS_FILE_H # include #endif /* HAVE_SYS_FILE_H */ #include #include #include #include "internal.h" #include "rccconfig.h" #include "plugin.h" #include "rcchome.h" #define MAX_HOME_CHARS 96 #define XPATH_LANGUAGE "//Language[@name]" static xmlDocPtr xmlctx = NULL; rcc_config rccGetConfiguration() { return (rcc_config)xmlctx; } static const char *rccXmlGetText(xmlNodePtr node) { if ((node)&&(node->children)&&(node->children->type == XML_TEXT_NODE)&&(node->children->content)) return (const char*)node->children->content; return NULL; } int rccXmlInit(int LoadConfiguration) { FILE *f; char config[MAX_HOME_CHARS + 32]; xmlXPathContextPtr xpathctx = NULL; xmlXPathObjectPtr obj = NULL; xmlNodeSetPtr node_set; unsigned long i, nnodes; xmlNodePtr enode, cnode, pnode, node; xmlAttrPtr attr; const char *lang, *engine_name; unsigned int pos, lpos, epos, cpos; const char *alias, *parent; unsigned int j, apos, rpos; rcc_engine *engine; xmlInitParser(); xmlKeepBlanksDefault(0); if (LoadConfiguration) { if (strlen(rcc_home_dir)>MAX_HOME_CHARS) config[0] = 0; else { sprintf(config, "%s/.rcc/rcc.xml", rcc_home_dir); f = fopen(config, "r"); if (f) fclose(f); else config[0] = 0; } if (!config[0]) { strcpy(config, "/etc/rcc.xml"); f = fopen(config, "r"); if (f) fclose(f); else config[0] = 0; } } else config[0] = 0; for (apos=0;rcc_default_aliases[apos].alias;apos++); // Load Extra Languages if (config[0]) { xmlctx = xmlReadFile(config, NULL, 0); if (!xmlctx) goto clear; xpathctx = xmlXPathNewContext(xmlctx); if (!xpathctx) goto clear; obj = xmlXPathEvalExpression((xmlChar*)XPATH_LANGUAGE, xpathctx); if (!obj) goto clear; node_set = obj->nodesetval; if (!node_set) goto clear; for (lpos = 0; rcc_default_languages[lpos].sn; lpos++); nnodes = node_set->nodeNr; for (i=0;inodeTab[i]; attr = xmlHasProp(pnode, (xmlChar*)"name"); lang = (const char*)attr->children->content; if ((!lang)||(!lang[0])) continue; pos = rccDefaultGetLanguageByName(lang); if (!pos) continue; if (pos == (rcc_language_id)-1) { for (rpos=0;rcc_default_relations[rpos].lang;rpos++); if (rpos < RCC_MAX_RELATIONS) { rcc_default_relations[rpos].parent = rcc_english_language_sn; rcc_default_relations[rpos++].lang = lang; rcc_default_relations[rpos].parent = NULL; rcc_default_relations[rpos].lang = NULL; } pos = lpos; } else if (pos == RCC_MAX_LANGUAGES) continue; for (epos = 1, cpos = 1,node=pnode->children;node;node=node->next) { if (node->type != XML_ELEMENT_NODE) continue; if (!xmlStrcmp(node->name, (xmlChar*)"Charsets")) { for (cnode=node->children;cnode;cnode=cnode->next) { if (cnode->type != XML_ELEMENT_NODE) continue; if ((!xmlStrcmp(cnode->name, (xmlChar*)"Charset"))&&(rccXmlGetText(cnode))&&(cposname, (xmlChar*)"Engines")) { for (enode=node->children;enode;enode=enode->next) { if (enode->type != XML_ELEMENT_NODE) continue; if ((!xmlStrcmp(enode->name, (xmlChar*)"Engine"))&&(eposname, (xmlChar*)"Aliases")) { for (enode=node->children;enode;enode=enode->next) { if (enode->type != XML_ELEMENT_NODE) continue; if ((!xmlStrcmp(enode->name, (xmlChar*)"Alias"))&&(aposname, (xmlChar*)"Relations")) { rpos = rccDefaultDropLanguageRelations(lang); for (enode=node->children;enode;enode=enode->next) { if (enode->type != XML_ELEMENT_NODE) continue; if ((!xmlStrcmp(enode->name, (xmlChar*)"Parrent"))&&(rpos 1)||(epos > 1)) { rcc_default_languages[pos].sn = lang; rcc_default_languages[pos].charsets[0] = rcc_default_charset; if (cpos > 1) rcc_default_languages[pos].charsets[cpos] = NULL; else { rcc_default_languages[pos].charsets[1] = rcc_utf8_charset; rcc_default_languages[pos].charsets[2] = NULL; } rcc_default_languages[pos].engines[0] = &rcc_default_engine; rcc_default_languages[pos].engines[epos] = NULL; if (pos == lpos) rcc_default_languages[++lpos].sn = NULL; } } clear: if (xmlctx) { if (xpathctx) { xmlXPathFreeContext(xpathctx); if (obj) { xmlXPathFreeObject(obj); } } } } return 0; } void rccXmlFree() { if (xmlctx) { xmlFreeDoc(xmlctx); xmlctx = NULL; } xmlCleanupParser(); } static xmlNodePtr rccNodeFind(xmlXPathContextPtr xpathctx, const char *request, ...) { xmlXPathObjectPtr obj; xmlNodeSetPtr node_set; xmlNodePtr res = NULL; unsigned int i, args = 0; unsigned int size = 64; va_list ap; char *req; if (!xpathctx) return NULL; for (req = strstr(request, "%s"); req; req = strstr(req + 1, "%s")) args++; if (args) { va_start(ap, request); for (i=0;inodesetval; if ((node_set)&&(node_set->nodeNr > 0)) { res = node_set->nodeTab[0]; } xmlXPathFreeObject(obj); } if (args) free(req); return res; } #define XPATH_SELECTED "/Config" #define XPATH_SELECTED_LANGUAGE "/Config/Language" #define XPATH_SELECTED_OPTIONS "/Config/Options" #define XPATH_SELECTED_OPTION "/Config/Options/Option[@name=\"%s\"]" #define XPATH_SELECTED_LANGS "/Config/Languages" #define XPATH_SELECTED_LANG "/Config/Languages/Language[@name=\"%s\"]" #define XPATH_SELECTED_ENGINE "/Config/Languages/Language[@name=\"%s\"]/Engine" #define XPATH_SELECTED_CLASSES "/Config/Languages/Language[@name=\"%s\"]/Classes" #define XPATH_SELECTED_CLASS "/Config/Languages/Language[@name=\"%s\"]/Classes/Class[@name=\"%s\"]" int rccSave(rcc_context ctx, const char *name) { int fd; char *config; struct stat st; unsigned int i, j, size; rcc_language_ptr *languages; rcc_language_ptr language; rcc_language_config cfg; rcc_class_ptr *classes; rcc_class_ptr cl; xmlXPathContextPtr xpathctx = NULL; xmlDocPtr doc = NULL; xmlNodePtr pnode, lnode, onode, llnode, cnode, enode, node; unsigned char oflag = 0, llflag = 0, cflag; rcc_option_description *odesc; rcc_option_value ovalue; const char *oname, *ovname; char value[16]; int memsize; xmlChar *mem; if (!ctx) { if (rcc_default_ctx) ctx = rcc_default_ctx; else return -1; } if ((!name)||(!strcmp(name, "rcc"))||(strlen(rcc_home_dir)<3)) name = "default"; size = strlen(rcc_home_dir) + strlen(name) + 32; config = (char*)malloc(size*sizeof(char)); if (!config) return -1; sprintf(config,"%s/.rcc/",rcc_home_dir); #ifdef _WIN32 mkdir(config); #else mkdir(config, 00755); #endif sprintf(config,"%s/.rcc/%s.xml",rcc_home_dir,name); fd = open(config, O_CREAT|O_RDWR,00644); if (fd == -1) goto clear; #if defined(HAVE_FLOCK) flock(fd, LOCK_EX); #elif defined(HAVE_LOCKF) lockf(fd, F_LOCK, 1); #endif if ((!fstat(fd, &st))&&(st.st_size)) { doc = xmlReadFd(fd, config, NULL, 0); } if (!doc) { doc = xmlNewDoc((xmlChar*)"1.0"); if (!doc) goto clear; pnode = NULL; } else { xpathctx = xmlXPathNewContext(doc); pnode = rccNodeFind(xpathctx, XPATH_SELECTED); } if (pnode) { lnode = rccNodeFind(xpathctx, XPATH_SELECTED_LANGUAGE); onode = rccNodeFind(xpathctx, XPATH_SELECTED_OPTIONS); llnode = rccNodeFind(xpathctx, XPATH_SELECTED_LANGS); } else { pnode = xmlNewChild((xmlNodePtr)doc, NULL, (xmlChar*)"Config", NULL); lnode = NULL; onode = NULL; llnode = NULL; } if (lnode) xmlNodeSetContent(lnode, (xmlChar*)rccGetSelectedLanguageName(ctx)); else lnode = xmlNewChild(pnode,NULL, (xmlChar*)"Language", (xmlChar*)rccGetSelectedLanguageName(ctx)); if (onode) oflag = 1; else onode = xmlNewChild(pnode, NULL, (xmlChar*)"Options", NULL); for (i=0;ilanguages; classes = ctx->classes; for (i=1;languages[i];i++) { language = languages[i]; cfg = rccCheckConfig(ctx, (rcc_language_id)i); if ((!cfg)||(!cfg->configured)) continue; if (llflag) lnode = rccNodeFind(xpathctx, XPATH_SELECTED_LANG, language->sn); else lnode = NULL; if (lnode) { enode = rccNodeFind(xpathctx, XPATH_SELECTED_ENGINE, language->sn); cnode = rccNodeFind(xpathctx, XPATH_SELECTED_CLASSES, language->sn); } else { lnode = xmlNewChild(llnode, NULL, (xmlChar*)"Language", NULL); xmlSetProp(lnode, (xmlChar*)"name", (xmlChar*)language->sn); enode = NULL; cnode = NULL; } if (enode) xmlNodeSetContent(enode, (xmlChar*)rccConfigGetSelectedEngineName(cfg)); else xmlNewChild(lnode, NULL, (xmlChar*)"Engine", (xmlChar*)rccConfigGetSelectedEngineName(cfg)); if (cnode) cflag = 1; else { cnode = xmlNewChild(lnode, NULL, (xmlChar*)"Classes", NULL); cflag = 0; } for (j=0;classes[j];j++) { cl = classes[j]; if (cl->flags&RCC_CLASS_FLAG_SKIP_SAVELOAD) continue; if (cflag) node = rccNodeFind(xpathctx, XPATH_SELECTED_CLASS, language->sn, cl->name); else node = NULL; if (node) xmlNodeSetContent(node, (xmlChar*)rccConfigGetSelectedCharsetName(cfg, (rcc_class_id)j)); else { node = xmlNewChild(cnode, NULL, (xmlChar*)"Class", (xmlChar*)rccConfigGetSelectedCharsetName(cfg, (rcc_class_id)j)); xmlSetProp(node, (xmlChar*)"name", (xmlChar*)cl->name); } } } xmlDocDumpFormatMemory(doc,&mem,&memsize,1); if (ftruncate(fd, 0) < 0) goto clear; if (lseek(fd, SEEK_SET, 0)) goto clear; if (mem) { ssize_t ret = write(fd, mem, memsize); // Retry once on signals if ((ret < 0)&&(errno = EINTR)) ret = write(fd, mem, memsize); free(mem); } clear: if (config) { if (fd != -1) { if (doc) { if (xpathctx) { xmlXPathFreeContext(xpathctx); } xmlFreeDoc(doc); } #ifdef HAVE_FSYNC fsync(fd); #endif /* HAVE_FSYNC */ #if defined(HAVE_FLOCK) flock(fd, LOCK_UN); #elif defined(HAVE_LOCKF) lockf(fd, F_ULOCK, 1); #endif close(fd); } free(config); } return 0; } int rccLoad(rcc_context ctx, const char *name) { int err; int fd, sysfd; char *config; struct stat st; unsigned int i, j, size; const char *tmp; rcc_option_description *odesc; rcc_option_value ovalue; const char *oname; rcc_language_config cfg; rcc_language_ptr *languages; rcc_language_ptr language; rcc_class_ptr *classes; rcc_class_ptr cl; xmlXPathContextPtr xpathctx = NULL, sysxpathctx = NULL, curxpathctx; xmlDocPtr doc = NULL, sysdoc = NULL; xmlNodePtr node, lnode; if (!ctx) { if (rcc_default_ctx) ctx = rcc_default_ctx; else return -1; } if ((!name)||(!strcmp(name, "rcc"))||(strlen(rcc_home_dir)<3)) name = "default"; size = strlen(rcc_home_dir) + strlen(name) + 32; config = (char*)malloc(size*sizeof(char)); if (!config) return -1; sprintf(config,"%s/.rcc/%s.xml",rcc_home_dir,name); fd = open(config, O_RDONLY); sprintf(config, "/etc/rcc/%s.xml",name); sysfd = open(config, O_RDONLY); free(config); if (fd != -1) { #if defined(HAVE_FLOCK) flock(fd, LOCK_EX); #elif defined(HAVE_LOCKF) lockf(fd, F_LOCK, 1); #endif if ((!fstat(fd, &st))&&(st.st_size)) { doc = xmlReadFd(fd, name, NULL, 0); } #if defined(HAVE_FLOCK) flock(fd, LOCK_UN); #elif defined(HAVE_LOCKF) lockf(fd, F_ULOCK, 1); #endif close(fd); if (doc) { xpathctx = xmlXPathNewContext(doc); if (!xpathctx) { xmlFreeDoc(doc); doc = NULL; } } } if (sysfd != -1) { #if defined(HAVE_FLOCK) flock(sysfd, LOCK_EX); #elif defined(HAVE_LOCKF) lockf(sysfd, F_LOCK, 1); #endif if ((!fstat(sysfd, &st))&&(st.st_size)) { sysdoc = xmlReadFd(sysfd, name, NULL, 0); } #if defined(HAVE_FLOCK) flock(sysfd, LOCK_UN); #elif defined(HAVE_LOCKF) lockf(sysfd, F_ULOCK, 1); #endif close(sysfd); if (sysdoc) { sysxpathctx = xmlXPathNewContext(sysdoc); if (!sysxpathctx) { xmlFreeDoc(sysdoc); sysdoc = NULL; } } } if ((!doc)&&(!sysdoc)) goto clear; node = rccNodeFind(xpathctx, XPATH_SELECTED_LANGUAGE); if (!node) node = rccNodeFind(sysxpathctx, XPATH_SELECTED_LANGUAGE); if (node) { tmp = rccXmlGetText(node); if (tmp) err = rccSetLanguageByName(ctx, tmp); else err = -1; } else err = -1; if (err) rccSetLanguage(ctx, 0); for (i=0;ilanguages; classes = ctx->classes; for (i=1;languages[i];i++) { language = languages[i]; lnode = rccNodeFind(xpathctx, XPATH_SELECTED_LANG, language->sn); if (lnode) curxpathctx = xpathctx; else { lnode = rccNodeFind(sysxpathctx, XPATH_SELECTED_LANG, language->sn); if (lnode) curxpathctx = sysxpathctx; else continue; } cfg = rccGetConfig(ctx, (rcc_language_id)i); if (!cfg) continue; node = rccNodeFind(curxpathctx, XPATH_SELECTED_ENGINE, language->sn); if (node) { tmp = rccXmlGetText(node); if (tmp) err = rccConfigSetEngineByName(cfg, tmp); else err = -1; } else err = -1; if (err) rccConfigSetEngineByName(cfg, NULL); for (j=0;classes[j];j++) { cl = classes[j]; if (cl->flags&RCC_CLASS_FLAG_SKIP_SAVELOAD) continue; node = rccNodeFind(curxpathctx, XPATH_SELECTED_CLASS, language->sn, cl->name); if (node) { tmp = rccXmlGetText(node); if (tmp) err = rccConfigSetCharsetByName(cfg, (rcc_class_id)j, tmp); else err = -1; } else err = -1; if (err) rccConfigSetCharset(cfg, (rcc_class_id)j, 0); } } clear: if (sysdoc) { if (sysxpathctx) { xmlXPathFreeContext(sysxpathctx); } xmlFreeDoc(sysdoc); } if (doc) { if (xpathctx) { xmlXPathFreeContext(xpathctx); } xmlFreeDoc(doc); } if ((!ctx->current_language)&&(rccGetOption(ctx, RCC_OPTION_CONFIGURED_LANGUAGES_ONLY))) { ctx->current_config = rccGetCurrentConfig(ctx); ctx->configure = 1; } return 0; }