]>
git.proxmox.com Git - ceph.git/blob - ceph/src/common/ConfUtils.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2011 New Dream Network
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
14 // #define BOOST_SPIRIT_DEBUG
18 #include <experimental/iterator>
26 #include <boost/algorithm/string.hpp>
27 #include <boost/algorithm/string/trim_all.hpp>
28 #include <boost/spirit/include/qi.hpp>
29 #include <boost/spirit/include/phoenix.hpp>
30 #include <boost/spirit/include/support_line_pos_iterator.hpp>
32 #include "include/buffer.h"
33 #include "common/errno.h"
34 #include "common/utf8.h"
35 #include "common/ConfUtils.h"
37 namespace fs
= std::filesystem
;
39 using std::ostringstream
;
42 #define MAX_CONFIG_FILE_SZ 0x40000000
44 conf_line_t::conf_line_t(const std::string
& key
, const std::string
& val
)
45 : key
{ConfFile::normalize_key_name(key
)},
46 val
{boost::algorithm::trim_copy_if(
49 return std::isspace(c
);
53 bool conf_line_t::operator<(const conf_line_t
&rhs
) const
55 // We only compare keys.
56 // If you have more than one line with the same key in a given section, the
61 std::ostream
&operator<<(std::ostream
& oss
, const conf_line_t
&l
)
63 oss
<< "conf_line_t(key = '" << l
.key
<< "', val='" << l
.val
<< "')";
67 conf_section_t::conf_section_t(const std::string
& heading
,
68 const std::vector
<conf_line_t
>& lines
)
71 for (auto& line
: lines
) {
72 auto [where
, inserted
] = insert(line
);
80 ///////////////////////// ConfFile //////////////////////////
82 ConfFile::ConfFile(const std::vector
<conf_section_t
>& sections
)
84 for (auto& section
: sections
) {
85 auto [old_sec
, sec_inserted
] = emplace(section
.heading
, section
);
87 // merge lines in section into old_sec
88 for (auto& line
: section
) {
89 auto [old_line
, line_inserted
] = old_sec
->second
.emplace(line
);
90 // and replace the existing ones if any
92 old_sec
->second
.erase(old_line
);
93 old_sec
->second
.insert(line
);
100 /* We load the whole file into memory and then parse it. Although this is not
101 * the optimal approach, it does mean that most of this code can be shared with
102 * the bufferlist loading function. Since bufferlists are always in-memory, the
103 * load_from_buffer interface works well for them.
104 * In general, configuration files should be a few kilobytes at maximum, so
105 * loading the whole configuration into memory shouldn't be a problem.
107 int ConfFile::parse_file(const std::string
&fname
,
108 std::ostream
*warnings
)
112 if (auto file_size
= fs::file_size(fname
); file_size
> MAX_CONFIG_FILE_SZ
) {
113 *warnings
<< __func__
<< ": config file '" << fname
114 << "' is " << file_size
<< " bytes, "
115 << "but the maximum is " << MAX_CONFIG_FILE_SZ
;
118 } catch (const fs::filesystem_error
& e
) {
120 auto is_other
= fs::is_other(fname
, ec
);
121 if (!ec
&& is_other
) {
125 *warnings
<< __func__
<< ": " << e
.what();
126 return -e
.code().value();
129 std::ifstream ifs
{fname
};
130 std::string buffer
{std::istreambuf_iterator
<char>(ifs
),
131 std::istreambuf_iterator
<char>()};
132 if (parse_buffer(buffer
, warnings
)) {
141 namespace qi
= boost::spirit::qi
;
142 namespace phoenix
= boost::phoenix
;
144 template<typename Iterator
, typename Skipper
>
145 struct IniGrammer
: qi::grammar
<Iterator
, ConfFile(), Skipper
>
147 struct error_handler_t
{
149 template<typename Iter
>
150 auto operator()(Iter first
, Iter last
, Iter where
,
151 const boost::spirit::info
& what
) const {
152 auto line_start
= boost::spirit::get_line_start(first
, where
);
153 os
<< "parse error: expected '" << what
154 << "' in line " << boost::spirit::get_line(where
)
155 << " at position " << boost::spirit::get_column(line_start
, where
) << "\n";
159 IniGrammer(Iterator begin
, std::ostream
& err
)
160 : IniGrammer::base_type
{conf_file
},
161 report_error
{error_handler_t
{err
}}
175 comment_start
= lit('#') | lit(';');
176 continue_marker
= lit('\\') >> eol
;
179 (lit('\\') >> (char_
- eol
)) |
180 (char_
- (comment_start
| eol
));
182 key
%= raw
[+(text_char
- char_("=[ ")) % +blank
];
184 lexeme
[lit('"') >> *(text_char
- '"') > '"'] |
185 lexeme
[lit('\'') >> *(text_char
- '\'') > '\''];
186 unquoted_value
%= *text_char
;
187 comment
= *blank
>> comment_start
> *(char_
- eol
);
188 empty_line
= -(blanks
|comment
) >> eol
;
189 value
%= quoted_value
| unquoted_value
;
191 (blanks
>> key
>> blanks
>> '=' > blanks
> value
> +empty_line
)
192 [_val
= phoenix::construct
<conf_line_t
>(_1
, _2
)];
194 heading
%= lit('[') > +(text_char
- ']') > ']' > +empty_line
;
196 (heading
>> *(key_val
- heading
) >> *eol
)
197 [_val
= phoenix::construct
<conf_section_t
>(_1
, _2
)];
199 (key_val
[_val
= phoenix::construct
<ConfFile
>(_1
)]
201 (*eol
>> (*section
)[_val
= phoenix::construct
<ConfFile
>(_1
)])
204 empty_line
.name("empty_line");
206 quoted_value
.name("quoted value");
207 unquoted_value
.name("unquoted value");
208 key_val
.name("key=val");
209 heading
.name("section name");
210 section
.name("section");
212 qi::on_error
<qi::fail
>(
214 report_error(qi::_1
, qi::_2
, qi::_3
, qi::_4
));
216 BOOST_SPIRIT_DEBUG_NODE(heading
);
217 BOOST_SPIRIT_DEBUG_NODE(section
);
218 BOOST_SPIRIT_DEBUG_NODE(key
);
219 BOOST_SPIRIT_DEBUG_NODE(quoted_value
);
220 BOOST_SPIRIT_DEBUG_NODE(unquoted_value
);
221 BOOST_SPIRIT_DEBUG_NODE(key_val
);
222 BOOST_SPIRIT_DEBUG_NODE(conf_file
);
225 qi::rule
<Iterator
> blanks
;
226 qi::rule
<Iterator
> empty_line
;
227 qi::rule
<Iterator
> comment_start
;
228 qi::rule
<Iterator
> continue_marker
;
229 qi::rule
<Iterator
, char()> text_char
;
230 qi::rule
<Iterator
, std::string(), Skipper
> key
;
231 qi::rule
<Iterator
, std::string(), Skipper
> quoted_value
;
232 qi::rule
<Iterator
, std::string(), Skipper
> unquoted_value
;
233 qi::rule
<Iterator
> comment
;
234 qi::rule
<Iterator
, std::string(), Skipper
> value
;
235 qi::rule
<Iterator
, conf_line_t(), Skipper
> key_val
;
236 qi::rule
<Iterator
, std::string(), Skipper
> heading
;
237 qi::rule
<Iterator
, conf_section_t(), Skipper
> section
;
238 qi::rule
<Iterator
, ConfFile(), Skipper
> conf_file
;
239 boost::phoenix::function
<error_handler_t
> report_error
;
243 bool ConfFile::parse_buffer(std::string_view buf
, std::ostream
* err
)
247 // We'll need to ensure that there's a new line at the end of the buffer,
248 // otherwise the config parsing will fail.
249 std::string _buf
= std::string(buf
) + "\n";
251 std::string_view _buf
= buf
;
253 if (int err_pos
= check_utf8(_buf
.data(), _buf
.size()); err_pos
> 0) {
254 *err
<< "parse error: invalid UTF-8 found at line "
255 << std::count(_buf
.begin(), std::next(_buf
.begin(), err_pos
), '\n') + 1;
258 using iter_t
= boost::spirit::line_pos_iterator
<decltype(_buf
.begin())>;
259 iter_t first
{_buf
.begin()};
260 using skipper_t
= qi::rule
<iter_t
>;
261 IniGrammer
<iter_t
, skipper_t
> grammar
{first
, *err
};
262 skipper_t skipper
= grammar
.continue_marker
| grammar
.comment
;
263 return qi::phrase_parse(first
, iter_t
{_buf
.end()},
264 grammar
, skipper
, *this);
267 int ConfFile::parse_bufferlist(ceph::bufferlist
*bl
,
268 std::ostream
*warnings
)
275 return parse_buffer({bl
->c_str(), bl
->length()}, warnings
) ? 0 : -EINVAL
;
278 int ConfFile::read(std::string_view section_name
,
279 std::string_view key
,
280 std::string
&val
) const
282 string
k(normalize_key_name(key
));
284 if (auto s
= base_type::find(section_name
); s
!= end()) {
285 conf_line_t exemplar
{k
, {}};
286 if (auto line
= s
->second
.find(exemplar
); line
!= s
->second
.end()) {
294 /* Normalize a key name.
296 * Normalized key names have no leading or trailing whitespace, and all
297 * whitespace is stored as underscores. The main reason for selecting this
298 * normal form is so that in common/config.cc, we can use a macro to stringify
299 * the field names of md_config_t and get a key in normal form.
301 std::string
ConfFile::normalize_key_name(std::string_view key
)
304 boost::algorithm::trim_fill_if(k
, "_", isspace
);
308 void ConfFile::check_old_style_section_names(const std::vector
<std::string
>& prefixes
,
311 // Warn about section names that look like old-style section names
312 std::vector
<std::string
> old_style_section_names
;
313 for (auto& [name
, section
] : *this) {
314 for (auto& prefix
: prefixes
) {
315 if (name
.find(prefix
) == 0 && name
.size() > 3 && name
[3] != '.') {
316 old_style_section_names
.push_back(name
);
320 if (!old_style_section_names
.empty()) {
321 os
<< "ERROR! old-style section name(s) found: ";
322 std::copy(std::begin(old_style_section_names
),
323 std::end(old_style_section_names
),
324 std::experimental::make_ostream_joiner(os
, ", "));
325 os
<< ". Please use the new style section names that include a period.";
329 std::ostream
&operator<<(std::ostream
&oss
, const ConfFile
&cf
)
331 for (auto& [name
, section
] : cf
) {
332 oss
<< "[" << name
<< "]\n";
333 for (auto& [key
, val
] : section
) {
335 oss
<< "\t" << key
<< " = \"" << val
<< "\"\n";