+++ /dev/null
-// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
-// vim: ts=8 sw=2 smarttab
-/*
- * Ceph - scalable distributed file system
- *
- * Copyright (C) 2011 New Dream Network
- *
- * This is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License version 2.1, as published by the Free Software
- * Foundation. See file COPYING.
- *
- */
-
-#include <algorithm>
-#include <errno.h>
-#include <list>
-#include <map>
-#include <sstream>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <string>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <iostream>
-
-#include <assert.h>
-#include "ConfUtils.h"
-
-using std::cerr;
-using std::ostringstream;
-using std::pair;
-using std::string;
-
-#define MAX_CONFIG_FILE_SZ 0x40000000
-
-////////////////////////////// ConfLine //////////////////////////////
-ConfLine::
-ConfLine(const std::string &key_, const std::string val_,
- const std::string newsection_, const std::string comment_, int line_no_)
- : key(key_), val(val_), newsection(newsection_)
-{
- // If you want to implement writable ConfFile support, you'll need to save
- // the comment and line_no arguments here.
-}
-
-bool ConfLine::
-operator<(const ConfLine &rhs) const
-{
- // We only compare keys.
- // If you have more than one line with the same key in a given section, the
- // last one wins.
- if (key < rhs.key)
- return true;
- else
- return false;
-}
-
-std::ostream &operator<<(std::ostream& oss, const ConfLine &l)
-{
- oss << "ConfLine(key = '" << l.key << "', val='"
- << l.val << "', newsection='" << l.newsection << "')";
- return oss;
-}
-///////////////////////// ConfFile //////////////////////////
-ConfFile::
-ConfFile()
-{
-}
-
-ConfFile::
-~ConfFile()
-{
-}
-
-void ConfFile::
-clear()
-{
- sections.clear();
-}
-
-/* We load the whole file into memory and then parse it. Although this is not
- * the optimal approach, it does mean that most of this code can be shared with
- * the bufferlist loading function. Since bufferlists are always in-memory, the
- * load_from_buffer interface works well for them.
- * In general, configuration files should be a few kilobytes at maximum, so
- * loading the whole configuration into memory shouldn't be a problem.
- */
-int ConfFile::
-parse_file(const std::string &fname, std::deque<std::string> *errors,
- std::ostream *warnings)
-{
- clear();
-
- int ret = 0;
- size_t sz;
- char *buf = NULL;
- char buf2[128];
- FILE *fp = fopen(fname.c_str(), "r");
- if (!fp) {
- ret = -errno;
- return ret;
- }
-
- struct stat st_buf;
- if (fstat(fileno(fp), &st_buf)) {
- ret = -errno;
- ostringstream oss;
- oss << "read_conf: failed to fstat '" << fname << "': " << strerror_r(ret, buf2, sizeof(buf2));
- errors->push_back(oss.str());
- goto done;
- }
-
- if (st_buf.st_size > MAX_CONFIG_FILE_SZ) {
- ostringstream oss;
- oss << "read_conf: config file '" << fname << "' is " << st_buf.st_size
- << " bytes, but the maximum is " << MAX_CONFIG_FILE_SZ;
- errors->push_back(oss.str());
- ret = -EINVAL;
- goto done;
- }
-
- sz = (size_t)st_buf.st_size;
- buf = (char*)malloc(sz);
- if (!buf) {
- ret = -ENOMEM;
- goto done;
- }
-
- if (fread(buf, 1, sz, fp) != sz) {
- if (ferror(fp)) {
- ret = -errno;
- ostringstream oss;
- oss << "read_conf: fread error while reading '" << fname << "': "
- << strerror_r(ret, buf2, sizeof(buf2));
- errors->push_back(oss.str());
- goto done;
- }
- else {
- ostringstream oss;
- oss << "read_conf: unexpected EOF while reading '" << fname << "': "
- << "possible concurrent modification?";
- errors->push_back(oss.str());
- ret = -EIO;
- goto done;
- }
- }
-
- load_from_buffer(buf, sz, errors, warnings);
- ret = 0;
-
-done:
- free(buf);
- fclose(fp);
- return ret;
-}
-
-int ConfFile::
-read(const std::string §ion, const std::string &key, std::string &val) const
-{
- string k(normalize_key_name(key));
-
- const_section_iter_t s = sections.find(section);
- if (s == sections.end())
- return -ENOENT;
- ConfLine exemplar(k, "", "", "", 0);
- ConfSection::const_line_iter_t l = s->second.lines.find(exemplar);
- if (l == s->second.lines.end())
- return -ENOENT;
- val = l->val;
- return 0;
-}
-
-ConfFile::const_section_iter_t ConfFile::
-sections_begin() const
-{
- return sections.begin();
-}
-
-ConfFile::const_section_iter_t ConfFile::
-sections_end() const
-{
- return sections.end();
-}
-
-void ConfFile::
-trim_whitespace(std::string &str, bool strip_internal)
-{
- // strip preceding
- const char *in = str.c_str();
- while (true) {
- char c = *in;
- if ((!c) || (!isspace(c)))
- break;
- ++in;
- }
- char output[strlen(in) + 1];
- strcpy(output, in);
-
- // strip trailing
- char *o = output + strlen(output);
- while (true) {
- if (o == output)
- break;
- --o;
- if (!isspace(*o)) {
- ++o;
- *o = '\0';
- break;
- }
- }
-
- if (!strip_internal) {
- str.assign(output);
- return;
- }
-
- // strip internal
- char output2[strlen(output) + 1];
- char *out2 = output2;
- bool prev_was_space = false;
- for (char *u = output; *u; ++u) {
- char c = *u;
- if (isspace(c)) {
- if (!prev_was_space)
- *out2++ = c;
- prev_was_space = true;
- }
- else {
- *out2++ = c;
- prev_was_space = false;
- }
- }
- *out2++ = '\0';
- str.assign(output2);
-}
-
-/* Normalize a key name.
- *
- * Normalized key names have no leading or trailing whitespace, and all
- * whitespace is stored as underscores. The main reason for selecting this
- * normal form is so that in common/config.cc, we can use a macro to stringify
- * the field names of md_config_t and get a key in normal form.
- */
-std::string ConfFile::
-normalize_key_name(const std::string &key)
-{
- string k(key);
- ConfFile::trim_whitespace(k, true);
- std::replace(k.begin(), k.end(), ' ', '_');
- return k;
-}
-
-std::ostream &operator<<(std::ostream &oss, const ConfFile &cf)
-{
- for (ConfFile::const_section_iter_t s = cf.sections_begin();
- s != cf.sections_end(); ++s) {
- oss << "[" << s->first << "]\n";
- for (ConfSection::const_line_iter_t l = s->second.lines.begin();
- l != s->second.lines.end(); ++l) {
- if (!l->key.empty()) {
- oss << "\t" << l->key << " = \"" << l->val << "\"\n";
- }
- }
- }
- return oss;
-}
-
-void ConfFile::
-load_from_buffer(const char *buf, size_t sz, std::deque<std::string> *errors,
- std::ostream *warnings)
-{
- errors->clear();
-
- section_iter_t::value_type vt("global", ConfSection());
- pair < section_iter_t, bool > vr(sections.insert(vt));
- assert(vr.second);
- section_iter_t cur_section = vr.first;
- std::string acc;
-
- const char *b = buf;
- int line_no = 0;
- size_t line_len = -1;
- size_t rem = sz;
- while (1) {
- b += line_len + 1;
- rem -= line_len + 1;
- if (rem == 0)
- break;
- line_no++;
-
- // look for the next newline
- const char *end = (const char*)memchr(b, '\n', rem);
- if (!end) {
- ostringstream oss;
- oss << "read_conf: ignoring line " << line_no << " because it doesn't "
- << "end with a newline! Please end the config file with a newline.";
- errors->push_back(oss.str());
- break;
- }
-
- // find length of line, and search for NULLs
- line_len = 0;
- bool found_null = false;
- for (const char *tmp = b; tmp != end; ++tmp) {
- line_len++;
- if (*tmp == '\0') {
- found_null = true;
- }
- }
-
- if (found_null) {
- ostringstream oss;
- oss << "read_conf: ignoring line " << line_no << " because it has "
- << "an embedded null.";
- errors->push_back(oss.str());
- acc.clear();
- continue;
- }
-
- if ((line_len >= 1) && (b[line_len-1] == '\\')) {
- // A backslash at the end of a line serves as a line continuation marker.
- // Combine the next line with this one.
- // Remove the backslash itself from the text.
- acc.append(b, line_len - 1);
- continue;
- }
-
- acc.append(b, line_len);
-
- //cerr << "acc = '" << acc << "'" << std::endl;
- ConfLine *cline = process_line(line_no, acc.c_str(), errors);
- acc.clear();
- if (!cline)
- continue;
- const std::string &csection(cline->newsection);
- if (!csection.empty()) {
- std::map <std::string, ConfSection>::value_type nt(csection, ConfSection());
- pair < section_iter_t, bool > nr(sections.insert(nt));
- cur_section = nr.first;
- }
- else {
- if (cur_section->second.lines.count(*cline)) {
- // replace an existing key/line in this section, so that
- // [mysection]
- // foo = 1
- // foo = 2
- // will result in foo = 2.
- cur_section->second.lines.erase(*cline);
- if (cline->key.length() && warnings)
- *warnings << "warning: line " << line_no << ": '" << cline->key << "' in section '"
- << cur_section->first << "' redefined " << std::endl;
- }
- // add line to current section
- //std::cerr << "cur_section = " << cur_section->first << ", " << *cline << std::endl;
- cur_section->second.lines.insert(*cline);
- }
- delete cline;
- }
-
- if (!acc.empty()) {
- ostringstream oss;
- oss << "read_conf: don't end with lines that end in backslashes!";
- errors->push_back(oss.str());
- }
-}
-
-/*
- * A simple state-machine based parser.
- * This probably could/should be rewritten with something like boost::spirit
- * or yacc if the grammar ever gets more complex.
- */
-ConfLine* ConfFile::
-process_line(int line_no, const char *line, std::deque<std::string> *errors)
-{
- enum acceptor_state_t {
- ACCEPT_INIT,
- ACCEPT_SECTION_NAME,
- ACCEPT_KEY,
- ACCEPT_VAL_START,
- ACCEPT_UNQUOTED_VAL,
- ACCEPT_QUOTED_VAL,
- ACCEPT_COMMENT_START,
- ACCEPT_COMMENT_TEXT,
- };
- const char *l = line;
- acceptor_state_t state = ACCEPT_INIT;
- string key, val, newsection, comment;
- bool escaping = false;
- while (true) {
- char c = *l++;
- switch (state) {
- case ACCEPT_INIT:
- if (c == '\0')
- return NULL; // blank line. Not an error, but not interesting either.
- else if (c == '[')
- state = ACCEPT_SECTION_NAME;
- else if ((c == '#') || (c == ';'))
- state = ACCEPT_COMMENT_TEXT;
- else if (c == ']') {
- ostringstream oss;
- oss << "unexpected right bracket at char " << (l - line)
- << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if (isspace(c)) {
- // ignore whitespace here
- }
- else {
- // try to accept this character as a key
- state = ACCEPT_KEY;
- --l;
- }
- break;
- case ACCEPT_SECTION_NAME:
- if (c == '\0') {
- ostringstream oss;
- oss << "error parsing new section name: expected right bracket "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == ']') && (!escaping)) {
- trim_whitespace(newsection, true);
- if (newsection.empty()) {
- ostringstream oss;
- oss << "error parsing new section name: no section name found? "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- state = ACCEPT_COMMENT_START;
- }
- else if (((c == '#') || (c == ';')) && (!escaping)) {
- ostringstream oss;
- oss << "unexpected comment marker while parsing new section name, at "
- << "char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- newsection += c;
- }
- break;
- case ACCEPT_KEY:
- if ((((c == '#') || (c == ';')) && (!escaping)) || (c == '\0')) {
- ostringstream oss;
- if (c == '\0') {
- oss << "end of key=val line " << line_no
- << " reached, no \"=val\" found...missing =?";
- } else {
- oss << "unexpected character while parsing putative key value, "
- << "at char " << (l - line) << ", line " << line_no;
- }
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '=') && (!escaping)) {
- key = normalize_key_name(key);
- if (key.empty()) {
- ostringstream oss;
- oss << "error parsing key name: no key name found? "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- state = ACCEPT_VAL_START;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- key += c;
- }
- break;
- case ACCEPT_VAL_START:
- if (c == '\0')
- return new ConfLine(key, val, newsection, comment, line_no);
- else if ((c == '#') || (c == ';'))
- state = ACCEPT_COMMENT_TEXT;
- else if (c == '"')
- state = ACCEPT_QUOTED_VAL;
- else if (isspace(c)) {
- // ignore whitespace
- }
- else {
- // try to accept character as a val
- state = ACCEPT_UNQUOTED_VAL;
- --l;
- }
- break;
- case ACCEPT_UNQUOTED_VAL:
- if (c == '\0') {
- if (escaping) {
- ostringstream oss;
- oss << "error parsing value name: unterminated escape sequence "
- << "at char " << (l - line) << ", line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- trim_whitespace(val, false);
- return new ConfLine(key, val, newsection, comment, line_no);
- }
- else if (((c == '#') || (c == ';')) && (!escaping)) {
- trim_whitespace(val, false);
- state = ACCEPT_COMMENT_TEXT;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- val += c;
- }
- break;
- case ACCEPT_QUOTED_VAL:
- if (c == '\0') {
- ostringstream oss;
- oss << "found opening quote for value, but not the closing quote. "
- << "line " << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- else if ((c == '"') && (!escaping)) {
- state = ACCEPT_COMMENT_START;
- }
- else if ((c == '\\') && (!escaping)) {
- escaping = true;
- }
- else {
- escaping = false;
- // Add anything, including whitespace.
- val += c;
- }
- break;
- case ACCEPT_COMMENT_START:
- if (c == '\0') {
- return new ConfLine(key, val, newsection, comment, line_no);
- }
- else if ((c == '#') || (c == ';')) {
- state = ACCEPT_COMMENT_TEXT;
- }
- else if (isspace(c)) {
- // ignore whitespace
- }
- else {
- ostringstream oss;
- oss << "unexpected character at char " << (l - line) << " of line "
- << line_no;
- errors->push_back(oss.str());
- return NULL;
- }
- break;
- case ACCEPT_COMMENT_TEXT:
- if (c == '\0')
- return new ConfLine(key, val, newsection, comment, line_no);
- else
- comment += c;
- break;
- default:
- assert(0);
- break;
- }
- assert(c != '\0'); // We better not go past the end of the input string.
- }
-}