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.
25 #include <sys/types.h>
30 #include "ConfUtils.h"
33 using std::ostringstream
;
37 #define MAX_CONFIG_FILE_SZ 0x40000000
39 ////////////////////////////// ConfLine //////////////////////////////
41 ConfLine(const std::string
&key_
, const std::string val_
,
42 const std::string newsection_
, const std::string comment_
, int line_no_
)
43 : key(key_
), val(val_
), newsection(newsection_
)
45 // If you want to implement writable ConfFile support, you'll need to save
46 // the comment and line_no arguments here.
50 operator<(const ConfLine
&rhs
) const
52 // We only compare keys.
53 // If you have more than one line with the same key in a given section, the
61 std::ostream
&operator<<(std::ostream
& oss
, const ConfLine
&l
)
63 oss
<< "ConfLine(key = '" << l
.key
<< "', val='"
64 << l
.val
<< "', newsection='" << l
.newsection
<< "')";
67 ///////////////////////// ConfFile //////////////////////////
84 /* We load the whole file into memory and then parse it. Although this is not
85 * the optimal approach, it does mean that most of this code can be shared with
86 * the bufferlist loading function. Since bufferlists are always in-memory, the
87 * load_from_buffer interface works well for them.
88 * In general, configuration files should be a few kilobytes at maximum, so
89 * loading the whole configuration into memory shouldn't be a problem.
92 parse_file(const std::string
&fname
, std::deque
<std::string
> *errors
,
93 std::ostream
*warnings
)
101 FILE *fp
= fopen(fname
.c_str(), "r");
108 if (fstat(fileno(fp
), &st_buf
)) {
111 oss
<< "read_conf: failed to fstat '" << fname
<< "': " << strerror_r(ret
, buf2
, sizeof(buf2
));
112 errors
->push_back(oss
.str());
116 if (st_buf
.st_size
> MAX_CONFIG_FILE_SZ
) {
118 oss
<< "read_conf: config file '" << fname
<< "' is " << st_buf
.st_size
119 << " bytes, but the maximum is " << MAX_CONFIG_FILE_SZ
;
120 errors
->push_back(oss
.str());
125 sz
= (size_t)st_buf
.st_size
;
126 buf
= (char*)malloc(sz
);
132 if (fread(buf
, 1, sz
, fp
) != sz
) {
136 oss
<< "read_conf: fread error while reading '" << fname
<< "': "
137 << strerror_r(ret
, buf2
, sizeof(buf2
));
138 errors
->push_back(oss
.str());
143 oss
<< "read_conf: unexpected EOF while reading '" << fname
<< "': "
144 << "possible concurrent modification?";
145 errors
->push_back(oss
.str());
151 load_from_buffer(buf
, sz
, errors
, warnings
);
161 read(const std::string
§ion
, const std::string
&key
, std::string
&val
) const
163 string
k(normalize_key_name(key
));
165 const_section_iter_t s
= sections
.find(section
);
166 if (s
== sections
.end())
168 ConfLine
exemplar(k
, "", "", "", 0);
169 ConfSection::const_line_iter_t l
= s
->second
.lines
.find(exemplar
);
170 if (l
== s
->second
.lines
.end())
176 ConfFile::const_section_iter_t
ConfFile::
177 sections_begin() const
179 return sections
.begin();
182 ConfFile::const_section_iter_t
ConfFile::
185 return sections
.end();
189 trim_whitespace(std::string
&str
, bool strip_internal
)
192 const char *in
= str
.c_str();
195 if ((!c
) || (!isspace(c
)))
199 char output
[strlen(in
) + 1];
203 char *o
= output
+ strlen(output
);
215 if (!strip_internal
) {
221 char output2
[strlen(output
) + 1];
222 char *out2
= output2
;
223 bool prev_was_space
= false;
224 for (char *u
= output
; *u
; ++u
) {
229 prev_was_space
= true;
233 prev_was_space
= false;
240 /* Normalize a key name.
242 * Normalized key names have no leading or trailing whitespace, and all
243 * whitespace is stored as underscores. The main reason for selecting this
244 * normal form is so that in common/config.cc, we can use a macro to stringify
245 * the field names of md_config_t and get a key in normal form.
247 std::string
ConfFile::
248 normalize_key_name(const std::string
&key
)
251 ConfFile::trim_whitespace(k
, true);
252 std::replace(k
.begin(), k
.end(), ' ', '_');
256 std::ostream
&operator<<(std::ostream
&oss
, const ConfFile
&cf
)
258 for (ConfFile::const_section_iter_t s
= cf
.sections_begin();
259 s
!= cf
.sections_end(); ++s
) {
260 oss
<< "[" << s
->first
<< "]\n";
261 for (ConfSection::const_line_iter_t l
= s
->second
.lines
.begin();
262 l
!= s
->second
.lines
.end(); ++l
) {
263 if (!l
->key
.empty()) {
264 oss
<< "\t" << l
->key
<< " = \"" << l
->val
<< "\"\n";
272 load_from_buffer(const char *buf
, size_t sz
, std::deque
<std::string
> *errors
,
273 std::ostream
*warnings
)
277 section_iter_t::value_type
vt("global", ConfSection());
278 pair
< section_iter_t
, bool > vr(sections
.insert(vt
));
280 section_iter_t cur_section
= vr
.first
;
285 size_t line_len
= -1;
294 // look for the next newline
295 const char *end
= (const char*)memchr(b
, '\n', rem
);
298 oss
<< "read_conf: ignoring line " << line_no
<< " because it doesn't "
299 << "end with a newline! Please end the config file with a newline.";
300 errors
->push_back(oss
.str());
304 // find length of line, and search for NULLs
306 bool found_null
= false;
307 for (const char *tmp
= b
; tmp
!= end
; ++tmp
) {
316 oss
<< "read_conf: ignoring line " << line_no
<< " because it has "
317 << "an embedded null.";
318 errors
->push_back(oss
.str());
323 if ((line_len
>= 1) && (b
[line_len
-1] == '\\')) {
324 // A backslash at the end of a line serves as a line continuation marker.
325 // Combine the next line with this one.
326 // Remove the backslash itself from the text.
327 acc
.append(b
, line_len
- 1);
331 acc
.append(b
, line_len
);
333 //cerr << "acc = '" << acc << "'" << std::endl;
334 ConfLine
*cline
= process_line(line_no
, acc
.c_str(), errors
);
338 const std::string
&csection(cline
->newsection
);
339 if (!csection
.empty()) {
340 std::map
<std::string
, ConfSection
>::value_type
nt(csection
, ConfSection());
341 pair
< section_iter_t
, bool > nr(sections
.insert(nt
));
342 cur_section
= nr
.first
;
345 if (cur_section
->second
.lines
.count(*cline
)) {
346 // replace an existing key/line in this section, so that
350 // will result in foo = 2.
351 cur_section
->second
.lines
.erase(*cline
);
352 if (cline
->key
.length() && warnings
)
353 *warnings
<< "warning: line " << line_no
<< ": '" << cline
->key
<< "' in section '"
354 << cur_section
->first
<< "' redefined " << std::endl
;
356 // add line to current section
357 //std::cerr << "cur_section = " << cur_section->first << ", " << *cline << std::endl;
358 cur_section
->second
.lines
.insert(*cline
);
365 oss
<< "read_conf: don't end with lines that end in backslashes!";
366 errors
->push_back(oss
.str());
371 * A simple state-machine based parser.
372 * This probably could/should be rewritten with something like boost::spirit
373 * or yacc if the grammar ever gets more complex.
376 process_line(int line_no
, const char *line
, std::deque
<std::string
> *errors
)
378 enum acceptor_state_t
{
385 ACCEPT_COMMENT_START
,
388 const char *l
= line
;
389 acceptor_state_t state
= ACCEPT_INIT
;
390 string key
, val
, newsection
, comment
;
391 bool escaping
= false;
397 return NULL
; // blank line. Not an error, but not interesting either.
399 state
= ACCEPT_SECTION_NAME
;
400 else if ((c
== '#') || (c
== ';'))
401 state
= ACCEPT_COMMENT_TEXT
;
404 oss
<< "unexpected right bracket at char " << (l
- line
)
405 << ", line " << line_no
;
406 errors
->push_back(oss
.str());
409 else if (isspace(c
)) {
410 // ignore whitespace here
413 // try to accept this character as a key
418 case ACCEPT_SECTION_NAME
:
421 oss
<< "error parsing new section name: expected right bracket "
422 << "at char " << (l
- line
) << ", line " << line_no
;
423 errors
->push_back(oss
.str());
426 else if ((c
== ']') && (!escaping
)) {
427 trim_whitespace(newsection
, true);
428 if (newsection
.empty()) {
430 oss
<< "error parsing new section name: no section name found? "
431 << "at char " << (l
- line
) << ", line " << line_no
;
432 errors
->push_back(oss
.str());
435 state
= ACCEPT_COMMENT_START
;
437 else if (((c
== '#') || (c
== ';')) && (!escaping
)) {
439 oss
<< "unexpected comment marker while parsing new section name, at "
440 << "char " << (l
- line
) << ", line " << line_no
;
441 errors
->push_back(oss
.str());
444 else if ((c
== '\\') && (!escaping
)) {
453 if ((((c
== '#') || (c
== ';')) && (!escaping
)) || (c
== '\0')) {
456 oss
<< "end of key=val line " << line_no
457 << " reached, no \"=val\" found...missing =?";
459 oss
<< "unexpected character while parsing putative key value, "
460 << "at char " << (l
- line
) << ", line " << line_no
;
462 errors
->push_back(oss
.str());
465 else if ((c
== '=') && (!escaping
)) {
466 key
= normalize_key_name(key
);
469 oss
<< "error parsing key name: no key name found? "
470 << "at char " << (l
- line
) << ", line " << line_no
;
471 errors
->push_back(oss
.str());
474 state
= ACCEPT_VAL_START
;
476 else if ((c
== '\\') && (!escaping
)) {
484 case ACCEPT_VAL_START
:
486 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
487 else if ((c
== '#') || (c
== ';'))
488 state
= ACCEPT_COMMENT_TEXT
;
490 state
= ACCEPT_QUOTED_VAL
;
491 else if (isspace(c
)) {
495 // try to accept character as a val
496 state
= ACCEPT_UNQUOTED_VAL
;
500 case ACCEPT_UNQUOTED_VAL
:
504 oss
<< "error parsing value name: unterminated escape sequence "
505 << "at char " << (l
- line
) << ", line " << line_no
;
506 errors
->push_back(oss
.str());
509 trim_whitespace(val
, false);
510 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
512 else if (((c
== '#') || (c
== ';')) && (!escaping
)) {
513 trim_whitespace(val
, false);
514 state
= ACCEPT_COMMENT_TEXT
;
516 else if ((c
== '\\') && (!escaping
)) {
524 case ACCEPT_QUOTED_VAL
:
527 oss
<< "found opening quote for value, but not the closing quote. "
528 << "line " << line_no
;
529 errors
->push_back(oss
.str());
532 else if ((c
== '"') && (!escaping
)) {
533 state
= ACCEPT_COMMENT_START
;
535 else if ((c
== '\\') && (!escaping
)) {
540 // Add anything, including whitespace.
544 case ACCEPT_COMMENT_START
:
546 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
548 else if ((c
== '#') || (c
== ';')) {
549 state
= ACCEPT_COMMENT_TEXT
;
551 else if (isspace(c
)) {
556 oss
<< "unexpected character at char " << (l
- line
) << " of line "
558 errors
->push_back(oss
.str());
562 case ACCEPT_COMMENT_TEXT
:
564 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
572 assert(c
!= '\0'); // We better not go past the end of the input string.