Boost::program_options
Boost::date_time
Boost::iostreams
+ StdFilesystem::filesystem
${BLKID_LIBRARIES}
${Backtrace_LIBRARIES}
${BLKIN_LIBRARIES}
bufferlist bl;
bli.copy_all(bl);
ConfFile cf;
- std::deque<std::string> parse_errors;
- if (cf.parse_bufferlist(&bl, &parse_errors, NULL) != 0) {
+ if (cf.parse_bufferlist(&bl, nullptr) != 0) {
throw buffer::malformed_input("cannot parse buffer");
}
- for (ConfFile::const_section_iter_t s = cf.sections_begin();
- s != cf.sections_end(); ++s) {
- string name = s->first;
+ for (auto& [name, section] : cf) {
if (name == "global")
continue;
throw buffer::malformed_input(oss.str().c_str());
}
- for (ConfSection::const_line_iter_t l = s->second.lines.begin();
- l != s->second.lines.end(); ++l) {
- if (l->key.empty())
+ for (auto& [k, val] : section) {
+ if (k.empty())
continue;
- string k(l->key);
- std::replace(k.begin(), k.end(), '_', ' ');
- ret = set_modifier(k.c_str(), l->val.c_str(), ename, caps);
+ string key;
+ std::replace_copy(k.begin(), k.end(), back_inserter(key), '_', ' ');
+ ret = set_modifier(key.c_str(), val.c_str(), ename, caps);
if (ret < 0) {
ostringstream oss;
- oss << "error setting modifier for [" << name << "] type=" << k
- << " val=" << l->val;
+ oss << "error setting modifier for [" << name << "] type=" << key
+ << " val=" << val;
throw buffer::malformed_input(oss.str().c_str());
}
}
* Foundation. See file COPYING.
*
*/
+// #define BOOST_SPIRIT_DEBUG
#include <algorithm>
+#include <cctype>
+#include <fstream>
+#include <iostream>
+#include <iterator>
#include <map>
#include <sstream>
-#include <sys/stat.h>
-#include <iostream>
+
+#if __has_include(<filesystem>)
+#include <filesystem>
+namespace fs = std::filesystem;
+#elif __has_include(<experimental/filesystem>)
+#include <experimental/filesystem>
+namespace fs = std::experimental::filesystem;
+#else
+#error std::filesystem not available!
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <boost/spirit/include/qi.hpp>
+#include <boost/spirit/include/phoenix.hpp>
+#include <boost/spirit/include/support_line_pos_iterator.hpp>
#include "include/buffer.h"
#include "common/errno.h"
#include "common/ConfUtils.h"
using std::ostringstream;
-using std::pair;
using std::string;
#define MAX_CONFIG_FILE_SZ 0x40000000
-////////////////////////////// ConfLine //////////////////////////////
-ConfLine::
-ConfLine(const std::string &key_, const std::string &val_,
- const std::string &newsection_, const std::string &comment_, int line_no_)
- : key(key_), val(val_), newsection(newsection_)
-{
- // If you want to implement writable ConfFile support, you'll need to save
- // the comment and line_no arguments here.
-}
+conf_line_t::conf_line_t(const std::string& key, const std::string& val)
+ : key{ConfFile::normalize_key_name(key)},
+ val{boost::algorithm::trim_copy_if(
+ val,
+ [](unsigned char c) {
+ return std::isspace(c);
+ })}
+{}
-bool ConfLine::
-operator<(const ConfLine &rhs) const
+bool conf_line_t::operator<(const conf_line_t &rhs) const
{
// We only compare keys.
// If you have more than one line with the same key in a given section, the
// last one wins.
- if (key < rhs.key)
- return true;
- else
- return false;
+ return key < rhs.key;
}
-std::ostream &operator<<(std::ostream& oss, const ConfLine &l)
+std::ostream &operator<<(std::ostream& oss, const conf_line_t &l)
{
- oss << "ConfLine(key = '" << l.key << "', val='"
- << l.val << "', newsection='" << l.newsection << "')";
+ oss << "conf_line_t(key = '" << l.key << "', val='" << l.val << "')";
return oss;
}
-///////////////////////// ConfFile //////////////////////////
-ConfFile::
-ConfFile()
-{
-}
-ConfFile::
-~ConfFile()
+conf_section_t::conf_section_t(const std::string& heading,
+ const std::vector<conf_line_t>& lines)
+ : heading{heading}
{
+ for (auto& line : lines) {
+ auto [where, inserted] = insert(line);
+ if (!inserted) {
+ erase(where);
+ insert(line);
+ }
+ }
}
-void ConfFile::
-clear()
+///////////////////////// ConfFile //////////////////////////
+
+ConfFile::ConfFile(const std::vector<conf_section_t>& sections)
{
- sections.clear();
+ for (auto& section : sections) {
+ auto [old_sec, sec_inserted] = emplace(section.heading, section);
+ if (!sec_inserted) {
+ // merge lines in section into old_sec
+ for (auto& line : section) {
+ auto [old_line, line_inserted] = old_sec->second.emplace(line);
+ // and replace the existing ones if any
+ if (!line_inserted) {
+ old_sec->second.erase(old_line);
+ old_sec->second.insert(line);
+ }
+ }
+ }
+ }
}
/* We load the whole file into memory and then parse it. Although this is not
* In general, configuration files should be a few kilobytes at maximum, so
* loading the whole configuration into memory shouldn't be a problem.
*/
-int ConfFile::
-parse_file(const std::string &fname, std::deque<std::string> *errors,
- std::ostream *warnings)
+int ConfFile::parse_file(const std::string &fname,
+ std::ostream *warnings)
{
clear();
-
- int ret = 0;
- size_t sz;
- char *buf = NULL;
- FILE *fp = fopen(fname.c_str(), "r");
- if (!fp) {
- ostringstream oss;
- oss << __func__ << ": cannot open " << fname << ": " << cpp_strerror(errno);
- errors->push_back(oss.str());
- ret = -errno;
- return ret;
+ try {
+ if (auto file_size = fs::file_size(fname); file_size > MAX_CONFIG_FILE_SZ) {
+ *warnings << __func__ << ": config file '" << fname
+ << "' is " << file_size << " bytes, "
+ << "but the maximum is " << MAX_CONFIG_FILE_SZ;
+ return -EINVAL;
+ }
+ } catch (const fs::filesystem_error& e) {
+ if (fs::is_other(fname)) {
+ // /dev/null?
+ return 0;
+ } else {
+ *warnings << __func__ << ": " << e.what();
+ return -e.code().value();
+ }
}
-
- struct stat st_buf;
- if (fstat(fileno(fp), &st_buf)) {
- ret = -errno;
- ostringstream oss;
- oss << __func__ << ": failed to fstat '" << fname << "': " << cpp_strerror(ret);
- errors->push_back(oss.str());
- goto done;
+ std::ifstream ifs{fname};
+ const std::string buffer{std::istreambuf_iterator<char>(ifs),
+ std::istreambuf_iterator<char>()};
+ if (load_from_buffer(buffer, warnings)) {
+ return 0;
+ } else {
+ return -EINVAL;
}
+}
- if (st_buf.st_size > MAX_CONFIG_FILE_SZ) {
- ostringstream oss;
- oss << __func__ << ": config file '" << fname << "' is " << st_buf.st_size
- << " bytes, but the maximum is " << MAX_CONFIG_FILE_SZ;
- errors->push_back(oss.str());
- ret = -EINVAL;
- goto done;
- }
+namespace {
- sz = (size_t)st_buf.st_size;
- buf = (char*)malloc(sz);
- if (!buf) {
- ret = -ENOMEM;
- goto done;
- }
+namespace qi = boost::spirit::qi;
+namespace phoenix = boost::phoenix;
- if (fread(buf, 1, sz, fp) != sz) {
- if (ferror(fp)) {
- ret = -errno;
- ostringstream oss;
- oss << __func__ << ": fread error while reading '" << fname << "': "
- << cpp_strerror(ret);
- errors->push_back(oss.str());
- goto done;
- }
- else {
- ostringstream oss;
- oss << __func__ << ": unexpected EOF while reading '" << fname << "': "
- << "possible concurrent modification?";
- errors->push_back(oss.str());
- ret = -EIO;
- goto done;
+template<typename Iterator, typename Skipper>
+struct IniGrammer : qi::grammar<Iterator, ConfFile(), Skipper>
+{
+ struct error_handler_t {
+ std::ostream& os;
+ template<typename Iter>
+ auto operator()(Iter first, Iter last, Iter where,
+ const boost::spirit::info& what) const {
+ auto line_start = boost::spirit::get_line_start(first, where);
+ os << "parse error: expected '" << what
+ << "' in line " << boost::spirit::get_line(where)
+ << " at position " << boost::spirit::get_column(line_start, where) << "\n";
+ return qi::fail;
}
+ };
+ IniGrammer(Iterator begin, std::ostream& err)
+ : IniGrammer::base_type{conf_file},
+ report_error{error_handler_t{err}}
+ {
+ using qi::_1;
+ using qi::_2;
+ using qi::_val;
+ using qi::char_;
+ using qi::eoi;
+ using qi::eol;
+ using qi::blank;
+ using qi::lexeme;
+ using qi::lit;
+ using qi::raw;
+
+ blanks = *blank;
+ comment_start = lit('#') | lit(';');
+ continue_marker = lit('\\') >> eol;
+
+ text_char %=
+ (lit('\\') >> (char_ - eol)) |
+ (char_ - (comment_start | eol));
+
+ key %= raw[+(text_char - char_("=[ ")) % +blank];
+ quoted_value %=
+ lexeme[lit('"') >> *(text_char - '"') > '"'] |
+ lexeme[lit('\'') >> *(text_char - '\'') > '\''];
+ unquoted_value %= *text_char;
+ comment = *blank >> comment_start > *(char_ - eol);
+ empty_line = -(blanks|comment) >> eol;
+ value %= quoted_value | unquoted_value;
+ key_val =
+ (blanks >> key >> blanks >> '=' > blanks > value > +empty_line)
+ [_val = phoenix::construct<conf_line_t>(_1, _2)];
+
+ heading %= lit('[') > +(text_char - ']') > ']' > +empty_line;
+ section =
+ (heading >> *(key_val - heading) >> *eol)
+ [_val = phoenix::construct<conf_section_t>(_1, _2)];
+ conf_file =
+ (key_val [_val = phoenix::construct<ConfFile>(_1)]
+ |
+ (*eol >> (*section)[_val = phoenix::construct<ConfFile>(_1)])
+ ) > eoi;
+
+ empty_line.name("empty_line");
+ key.name("key");
+ quoted_value.name("quoted value");
+ unquoted_value.name("unquoted value");
+ key_val.name("key=val");
+ heading.name("section name");
+ section.name("section");
+
+ qi::on_error<qi::fail>(
+ conf_file,
+ report_error(qi::_1, qi::_2, qi::_3, qi::_4));
+
+ BOOST_SPIRIT_DEBUG_NODE(heading);
+ BOOST_SPIRIT_DEBUG_NODE(section);
+ BOOST_SPIRIT_DEBUG_NODE(key);
+ BOOST_SPIRIT_DEBUG_NODE(quoted_value);
+ BOOST_SPIRIT_DEBUG_NODE(unquoted_value);
+ BOOST_SPIRIT_DEBUG_NODE(key_val);
+ BOOST_SPIRIT_DEBUG_NODE(conf_file);
}
- load_from_buffer(buf, sz, errors, warnings);
- ret = 0;
-
-done:
- free(buf);
- fclose(fp);
- return ret;
+ qi::rule<Iterator> blanks;
+ qi::rule<Iterator> empty_line;
+ qi::rule<Iterator> comment_start;
+ qi::rule<Iterator> continue_marker;
+ qi::rule<Iterator, char()> text_char;
+ qi::rule<Iterator, std::string(), Skipper> key;
+ qi::rule<Iterator, std::string(), Skipper> quoted_value;
+ qi::rule<Iterator, std::string(), Skipper> unquoted_value;
+ qi::rule<Iterator> comment;
+ qi::rule<Iterator, std::string(), Skipper> value;
+ qi::rule<Iterator, conf_line_t(), Skipper> key_val;
+ qi::rule<Iterator, std::string(), Skipper> heading;
+ qi::rule<Iterator, conf_section_t(), Skipper> section;
+ qi::rule<Iterator, ConfFile(), Skipper> conf_file;
+ boost::phoenix::function<error_handler_t> report_error;
+};
}
-int ConfFile::
-parse_bufferlist(ceph::bufferlist *bl, std::deque<std::string> *errors,
- std::ostream *warnings)
+bool ConfFile::load_from_buffer(std::string_view buf, std::ostream* err)
{
- clear();
-
- load_from_buffer(bl->c_str(), bl->length(), errors, warnings);
- return 0;
+ if (int err_pos = check_utf8(buf.data(), buf.size()); err_pos > 0) {
+ *err << "parse error: invalid UTF-8 found at line "
+ << std::count(buf.begin(), std::next(buf.begin(), err_pos), '\n') + 1;
+ return false;
+ }
+ using iter_t = boost::spirit::line_pos_iterator<decltype(buf.begin())>;
+ iter_t first{buf.begin()};
+ using skipper_t = qi::rule<iter_t>;
+ IniGrammer<iter_t, skipper_t> grammar{first, *err};
+ skipper_t skipper = grammar.continue_marker | grammar.comment;
+ return qi::phrase_parse(first, iter_t{buf.end()},
+ grammar, skipper, *this);
}
-int ConfFile::
-read(const std::string §ion, const std::string_view key, std::string &val) const
+int ConfFile::parse_bufferlist(ceph::bufferlist *bl,
+ std::ostream *warnings)
{
- string k(normalize_key_name(key));
-
- const_section_iter_t s = sections.find(section);
- if (s == sections.end())
- return -ENOENT;
- ConfLine exemplar(k, "", "", "", 0);
- ConfSection::const_line_iter_t l = s->second.lines.find(exemplar);
- if (l == s->second.lines.end())
- return -ENOENT;
- val = l->val;
- return 0;
+ clear();
+ ostringstream oss;
+ if (!warnings) {
+ warnings = &oss;
+ }
+ return load_from_buffer({bl->c_str(), bl->length()}, warnings) ? 0 : -EINVAL;
}
-ConfFile::const_section_iter_t ConfFile::
-sections_begin() const
+int ConfFile::read(const std::string& section_name,
+ std::string_view key,
+ std::string &val) const
{
- return sections.begin();
-}
+ string k(normalize_key_name(key));
-ConfFile::const_section_iter_t ConfFile::
-sections_end() const
-{
- return sections.end();
+ if (auto s = base_type::find(section_name); s != end()) {
+ conf_line_t exemplar{k, {}};
+ if (auto line = s->second.find(exemplar); line != s->second.end()) {
+ val = line->val;
+ return 0;
+ }
+ }
+ return -ENOENT;
}
void ConfFile::
std::ostream &operator<<(std::ostream &oss, const ConfFile &cf)
{
- for (ConfFile::const_section_iter_t s = cf.sections_begin();
- s != cf.sections_end(); ++s) {
- oss << "[" << s->first << "]\n";
- for (ConfSection::const_line_iter_t l = s->second.lines.begin();
- l != s->second.lines.end(); ++l) {
- if (!l->key.empty()) {
- oss << "\t" << l->key << " = \"" << l->val << "\"\n";
+ for (auto& [name, section] : cf) {
+ oss << "[" << name << "]\n";
+ for (auto& [key, val] : section) {
+ if (!key.empty()) {
+ oss << "\t" << key << " = \"" << val << "\"\n";
}
}
}
return oss;
}
-
-void ConfFile::
-load_from_buffer(const char *buf, size_t sz, std::deque<std::string> *errors,
- std::ostream *warnings)
-{
- errors->clear();
-
- section_iter_t::value_type vt("global", ConfSection());
- pair < section_iter_t, bool > vr(sections.insert(vt));
- ceph_assert(vr.second);
- section_iter_t cur_section = vr.first;
- std::string acc;
-
- const char *b = buf;
- int line_no = 0;
- size_t line_len = -1;
- size_t rem = sz;
- while (1) {
- b += line_len + 1;
- if ((line_len + 1) > rem)
- break;
- rem -= line_len + 1;
- if (rem == 0)
- break;
- line_no++;
-
- // look for the next newline
- const char *end = (const char*)memchr(b, '\n', rem);
- if (!end) {
- ostringstream oss;
- oss << "read_conf: ignoring line " << line_no << " because it doesn't "
- << "end with a newline! Please end the config file with a newline.";
- errors->push_back(oss.str());
- break;
- }
-
- // find length of line, and search for NULLs
- line_len = 0;
- bool found_null = false;
- for (const char *tmp = b; tmp != end; ++tmp) {
- line_len++;
- if (*tmp == '\0') {
- found_null = true;
- }
- }
-
- if (found_null) {
- ostringstream oss;
- oss << "read_conf: ignoring line " << line_no << " because it has "
- << "an embedded null.";
- errors->push_back(oss.str());
- acc.clear();
- continue;
- }
-
- if (check_utf8(b, line_len)) {
- ostringstream oss;
- oss << "read_conf: ignoring line " << line_no << " because it is not "
- << "valid UTF8.";
- errors->push_back(oss.str());
- acc.clear();
- continue;
- }
-
- if ((line_len >= 1) && (b[line_len-1] == '\\')) {
- // A backslash at the end of a line serves as a line continuation marker.
- // Combine the next line with this one.
- // Remove the backslash itself from the text.
- acc.append(b, line_len - 1);
- continue;
- }
-
- acc.append(b, line_len);
-
- //cerr << "acc = '" << acc << "'" << std::endl;
- ConfLine *cline = process_line(line_no, acc.c_str(), errors);
- acc.clear();
- if (!cline)
- continue;
- const std::string &csection(cline->newsection);
- if (!csection.empty()) {
- std::map <std::string, ConfSection>::value_type nt(csection, ConfSection());
- pair < section_iter_t, bool > nr(sections.insert(nt));
- cur_section = nr.first;
- }
- else {
- if (cur_section->second.lines.count(*cline)) {
- // replace an existing key/line in this section, so that
- // [mysection]
- // foo = 1
- // foo = 2
- // will result in foo = 2.
- cur_section->second.lines.erase(*cline);
- if (cline->key.length() && warnings)
- *warnings << "warning: line " << line_no << ": '" << cline->key << "' in section '"
- << cur_section->first << "' redefined " << std::endl;
- }
- // add line to current section
- //std::cerr << "cur_section = " << cur_section->first << ", " << *cline << std::endl;
- cur_section->second.lines.insert(*cline);
- }
- delete cline;
- }
-
- if (!acc.empty()) {
- ostringstream oss;
- oss << "read_conf: don't end with lines that end in backslashes!";
- errors->push_back(oss.str());
- }
-}
-
-/*
- * A simple state-machine based parser.
- * This probably could/should be rewritten with something like boost::spirit
- * or yacc if the grammar ever gets more complex.
- */
-ConfLine* ConfFile::
-process_line(int line_no, const char *line, std::deque<std::string> *errors)
-{
- enum acceptor_state_t {
- ACCEPT_INIT,
- ACCEPT_SECTION_NAME,
- ACCEPT_KEY,
- ACCEPT_VAL_START,
- ACCEPT_UNQUOTED_VAL,
- ACCEPT_QUOTED_VAL,
- ACCEPT_COMMENT_START,
- ACCEPT_COMMENT_TEXT,
- };
- const char *l = line;
- acceptor_state_t state = ACCEPT_INIT;
- string key, val, newsection, comment;
- bool escaping = false;
- while (true) {
- char c = *l++;
- switch (state) {
- case ACCEPT_INIT:
- if (c == '\0')
- return NULL; // blank line. Not an error, but not interesting either.
- else if (c == '[')
- state = ACCEPT_SECTION_NAME;
- else if ((c == '#') || (c == ';'))
- state = ACCEPT_COMMENT_TEXT;
- else if (c == ']') {
- ostringstream oss;
- oss << "unexpected right bracket at char " << (l - line)
- << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if (isspace(c)) {
- // ignore whitespace here
- }
- else {
- // try to accept this character as a key
- state = ACCEPT_KEY;
- --l;
- }
- break;
- case ACCEPT_SECTION_NAME:
- if (c == '\0') {
- ostringstream oss;
- oss << "error parsing new section name: expected right bracket "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == ']') && (!escaping)) {
- trim_whitespace(newsection, true);
- if (newsection.empty()) {
- ostringstream oss;
- oss << "error parsing new section name: no section name found? "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- state = ACCEPT_COMMENT_START;
- }
- else if (((c == '#') || (c == ';')) && (!escaping)) {
- ostringstream oss;
- oss << "unexpected comment marker while parsing new section name, at "
- << "char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- newsection += c;
- }
- break;
- case ACCEPT_KEY:
- if ((((c == '#') || (c == ';')) && (!escaping)) || (c == '\0')) {
- ostringstream oss;
- if (c == '\0') {
- oss << "end of key=val line " << line_no
- << " reached, no \"=val\" found...missing =?";
- } else {
- oss << "unexpected character while parsing putative key value, "
- << "at char " << (l - line) << ", line " << line_no;
- }
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '=') && (!escaping)) {
- key = normalize_key_name(key);
- if (key.empty()) {
- ostringstream oss;
- oss << "error parsing key name: no key name found? "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- state = ACCEPT_VAL_START;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- key += c;
- }
- break;
- case ACCEPT_VAL_START:
- if (c == '\0')
- return new ConfLine(key, val, newsection, comment, line_no);
- else if ((c == '#') || (c == ';'))
- state = ACCEPT_COMMENT_TEXT;
- else if (c == '"')
- state = ACCEPT_QUOTED_VAL;
- else if (isspace(c)) {
- // ignore whitespace
- }
- else {
- // try to accept character as a val
- state = ACCEPT_UNQUOTED_VAL;
- --l;
- }
- break;
- case ACCEPT_UNQUOTED_VAL:
- if (c == '\0') {
- if (escaping) {
- ostringstream oss;
- oss << "error parsing value name: unterminated escape sequence "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- trim_whitespace(val, false);
- return new ConfLine(key, val, newsection, comment, line_no);
- }
- else if (((c == '#') || (c == ';')) && (!escaping)) {
- trim_whitespace(val, false);
- state = ACCEPT_COMMENT_TEXT;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- val += c;
- }
- break;
- case ACCEPT_QUOTED_VAL:
- if (c == '\0') {
- ostringstream oss;
- oss << "found opening quote for value, but not the closing quote. "
- << "line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '"') && (!escaping)) {
- state = ACCEPT_COMMENT_START;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- // Add anything, including whitespace.
- val += c;
- }
- break;
- case ACCEPT_COMMENT_START:
- if (c == '\0') {
- return new ConfLine(key, val, newsection, comment, line_no);
- }
- else if ((c == '#') || (c == ';')) {
- state = ACCEPT_COMMENT_TEXT;
- }
- else if (isspace(c)) {
- // ignore whitespace
- }
- else {
- ostringstream oss;
- oss << "unexpected character at char " << (l - line) << " of line "
- << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- break;
- case ACCEPT_COMMENT_TEXT:
- if (c == '\0')
- return new ConfLine(key, val, newsection, comment, line_no);
- else
- comment += c;
- break;
- default:
- ceph_abort();
- break;
- }
- ceph_assert(c != '\0'); // We better not go past the end of the input string.
- }
-}
#include <map>
#include <set>
#include <string>
+#include <string_view>
+#include <vector>
#include "include/buffer_fwd.h"
* writing them back out without too much difficulty. Currently, this is not
* implemented, and the file is read-only.
*/
-class ConfLine {
-public:
- ConfLine(const std::string &key_, const std::string &val_,
- const std::string &newsection_, const std::string &comment_, int line_no_);
- bool operator<(const ConfLine &rhs) const;
- friend std::ostream &operator<<(std::ostream& oss, const ConfLine &l);
-
- std::string key, val, newsection;
+struct conf_line_t {
+ conf_line_t() = default;
+ conf_line_t(const std::string& key, const std::string& val);
+ bool operator<(const conf_line_t& rhs) const;
+ std::string key;
+ std::string val;
};
-class ConfSection {
-public:
- typedef std::set <ConfLine>::const_iterator const_line_iter_t;
+std::ostream &operator<<(std::ostream& oss, const conf_line_t& line);
- std::set <ConfLine> lines;
+class conf_section_t : public std::set<conf_line_t> {
+public:
+ conf_section_t() = default;
+ conf_section_t(const std::string& heading,
+ const std::vector<conf_line_t>& lines);
+ std::string heading;
+ friend std::ostream& operator<<(std::ostream& os, const conf_section_t&);
};
-class ConfFile {
+class ConfFile : public std::map<std::string, conf_section_t> {
+ using base_type = std::map<std::string, conf_section_t>;
public:
- typedef std::map <std::string, ConfSection>::iterator section_iter_t;
- typedef std::map <std::string, ConfSection>::const_iterator const_section_iter_t;
-
- ConfFile();
- ~ConfFile();
- void clear();
- int parse_file(const std::string &fname, std::deque<std::string> *errors, std::ostream *warnings);
- int parse_bufferlist(ceph::bufferlist *bl, std::deque<std::string> *errors, std::ostream *warnings);
- int read(const std::string §ion, const std::string_view key,
- std::string &val) const;
-
- const_section_iter_t sections_begin() const;
- const_section_iter_t sections_end() const;
-
+ ConfFile()
+ : ConfFile{std::vector<conf_section_t>{}}
+ {}
+ ConfFile(const conf_line_t& line)
+ : ConfFile{{conf_section_t{"global", {line}}}}
+ {}
+ ConfFile(const std::vector<conf_section_t>& sections);
+ int parse_file(const std::string &fname, std::ostream *warnings);
+ int parse_bufferlist(ceph::bufferlist *bl, std::ostream *warnings);
+ int read(const std::string& section, std::string_view key,
+ std::string &val) const;
static void trim_whitespace(std::string &str, bool strip_internal);
static std::string normalize_key_name(std::string_view key);
- friend std::ostream &operator<<(std::ostream &oss, const ConfFile &cf);
-
private:
- void load_from_buffer(const char *buf, size_t sz,
- std::deque<std::string> *errors, std::ostream *warnings);
- static ConfLine* process_line(int line_no, const char *line,
- std::deque<std::string> *errors);
-
- std::map <std::string, ConfSection> sections;
+ bool load_from_buffer(std::string_view buf, std::ostream* warning);
};
+std::ostream &operator<<(std::ostream& oss, const ConfFile& cf);
+
#endif
cf.clear();
string fn = *c;
- int ret = cf.parse_file(fn.c_str(), &parse_errors, warnings);
+ ostringstream oss;
+ int ret = cf.parse_file(fn.c_str(), &oss);
if (ret == 0)
break;
- else if (ret != -ENOENT)
+ if (ret) {
+ parse_errors.push_back(oss.str());
+ }
+ if (ret != -ENOENT)
return ret;
}
// it must have been all ENOENTs, that's the only way we got here
// Warn about section names that look like old-style section names
std::deque < std::string > old_style_section_names;
- for (ConfFile::const_section_iter_t s = cf.sections_begin();
- s != cf.sections_end(); ++s) {
- const string &str(s->first);
- if (((str.find("mds") == 0) || (str.find("mon") == 0) ||
- (str.find("osd") == 0)) && (str.size() > 3) && (str[3] != '.')) {
- old_style_section_names.push_back(str);
+ for (auto& [name, section] : cf) {
+ if (((name.find("mds") == 0) || (name.find("mon") == 0) ||
+ (name.find("osd") == 0)) && (name.size() > 3) && (name[3] != '.')) {
+ old_style_section_names.push_back(name);
}
}
if (!old_style_section_names.empty()) {
// Return a list of all sections
int md_config_t::get_all_sections(std::vector <std::string> §ions) const
{
- for (ConfFile::const_section_iter_t s = cf.sections_begin();
- s != cf.sections_end(); ++s) {
- sections.push_back(s->first);
+ for (auto [section_name, section] : cf) {
+ sections.push_back(section_name);
+ std::ignore = section;
}
return 0;
}
goto update;
} else if (prefix == "config assimilate-conf") {
ConfFile cf;
- deque<string> errors;
bufferlist bl = m->get_data();
- err = cf.parse_bufferlist(&bl, &errors, &ss);
+ err = cf.parse_bufferlist(&bl, &ss);
if (err < 0) {
- ss << "parse errors: " << errors;
goto reply;
}
bool updated = false;
ostringstream newconf;
- for (auto i = cf.sections_begin(); i != cf.sections_end(); ++i) {
- string section = i->first;
- const ConfSection& s = i->second;
+ for (auto& [section, s] : cf) {
dout(20) << __func__ << " [" << section << "]" << dendl;
bool did_section = false;
- for (auto& j : s.lines) {
+ for (auto& [key, val] : s) {
Option::value_t real_value;
string value;
string errstr;
- if (!j.key.size()) {
+ if (key.empty()) {
continue;
}
// a known and worthy option?
- const Option *o = g_conf().find_option(j.key);
+ const Option *o = g_conf().find_option(key);
if (!o) {
- o = mon->mgrmon()->find_module_option(j.key);
+ o = mon->mgrmon()->find_module_option(key);
}
if (!o ||
o->flags & Option::FLAG_NO_MON_UPDATE) {
goto skip;
}
// normalize
- err = o->parse_value(j.val, &real_value, &errstr, &value);
+ err = o->parse_value(val, &real_value, &errstr, &value);
if (err < 0) {
- dout(20) << __func__ << " failed to parse " << j.key << " = '"
- << j.val << "'" << dendl;
+ dout(20) << __func__ << " failed to parse " << key << " = '"
+ << val << "'" << dendl;
goto skip;
}
// does it conflict with an existing value?
{
const Section *s = config_map.find_section(section);
if (s) {
- auto k = s->options.find(j.key);
+ auto k = s->options.find(key);
if (k != s->options.end()) {
if (value != k->second.raw_value) {
- dout(20) << __func__ << " have " << j.key
+ dout(20) << __func__ << " have " << key
<< " = " << k->second.raw_value
<< " (not " << value << ")" << dendl;
goto skip;
}
- dout(20) << __func__ << " already have " << j.key
+ dout(20) << __func__ << " already have " << key
<< " = " << k->second.raw_value << dendl;
continue;
}
}
}
- dout(20) << __func__ << " add " << j.key << " = " << value
- << " (" << j.val << ")" << dendl;
+ dout(20) << __func__ << " add " << key << " = " << value
+ << " (" << val << ")" << dendl;
{
- string key = section + "/" + j.key;
bufferlist bl;
bl.append(value);
- pending[key] = bl;
+ pending[section + "/" + key] = bl;
updated = true;
}
continue;
skip:
- dout(20) << __func__ << " skip " << j.key << " = " << value
- << " (" << j.val << ")" << dendl;
+ dout(20) << __func__ << " skip " << key << " = " << value
+ << " (" << val << ")" << dendl;
if (!did_section) {
newconf << "\n[" << section << "]\n";
did_section = true;
}
- newconf << "\t" << j.key << " = " << j.val << "\n";
+ newconf << "\t" << key << " = " << val << "\n";
}
}
odata.append(newconf.str());
$ env CEPH_CONF=from-env ceph-conf -s foo bar
did not load config file, using default settings.
.* \-1 Errors while parsing config file! (re)
- .* \-1 parse_file: cannot open from-env: \(2\) No such file or directory (re)
+ .* \-1 parse_file: filesystem error: cannot get file size: No such file or directory \[from-env\] (re)
.* \-1 Errors while parsing config file! (re)
- .* \-1 parse_file: cannot open from-env: \(2\) No such file or directory (re)
+ .* \-1 parse_file: filesystem error: cannot get file size: No such file or directory \[from-env\] (re)
[1]
# command-line arguments should override environment
bar
baz
foo
- global
nobar
thud
bar
baz
foo
- global
nobar
thud
bar
baz
foo
- global
nobar
thud
keyring = osd_keyring ; osd's keyring\n\
";
-// illegal because it has a backslash at the very end
-const char illegal_conf5[] = "\
-[global]\n\
- keyring = something awful\\\\\n\
-";
-
// unicode config file
const char unicode_config_1[] = "\
[global]\n\
}
TEST(ConfUtils, ParseFiles0) {
- std::deque<std::string> err;
std::string val;
- std::ostringstream warn;
- std::string trivial_conf_1_f(next_tempfile(trivial_conf_1));
- ConfFile cf1;
- ASSERT_EQ(cf1.parse_file(trivial_conf_1_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
-
- std::string trivial_conf_2_f(next_tempfile(trivial_conf_2));
- ConfFile cf2;
- ASSERT_EQ(cf2.parse_file(trivial_conf_2_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
-
- bufferlist bl3;
- bl3.append(trivial_conf_3, strlen(trivial_conf_3));
- ConfFile cf3;
- ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
- ASSERT_EQ(cf3.read("global", "log dir", val), 0);
- ASSERT_EQ(val, "barfoo");
-
- std::string trivial_conf_4_f(next_tempfile(trivial_conf_4));
- ConfFile cf4;
- ASSERT_EQ(cf4.parse_file(trivial_conf_4_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
- ASSERT_EQ(cf4.read("global", "log dir", val), 0);
- ASSERT_EQ(val, "barbaz");
+ {
+ std::ostringstream err;
+ std::string trivial_conf_1_f(next_tempfile(trivial_conf_1));
+ ConfFile cf1;
+ ASSERT_EQ(cf1.parse_file(trivial_conf_1_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
+ }
+ {
+ std::ostringstream err;
+ std::string trivial_conf_2_f(next_tempfile(trivial_conf_2));
+ ConfFile cf2;
+ ASSERT_EQ(cf2.parse_file(trivial_conf_2_f.c_str(), &err), -EINVAL);
+ ASSERT_GT(err.tellp(), 0U);
+ }
+ {
+ std::ostringstream err;
+ bufferlist bl3;
+ bl3.append(trivial_conf_3, strlen(trivial_conf_3));
+ ConfFile cf3;
+ ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
+ ASSERT_EQ(cf3.read("global", "log dir", val), 0);
+ ASSERT_EQ(val, "barfoo");
+ }
+ {
+ std::ostringstream err;
+ std::string trivial_conf_4_f(next_tempfile(trivial_conf_4));
+ ConfFile cf4;
+ ASSERT_EQ(cf4.parse_file(trivial_conf_4_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
+ ASSERT_EQ(cf4.read("global", "log dir", val), 0);
+ ASSERT_EQ(val, "barbaz");
+ }
}
TEST(ConfUtils, ParseFiles1) {
- std::deque<std::string> err;
- std::ostringstream warn;
+ std::ostringstream err;
std::string simple_conf_1_f(next_tempfile(simple_conf_1));
ConfFile cf1;
- ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
std::string simple_conf_2_f(next_tempfile(simple_conf_1));
ConfFile cf2;
- ASSERT_EQ(cf2.parse_file(simple_conf_2_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf2.parse_file(simple_conf_2_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
bufferlist bl3;
bl3.append(simple_conf_1, strlen(simple_conf_1));
ConfFile cf3;
- ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf3.parse_bufferlist(&bl3, &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
bufferlist bl4;
bl4.append(simple_conf_2, strlen(simple_conf_2));
ConfFile cf4;
- ASSERT_EQ(cf4.parse_bufferlist(&bl4, &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf4.parse_bufferlist(&bl4, &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
}
TEST(ConfUtils, ReadFiles1) {
- std::deque<std::string> err;
- std::ostringstream warn;
+ std::ostringstream err;
std::string simple_conf_1_f(next_tempfile(simple_conf_1));
ConfFile cf1;
- ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf1.parse_file(simple_conf_1_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
std::string val;
ASSERT_EQ(cf1.read("global", "keyring", val), 0);
bufferlist bl2;
bl2.append(simple_conf_2, strlen(simple_conf_2));
ConfFile cf2;
- ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
ASSERT_EQ(cf2.read("osd0", "keyring", val), 0);
ASSERT_EQ(val, "osd_keyring");
}
TEST(ConfUtils, ReadFiles2) {
- std::deque<std::string> err;
- std::ostringstream warn;
+ std::ostringstream err;
std::string conf3_f(next_tempfile(conf3));
ConfFile cf1;
std::string val;
- ASSERT_EQ(cf1.parse_file(conf3_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf1.parse_file(conf3_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
ASSERT_EQ(cf1.read("global", "log file", val), 0);
ASSERT_EQ(val, "/quite/a/long/path/for/a/log/file");
ASSERT_EQ(cf1.read("global", "pid file", val), 0);
std::string unicode_config_1f(next_tempfile(unicode_config_1));
ConfFile cf2;
- ASSERT_EQ(cf2.parse_file(unicode_config_1f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf2.parse_file(unicode_config_1f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
ASSERT_EQ(cf2.read("global", "log file", val), 0);
ASSERT_EQ(val, "\x66\xd1\x86\xd1\x9d\xd3\xad\xd3\xae");
}
TEST(ConfUtils, IllegalFiles) {
- std::deque<std::string> err;
- std::ostringstream warn;
- std::string illegal_conf1_f(next_tempfile(illegal_conf1));
- ConfFile cf1;
- ASSERT_EQ(cf1.parse_file(illegal_conf1_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
-
- bufferlist bl2;
- bl2.append(illegal_conf2, strlen(illegal_conf2));
- ConfFile cf2;
- ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
-
- std::string illegal_conf3_f(next_tempfile(illegal_conf3));
- ConfFile cf3;
- ASSERT_EQ(cf3.parse_file(illegal_conf3_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
-
- std::string illegal_conf4_f(next_tempfile(illegal_conf4));
- ConfFile cf4;
- ASSERT_EQ(cf4.parse_file(illegal_conf4_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
-
- std::string illegal_conf5_f(next_tempfile(illegal_conf5));
- ConfFile cf5;
- ASSERT_EQ(cf5.parse_file(illegal_conf5_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 1U);
+ {
+ std::ostringstream err;
+ ConfFile cf1;
+ std::string illegal_conf1_f(next_tempfile(illegal_conf1));
+ ASSERT_EQ(cf1.parse_file(illegal_conf1_f.c_str(), &err), -EINVAL);
+ ASSERT_GT(err.tellp(), 0U);
+ }
+ {
+ std::ostringstream err;
+ bufferlist bl2;
+ bl2.append(illegal_conf2, strlen(illegal_conf2));
+ ConfFile cf2;
+ ASSERT_EQ(cf2.parse_bufferlist(&bl2, &err), -EINVAL);
+ ASSERT_GT(err.tellp(), 0U);
+ }
+ {
+ std::ostringstream err;
+ std::string illegal_conf3_f(next_tempfile(illegal_conf3));
+ ConfFile cf3;
+ ASSERT_EQ(cf3.parse_file(illegal_conf3_f.c_str(), &err), -EINVAL);
+ ASSERT_GT(err.tellp(), 0U);
+ }
+ {
+ std::ostringstream err;
+ std::string illegal_conf4_f(next_tempfile(illegal_conf4));
+ ConfFile cf4;
+ ASSERT_EQ(cf4.parse_file(illegal_conf4_f.c_str(), &err), -EINVAL);
+ ASSERT_GT(err.tellp(), 0U);
+ }
}
TEST(ConfUtils, EscapingFiles) {
- std::deque<std::string> err;
- std::ostringstream warn;
+ std::ostringstream err;
std::string escaping_conf_1_f(next_tempfile(escaping_conf_1));
ConfFile cf1;
std::string val;
- ASSERT_EQ(cf1.parse_file(escaping_conf_1_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf1.parse_file(escaping_conf_1_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
ASSERT_EQ(cf1.read("global", "log file", val), 0);
ASSERT_EQ(val, "the \"scare quotes\"");
std::string escaping_conf_2_f(next_tempfile(escaping_conf_2));
ConfFile cf2;
- ASSERT_EQ(cf2.parse_file(escaping_conf_2_f.c_str(), &err, &warn), 0);
- ASSERT_EQ(err.size(), 0U);
+ ASSERT_EQ(cf2.parse_file(escaping_conf_2_f.c_str(), &err), 0);
+ ASSERT_EQ(err.tellp(), 0U);
ASSERT_EQ(cf2.read("apple ][", "log file", val), 0);
ASSERT_EQ(val, "floppy disk");
}
if (!caps_fn.empty()) {
ConfFile cf;
- std::deque<std::string> parse_errors;
- if (cf.parse_file(caps_fn, &parse_errors, &cerr) != 0) {
+ if (cf.parse_file(caps_fn, &cerr) != 0) {
cerr << "could not parse caps file " << caps_fn << std::endl;
exit(1);
}
- complain_about_parse_errors(g_ceph_context, &parse_errors);
map<string, bufferlist> caps;
const char *key_names[] = { "mon", "osd", "mds", "mgr", NULL };
for (int i=0; key_names[i]; i++) {