/** * conf.c: parser for a subset of the Python encoded Xen configuration files * * Copyright (C) 2006, 2007, 2008, 2009 Red Hat, Inc. * * See COPYING.LIB for the License of this software * * Daniel Veillard */ #include #include #include #include #include #include #include #include "virterror_internal.h" #include "buf.h" #include "conf.h" #include "util.h" #include "c-ctype.h" #include "memory.h" #define VIR_FROM_THIS VIR_FROM_CONF /************************************************************************ * * * Structures and macros used by the mini parser * * * ************************************************************************/ typedef struct _virConfParserCtxt virConfParserCtxt; typedef virConfParserCtxt *virConfParserCtxtPtr; struct _virConfParserCtxt { const char* filename; const char* base; const char* cur; const char *end; int line; virConfPtr conf; }; #define CUR (*ctxt->cur) #define NEXT if (ctxt->cur < ctxt->end) ctxt->cur++; #define IS_EOL(c) (((c) == '\n') || ((c) == '\r')) #define SKIP_BLANKS_AND_EOL \ do { while ((ctxt->cur < ctxt->end) && (c_isblank(CUR) || IS_EOL(CUR))) { \ if (CUR == '\n') ctxt->line++; \ ctxt->cur++;}} while (0) #define SKIP_BLANKS \ do { while ((ctxt->cur < ctxt->end) && (c_isblank(CUR))) \ ctxt->cur++; } while (0) /************************************************************************ * * * Structures used by configuration data * * * ************************************************************************/ typedef struct _virConfEntry virConfEntry; typedef virConfEntry *virConfEntryPtr; struct _virConfEntry { virConfEntryPtr next; char* name; char* comment; virConfValuePtr value; }; struct _virConf { const char* filename; unsigned int flags; virConfEntryPtr entries; }; /** * virConfError: * @ctxt: the parser context if available or NULL * @error: the error number * @info: extra information string * * Handle an error at the xend daemon interface */ static void virConfError(virConfParserCtxtPtr ctxt, virErrorNumber error, const char *info) { const char *format; if (error == VIR_ERR_OK) return; /* Construct the string 'filename:line: info' if we have that. */ if (ctxt && ctxt->filename) { virRaiseError(NULL, NULL, NULL, VIR_FROM_CONF, error, VIR_ERR_ERROR, info, ctxt->filename, NULL, ctxt->line, 0, "%s:%d: %s", ctxt->filename, ctxt->line, info); } else { format = virErrorMsg(error, info); virRaiseError(NULL, NULL, NULL, VIR_FROM_CONF, error, VIR_ERR_ERROR, info, NULL, NULL, ctxt ? ctxt->line : 0, 0, format, info); } } /************************************************************************ * * * Structures allocations and deallocations * * * ************************************************************************/ /** * virConfFreeList: * @list: the list to free * * Free a list */ static void virConfFreeList(virConfValuePtr list) { virConfValuePtr next; while (list != NULL) { next = list->next; list->next = NULL; virConfFreeValue(list); list = next; } } /** * virConfFreeValue: * @val: the value to free * * Free a value */ void virConfFreeValue(virConfValuePtr val) { if (val == NULL) return; if (val->type == VIR_CONF_STRING && val->str != NULL) VIR_FREE(val->str); if (val->type == VIR_CONF_LIST && val->list != NULL) virConfFreeList(val->list); VIR_FREE(val); } virConfPtr virConfNew(void) { virConfPtr ret; if (VIR_ALLOC(ret) < 0) { virReportOOMError(NULL); return(NULL); } ret->filename = NULL; ret->flags = 0; return(ret); } /** * virConfCreate: * @filename: the name to report errors * @flags: combination of virConfFlag(s) * * Create a configuration internal structure * * Returns a pointer or NULL in case of error. */ static virConfPtr virConfCreate(const char *filename, unsigned int flags) { virConfPtr ret = virConfNew(); if (ret) { ret->filename = filename; ret->flags = flags; } return(ret); } /** * virConfAddEntry: * @conf: the conf structure * @name: name of the entry or NULL for comment * @value: the value if any * @comm: extra comment for that entry if any * * add one entry to the conf, the parameters are included in the conf * if successful and freed on virConfFree() * * Returns a pointer to the entry or NULL in case of failure */ static virConfEntryPtr virConfAddEntry(virConfPtr conf, char *name, virConfValuePtr value, char *comm) { virConfEntryPtr ret, prev; if (conf == NULL) return(NULL); if ((comm == NULL) && (name == NULL)) return(NULL); if (VIR_ALLOC(ret) < 0) { virReportOOMError(NULL); return(NULL); } ret->name = name; ret->value = value; ret->comment = comm; if (conf->entries == NULL) { conf->entries = ret; } else { prev = conf->entries; while (prev->next != NULL) prev = prev->next; prev->next = ret; } return(ret); } /************************************************************************ * * * Serialization * * * ************************************************************************/ /** * virConfSaveValue: * @buf: output buffer * @val: a value * * Serialize the value to the buffer * * Returns 0 in case of success, -1 in case of error. */ static int virConfSaveValue(virBufferPtr buf, virConfValuePtr val) { if (val == NULL) return(-1); switch (val->type) { case VIR_CONF_NONE: return(-1); case VIR_CONF_LONG: virBufferVSprintf(buf, "%ld", val->l); break; case VIR_CONF_STRING: if (strchr(val->str, '\n') != NULL) { virBufferVSprintf(buf, "\"\"\"%s\"\"\"", val->str); } else if (strchr(val->str, '"') == NULL) { virBufferVSprintf(buf, "\"%s\"", val->str); } else if (strchr(val->str, '\'') == NULL) { virBufferVSprintf(buf, "'%s'", val->str); } else { virBufferVSprintf(buf, "\"\"\"%s\"\"\"", val->str); } break; case VIR_CONF_LIST: { virConfValuePtr cur; cur = val->list; virBufferAddLit(buf, "[ "); if (cur != NULL) { virConfSaveValue(buf, cur); cur = cur->next; while (cur != NULL) { virBufferAddLit(buf, ", "); virConfSaveValue(buf, cur); cur = cur->next; } } virBufferAddLit(buf, " ]"); break; } default: return(-1); } return(0); } /** * virConfSaveEntry: * @buf: output buffer * @cur: a conf entry * * Serialize the entry to the buffer * * Returns 0 in case of success, -1 in case of error. */ static int virConfSaveEntry(virBufferPtr buf, virConfEntryPtr cur) { if (cur->name != NULL) { virBufferAdd(buf, cur->name, -1); virBufferAddLit(buf, " = "); virConfSaveValue(buf, cur->value); if (cur->comment != NULL) { virBufferAddLit(buf, " #"); virBufferAdd(buf, cur->comment, -1); } } else if (cur->comment != NULL) { virBufferAddLit(buf, "#"); virBufferAdd(buf, cur->comment, -1); } virBufferAddLit(buf, "\n"); return(0); } /************************************************************************ * * * The parser core * * * ************************************************************************/ /** * virConfParseLong: * @ctxt: the parsing context * @val: the result * * Parse one long int value * * Returns 0 in case of success and -1 in case of error */ static int virConfParseLong(virConfParserCtxtPtr ctxt, long *val) { long l = 0; int neg = 0; if (CUR == '-') { neg = 1; NEXT; } else if (CUR == '+') { NEXT; } if ((ctxt->cur >= ctxt->end) || (!c_isdigit(CUR))) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("unterminated number")); return(-1); } while ((ctxt->cur < ctxt->end) && (c_isdigit(CUR))) { l = l * 10 + (CUR - '0'); NEXT; } if (neg) l = -l; *val = l; return(0); } /** * virConfParseString: * @ctxt: the parsing context * * Parse one string * * Returns a pointer to the string or NULL in case of error */ static char * virConfParseString(virConfParserCtxtPtr ctxt) { const char *base; char *ret = NULL; if (CUR == '\'') { NEXT; base = ctxt->cur; while ((ctxt->cur < ctxt->end) && (CUR != '\'') && (!IS_EOL(CUR))) NEXT; if (CUR != '\'') { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("unterminated string")); return(NULL); } ret = strndup(base, ctxt->cur - base); if (ret == NULL) { virReportOOMError(NULL); return NULL; } NEXT; } else if ((ctxt->cur + 6 < ctxt->end) && (ctxt->cur[0] == '"') && (ctxt->cur[1] == '"') && (ctxt->cur[2] == '"')) { ctxt->cur += 3; base = ctxt->cur; while ((ctxt->cur + 2 < ctxt->end) && (ctxt->cur[0] == '"') && (ctxt->cur[1] == '"') && (ctxt->cur[2] == '"')) { if (CUR == '\n') ctxt->line++; NEXT; } if ((ctxt->cur[0] != '"') || (ctxt->cur[1] != '"') || (ctxt->cur[2] != '"')) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("unterminated string")); return(NULL); } ret = strndup(base, ctxt->cur - base); if (ret == NULL) { virReportOOMError(NULL); return NULL; } ctxt->cur += 3; } else if (CUR == '"') { NEXT; base = ctxt->cur; while ((ctxt->cur < ctxt->end) && (CUR != '"') && (!IS_EOL(CUR))) NEXT; if (CUR != '"') { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("unterminated string")); return(NULL); } ret = strndup(base, ctxt->cur - base); if (ret == NULL) { virReportOOMError(NULL); return NULL; } NEXT; } return(ret); } /** * virConfParseValue: * @ctxt: the parsing context * * Parse one value * * Returns a pointer to the value or NULL in case of error */ static virConfValuePtr virConfParseValue(virConfParserCtxtPtr ctxt) { virConfValuePtr ret, lst = NULL, tmp, prev; virConfType type = VIR_CONF_NONE; char *str = NULL; long l = 0; SKIP_BLANKS; if (ctxt->cur >= ctxt->end) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting a value")); return(NULL); } if ((CUR == '"') || (CUR == '\'')) { type = VIR_CONF_STRING; str = virConfParseString(ctxt); if (str == NULL) return(NULL); } else if (CUR == '[') { if (ctxt->conf->flags & VIR_CONF_FLAG_VMX_FORMAT) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("lists not allowed in VMX format")); return(NULL); } type = VIR_CONF_LIST; NEXT; SKIP_BLANKS_AND_EOL; if ((ctxt->cur < ctxt->end) && (CUR != ']')) { if ((lst = virConfParseValue(ctxt)) == NULL) return(NULL); SKIP_BLANKS_AND_EOL; } while ((ctxt->cur < ctxt->end) && (CUR != ']')) { if (CUR != ',') { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting a separator in list")); virConfFreeList(lst); return(NULL); } NEXT; SKIP_BLANKS_AND_EOL; if (CUR == ']') { break; } tmp = virConfParseValue(ctxt); if (tmp == NULL) { virConfFreeList(lst); return(NULL); } prev = lst; while (prev->next != NULL) prev = prev->next; prev->next = tmp; SKIP_BLANKS_AND_EOL; } if (CUR == ']') { NEXT; } else { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("list is not closed with ]")); virConfFreeList(lst); return(NULL); } } else if (c_isdigit(CUR) || (CUR == '-') || (CUR == '+')) { if (ctxt->conf->flags & VIR_CONF_FLAG_VMX_FORMAT) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("numbers not allowed in VMX format")); return(NULL); } if (virConfParseLong(ctxt, &l) < 0) { return(NULL); } type = VIR_CONF_LONG; } else { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting a value")); return(NULL); } if (VIR_ALLOC(ret) < 0) { virReportOOMError(NULL); virConfFreeList(lst); VIR_FREE(str); return(NULL); } ret->type = type; ret->l = l; ret->str = str; ret->list = lst; return(ret); } /** * virConfParseName: * @ctxt: the parsing context * * Parse one name * * Returns a copy of the new string, NULL in case of error */ static char * virConfParseName(virConfParserCtxtPtr ctxt) { const char *base; char *ret; SKIP_BLANKS; base = ctxt->cur; /* TODO: probably need encoding support and UTF-8 parsing ! */ if (!c_isalpha(CUR) && !((ctxt->conf->flags & VIR_CONF_FLAG_VMX_FORMAT) && (CUR == '.'))) { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting a name")); return(NULL); } while ((ctxt->cur < ctxt->end) && (c_isalnum(CUR) || (CUR == '_') || ((ctxt->conf->flags & VIR_CONF_FLAG_VMX_FORMAT) && ((CUR == ':') || (CUR == '.'))))) NEXT; ret = strndup(base, ctxt->cur - base); if (ret == NULL) { virReportOOMError(NULL); return(NULL); } return(ret); } /** * virConfParseComment: * @ctxt: the parsing context * * Parse one standalone comment in the configuration file * * Returns 0 in case of success and -1 in case of error */ static int virConfParseComment(virConfParserCtxtPtr ctxt) { const char *base; char *comm; if (CUR != '#') return(-1); NEXT; base = ctxt->cur; while ((ctxt->cur < ctxt->end) && (!IS_EOL(CUR))) NEXT; comm = strndup(base, ctxt->cur - base); if (comm == NULL) { virReportOOMError(NULL); return(-1); } virConfAddEntry(ctxt->conf, NULL, NULL, comm); return(0); } /** * virConfParseSeparator: * @ctxt: the parsing context * * Parse one separator between statement if not at the end. * * Returns 0 in case of success and -1 in case of error */ static int virConfParseSeparator(virConfParserCtxtPtr ctxt) { SKIP_BLANKS; if (ctxt->cur >= ctxt->end) return(0); if (IS_EOL(CUR)) { SKIP_BLANKS_AND_EOL; } else if (CUR == ';') { NEXT; SKIP_BLANKS_AND_EOL; } else { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting a separator")); return(-1); } return(0); } /** * virConfParseStatement: * @ctxt: the parsing context * * Parse one statement in the conf file * * Returns 0 in case of success and -1 in case of error */ static int virConfParseStatement(virConfParserCtxtPtr ctxt) { const char *base; char *name; virConfValuePtr value; char *comm = NULL; SKIP_BLANKS_AND_EOL; if (CUR == '#') { return(virConfParseComment(ctxt)); } name = virConfParseName(ctxt); if (name == NULL) return(-1); SKIP_BLANKS; if (CUR != '=') { virConfError(ctxt, VIR_ERR_CONF_SYNTAX, _("expecting an assignment")); return(-1); } NEXT; SKIP_BLANKS; value = virConfParseValue(ctxt); if (value == NULL) { VIR_FREE(name); return(-1); } SKIP_BLANKS; if (CUR == '#') { NEXT; base = ctxt->cur; while ((ctxt->cur < ctxt->end) && (!IS_EOL(CUR))) NEXT; comm = strndup(base, ctxt->cur - base); if (comm == NULL) { virReportOOMError(NULL); VIR_FREE(name); virConfFreeValue(value); return(-1); } } if (virConfAddEntry(ctxt->conf, name, value, comm) == NULL) { VIR_FREE(name); virConfFreeValue(value); VIR_FREE(comm); return(-1); } return(0); } /** * virConfParse: * @filename: the name to report errors * @content: the configuration content in memory * @len: the length in bytes * @flags: combination of virConfFlag(s) * * Parse the subset of the Python language needed to handle simple * Xen configuration files. * * Returns an handle to lookup settings or NULL if it failed to * read or parse the file, use virConfFree() to free the data. */ static virConfPtr virConfParse(const char *filename, const char *content, int len, unsigned int flags) { virConfParserCtxt ctxt; ctxt.filename = filename; ctxt.base = ctxt.cur = content; ctxt.end = content + len - 1; ctxt.line = 1; ctxt.conf = virConfCreate(filename, flags); if (ctxt.conf == NULL) return(NULL); while (ctxt.cur < ctxt.end) { if (virConfParseStatement(&ctxt) < 0) goto error; if (virConfParseSeparator(&ctxt) < 0) goto error; } return(ctxt.conf); error: virConfFree(ctxt.conf); return(NULL); } /************************************************************************ * * * The module entry points * * * ************************************************************************/ /* 10 MB limit on config file size as a sanity check */ #define MAX_CONFIG_FILE_SIZE (1024*1024*10) /** * virConfReadFile: * @filename: the path to the configuration file. * @flags: combination of virConfFlag(s) * * Reads a configuration file. * * Returns an handle to lookup settings or NULL if it failed to * read or parse the file, use virConfFree() to free the data. */ virConfPtr virConfReadFile(const char *filename, unsigned int flags) { char *content; int len; virConfPtr conf; if (filename == NULL) { virConfError(NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); return(NULL); } if ((len = virFileReadAll(filename, MAX_CONFIG_FILE_SIZE, &content)) < 0) { return NULL; } conf = virConfParse(filename, content, len, flags); VIR_FREE(content); return conf; } /** * virConfReadMem: * @memory: pointer to the content of the configuration file * @len: length in byte * @flags: combination of virConfFlag(s) * * Reads a configuration file loaded in memory. The string can be * zero terminated in which case @len can be 0 * * Returns an handle to lookup settings or NULL if it failed to * parse the content, use virConfFree() to free the data. */ virConfPtr virConfReadMem(const char *memory, int len, unsigned int flags) { if ((memory == NULL) || (len < 0)) { virConfError(NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); return(NULL); } if (len == 0) len = strlen(memory); return(virConfParse("memory conf", memory, len, flags)); } /** * virConfFree: * @conf: a configuration file handle * * Frees all data associated to the handle * * Returns 0 in case of success, -1 in case of error. */ int virConfFree(virConfPtr conf) { virConfEntryPtr tmp; if (conf == NULL) { virConfError(NULL, VIR_ERR_INVALID_ARG, __FUNCTION__); return(-1); } tmp = conf->entries; while (tmp) { virConfEntryPtr next; VIR_FREE(tmp->name); virConfFreeValue(tmp->value); VIR_FREE(tmp->comment); next = tmp->next; VIR_FREE(tmp); tmp = next; } VIR_FREE(conf); return(0); } /** * virConfGetValue: * @conf: a configuration file handle * @entry: the name of the entry * * Lookup the value associated to this entry in the configuration file * * Returns a pointer to the value or NULL if the lookup failed, the data * associated will be freed when virConfFree() is called */ virConfValuePtr virConfGetValue(virConfPtr conf, const char *setting) { virConfEntryPtr cur; cur = conf->entries; while (cur != NULL) { if ((cur->name != NULL) && (STREQ(cur->name, setting))) return(cur->value); cur = cur->next; } return(NULL); } /** * virConfSetValue: * @conf: a configuration file handle * @entry: the name of the entry * @value: the new configuration value * * Set (or replace) the value associated to this entry in the configuration * file. The passed in 'value' will be owned by the conf object upon return * of this method, even in case of error. It should not be referenced again * by the caller. * * Returns 0 on success, or -1 on failure. */ int virConfSetValue (virConfPtr conf, const char *setting, virConfValuePtr value) { virConfEntryPtr cur, prev = NULL; cur = conf->entries; while (cur != NULL) { if ((cur->name != NULL) && (STREQ(cur->name, setting))) { break; } prev = cur; cur = cur->next; } if (!cur) { if (VIR_ALLOC(cur) < 0) { virReportOOMError(NULL); virConfFreeValue(value); return (-1); } cur->comment = NULL; if (!(cur->name = strdup(setting))) { virReportOOMError(NULL); virConfFreeValue(value); VIR_FREE(cur); return (-1); } cur->value = value; if (prev) { cur->next = prev->next; prev->next = cur; } else { cur->next = conf->entries; conf->entries = cur; } } else { if (cur->value) { virConfFreeValue(cur->value); } cur->value = value; } return (0); } /** * virConfWriteFile: * @filename: the path to the configuration file. * @conf: the conf * * Writes a configuration file back to a file. * * Returns the number of bytes written or -1 in case of error. */ int virConfWriteFile(const char *filename, virConfPtr conf) { virBuffer buf = VIR_BUFFER_INITIALIZER; virConfEntryPtr cur; int ret; int fd; char *content; unsigned int use; if (conf == NULL) return(-1); cur = conf->entries; while (cur != NULL) { virConfSaveEntry(&buf, cur); cur = cur->next; } if (virBufferError(&buf)) { virReportOOMError(NULL); return -1; } fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR ); if (fd < 0) { char *tmp = virBufferContentAndReset(&buf); virConfError(NULL, VIR_ERR_WRITE_FAILED, _("failed to open file")); VIR_FREE(tmp); return -1; } use = virBufferUse(&buf); content = virBufferContentAndReset(&buf); ret = safewrite(fd, content, use); VIR_FREE(content); close(fd); if (ret != (int)use) { virConfError(NULL, VIR_ERR_WRITE_FAILED, _("failed to save content")); return -1; } return ret; } /** * virConfWriteMem: * @memory: pointer to the memory to store the config file * @len: pointer to the length in bytes of the store, on output the size * @conf: the conf * * Writes a configuration file back to a memory area. @len is an IN/OUT * parameter, it indicates the size available in bytes, and on output the * size required for the configuration file (even if the call fails due to * insufficient space). * * Returns the number of bytes written or -1 in case of error. */ int virConfWriteMem(char *memory, int *len, virConfPtr conf) { virBuffer buf = VIR_BUFFER_INITIALIZER; virConfEntryPtr cur; char *content; unsigned int use; if ((memory == NULL) || (len == NULL) || (*len <= 0) || (conf == NULL)) return(-1); cur = conf->entries; while (cur != NULL) { virConfSaveEntry(&buf, cur); cur = cur->next; } if (virBufferError(&buf)) { virReportOOMError(NULL); return -1; } use = virBufferUse(&buf); content = virBufferContentAndReset(&buf); if ((int)use >= *len) { *len = (int)use; VIR_FREE(content); return -1; } memcpy(memory, content, use); VIR_FREE(content); *len = use; return use; }