]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
src: use spirit based ceph.conf parser
authorKefu Chai <kchai@redhat.com>
Sun, 16 Jun 2019 10:08:50 +0000 (18:08 +0800)
committerKefu Chai <kchai@redhat.com>
Tue, 25 Jun 2019 07:09:59 +0000 (15:09 +0800)
before this change, the ceph.conf parser was implemented using a
hand-written state machine. which works fine. but it's hard to
understand and hence difficult to maintain.

in this change, it's rewritten using boost::spirit. which well, is also
a state machine under the hood, but the declarative pattern matching
rules help the human readers to understand the machinary.

before this change, the state machine is able to skip the lines with
invalid syntax and spit a warning message for each of them. after this
change, the parser will just bail out when it fails to match the rules.

and the line continuation marker, i.e., "\" is simply skipped, so it's
allowed to have nothing after the backslash in the value of key-value
pair.

changes are listed as following:

* invalid UTF-8 key/value is now treated as an error
* backslash at the very end is now allowed
* repeated settings are tolerated, and no warnings will be printed.
  the last one wins. before this change, we will print out warning
  messages at seeing lines with duplicated keys.
* malformed section header is now an error
* a setting line which does not parse is now an error
* unmatched quotes in setting value is now an error
* "\" following an empty line is allowed now.
* the format of error message is changed, so the cli test is updated
  accordingly.
* [global] section is not added by default. unless there is a single
  line in the conf file. this is only for the convenience of testing.

Signed-off-by: Kefu Chai <kchai@redhat.com>
src/CMakeLists.txt
src/auth/KeyRing.cc
src/common/ConfUtils.cc
src/common/ConfUtils.h
src/common/config.cc
src/mon/ConfigMonitor.cc
src/test/cli/ceph-conf/env-vs-args.t
src/test/cli/ceph-conf/option.t
src/test/confutils.cc
src/tools/ceph_authtool.cc

index 6e3b18e4182850040cd843bb34bf72481f293d2f..7d353a04f2e7fd809f0e3725ee41da9c70d74236 100644 (file)
@@ -371,6 +371,7 @@ set(ceph_common_deps
   Boost::program_options
   Boost::date_time
   Boost::iostreams
+  StdFilesystem::filesystem
   ${BLKID_LIBRARIES}
   ${Backtrace_LIBRARIES}
   ${BLKIN_LIBRARIES}
index c436df52b5646e3d00fffa106b91c5ff8386e557..816d53e35ee9340d9b24af880477abc6a21d7ddf 100644 (file)
@@ -159,15 +159,12 @@ void KeyRing::decode_plaintext(bufferlist::const_iterator& bli)
   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;
 
@@ -179,17 +176,16 @@ void KeyRing::decode_plaintext(bufferlist::const_iterator& bli)
       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());
       }
     }
index ead1a5cce9ed98c7aabb63d7d279835ccc04d128..d4d2c262a6d904bef26ccfd174637019d3ea4fc7 100644 (file)
  * 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
@@ -81,113 +109,181 @@ clear()
  * 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 &section, 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::
@@ -263,331 +359,13 @@ normalize_key_name(std::string_view key)
 
 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.
-  }
-}
index 68fe54136a8d66085a31e50b03aad62ca4d4ec56..2672ccf41a4150ee82255b6448d4d03c8bf20f70 100644 (file)
@@ -19,6 +19,8 @@
 #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 &section, 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
index 5c4d21f83c72231baf10df5c58b0678801cd2636..f3a6eebaf9f713326c53123bb35a56cf1df4f00d 100644 (file)
@@ -375,10 +375,14 @@ int md_config_t::parse_config_files(ConfigValues& values,
     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
@@ -426,12 +430,10 @@ int md_config_t::parse_config_files(ConfigValues& values,
 
   // 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()) {
@@ -1240,9 +1242,9 @@ void md_config_t::_get_my_sections(const ConfigValues& values,
 // Return a list of all sections
 int md_config_t::get_all_sections(std::vector <std::string> &sections) 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;
 }
index b1395397e5d0011e38f2b065d461bf82f8dec723..45704cb455053d8bc06c17694352ff2bbe12090f 100644 (file)
@@ -552,80 +552,75 @@ bool ConfigMonitor::prepare_command(MonOpRequestRef op)
     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());
index b4cf858f182b776bf6d159eb21e5841a39125259..db8965dedad5c16db3020835fc255fd88a6f7a65 100644 (file)
@@ -2,9 +2,9 @@
   $ 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
index a8b5b6e3e2a96208736bd5ab74b0ca61634855c3..19448843850c503d3d82b53e7d86440d1fbdc9bd 100644 (file)
@@ -22,7 +22,6 @@
   bar
   baz
   foo
-  global
   nobar
   thud
 
@@ -30,7 +29,6 @@
   bar
   baz
   foo
-  global
   nobar
   thud
 
@@ -38,7 +36,6 @@
   bar
   baz
   foo
-  global
   nobar
   thud
 
index e89c80730ea5da3019d69fc32ff6981fcc34492d..981232716c45fd6fa0af35106b0256dd6271aa26 100644 (file)
@@ -231,12 +231,6 @@ const char illegal_conf4[] = "\
         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\
@@ -301,69 +295,74 @@ TEST(ConfUtils, Whitespace) {
 }
 
 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);
@@ -380,8 +379,8 @@ TEST(ConfUtils, ReadFiles1) {
   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");
 
@@ -391,13 +390,12 @@ TEST(ConfUtils, ReadFiles1) {
 }
 
 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);
@@ -405,50 +403,51 @@ TEST(ConfUtils, ReadFiles2) {
 
   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\"");
@@ -459,8 +458,8 @@ TEST(ConfUtils, EscapingFiles) {
 
   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");
index f5a78c52726438c5683fd129e2e5b6267ae57289..c650cc880496c1074be37431c9409fadf023db28 100644 (file)
@@ -254,12 +254,10 @@ int main(int argc, const char **argv)
   }
   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++) {