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.
21 #include "include/buffer.h"
22 #include "common/errno.h"
23 #include "common/utf8.h"
24 #include "common/ConfUtils.h"
26 using std::ostringstream;
30 #define MAX_CONFIG_FILE_SZ 0x40000000
32 ////////////////////////////// ConfLine //////////////////////////////
34 ConfLine(const std::string &key_, const std::string &val_,
35 const std::string &newsection_, const std::string &comment_, int line_no_)
36 : key(key_), val(val_), newsection(newsection_)
38 // If you want to implement writable ConfFile support, you'll need to save
39 // the comment and line_no arguments here.
43 operator<(const ConfLine &rhs) const
45 // We only compare keys.
46 // If you have more than one line with the same key in a given section, the
54 std::ostream &operator<<(std::ostream& oss, const ConfLine &l)
56 oss << "ConfLine(key = '" << l.key << "', val='"
57 << l.val << "', newsection='" << l.newsection << "')";
60 ///////////////////////// ConfFile //////////////////////////
77 /* We load the whole file into memory and then parse it. Although this is not
78 * the optimal approach, it does mean that most of this code can be shared with
79 * the bufferlist loading function. Since bufferlists are always in-memory, the
80 * load_from_buffer interface works well for them.
81 * In general, configuration files should be a few kilobytes at maximum, so
82 * loading the whole configuration into memory shouldn't be a problem.
85 parse_file(const std::string &fname, std::deque<std::string> *errors,
86 std::ostream *warnings)
93 FILE *fp = fopen(fname.c_str(), "r");
96 oss << __func__ << ": cannot open " << fname << ": " << cpp_strerror(errno);
97 errors->push_back(oss.str());
103 if (fstat(fileno(fp), &st_buf)) {
106 oss << __func__ << ": failed to fstat '" << fname << "': " << cpp_strerror(ret);
107 errors->push_back(oss.str());
111 if (st_buf.st_size > MAX_CONFIG_FILE_SZ) {
113 oss << __func__ << ": config file '" << fname << "' is " << st_buf.st_size
114 << " bytes, but the maximum is " << MAX_CONFIG_FILE_SZ;
115 errors->push_back(oss.str());
120 sz = (size_t)st_buf.st_size;
121 buf = (char*)malloc(sz);
127 if (fread(buf, 1, sz, fp) != sz) {
131 oss << __func__ << ": fread error while reading '" << fname << "': "
132 << cpp_strerror(ret);
133 errors->push_back(oss.str());
138 oss << __func__ << ": unexpected EOF while reading '" << fname << "': "
139 << "possible concurrent modification?";
140 errors->push_back(oss.str());
146 load_from_buffer(buf, sz, errors, warnings);
156 parse_bufferlist(ceph::bufferlist *bl, std::deque<std::string> *errors,
157 std::ostream *warnings)
161 load_from_buffer(bl->c_str(), bl->length(), errors, warnings);
166 read(const std::string §ion, const std::string &key, std::string &val) const
168 string k(normalize_key_name(key));
170 const_section_iter_t s = sections.find(section);
171 if (s == sections.end())
173 ConfLine exemplar(k, "", "", "", 0);
174 ConfSection::const_line_iter_t l = s->second.lines.find(exemplar);
175 if (l == s->second.lines.end())
181 ConfFile::const_section_iter_t ConfFile::
182 sections_begin() const
184 return sections.begin();
187 ConfFile::const_section_iter_t ConfFile::
190 return sections.end();
194 trim_whitespace(std::string &str, bool strip_internal)
197 const char *in = str.c_str();
200 if ((!c) || (!isspace(c)))
204 char output[strlen(in) + 1];
208 char *o = output + strlen(output);
220 if (!strip_internal) {
226 char output2[strlen(output) + 1];
227 char *out2 = output2;
228 bool prev_was_space = false;
229 for (char *u = output; *u; ++u) {
234 prev_was_space = true;
238 prev_was_space = false;
245 /* Normalize a key name.
247 * Normalized key names have no leading or trailing whitespace, and all
248 * whitespace is stored as underscores. The main reason for selecting this
249 * normal form is so that in common/config.cc, we can use a macro to stringify
250 * the field names of md_config_t and get a key in normal form.
252 std::string ConfFile::
253 normalize_key_name(const std::string &key)
256 ConfFile::trim_whitespace(k, true);
257 std::replace(k.begin(), k.end(), ' ', '_');
261 std::ostream &operator<<(std::ostream &oss, const ConfFile &cf)
263 for (ConfFile::const_section_iter_t s = cf.sections_begin();
264 s != cf.sections_end(); ++s) {
265 oss << "[" << s->first << "]\n";
266 for (ConfSection::const_line_iter_t l = s->second.lines.begin();
267 l != s->second.lines.end(); ++l) {
268 if (!l->key.empty()) {
269 oss << "\t" << l->key << " = \"" << l->val << "\"\n";
277 load_from_buffer(const char *buf, size_t sz, std::deque<std::string> *errors,
278 std::ostream *warnings)
282 section_iter_t::value_type vt("global", ConfSection());
283 pair < section_iter_t, bool > vr(sections.insert(vt));
285 section_iter_t cur_section = vr.first;
290 size_t line_len = -1;
294 if ((line_len + 1) > rem)
301 // look for the next newline
302 const char *end = (const char*)memchr(b, '\n', rem);
305 oss << "read_conf: ignoring line " << line_no << " because it doesn't "
306 << "end with a newline! Please end the config file with a newline.";
307 errors->push_back(oss.str());
311 // find length of line, and search for NULLs
313 bool found_null = false;
314 for (const char *tmp = b; tmp != end; ++tmp) {
323 oss << "read_conf: ignoring line " << line_no << " because it has "
324 << "an embedded null.";
325 errors->push_back(oss.str());
330 if (check_utf8(b, line_len)) {
332 oss << "read_conf: ignoring line " << line_no << " because it is not "
334 errors->push_back(oss.str());
339 if ((line_len >= 1) && (b[line_len-1] == '\\')) {
340 // A backslash at the end of a line serves as a line continuation marker.
341 // Combine the next line with this one.
342 // Remove the backslash itself from the text.
343 acc.append(b, line_len - 1);
347 acc.append(b, line_len);
349 //cerr << "acc = '" << acc << "'" << std::endl;
350 ConfLine *cline = process_line(line_no, acc.c_str(), errors);
354 const std::string &csection(cline->newsection);
355 if (!csection.empty()) {
356 std::map <std::string, ConfSection>::value_type nt(csection, ConfSection());
357 pair < section_iter_t, bool > nr(sections.insert(nt));
358 cur_section = nr.first;
361 if (cur_section->second.lines.count(*cline)) {
362 // replace an existing key/line in this section, so that
366 // will result in foo = 2.
367 cur_section->second.lines.erase(*cline);
368 if (cline->key.length() && warnings)
369 *warnings << "warning: line " << line_no << ": '" << cline->key << "' in section '"
370 << cur_section->first << "' redefined " << std::endl;
372 // add line to current section
373 //std::cerr << "cur_section = " << cur_section->first << ", " << *cline << std::endl;
374 cur_section->second.lines.insert(*cline);
381 oss << "read_conf: don't end with lines that end in backslashes!";
382 errors->push_back(oss.str());
387 * A simple state-machine based parser.
388 * This probably could/should be rewritten with something like boost::spirit
389 * or yacc if the grammar ever gets more complex.
392 process_line(int line_no, const char *line, std::deque<std::string> *errors)
394 enum acceptor_state_t {
401 ACCEPT_COMMENT_START,
404 const char *l = line;
405 acceptor_state_t state = ACCEPT_INIT;
406 string key, val, newsection, comment;
407 bool escaping = false;
413 return NULL; // blank line. Not an error, but not interesting either.
415 state = ACCEPT_SECTION_NAME;
416 else if ((c == '#') || (c == ';'))
417 state = ACCEPT_COMMENT_TEXT;
420 oss << "unexpected right bracket at char " << (l - line)
421 << ", line " << line_no;
422 errors->push_back(oss.str());
425 else if (isspace(c)) {
426 // ignore whitespace here
429 // try to accept this character as a key
434 case ACCEPT_SECTION_NAME:
437 oss << "error parsing new section name: expected right bracket "
438 << "at char " << (l - line) << ", line " << line_no;
439 errors->push_back(oss.str());
442 else if ((c == ']') && (!escaping)) {
443 trim_whitespace(newsection, true);
444 if (newsection.empty()) {
446 oss << "error parsing new section name: no section name found? "
447 << "at char " << (l - line) << ", line " << line_no;
448 errors->push_back(oss.str());
451 state = ACCEPT_COMMENT_START;
453 else if (((c == '#') || (c == ';')) && (!escaping)) {
455 oss << "unexpected comment marker while parsing new section name, at "
456 << "char " << (l - line) << ", line " << line_no;
457 errors->push_back(oss.str());
460 else if ((c == '\\') && (!escaping)) {
469 if ((((c == '#') || (c == ';')) && (!escaping)) || (c == '\0')) {
472 oss << "end of key=val line " << line_no
473 << " reached, no \"=val\" found...missing =?";
475 oss << "unexpected character while parsing putative key value, "
476 << "at char " << (l - line) << ", line " << line_no;
478 errors->push_back(oss.str());
481 else if ((c == '=') && (!escaping)) {
482 key = normalize_key_name(key);
485 oss << "error parsing key name: no key name found? "
486 << "at char " << (l - line) << ", line " << line_no;
487 errors->push_back(oss.str());
490 state = ACCEPT_VAL_START;
492 else if ((c == '\\') && (!escaping)) {
500 case ACCEPT_VAL_START:
502 return new ConfLine(key, val, newsection, comment, line_no);
503 else if ((c == '#') || (c == ';'))
504 state = ACCEPT_COMMENT_TEXT;
506 state = ACCEPT_QUOTED_VAL;
507 else if (isspace(c)) {
511 // try to accept character as a val
512 state = ACCEPT_UNQUOTED_VAL;
516 case ACCEPT_UNQUOTED_VAL:
520 oss << "error parsing value name: unterminated escape sequence "
521 << "at char " << (l - line) << ", line " << line_no;
522 errors->push_back(oss.str());
525 trim_whitespace(val, false);
526 return new ConfLine(key, val, newsection, comment, line_no);
528 else if (((c == '#') || (c == ';')) && (!escaping)) {
529 trim_whitespace(val, false);
530 state = ACCEPT_COMMENT_TEXT;
532 else if ((c == '\\') && (!escaping)) {
540 case ACCEPT_QUOTED_VAL:
543 oss << "found opening quote for value, but not the closing quote. "
544 << "line " << line_no;
545 errors->push_back(oss.str());
548 else if ((c == '"') && (!escaping)) {
549 state = ACCEPT_COMMENT_START;
551 else if ((c == '\\') && (!escaping)) {
556 // Add anything, including whitespace.
560 case ACCEPT_COMMENT_START:
562 return new ConfLine(key, val, newsection, comment, line_no);
564 else if ((c == '#') || (c == ';')) {
565 state = ACCEPT_COMMENT_TEXT;
567 else if (isspace(c)) {
572 oss << "unexpected character at char " << (l - line) << " of line "
574 errors->push_back(oss.str());
578 case ACCEPT_COMMENT_TEXT:
580 return new ConfLine(key, val, newsection, comment, line_no);
588 assert(c != '\0'); // We better not go past the end of the input string.