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.