// // Copyright © 2005-2009 Claes Nästén // // This program is licensed under the GNU GPL. // See the LICENSE file for more information. // #include "CfgParser.hh" #include "Compat.hh" #include "Util.hh" #include #include #include #include #include enum { PARSE_BUF_SIZE = 1024 }; using std::cerr; using std::endl; using std::list; using std::map; using std::set; using std::string; using std::auto_ptr; const string CfgParser::_root_source_name = string(""); const char *CP_PARSE_BLANKS = " \t\n"; //! @brief CfgParser::Entry constructor. CfgParser::Entry::Entry(const std::string &source_name, int line, const std::string &name, const std::string &value, CfgParser::Entry *section) : _section(section), _name(name), _value(value), _line(line), _source_name(source_name) { } /** * Copy Entry together with the content. */ CfgParser::Entry::Entry(const CfgParser::Entry &entry) : _section(0), _name(entry._name), _value(entry._value), _line(entry._line), _source_name(_source_name) { list::const_iterator it(entry._entries.begin()); for (; it != entry._entries.end(); ++it) { _entries.push_back(new Entry(*(*it))); } if (entry._section) { _section = new Entry(*entry._section); } } //! @brief CfgParser::Entry destructor. CfgParser::Entry::~Entry(void) { for_each(_entries.begin(), _entries.end(), Util::Free()); if (_section) { delete _section; _section = 0; } } /** * Append Entry to the end of Entry list at current depth. */ CfgParser::Entry* CfgParser::Entry::add_entry(CfgParser::Entry *entry, bool overwrite) { CfgParser::Entry *entry_search = 0; if (overwrite) { if (entry->get_section()) { entry_search = find_entry(entry->get_name(), true, entry->get_section()->get_value().c_str()); } else { entry_search = find_entry(entry->get_name(), false); } } // This is a bit awkward but to keep compatible with old // configuration syntax overwriting of section is only allowed // when the value is the same. if (entry_search && (! entry_search->get_section() || strcasecmp(entry->get_value().c_str(), entry_search->get_value().c_str()) == 0)) { entry_search->_value = entry->get_value(); entry_search->set_section(entry->get_section(), overwrite); // Clear resources used by entry entry->_section = 0; delete entry; entry = entry_search; } else { _entries.push_back(entry); } return entry; } //! @brief Adds Entry to the end of Entry list at current depth. CfgParser::Entry* CfgParser::Entry::add_entry(const std::string &source_name, int line, const std::string &name, const std::string &value, CfgParser::Entry *section, bool overwrite) { return add_entry(new Entry(source_name, line, name, value, section), overwrite); } /** * Set section, copy section entires over if overwrite. */ CfgParser::Entry* CfgParser::Entry::set_section(CfgParser::Entry *section, bool overwrite) { if (_section) { if (overwrite) { _section->copy_tree_into(section); delete section; } else { delete _section; _section = section; } } else { _section = section; } return _section; } //! @brief Gets next entry without subsection matching the name name. //! @param name Name of Entry to look for. CfgParser::Entry* CfgParser::Entry::find_entry(const std::string &name, bool include_sections, const char *value) { CfgParser::Entry *value_check; list::iterator it(_entries.begin()); for (; it != _entries.end(); ++it) { value_check = include_sections ? (*it)->get_section() : (*it); if (*(*it) == name.c_str() && (! (*it)->get_section() || include_sections) && (! value || (value_check && value_check->get_value() == value))) { return *it; } } return 0; } //! @brief Gets the next entry with subsection matchin the name name. //! @param name Name of Entry to look for. CfgParser::Entry* CfgParser::Entry::find_section(const std::string &name, const char *value) { list::iterator it(_entries.begin()); for (; it != _entries.end(); ++it) { if ((*it)->get_section() && *(*it) == name.c_str() && (! value || (*it)->get_section()->get_value() == value)) { return (*it)->get_section(); } } return 0; } //! @brief Sets and validates data specified by key list. void CfgParser::Entry::parse_key_values(std::list::iterator begin, std::list::iterator end) { CfgParser::Entry *value; list::iterator it; for (it = begin; it != end; ++it) { value = find_entry((*it)->get_name()); if (value) { try { (*it)->parse_value(value->get_value()); } catch (string &ex) { cerr << " *** WARNING " << ex << endl << " " << *value << endl; } } } } /** * Copy tree into current entry, overwrite entries if overwrite is * true. */ void CfgParser::Entry::copy_tree_into(CfgParser::Entry *from, bool overwrite) { // Copy section if (from->get_section()) { if (_section) { _section->copy_tree_into(from->get_section(), overwrite); } else { _section = new Entry(*(from->get_section())); } } // Copy elements CfgParser::iterator it(from->begin()); for (; it != from->end(); ++it) { CfgParser::Entry *entry_section = 0; if ((*it)->get_section()) { entry_section = new Entry(*((*it)->get_section())); } add_entry((*it)->get_source_name(), (*it)->get_line(), (*it)->get_name(), (*it)->get_value(), entry_section, true); } } //! @brief Operator <<, return info on source, line, name and value. std::ostream& operator<<(std::ostream &stream, const CfgParser::Entry &entry) { stream << entry.get_source_name() << "@" << entry.get_line() << " " << entry.get_name() << " = " << entry.get_value(); return stream; } //! @brief CfgParser constructor. CfgParser::CfgParser(void) : _source(0), _root_entry(0), _is_dynamic_content(false), _section(_root_entry), _overwrite(false) { _root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", ""); _section = _root_entry; } //! @brief CfgParser destructor. CfgParser::~CfgParser(void) { clear(false); } /** * Clear resources used by parser, end up in the same state as in * after construction. * * @param realloc If realloc is false, root_entry will be cleared as well rendering the parser useless. Defaults to true. */ void CfgParser::clear(bool realloc) { _source = 0; delete _root_entry; if (realloc) { _root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", ""); } else { _root_entry = 0; } _section = _root_entry; _overwrite = false; // Clear lists _source_list.clear(); _source_name_list.clear(); _source_name_set.clear(); _section_list.clear(); _var_map.clear(); // Remove sections map::iterator it(_section_map.begin()); for (; it != _section_map.end(); ++it) { delete it->second; } _section_map.clear(); } /** * Parses source and fills root section with data. * * @param src Source. * @param type Type of source, defaults to file. * @param overwrite Overwrite or append duplicate elements, defaults to false. */ bool CfgParser::parse(const std::string &src, CfgParserSource::Type type, bool overwrite) { // Set overwrite _overwrite = overwrite; // Init parse buffer and reserve memory. string buf, value; buf.reserve(PARSE_BUF_SIZE); // Open initial source. parse_source_new(src, type); if (_source_list.size() == 0) { return false; } int c, next; while (_source_list.size()) { _source = _source_list.back(); if (_source->is_dynamic()) { _is_dynamic_content = true; } while ((c = _source->getc()) != EOF) { switch (c) { case '\n': // To be able to handle entry ends AND { after \n a check // to see what comes after the newline is done. If { appears // we continue as nothing happened else we finish the entry. next = parse_skip_blank(_source); if (next != '{') { parse_entry_finish(buf, value); } break; case ';': parse_entry_finish(buf, value); break; case '{': if (parse_name(buf)) { parse_section_finish(buf, value); } else { cerr << "Ignoring section as name is empty." << endl; } buf.clear(); value.clear(); break; case '}': if (_section_list.size() > 0) { if (buf.size() && parse_name(buf)) { parse_entry_finish(buf, value); buf.clear(); value.clear(); } _section = _section_list.back(); _section_list.pop_back(); } else { cerr << "Extra } character found, ignoring." << endl; } break; case '=': value.clear(); parse_value(_source, value); break; case '#': parse_comment_line(_source); break; case '/': next = _source->getc(); if (next == '/') { parse_comment_line(_source); } else if (next == '*') { parse_comment_c(_source); } else { buf += c; _source->ungetc(next); } break; default: buf += c; break; } } try { _source->close(); } catch (string &ex) { cerr << ex << endl; } delete _source; _source_list.pop_back(); _source_name_list.pop_back(); } if (buf.size()) { parse_entry_finish(buf, value); } return true; } //! @brief Creates and opens new CfgParserSource. void CfgParser::parse_source_new(const std::string &name_orig, CfgParserSource::Type type) { int done = 0; string name(name_orig); do { CfgParserSource *source = source_new(name, type); assert(source); // Open and set as active, delete if fails. try { source->open(); // Add source to file list if file if (type == CfgParserSource::SOURCE_FILE) { _file_list[name] = Util::getMtime(name); } _source = source; _source_list.push_back(_source); done = 1; } catch (string &ex) { delete source; // Previously added in source_new _source_name_list.pop_back(); // Display error message on second try if (done) { cerr << ex << endl; } // If the open fails and we are trying to open a file, try // to open the file from the current files directory. if (! done && (type == CfgParserSource::SOURCE_FILE)) { if (_source_name_list.size() && (name[0] != '/')) { name = Util::getDir(_source_name_list.back()); name += "/" + name_orig; } } } } while (! done++ && (type == CfgParserSource::SOURCE_FILE)); } //! @brief Parses from beginning to first blank. bool CfgParser::parse_name(std::string &buf) { if (! buf.size()) { cerr << "Unable to parse empty name." << endl; return false; } // Identify name. string::size_type begin, end; begin = buf.find_first_not_of(CP_PARSE_BLANKS); if (begin == string::npos) { return false; } end = buf.find_first_of(CP_PARSE_BLANKS, begin); // Check if there is any garbage after the value. if (end != string::npos) { if (buf.find_first_not_of(CP_PARSE_BLANKS, end) != string::npos) { // Pass, do notihng } } // Set name. buf = buf.substr(begin, end - begin); return true; } //! @brief Parses from = to end of " pair. void CfgParser::parse_value(CfgParserSource *source, std::string &value) { // We expect to get a " after the =, however we ignore anything else. int c; while ((c = source->getc()) != EOF && c != '"') ; // Check if we got to a " or found EOF first. if (c == EOF) { cerr << "Reached EOF before opening \" in value." << endl; return; } // Parse until next ", and escape characters after \. while ((c = source->getc()) != EOF && c != '"') { // Escape character after \, if newline drop it. if (c == '\\') { c = source->getc(); if (c == '\n' || c == EOF) { continue; } } value += c; } if (c == EOF) { cerr << "Reached EOF before closing \" in value." << endl; } // If the value is empty, parse_entry_finish() might later just skip // the complete entry. To allow empty config options we add a dummy space. if (!value.size()) { value = " "; } } //! @brief Parses entry (name + value) and executes command accordingly. void CfgParser::parse_entry_finish(std::string &buf, std::string &value) { if (value.size()) { parse_entry_finish_standard(buf, value); } else { // Template handling, expand or define template. if (buf.size() && parse_name(buf) && buf[0] == '@') { parse_entry_finish_template(buf); } buf.clear(); } } /** * Finish standard entry. */ void CfgParser::parse_entry_finish_standard(std::string &buf, std::string &value) { if (parse_name(buf)) { if (buf[0] == '$') { variable_define(buf, value); } else { variable_expand(value); if (buf == "INCLUDE") { parse_source_new(value, CfgParserSource::SOURCE_FILE); } else if (buf == "COMMAND") { parse_source_new(value, CfgParserSource::SOURCE_COMMAND); } else { _section->add_entry(_source->get_name(), _source->get_line(), buf, value, 0, _overwrite); } } } else { cerr << "Dropping entry with empty name." << endl; } value.clear(); buf.clear(); } /** * Finish template entry, copy data into current section. */ void CfgParser::parse_entry_finish_template(std::string &name) { map::iterator it(_section_map.find(name.c_str() + 1)); if (it == _section_map.end()) { cerr << " *** WARNING: No such template " << name << endl; return; } _section->copy_tree_into(it->second); } //! @brief Creates new Section on { void CfgParser::parse_section_finish(std::string &buf, std::string &value) { // Create Entry representing Section Entry *section = 0; if (buf.size() == 6 && strcasecmp(buf.c_str(), "DEFINE") == 0) { // Look for define section, started with Define = "Name" { map::iterator it(_section_map.find(value)); if (it != _section_map.end()) { delete it->second; _section_map.erase(it); } section = new Entry(_source->get_name(), _source->get_line(), buf, value); _section_map[value] = section; } else { // Create Entry for sub-section. section = new Entry(_source->get_name(), _source->get_line(), buf, value); // Add parent section, get section from parent section as it // can be different from the newly created if it is not // overwritten. CfgParser::Entry *parent = _section->add_entry(_source->get_name(), _source->get_line(), buf, value, section, _overwrite); section = parent->get_section(); } // Set current Entry to newly created Section. _section_list.push_back(_section); _section = section; } //! @brief Parses Source until end of line discarding input. void CfgParser::parse_comment_line(CfgParserSource *source) { int c; while (((c = source->getc()) != EOF) && (c != '\n')) ; // Give back the newline, needed for flushing value before comment if (c == '\n') { source->ungetc(c); } } //! @brief Parses Source until */ is found. void CfgParser::parse_comment_c(CfgParserSource *source) { int c; while ((c = source->getc()) != EOF) { if (c == '*') { if ((c = source->getc()) == '/') { break; } else if (c != EOF) { source->ungetc(c); } } } if (c == EOF) { cerr << "Reached EOF before closing */ in comment." << endl; } } //! @brief Parses Source until next non whitespace char is found. char CfgParser::parse_skip_blank(CfgParserSource *source) { int c; while (((c = source->getc()) != EOF) && isspace(c)) ; if (c != EOF) { source->ungetc(c); } return c; } //! @brief Creates a CfgParserSource of type type. CfgParserSource* CfgParser::source_new(const std::string &name, CfgParserSource::Type type) { CfgParserSource *source = 0; // Create CfgParserSource. _source_name_list.push_back(name); _source_name_set.insert(name); switch (type) { case CfgParserSource::SOURCE_FILE: source = new CfgParserSourceFile(*_source_name_set.find(name)); break; case CfgParserSource::SOURCE_COMMAND: source = new CfgParserSourceCommand(*_source_name_set.find(name)); break; default: break; } return source; } //! @brief Defines a variable in the _var_map/setenv. void CfgParser::variable_define(const std::string &name, const std::string &value) { _var_map[name] = value; // If the variable begins with $_ it should update the environment aswell. if ((name.size() > 2) && (name[1] == '_')) { setenv(name.c_str() + 2, value.c_str(), 1); } } //! @brief Expands all $ variables in a string. void CfgParser::variable_expand(std::string &var) { bool did_expand; do { did_expand = false; string::size_type begin = 0, end = 0; while ((begin = var.find_first_of('$', end)) != string::npos) { end = begin + 1; // Skip escaped \$ if ((begin > 0) && (var[begin - 1] == '\\')) { continue; } // Find end of variable for (; end != var.size(); ++end) { if ((isalnum(var[end]) == 0) && (var[end] != '_')) { break; } } did_expand = variable_expand_name(var, begin, end) || did_expand; } } while (did_expand); } bool CfgParser::variable_expand_name(std::string &var, string::size_type begin, string::size_type &end) { bool did_expand = false; string var_name(var.substr(begin, end - begin)); // If the variable starts with _ it is considered an environment // variable, use getenv to see if it is available if (var_name.size() > 2 && var_name[1] == '_') { char *value = getenv(var_name.c_str() + 2); if (value) { var.replace(begin, end - begin, value); end = begin + strlen(value); did_expand = true; } else { cerr << "Trying to use undefined environment variable: " << var_name << endl;; } } else { map::iterator it(_var_map.find(var_name)); if (it != _var_map.end()) { var.replace(begin, end - begin, it->second); end = begin + it->second.size(); did_expand = true; } else { cerr << "Trying to use undefined variable: " << var_name << endl; } } return did_expand; }