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.
26 #include "include/buffer.h"
27 #include "common/errno.h"
28 #include "common/utf8.h"
29 #include "common/ConfUtils.h"
32 using std::ostringstream
;
36 #define MAX_CONFIG_FILE_SZ 0x40000000
38 ////////////////////////////// ConfLine //////////////////////////////
40 ConfLine(const std::string
&key_
, const std::string
&val_
,
41 const std::string
&newsection_
, const std::string
&comment_
, int line_no_
)
42 : key(key_
), val(val_
), newsection(newsection_
)
44 // If you want to implement writable ConfFile support, you'll need to save
45 // the comment and line_no arguments here.
49 operator<(const ConfLine
&rhs
) const
51 // We only compare keys.
52 // If you have more than one line with the same key in a given section, the
60 std::ostream
&operator<<(std::ostream
& oss
, const ConfLine
&l
)
62 oss
<< "ConfLine(key = '" << l
.key
<< "', val='"
63 << l
.val
<< "', newsection='" << l
.newsection
<< "')";
66 ///////////////////////// ConfFile //////////////////////////
83 /* We load the whole file into memory and then parse it. Although this is not
84 * the optimal approach, it does mean that most of this code can be shared with
85 * the bufferlist loading function. Since bufferlists are always in-memory, the
86 * load_from_buffer interface works well for them.
87 * In general, configuration files should be a few kilobytes at maximum, so
88 * loading the whole configuration into memory shouldn't be a problem.
91 parse_file(const std::string
&fname
, std::deque
<std::string
> *errors
,
92 std::ostream
*warnings
)
99 FILE *fp
= fopen(fname
.c_str(), "r");
102 oss
<< __func__
<< ": cannot open " << fname
<< ": " << cpp_strerror(errno
);
103 errors
->push_back(oss
.str());
109 if (fstat(fileno(fp
), &st_buf
)) {
112 oss
<< __func__
<< ": failed to fstat '" << fname
<< "': " << cpp_strerror(ret
);
113 errors
->push_back(oss
.str());
117 if (st_buf
.st_size
> MAX_CONFIG_FILE_SZ
) {
119 oss
<< __func__
<< ": config file '" << fname
<< "' is " << st_buf
.st_size
120 << " bytes, but the maximum is " << MAX_CONFIG_FILE_SZ
;
121 errors
->push_back(oss
.str());
126 sz
= (size_t)st_buf
.st_size
;
127 buf
= (char*)malloc(sz
);
133 if (fread(buf
, 1, sz
, fp
) != sz
) {
137 oss
<< __func__
<< ": fread error while reading '" << fname
<< "': "
138 << cpp_strerror(ret
);
139 errors
->push_back(oss
.str());
144 oss
<< __func__
<< ": unexpected EOF while reading '" << fname
<< "': "
145 << "possible concurrent modification?";
146 errors
->push_back(oss
.str());
152 load_from_buffer(buf
, sz
, errors
, warnings
);
162 parse_bufferlist(ceph::bufferlist
*bl
, std::deque
<std::string
> *errors
,
163 std::ostream
*warnings
)
167 load_from_buffer(bl
->c_str(), bl
->length(), errors
, warnings
);
172 read(const std::string
§ion
, const std::string
&key
, std::string
&val
) const
174 string
k(normalize_key_name(key
));
176 const_section_iter_t s
= sections
.find(section
);
177 if (s
== sections
.end())
179 ConfLine
exemplar(k
, "", "", "", 0);
180 ConfSection::const_line_iter_t l
= s
->second
.lines
.find(exemplar
);
181 if (l
== s
->second
.lines
.end())
187 ConfFile::const_section_iter_t
ConfFile::
188 sections_begin() const
190 return sections
.begin();
193 ConfFile::const_section_iter_t
ConfFile::
196 return sections
.end();
200 trim_whitespace(std::string
&str
, bool strip_internal
)
203 const char *in
= str
.c_str();
206 if ((!c
) || (!isspace(c
)))
210 char output
[strlen(in
) + 1];
214 char *o
= output
+ strlen(output
);
226 if (!strip_internal
) {
232 char output2
[strlen(output
) + 1];
233 char *out2
= output2
;
234 bool prev_was_space
= false;
235 for (char *u
= output
; *u
; ++u
) {
240 prev_was_space
= true;
244 prev_was_space
= false;
251 /* Normalize a key name.
253 * Normalized key names have no leading or trailing whitespace, and all
254 * whitespace is stored as underscores. The main reason for selecting this
255 * normal form is so that in common/config.cc, we can use a macro to stringify
256 * the field names of md_config_t and get a key in normal form.
258 std::string
ConfFile::
259 normalize_key_name(const std::string
&key
)
262 ConfFile::trim_whitespace(k
, true);
263 std::replace(k
.begin(), k
.end(), ' ', '_');
267 std::ostream
&operator<<(std::ostream
&oss
, const ConfFile
&cf
)
269 for (ConfFile::const_section_iter_t s
= cf
.sections_begin();
270 s
!= cf
.sections_end(); ++s
) {
271 oss
<< "[" << s
->first
<< "]\n";
272 for (ConfSection::const_line_iter_t l
= s
->second
.lines
.begin();
273 l
!= s
->second
.lines
.end(); ++l
) {
274 if (!l
->key
.empty()) {
275 oss
<< "\t" << l
->key
<< " = \"" << l
->val
<< "\"\n";
283 load_from_buffer(const char *buf
, size_t sz
, std::deque
<std::string
> *errors
,
284 std::ostream
*warnings
)
288 section_iter_t::value_type
vt("global", ConfSection());
289 pair
< section_iter_t
, bool > vr(sections
.insert(vt
));
291 section_iter_t cur_section
= vr
.first
;
296 size_t line_len
= -1;
300 if ((line_len
+ 1) > rem
)
307 // look for the next newline
308 const char *end
= (const char*)memchr(b
, '\n', rem
);
311 oss
<< "read_conf: ignoring line " << line_no
<< " because it doesn't "
312 << "end with a newline! Please end the config file with a newline.";
313 errors
->push_back(oss
.str());
317 // find length of line, and search for NULLs
319 bool found_null
= false;
320 for (const char *tmp
= b
; tmp
!= end
; ++tmp
) {
329 oss
<< "read_conf: ignoring line " << line_no
<< " because it has "
330 << "an embedded null.";
331 errors
->push_back(oss
.str());
336 if (check_utf8(b
, line_len
)) {
338 oss
<< "read_conf: ignoring line " << line_no
<< " because it is not "
340 errors
->push_back(oss
.str());
345 if ((line_len
>= 1) && (b
[line_len
-1] == '\\')) {
346 // A backslash at the end of a line serves as a line continuation marker.
347 // Combine the next line with this one.
348 // Remove the backslash itself from the text.
349 acc
.append(b
, line_len
- 1);
353 acc
.append(b
, line_len
);
355 //cerr << "acc = '" << acc << "'" << std::endl;
356 ConfLine
*cline
= process_line(line_no
, acc
.c_str(), errors
);
360 const std::string
&csection(cline
->newsection
);
361 if (!csection
.empty()) {
362 std::map
<std::string
, ConfSection
>::value_type
nt(csection
, ConfSection());
363 pair
< section_iter_t
, bool > nr(sections
.insert(nt
));
364 cur_section
= nr
.first
;
367 if (cur_section
->second
.lines
.count(*cline
)) {
368 // replace an existing key/line in this section, so that
372 // will result in foo = 2.
373 cur_section
->second
.lines
.erase(*cline
);
374 if (cline
->key
.length() && warnings
)
375 *warnings
<< "warning: line " << line_no
<< ": '" << cline
->key
<< "' in section '"
376 << cur_section
->first
<< "' redefined " << std::endl
;
378 // add line to current section
379 //std::cerr << "cur_section = " << cur_section->first << ", " << *cline << std::endl;
380 cur_section
->second
.lines
.insert(*cline
);
387 oss
<< "read_conf: don't end with lines that end in backslashes!";
388 errors
->push_back(oss
.str());
393 * A simple state-machine based parser.
394 * This probably could/should be rewritten with something like boost::spirit
395 * or yacc if the grammar ever gets more complex.
398 process_line(int line_no
, const char *line
, std::deque
<std::string
> *errors
)
400 enum acceptor_state_t
{
407 ACCEPT_COMMENT_START
,
410 const char *l
= line
;
411 acceptor_state_t state
= ACCEPT_INIT
;
412 string key
, val
, newsection
, comment
;
413 bool escaping
= false;
419 return NULL
; // blank line. Not an error, but not interesting either.
421 state
= ACCEPT_SECTION_NAME
;
422 else if ((c
== '#') || (c
== ';'))
423 state
= ACCEPT_COMMENT_TEXT
;
426 oss
<< "unexpected right bracket at char " << (l
- line
)
427 << ", line " << line_no
;
428 errors
->push_back(oss
.str());
431 else if (isspace(c
)) {
432 // ignore whitespace here
435 // try to accept this character as a key
440 case ACCEPT_SECTION_NAME
:
443 oss
<< "error parsing new section name: expected right bracket "
444 << "at char " << (l
- line
) << ", line " << line_no
;
445 errors
->push_back(oss
.str());
448 else if ((c
== ']') && (!escaping
)) {
449 trim_whitespace(newsection
, true);
450 if (newsection
.empty()) {
452 oss
<< "error parsing new section name: no section name found? "
453 << "at char " << (l
- line
) << ", line " << line_no
;
454 errors
->push_back(oss
.str());
457 state
= ACCEPT_COMMENT_START
;
459 else if (((c
== '#') || (c
== ';')) && (!escaping
)) {
461 oss
<< "unexpected comment marker while parsing new section name, at "
462 << "char " << (l
- line
) << ", line " << line_no
;
463 errors
->push_back(oss
.str());
466 else if ((c
== '\\') && (!escaping
)) {
475 if ((((c
== '#') || (c
== ';')) && (!escaping
)) || (c
== '\0')) {
478 oss
<< "end of key=val line " << line_no
479 << " reached, no \"=val\" found...missing =?";
481 oss
<< "unexpected character while parsing putative key value, "
482 << "at char " << (l
- line
) << ", line " << line_no
;
484 errors
->push_back(oss
.str());
487 else if ((c
== '=') && (!escaping
)) {
488 key
= normalize_key_name(key
);
491 oss
<< "error parsing key name: no key name found? "
492 << "at char " << (l
- line
) << ", line " << line_no
;
493 errors
->push_back(oss
.str());
496 state
= ACCEPT_VAL_START
;
498 else if ((c
== '\\') && (!escaping
)) {
506 case ACCEPT_VAL_START
:
508 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
509 else if ((c
== '#') || (c
== ';'))
510 state
= ACCEPT_COMMENT_TEXT
;
512 state
= ACCEPT_QUOTED_VAL
;
513 else if (isspace(c
)) {
517 // try to accept character as a val
518 state
= ACCEPT_UNQUOTED_VAL
;
522 case ACCEPT_UNQUOTED_VAL
:
526 oss
<< "error parsing value name: unterminated escape sequence "
527 << "at char " << (l
- line
) << ", line " << line_no
;
528 errors
->push_back(oss
.str());
531 trim_whitespace(val
, false);
532 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
534 else if (((c
== '#') || (c
== ';')) && (!escaping
)) {
535 trim_whitespace(val
, false);
536 state
= ACCEPT_COMMENT_TEXT
;
538 else if ((c
== '\\') && (!escaping
)) {
546 case ACCEPT_QUOTED_VAL
:
549 oss
<< "found opening quote for value, but not the closing quote. "
550 << "line " << line_no
;
551 errors
->push_back(oss
.str());
554 else if ((c
== '"') && (!escaping
)) {
555 state
= ACCEPT_COMMENT_START
;
557 else if ((c
== '\\') && (!escaping
)) {
562 // Add anything, including whitespace.
566 case ACCEPT_COMMENT_START
:
568 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
570 else if ((c
== '#') || (c
== ';')) {
571 state
= ACCEPT_COMMENT_TEXT
;
573 else if (isspace(c
)) {
578 oss
<< "unexpected character at char " << (l
- line
) << " of line "
580 errors
->push_back(oss
.str());
584 case ACCEPT_COMMENT_TEXT
:
586 return new ConfLine(key
, val
, newsection
, comment
, line_no
);
594 assert(c
!= '\0'); // We better not go past the end of the input string.