Line data Source code
1 : /*
2 : * (C) Copyright 2010 Marek Dopiera
3 : *
4 : * This file is part of CoherentDB.
5 : *
6 : * CoherentDB is free software: you can redistribute it and/or modify it
7 : * under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation, either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * CoherentDB is distributed in the hope that it will be useful, but
12 : * WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 : * General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public
17 : * License along with CoherentDB. If not, see
18 : * http://www.gnu.org/licenses/.
19 : */
20 :
21 : #include <ctype.h>
22 : #include <fcntl.h>
23 : #include <sys/param.h>
24 :
25 : #include <sstream>
26 : #include <iostream>
27 : #include <cstdlib>
28 : #include <string>
29 : #include <algorithm>
30 : #include <functional>
31 : #include <fstream>
32 : #include <sstream>
33 :
34 : #include <boost/regex.hpp>
35 : #include <boost/lexical_cast.hpp>
36 : #include <boost/filesystem/path.hpp>
37 : #include <boost/filesystem/convenience.hpp>
38 : #include <boost/filesystem/operations.hpp>
39 :
40 : #include <config/config.h>
41 : #include <config/cmake_config.h>
42 : #include <debug/common.h>
43 : #include <log/log.h>
44 :
45 : #include <version.h>
46 :
47 : using namespace std;
48 : using namespace boost;
49 : using namespace boost::filesystem;
50 : using namespace log4cxx;
51 :
52 : using namespace coherent::log;
53 : using namespace coherent::debug;
54 :
55 : namespace coherent {
56 : namespace config {
57 :
58 : string get_version_string()
59 0 : {
60 : string res = "CoherentDB";
61 0 : #ifdef OFFICIAL_RELEASE
62 : res += " " OFFICIAL_RELEASE;
63 : #else
64 : res += " unofficial release";
65 0 : #ifdef GIT_HASH
66 : #ifdef GIT_BRANCH
67 : res += " branch \"" GIT_BRANCH "\"";
68 0 : #endif /*GIT_BANCH*/
69 : res += " commit hash: " GIT_HASH;
70 0 : #else
71 : res += " (unknown hash)";
72 : #endif /* GIT_HASH */
73 : #endif /* OFFICIAL_RELEASE */
74 : return res;
75 : }
76 :
77 : string get_build_information()
78 0 : {
79 : stringstream ss;
80 0 : ss << "Built for " BUILD_OS "(" BUILD_ARCH ") on " BUILD_TIME << endl <<
81 0 : "Full build sytem information:" << endl << FULL_UNAME << endl <<
82 0 : "Compiled with CXXFLAGS:" << COMPILED_CXX_FLAGS << endl << "CFLAGS:" << COMPILED_C_FLAGS;
83 0 : return ss.str();
84 0 : }
85 :
86 : void print_running_information()
87 0 : {
88 : cout << "Running on:" << endl;
89 0 : int ret = ::system("uname -a");
90 0 : if (ret == -1)
91 0 : cout << "(unable to obtain)" << endl;
92 0 : }
93 0 :
94 : class ini_config
95 : {
96 7 : public:
97 : typedef std::string section;
98 : typedef std::vector<section> sections;
99 : typedef std::vector<std::string> attribute_names;
100 :
101 : ini_config(std::string const & file);
102 : sections get_sections() const;
103 : attribute_names get_attribute_names(section const & section) const;
104 : std::string get_value(section const & sect, std::string const & attr) const;
105 :
106 : private:
107 : typedef std::pair<section, std::string> id; //section, attr
108 : typedef std::map<id, std::string> value_map;
109 : typedef value_map::iterator value_map_it;
110 :
111 : value_map values;
112 : };
113 :
114 : //================ ini_config implementation ===================================
115 :
116 : struct is_not_blank
117 : {
118 : bool operator()(char c) const
119 : {
120 70 : return !isblank(c);
121 : }
122 70 : };
123 :
124 : static bool line_is_white(string const & s)
125 : {
126 238 : return find_if(s.begin(), s.end(), is_not_blank()) == s.end();
127 : }
128 238 :
129 : static string strip_comments(string const & s)
130 : {
131 203 : size_t hash = s.find(';');
132 : string res;
133 203 : if (hash == string::npos)
134 406 : res = s;
135 203 : else
136 63 : res = s.substr(0, hash);
137 : if (line_is_white(res))
138 140 : return "";
139 203 : else
140 168 : return res;
141 : }
142 35 :
143 : static string get_attr(string const & attr_line)
144 : {
145 21 : string tmp = attr_line;
146 : size_t pos = tmp.find('=');
147 : d_assert(pos != string::npos, "line \"" << tmp << "\" does not contain '=' sign!");
148 21 : tmp.resize(pos);
149 21 : stringstream ss(tmp);
150 21 : string res;
151 42 : ss >> res;
152 21 : return res;
153 21 : }
154 :
155 : static string get_val(string const & attr_line)
156 : {
157 21 : size_t const pos = attr_line.find('=');
158 : d_assert(pos != string::npos, "line \"" << attr_line << "\" does not contain '=' sign!");
159 : d_assert(pos + 1 < attr_line.length(), "line \"" << attr_line <<
160 : "\" does not contain a value");
161 21 : string const res = attr_line.substr(pos + 1);
162 : d_assert(!line_is_white(res), "line \"" << attr_line <<
163 : "\" does not contain a value");
164 21 : return res;
165 21 : }
166 :
167 : static string get_sect_name(string const & s)
168 : {
169 14 : size_t const begin = s.find('[');
170 : d_assert(begin != string::npos, "section name \"" << s << "\" does not contain '[' sign");
171 14 : size_t const end = s.find(']');
172 : d_assert(end != string::npos, "section name \"" << s << "\" does not contain ']' sign");
173 : d_assert(begin < end, "']' cannot be before '[' in secton \"" << s << "\"");
174 14 : string const & res_with_white = s.substr(begin + 1, end - begin - 1);
175 : d_assert(!line_is_white(res_with_white), "no section name in \"" << s << "\"");
176 28 : stringstream ss(res_with_white);
177 14 : string res;
178 28 : ss >> res;
179 : d_assert(!res.empty(), "no section name in \"" << s << "\"");
180 14 : return res;
181 14 : }
182 :
183 : ini_config::ini_config(string const & file)
184 : {
185 : LOG(TRACE, "parsing file " << file);
186 : regex const reg_attr("[ \\t]*[a-zA-Z][a-zA-Z0-9_]*[ \\t]*=[ \\t]*[^ \\t=\\[\\[][^=\\[\\]]*");
187 7 : regex const reg_sect("[ \\t]*\\[[ \\t]*[a-zA-Z][a-zA-Z0-9_]*[ \\t]*\\][ \\t]*");
188 14 :
189 14 : section cur_sect;
190 : int line_no = 0;
191 14 :
192 7 : ifstream in_f(file.c_str());
193 : if (!in_f)
194 14 : throw config_exception(string("failed to open file \"") + file + "\"");
195 7 : while (!!in_f) {
196 0 : ++line_no;
197 210 : string line;
198 203 : getline(in_f, line);
199 406 : line = strip_comments(line);
200 203 : if (line == "")
201 203 : continue;
202 203 : if (regex_match(line, reg_attr)) {
203 168 : if (cur_sect.empty())
204 35 : throw config_exception(line_no,
205 21 : "each attribute has to be in a section");
206 : string const & val = get_val(line);
207 0 : string const & attr = get_attr(line);
208 42 :
209 42 : if (!this->values.insert(make_pair(id(cur_sect, attr), val)).second)
210 : throw config_exception(line_no, "duplicate entry");
211 21 :
212 0 : } else if (regex_match(line, reg_sect)) {
213 : cur_sect = get_sect_name(line);
214 14 : value_map_it it = this->values.lower_bound(id(cur_sect, ""));
215 14 : if (it != this->values.end() && it->first.first == cur_sect)
216 14 : throw config_exception(line_no, "section redefinition");
217 : LOG(TRACE, "parsing section " << cur_sect);
218 0 : } else
219 14 : throw config_exception(line_no, "invalid line format");
220 : }
221 35 :
222 : LOG(INFO, "Config file dump follows:");
223 : for (value_map_it it = this->values.begin(); it != this->values.end(); ++it)
224 : LOG(INFO, it->first.first << ", " << it->first.second << " = \"" <<
225 : it->second << "\"");
226 : LOG(INFO, "Config file dump finished.");
227 21 : }
228 7 :
229 7 : string ini_config::get_value(section const & sect, string const & attr) const
230 : {
231 21 : value_map::const_iterator const it = this->values.find(id(sect, attr));
232 : d_assert(it != this->values.end(), "referring to non-existent sect " << sect
233 : << " attr " << attr);
234 21 : return it->second;
235 0 : }
236 21 :
237 : ini_config::attribute_names ini_config::get_attribute_names(section const & sect) const
238 : {
239 14 : attribute_names res;
240 : for (
241 14 : value_map::const_iterator it = this->values.lower_bound(id(sect, ""));
242 98 : it != this->values.end() && it->first.first == sect;
243 14 : ++it
244 63 : )
245 : res.push_back(it->first.second);
246 : return res;
247 21 : }
248 :
249 : //================= config_exception implementation ============================
250 :
251 : config_exception::config_exception(
252 : std::string const & descr)
253 0 : :
254 : line_no(NO_LINE), descr(descr)
255 : {
256 0 : }
257 :
258 0 : config_exception::config_exception(
259 : int line_no, std::string const & descr)
260 0 : :
261 : line_no(line_no), descr(descr)
262 : {
263 0 : }
264 :
265 0 : string config_exception::to_string()
266 : {
267 0 : if (line_no == NO_LINE)
268 : return this->descr;
269 0 : else
270 0 : return string("line ") + lexical_cast<string>(this->line_no) + ": " +
271 : this->descr;
272 0 : }
273 0 :
274 : //================= config_section_base implementation =========================
275 :
276 : config_section_base::config_section_base(
277 : string const & sect,
278 14 : ini_config const & conf
279 : ) :
280 : conf(conf), name(sect)
281 : {
282 14 : ini_config::attribute_names const & names =
283 : this->conf.get_attribute_names(this->name);
284 : this->present_names.insert(names.begin(), names.end());
285 28 : }
286 14 :
287 14 : template<typename T>
288 : T config_section_base::get_value(std::string const & name, T const & def)
289 : {
290 7 : bool const insert_res = this->valid_names.insert(name).second;
291 : d_assert(insert_res, "Referring to attribute " << name << "in section " <<
292 : this->name << " twice");
293 7 : set<string>::const_iterator const i = this->present_names.find(name);
294 0 : if (i == this->present_names.end())
295 7 : return def;
296 7 : else {
297 7 : try {
298 : T const & res = lexical_cast<T>(this->conf.get_value(this->name, name));
299 : return res;
300 0 : } catch (bad_lexical_cast &) {
301 7 : throw config_exception(string("section ") + this->name
302 0 : + ", attribute " + name + ": invalid value");
303 0 : }
304 : }
305 : }
306 :
307 : template<typename T>
308 : T config_section_base::get_value(std::string const & name)
309 : {
310 21 : bool const insert_res = this->valid_names.insert(name).second;
311 : d_assert(insert_res, "Referring to attribute " << name << "in section " <<
312 : this->name << " twice");
313 21 : set<string>::const_iterator const i = this->present_names.find(name);
314 0 : if (i == this->present_names.end())
315 21 : throw config_exception(string("section ") + this->name + ", attribute "
316 21 : + name + ": required attribute not present");
317 0 : else {
318 : try {
319 : T const & res = lexical_cast<T>(this->conf.get_value(this->name, name));
320 : return res;
321 21 : } catch (bad_lexical_cast &) {
322 21 : throw config_exception(string("section ") + this->name
323 0 : + ", attribute " + name + ": invalid value");
324 0 : }
325 : }
326 : }
327 :
328 : void config_section_base::check_no_others()
329 : {
330 14 : for (
331 : set<string>::const_iterator it = this->present_names.begin();
332 70 : it != this->present_names.end();
333 14 : ++it
334 35 : ) {
335 : if (this->valid_names.find(*it) == this->valid_names.end())
336 : throw config_exception(string("section ") + this->name
337 21 : + ", attribute " + *it + ": unknown attribute");
338 0 : }
339 0 : }
340 :
341 14 : //================= global_config implementation ===============================
342 :
343 : global_config::global_config(string const & file_name) throw(config_exception) :
344 : conf(new ini_config(file_name)),
345 0 : buffer_cache(*this->conf),
346 7 : memory_manager(*this->conf)
347 7 : {
348 21 :
349 : }
350 :
351 7 : //================= buffer_cache_sect implementation ===========================
352 :
353 : global_config::buffer_cache_sect::buffer_cache_sect(ini_config const & conf) :
354 : config_section_base("buffer_cache", conf),
355 7 : size(this->get_value<uint64_t>("size")),
356 : syncer_sleep_time(this->get_value<uint16_t>("syncer_sleep_time", 30))
357 7 : {
358 14 : this->check_no_others();
359 : }
360 7 :
361 7 : //================= memory_manager_sect implementation =======================
362 :
363 : global_config::memory_manager_sect::memory_manager_sect(ini_config const& conf) :
364 : config_section_base("memory_manager", conf),
365 7 : initialLimitBytes(this->get_value<uint64_t>("initialLimitBytes")),
366 : defaultSessionLimitBytes(this->get_value<uint32_t>("defaultSessionLimitBytes"))
367 7 : {
368 14 : this->check_no_others();
369 : }
370 7 :
371 7 : global_config::~global_config()
372 : {
373 7 : }
374 :
375 7 : //================= scoped_test_enabler implementation =========================
376 :
377 : scoped_test_enabler::scoped_test_enabler(int argc,
378 : char const * const * const argv, LevelPtr def_log_level)
379 7 : {
380 7 : #define cerr_assert(cond, msg) do { if (!(cond)) { cerr << msg << endl; ::abort(); } ; } while (0)
381 : string const required_path = "/bin/test/"; //just for safety
382 : string const work_dir = "run";
383 14 : string const tests_dir = FOR_TESTS_BIN_DIR "/" + work_dir;
384 14 :
385 14 : char full_path_raw[MAXPATHLEN];
386 : cerr_assert(realpath(argv[0], full_path_raw), "real_path failed with errno=" << errno);
387 : string const full_path = full_path_raw;
388 7 :
389 14 : cerr_assert(full_path.find(required_path) != string::npos,
390 : "This is bad, path=" << tests_dir);
391 7 : size_t pos = full_path.rfind('/');
392 : if (pos == string::npos)
393 7 : pos = 0;
394 7 : string const test_name = full_path.substr(pos);
395 0 : string const target_dir_name = tests_dir + "/" + test_name;
396 14 : this->working_dir = target_dir_name;
397 14 :
398 7 : if (argc != 2 || strcmp(argv[1], "ready")) {
399 : //XXX unix specific
400 7 : if (exists(target_dir_name)) {
401 : cerr_assert(is_directory(target_dir_name), "I wanted to create a working "
402 0 : " directory for test but it already exists and is not a directory: "
403 0 : << target_dir_name);
404 : remove_all(target_dir_name);
405 : }
406 0 : create_directories(target_dir_name);
407 : int err = chdir(target_dir_name.c_str());
408 0 : cerr_assert(!err, "chdir to " << target_dir_name << " failed with code " << errno);
409 0 :
410 0 : int out_fd = open("stdout", O_WRONLY | O_CREAT | O_EXCL, 0644);
411 : cerr_assert(err != -1, "creating stdout file failed with errno=" << errno);
412 0 : int err_fd = open("stderr", O_WRONLY | O_CREAT | O_EXCL, 0644);
413 0 : cerr_assert(err != -1, "creating stderr file failed with errno=" << errno);
414 0 : err = dup2(out_fd, 1);
415 0 : cerr_assert(err != -1, "dup2 of stdout file failed with errno=" << errno);
416 0 : err = dup2(err_fd, 2);
417 0 : cerr_assert(err != -1, "dup2 of stderr file failed with errno=" << errno);
418 0 : err = close(out_fd);
419 0 : cerr_assert(err != -1, "close of stdout file failed with errno=" << errno);
420 0 : err = close(err_fd);
421 0 : cerr_assert(err != -1, "close of stderr file failed with errno=" << errno);
422 0 :
423 0 : vector<string> args;
424 : if (argc > 1) {
425 0 : for (int i = 1; i < argc; ++i)
426 0 : args.push_back(argv[i]);
427 0 : }
428 0 : args.push_back(full_path);
429 : args.push_back("ready");
430 0 : char const * new_argv[args.size()+1];
431 0 : for (size_t i = 0; i < args.size(); ++i)
432 0 : new_argv[i] = args[i].c_str();
433 0 : new_argv[args.size()] = 0;
434 0 : char const * const to_run = (argc > 1) ? argv[1] : full_path.c_str();
435 0 : execvp(to_run, const_cast<char * const *>(new_argv));
436 0 : cerr_assert(false, "execvp failed with errno=" << errno);
437 0 : }
438 0 : setup_logger_test(target_dir_name + "/logs", def_log_level);
439 : LOG(INFO, "Test " << test_name << " starts");
440 7 :
441 7 : set_terminate_handler();
442 :
443 7 : this->config = shared_ptr<global_config>(
444 : new global_config(FOR_TESTS_SRC_DIR "/doc/default.conf"));
445 :
446 7 : }
447 :
448 7 : scoped_test_enabler::~scoped_test_enabler()
449 : {
450 : LOG(INFO, "Test finished successfully.");
451 : }
452 7 :
453 7 : string scoped_test_enabler::get_working_dir()
454 : {
455 0 : return this->working_dir;
456 : }
457 0 :
458 : shared_ptr<global_config> scoped_test_enabler::get_config()
459 : {
460 1 : return this->config;
461 : }
462 1 :
463 : } // namespace config
464 : } // namespace coherent
|