26 #include "yaml_node.h"
28 #include <core/exceptions/software.h>
29 #include <core/threading/mutex.h>
30 #include <core/threading/mutex_locker.h>
31 #include <logging/liblogger.h>
32 #include <sys/socket.h>
34 #include <utils/misc/string_split.h>
35 #include <utils/system/fam_thread.h>
36 #include <yaml-cpp/exceptions.h>
50 #define YAML_FILE_REGEX "^[a-zA-Z0-9_-]+\\.yaml$"
65 current_ = nodes_.end();
72 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> &nodes)
73 : first_(true), nodes_(nodes)
75 current_ = nodes_.begin();
86 return (current_ != nodes_.end());
92 return (current_ != nodes_.end());
98 if (current_ == nodes_.end()) {
99 throw Exception(
"YamlValueIterator: cannot get path of invalid iterator");
101 return current_->first.c_str();
107 if (current_ == nodes_.end()) {
108 throw Exception(
"YamlValueIterator: cannot get type of invalid iterator");
110 return YamlConfigurationNode::Type::to_string(current_->second->get_type());
116 if (current_ == nodes_.end()) {
117 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
119 return (current_->second->is_type<
float>());
125 if (current_ == nodes_.end()) {
126 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
128 return (current_->second->is_type<
unsigned int>());
134 if (current_ == nodes_.end()) {
135 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
137 return (current_->second->is_type<
int>());
143 if (current_ == nodes_.end()) {
144 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
146 return (current_->second->is_type<
bool>());
152 if (current_ == nodes_.end()) {
153 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
155 return (current_->second->is_type<std::string>());
161 if (current_ == nodes_.end()) {
162 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
164 return current_->second->get_type() == YamlConfigurationNode::Type::SEQUENCE;
170 if (current_ == nodes_.end()) {
171 throw Exception(
"YamlValueIterator: cannot check type on invalid iterator");
173 if (current_->second->get_type() != YamlConfigurationNode::Type::SEQUENCE) {
174 throw Exception(
"YamlValueIterator: cannot get list size of non-list value");
176 return current_->second->get_list_size();
182 if (current_ == nodes_.end()) {
183 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
185 return current_->second->get_value<
float>();
191 if (current_ == nodes_.end()) {
192 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
194 return current_->second->get_value<
unsigned int>();
200 if (current_ == nodes_.end()) {
201 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
203 return current_->second->get_value<
int>();
209 if (current_ == nodes_.end()) {
210 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
212 return current_->second->get_value<
bool>();
218 if (current_ == nodes_.end()) {
219 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
221 return current_->second->get_value<std::string>();
227 if (current_ == nodes_.end()) {
228 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
230 if (current_->second->get_type() == YamlConfigurationNode::Type::SEQUENCE) {
231 return current_->second->get_list_as_string();
233 return current_->second->get_value<std::string>();
240 if (current_ == nodes_.end()) {
241 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
243 return current_->second->get_list<
float>();
246 std::vector<unsigned int>
249 if (current_ == nodes_.end()) {
250 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
252 return current_->second->get_list<
unsigned int>();
258 if (current_ == nodes_.end()) {
259 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
261 return current_->second->get_list<
int>();
267 if (current_ == nodes_.end()) {
268 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
270 return current_->second->get_list<
bool>();
273 std::vector<std::string>
276 if (current_ == nodes_.end()) {
277 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
279 return current_->second->get_list<std::string>();
291 if (current_ == nodes_.end()) {
292 throw Exception(
"YamlValueIterator: cannot get value of invalid iterator");
294 return current_->second->is_default();
307 write_pending_ =
false;
308 write_pending_mutex_ =
new Mutex();
327 write_pending_ =
false;
328 write_pending_mutex_ =
new Mutex();
330 sysconfdir_ = strdup(sysconfdir);
332 if (userconfdir != NULL) {
333 userconfdir_ = strdup(userconfdir);
335 const char *homedir = getenv(
"HOME");
336 if (homedir == NULL) {
337 userconfdir_ = strdup(sysconfdir);
339 if (asprintf(&userconfdir_,
"%s/%s", homedir, USERDIR) == -1) {
340 userconfdir_ = strdup(sysconfdir);
349 if (write_pending_) {
364 delete write_pending_mutex_;
370 if (file_path == NULL) {
371 file_path =
"config.yaml";
374 std::string filename;
375 if (file_path[0] ==
'/') {
376 filename = file_path;
378 const char *try_paths[] = {userconfdir_, sysconfdir_};
379 int try_paths_len = 2;
381 for (
int i = 0; i < try_paths_len; ++i) {
383 if (asprintf(&path,
"%s/%s", try_paths[i], file_path) != -1) {
384 if (access(path, R_OK) == 0) {
392 if (filename ==
"") {
393 throw Exception(
"YamlConfig: cannot find configuration file %s/%s or %s/%s",
401 config_file_ = filename;
404 std::list<std::string> files, dirs;
405 read_yaml_config(filename, host_file_, root_, host_root_, files, dirs);
408 fam_thread_ =
new FamThread();
409 RefPtr<FileAlterationMonitor> fam = fam_thread_->
get_fam();
410 fam->add_filter(
"^[^.].*\\.yaml$");
411 std::list<std::string>::iterator f;
412 for (f = files.begin(); f != files.end(); ++f) {
414 fam->watch_file(f->c_str());
416 for (f = dirs.begin(); f != dirs.end(); ++f) {
418 fam->watch_dir(f->c_str());
420 fam->add_listener(
this);
421 fam_thread_->
start();
427 std::shared_ptr<YamlConfigurationNode>
428 YamlConfiguration::read_yaml_file(std::string filename,
430 std::queue<LoadQueueEntry> &load_queue,
431 std::string & host_file)
433 if (access(filename.c_str(), R_OK) == -1) {
434 if (ignore_missing) {
437 throw Exception(errno,
"YamlConfig: cannot access file %s", filename.c_str());
440 std::vector<YAML::Node> docs;
441 bool have_doc1 =
false, have_doc2 =
false;
444 docs = YAML::LoadAllFromFile(filename);
445 have_doc1 = docs.size() > 0;
446 have_doc2 = docs.size() > 1;
447 }
catch (YAML::ParserException &e) {
448 throw CouldNotOpenConfigException(
"Failed to parse %s line %i column %i: %s",
455 std::shared_ptr<YamlConfigurationNode> sub_root;
460 }
else if (have_doc1 && have_doc2) {
462 read_meta_doc(docs[0], load_queue, host_file);
463 sub_root = read_config_doc(docs[1]);
467 sub_root = read_config_doc(docs[0]);
474 YamlConfiguration::read_yaml_config(std::string filename,
475 std::string & host_file,
476 std::shared_ptr<YamlConfigurationNode> &root,
477 std::shared_ptr<YamlConfigurationNode> &host_root,
478 std::list<std::string> & files,
479 std::list<std::string> & dirs)
481 root = std::make_shared<YamlConfigurationNode>();
483 std::queue<LoadQueueEntry> load_queue;
484 load_queue.push(LoadQueueEntry(filename,
false));
486 while (!load_queue.empty()) {
487 LoadQueueEntry &qe = load_queue.front();
490 dirs.push_back(qe.filename);
496 std::shared_ptr<YamlConfigurationNode> sub_root =
497 read_yaml_file(qe.filename, qe.ignore_missing, load_queue, host_file);
500 files.push_back(qe.filename);
508 if (host_file !=
"") {
511 std::queue<LoadQueueEntry> host_load_queue;
512 host_root = read_yaml_file(host_file,
true, host_load_queue, host_file);
513 if (!host_load_queue.empty()) {
514 throw CouldNotOpenConfigException(
"YamlConfig: includes are not allowed "
519 files.push_back(host_file);
521 host_root = std::make_shared<YamlConfigurationNode>();
524 host_root = std::make_shared<YamlConfigurationNode>();
531 MutexLocker
lock(mutex);
533 std::string host_file =
"";
534 std::list<std::string> files, dirs;
535 std::shared_ptr<YamlConfigurationNode> root, host_root;
536 read_yaml_config(config_file_, host_file, root, host_root, files, dirs);
538 std::list<std::string> changes = YamlConfigurationNode::diff(root_, root);
540 if (!changes.empty()) {
542 host_root_ = host_root;
543 host_file_ = host_file;
545 std::list<std::string>::iterator c;
546 for (c = changes.begin(); c != changes.end(); ++c) {
555 RefPtr<FileAlterationMonitor> fam = fam_thread_->
get_fam();
557 std::list<std::string>::iterator f;
558 for (f = files.begin(); f != files.end(); ++f) {
559 fam->watch_file(f->c_str());
561 for (f = dirs.begin(); f != dirs.end(); ++f) {
562 fam->watch_dir(f->c_str());
565 }
catch (Exception &e) {
566 LibLogger::log_warn(
"YamlConfiguration",
"Failed to reload changed config, exception follows");
580 if (path[0] ==
'/') {
583 return std::string(CONFDIR) +
"/" + path;
594 const std::string to_replace =
"$host";
595 static char * hostname = NULL;
596 if (hostname == NULL) {
597 hostname =
new char[256];
598 gethostname(hostname, 256);
600 size_t repl_position = prelim.find(to_replace);
601 if (repl_position == std::string::npos) {
604 return prelim.replace(repl_position, to_replace.length(), std::string(hostname));
609 YamlConfiguration::read_meta_doc(YAML::Node & doc,
610 std::queue<LoadQueueEntry> &load_queue,
611 std::string & host_file)
614 const YAML::Node &includes = doc[
"include"];
615 for (YAML::const_iterator it = includes.begin(); it != includes.end(); ++it) {
617 bool ignore_missing =
false;
618 if (it->Tag() ==
"tag:fawkesrobotics.org,cfg/ignore-missing") {
619 ignore_missing =
true;
622 if (it->Tag() ==
"tag:fawkesrobotics.org,cfg/host-specific") {
623 if (host_file !=
"") {
624 throw Exception(
"YamlConfig: Only one host-specific file can be specified");
631 if (include.empty()) {
632 throw Exception(
"YamlConfig: invalid empty include");
635 if (include[include.size() - 1] ==
'/') {
638 struct stat dir_stat;
639 if ((stat(dirname.c_str(), &dir_stat) != 0)) {
642 throw Exception(errno,
"YamlConfig: Failed to stat directory %s", dirname.c_str());
645 if (!S_ISDIR(dir_stat.st_mode)) {
646 throw Exception(
"YamlConfig: %s is not a directory", dirname.c_str());
649 DIR *d = opendir(dirname.c_str());
651 throw Exception(errno,
"YamlConfig: failed to open directory %s", dirname.c_str());
654 load_queue.push(LoadQueueEntry(dirname, ignore_missing,
true));
656 std::list<std::string> files;
658 std::regex yaml_regex{YAML_REGEX, std::regex_constants::extended};
661 while ((dent = readdir(d)) != NULL) {
662 if (regex_search(dent->d_name, yaml_regex)) {
663 std::string dn = dent->d_name;
664 files.push_back(dirname + dn);
670 for (std::list<std::string>::iterator f = files.begin(); f != files.end(); ++f) {
671 load_queue.push(LoadQueueEntry(*f, ignore_missing));
675 load_queue.push(LoadQueueEntry(
abs_cfg_path(include), ignore_missing));
678 }
catch (YAML::KeyNotFound &e) {
683 std::shared_ptr<YamlConfigurationNode>
684 YamlConfiguration::read_config_doc(
const YAML::Node &doc)
686 return YamlConfigurationNode::create(doc);
690 YamlConfiguration::write_host_file()
692 if (host_file_ ==
"") {
693 throw Exception(
"YamlConfig: no host config file specified");
697 host_root_->emit(host_file_);
700 write_pending_mutex_->
unlock();
705 write_pending_mutex_->
lock();
706 write_pending_ =
true;
707 write_pending_mutex_->
unlock();
714 throw NotImplementedException(
"YamlConfig does not support copying of a configuration");
721 std::shared_ptr<YamlConfigurationNode> n = root_->find(path);
722 return !n->has_children();
731 std::shared_ptr<YamlConfigurationNode> n = root_->find(path);
732 if (n->has_children()) {
733 throw ConfigEntryNotFoundException(path);
736 return YamlConfigurationNode::Type::to_string(n->get_type());
752 template <
typename T>
754 get_value_as(std::shared_ptr<YamlConfigurationNode> root,
const char *path)
756 std::shared_ptr<YamlConfigurationNode> n = root->find(path);
757 if (n->has_children()) {
760 return n->get_value<T>();
770 template <
typename T>
771 static inline std::vector<T>
772 get_list(std::shared_ptr<YamlConfigurationNode> root,
const char *path)
774 std::shared_ptr<YamlConfigurationNode> n = root->find(path);
775 if (n->has_children()) {
776 throw ConfigEntryNotFoundException(path);
778 return n->get_list<T>();
784 return get_value_as<float>(root_, path);
790 return get_value_as<unsigned int>(root_, path);
796 return get_value_as<int>(root_, path);
802 return get_value_as<bool>(root_, path);
808 return get_value_as<std::string>(root_, path);
814 return get_list<float>(root_, path);
817 std::vector<unsigned int>
820 return get_list<unsigned int>(root_, path);
826 return get_list<int>(root_, path);
832 return get_list<bool>(root_, path);
835 std::vector<std::string>
838 return get_list<std::string>(root_, path);
846 template <
typename T>
848 is_type(std::shared_ptr<YamlConfigurationNode> root,
const char *path)
850 std::shared_ptr<YamlConfigurationNode> n = root->find(path);
851 if (n->has_children()) {
854 return n->is_type<T>();
860 return is_type<float>(root_, path);
866 std::shared_ptr<YamlConfigurationNode> n = root_->find(path);
867 if (n->has_children()) {
871 if (!n->is_type<
unsigned int>())
874 int v = n->get_value<
int>();
881 return is_type<int>(root_, path);
887 return is_type<bool>(root_, path);
893 return is_type<std::string>(root_, path);
899 std::shared_ptr<YamlConfigurationNode> n = root_->find(path);
900 if (n->has_children()) {
903 return (n->get_type() == YamlConfigurationNode::Type::SEQUENCE);
922 std::shared_ptr<YamlConfigurationNode> n = root_->find(path);
923 if (n->has_children()) {
926 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> nodes;
928 return new YamlValueIterator(nodes);
929 }
catch (ConfigEntryNotFoundException &e) {
930 return new YamlValueIterator();
937 root_->set_value(path, f);
938 host_root_->set_value(path, f);
946 root_->set_value(path, uint);
947 host_root_->set_value(path, uint);
955 root_->set_value(path, i);
956 host_root_->set_value(path, i);
964 root_->set_value(path, b);
965 host_root_->set_value(path, b);
973 root_->set_value(path, std::string(s));
974 host_root_->set_value(path, std::string(s));
988 root_->set_list(path, f);
989 host_root_->set_list(path, f);
997 root_->set_list(path, u);
998 host_root_->set_list(path, u);
1006 root_->set_list(path, i);
1007 host_root_->set_list(path, i);
1015 root_->set_list(path, b);
1016 host_root_->set_list(path, b);
1024 root_->set_list(path, s);
1025 host_root_->set_list(path, s);
1033 root_->set_list(path, s);
1034 host_root_->set_list(path, s);
1052 host_root_->erase(path);
1137 write_pending_mutex_->
lock();
1138 if (write_pending_) {
1139 host_root_->emit(host_file_);
1140 write_pending_ =
false;
1142 write_pending_mutex_->
unlock();
1151 Configuration::ValueIterator *
1154 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> nodes;
1155 root_->enum_leafs(nodes);
1162 std::string tmp_path = path;
1163 std::string::size_type tl = tmp_path.length();
1164 if ((tl > 0) && (tmp_path[tl - 1] ==
'/')) {
1165 tmp_path.resize(tl - 1);
1168 std::shared_ptr<YamlConfigurationNode> n = root_->find(tmp_path.c_str());
1169 std::map<std::string, std::shared_ptr<YamlConfigurationNode>> nodes;
1170 n->enum_leafs(nodes, tmp_path);
1171 return new YamlValueIterator(nodes);
1172 }
catch (Exception &e) {
1173 return new YamlValueIterator();
1182 std::shared_ptr<YamlConfigurationNode>
1183 YamlConfiguration::query(
const char *path)
const
1186 return root_->find(pel_q);