PageRenderTime 27ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Config_Options.cpp

https://github.com/volkszaehler/vzlogger
C++ | 324 lines | 244 code | 34 blank | 46 comment | 128 complexity | d1c4df9bf28b1cf5d91f1a796e393282 MD5 | raw file
  1. /**
  2. * Parsing Apache HTTPd-like configuration
  3. *
  4. * @author Steffen Vogel <info@steffenvogel.de>
  5. * @copyright Copyright (c) 2011, The volkszaehler.org project
  6. * @package vzlogger
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. */
  9. /*
  10. * This file is part of volkzaehler.org
  11. *
  12. * volkzaehler.org is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * any later version.
  16. *
  17. * volkzaehler.org is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with volkszaehler.org. If not, see <http://www.gnu.org/licenses/>.
  24. */
  25. #include <ctype.h>
  26. #include <errno.h>
  27. #include <regex>
  28. #include <stdio.h>
  29. #include "Channel.hpp"
  30. #include "config.hpp"
  31. #include <Config_Options.hpp>
  32. #include <VZException.hpp>
  33. #ifdef ENABLE_MQTT
  34. #include "mqtt.hpp"
  35. #endif
  36. static const char *option_type_str[] = {"null", "boolean", "double", "int",
  37. "object", "array", "string"};
  38. Config_Options::Config_Options()
  39. : _config("/etc/vzlogger.conf"), _log(""), _pds(0), _port(8080), _verbosity(0),
  40. _comet_timeout(30), _buffer_length(-1), _retry_pause(15), _local(false), _foreground(false),
  41. _time_machine(false) {
  42. _logfd = NULL;
  43. }
  44. Config_Options::Config_Options(const std::string filename)
  45. : _config(filename), _log(""), _pds(0), _port(8080), _verbosity(0), _comet_timeout(30),
  46. _buffer_length(-1), _retry_pause(15), _local(false), _foreground(false),
  47. _time_machine(false) {
  48. _logfd = NULL;
  49. }
  50. void Config_Options::config_parse(MapContainer &mappings) {
  51. struct json_object *json_cfg = NULL;
  52. struct json_tokener *json_tok = json_tokener_new();
  53. char buf[JSON_FILE_BUF_SIZE];
  54. int line = 0;
  55. /* open configuration file */
  56. FILE *file = fopen(_config.c_str(), "r");
  57. if (file == NULL) {
  58. print(log_alert, "Cannot open configfile %s: %s", NULL, _config.c_str(),
  59. strerror(errno)); /* why didn't the file open? */
  60. throw vz::VZException("Cannot open configfile.");
  61. } else {
  62. print(log_info, "Start parsing configuration from %s", NULL, _config.c_str());
  63. }
  64. #ifdef HAVE_CPP_REGEX
  65. std::regex regex("^\\s*(//(.*|)|$)"); // if you change this pls adjust unit test in
  66. // ut_api_volkszaehler.cpp regex_for_configs as well!
  67. #endif
  68. /* parse JSON */
  69. while (fgets(buf, JSON_FILE_BUF_SIZE, file)) {
  70. line++;
  71. if (json_cfg != NULL) {
  72. #ifdef HAVE_CPP_REGEX
  73. // let's ignore whitespace and single line comments here:
  74. if (!std::regex_match((const char *)buf, regex)) {
  75. #else
  76. // let's accept at least whitespace here:
  77. std::string strline(buf);
  78. if (!std::all_of(strline.begin(), strline.end(), isspace)) {
  79. #endif
  80. print(log_alert, "extra data after end of configuration in %s:%d", NULL,
  81. _config.c_str(), line);
  82. throw vz::VZException("extra data after end of configuration");
  83. }
  84. } else {
  85. json_cfg = json_tokener_parse_ex(json_tok, buf, strlen(buf));
  86. if (json_tok->err > 1) {
  87. print(log_alert, "Error in %s:%d %s at offset %d", NULL, _config.c_str(), line,
  88. json_tokener_error_desc(json_tok->err), json_tok->char_offset);
  89. json_object_put(json_cfg);
  90. json_cfg = 0;
  91. throw vz::VZException("Parse configuaration failed.");
  92. }
  93. }
  94. }
  95. /* householding */
  96. fclose(file);
  97. json_tokener_free(json_tok);
  98. if (json_cfg == NULL)
  99. throw vz::VZException("configuration file incomplete, missing closing braces/parens?");
  100. try {
  101. /* parse options */
  102. json_object_object_foreach(json_cfg, key, value) {
  103. enum json_type type = json_object_get_type(value);
  104. if (strcmp(key, "daemon") == 0 && type == json_type_boolean) {
  105. if (!json_object_get_boolean(value)) {
  106. throw vz::VZException("\"daemon\" option is not supported anymore, "
  107. "you probably want to use -f instead.");
  108. }
  109. } else if (strcmp(key, "log") == 0 && type == json_type_string) {
  110. _log = json_object_get_string(value);
  111. } else if (strcmp(key, "retry") == 0 && type == json_type_int) {
  112. _retry_pause = json_object_get_int(value);
  113. } else if (strcmp(key, "verbosity") == 0 && type == json_type_int) {
  114. _verbosity = json_object_get_int(value);
  115. } else if (strcmp(key, "local") == 0) {
  116. json_object_object_foreach(value, key, local_value) {
  117. enum json_type local_type = json_object_get_type(local_value);
  118. if (strcmp(key, "enabled") == 0 && local_type == json_type_boolean) {
  119. _local = json_object_get_boolean(local_value);
  120. } else if (strcmp(key, "port") == 0 && local_type == json_type_int) {
  121. _port = json_object_get_int(local_value);
  122. } else if (strcmp(key, "timeout") == 0 && local_type == json_type_int) {
  123. _comet_timeout = json_object_get_int(local_value);
  124. } else if (strcmp(key, "buffer") == 0 && local_type == json_type_int) {
  125. _buffer_length = json_object_get_int(local_value);
  126. if (!_buffer_length)
  127. _buffer_length =
  128. -1; // 0 makes no sense, use size based mode with 1 element
  129. } else if (strcmp(key, "index") == 0 && local_type == json_type_boolean) {
  130. _channel_index = json_object_get_boolean(local_value);
  131. } else {
  132. print(log_alert, "Ignoring invalid field or type: %s=%s (%s)", NULL, key,
  133. json_object_get_string(local_value), option_type_str[local_type]);
  134. }
  135. }
  136. } else if ((strcmp(key, "sensors") == 0 || strcmp(key, "meters") == 0) &&
  137. type == json_type_array) {
  138. int len = json_object_array_length(value);
  139. for (int i = 0; i < len; i++) {
  140. Json::Ptr jso(new Json(json_object_array_get_idx(value, i)));
  141. config_parse_meter(mappings, jso);
  142. }
  143. } else if ((strcmp(key, "push") == 0) && type == json_type_array) {
  144. int len = json_object_array_length(value);
  145. if (!_pds && len > 0) {
  146. _pds = new PushDataServer(value);
  147. } else
  148. print(log_error, "Ignoring push entry due to empty array or duplicate section",
  149. "push");
  150. }
  151. #ifdef ENABLE_MQTT
  152. else if ((strcmp(key, "mqtt") == 0) && type == json_type_object) {
  153. if (!mqttClient) {
  154. mqttClient = new MqttClient(value);
  155. if (!mqttClient->isConfigured()) {
  156. delete mqttClient;
  157. mqttClient = 0;
  158. print(log_debug, "mqtt client not configured. stopped.", "mqtt");
  159. }
  160. } else
  161. print(log_error, "Ignoring mqtt entry due to empty array or duplicate section",
  162. "mqtt");
  163. }
  164. #endif
  165. else if ((strcmp(key, "i_have_a_time_machine") == 0) && type == json_type_boolean) {
  166. _time_machine = json_object_get_boolean(value);
  167. } else {
  168. print(log_alert, "Ignoring invalid field or type: %s=%s (%s)", NULL, key,
  169. json_object_get_string(value), option_type_str[type]);
  170. }
  171. }
  172. } catch (std::exception &e) {
  173. json_object_put(json_cfg); /* free allocated memory */
  174. std::stringstream oss;
  175. oss << e.what();
  176. print(log_alert, "parse configuration failed due to:", "", oss.str().c_str());
  177. throw;
  178. }
  179. print(log_debug, "Have %d meters.", NULL, mappings.size());
  180. json_object_put(json_cfg); /* free allocated memory */
  181. }
  182. void Config_Options::config_parse_meter(MapContainer &mappings, Json::Ptr jso) {
  183. std::list<Json> json_channels;
  184. std::list<Option> options;
  185. json_object_object_foreach(jso->Object(), key, value) {
  186. enum json_type type = json_object_get_type(value);
  187. if (strcmp(key, "channels") == 0 && type == json_type_array) {
  188. int len = json_object_array_length(value);
  189. for (int i = 0; i < len; i++) {
  190. json_channels.push_back(Json(json_object_array_get_idx(value, i)));
  191. }
  192. } else if (strcmp(key, "channel") == 0 && type == json_type_object) {
  193. json_channels.push_back(Json(value));
  194. } else { /* all other options will be passed to meter_init() */
  195. Option option(key, value);
  196. options.push_back(option);
  197. }
  198. }
  199. /* init meter */
  200. MeterMap metermap(options);
  201. print(log_info, "New meter initialized (protocol=%s)", NULL /*(mapping*/,
  202. meter_get_details(metermap.meter()->protocolId())->name);
  203. /* init channels */
  204. for (std::list<Json>::iterator it = json_channels.begin(); it != json_channels.end(); it++) {
  205. config_parse_channel(*it, metermap);
  206. }
  207. /* householding */
  208. mappings.push_back(metermap);
  209. }
  210. void Config_Options::config_parse_channel(Json &jso, MeterMap &mapping) {
  211. std::list<Option> options;
  212. const char *uuid = NULL;
  213. const char *id_str = NULL;
  214. std::string apiProtocol_str;
  215. print(log_debug, "Configure channel.", NULL);
  216. json_object_object_foreach(jso.Object(), key, value) {
  217. enum json_type type = json_object_get_type(value);
  218. if (strcmp(key, "uuid") == 0 && type == json_type_string) {
  219. uuid = json_object_get_string(value);
  220. }
  221. // else if (strcmp(key, "middleware") == 0 && type == json_type_string) {
  222. // middleware = json_object_get_string(value);
  223. //}
  224. else if (strcmp(key, "identifier") == 0 && type == json_type_string) {
  225. id_str = json_object_get_string(value);
  226. } else if (strcmp(key, "api") == 0 && type == json_type_string) {
  227. apiProtocol_str = json_object_get_string(value);
  228. } else { /* all other options will be passed to meter_init() */
  229. Option option(key, value);
  230. options.push_back(option);
  231. // print(log_alert, "Ignoring invalid field or type: %s=%s (%s)",
  232. // NULL, key, json_object_get_string(value), option_type_str[type]);
  233. }
  234. }
  235. /* check uuid and middleware */
  236. if (uuid == NULL) {
  237. print(log_alert, "Missing UUID", NULL);
  238. throw vz::VZException("Missing UUID");
  239. }
  240. if (!config_validate_uuid(uuid)) {
  241. print(log_alert, "Invalid UUID: %s", NULL, uuid);
  242. throw vz::VZException("Invalid UUID.");
  243. }
  244. // check if identifier is set. If not, use default
  245. if (id_str == NULL) {
  246. print(log_error, "Identifier is not set. Using default value 'NilIdentifier'.", NULL);
  247. id_str = "NilIdentifier";
  248. }
  249. // if (middleware == NULL) {
  250. // print(log_error, "Missing middleware", NULL);
  251. // throw vz::VZException("Missing middleware.");
  252. //}
  253. if (apiProtocol_str.length() == 0) {
  254. apiProtocol_str = "volkszaehler";
  255. }
  256. /* parse identifier */
  257. ReadingIdentifier::Ptr id;
  258. try {
  259. id = reading_id_parse(mapping.meter()->protocolId(), (const char *)id_str);
  260. } catch (vz::VZException &e) {
  261. std::stringstream oss;
  262. oss << e.what();
  263. print(log_alert, "Invalid id: %s due to: '%s'", NULL, id_str, oss.str().c_str());
  264. throw vz::VZException("Invalid reader.");
  265. }
  266. Channel::Ptr ch(new Channel(options, apiProtocol_str.c_str(), uuid, id));
  267. print(log_info, "New channel initialized (uuid=...%s api=%s id=%s)", ch->name(), uuid + 30,
  268. apiProtocol_str.c_str(), (id_str) ? id_str : "(none)");
  269. mapping.push_back(ch);
  270. }
  271. bool config_validate_uuid(const char *uuid) {
  272. for (const char *p = uuid; *p; p++) {
  273. switch (p - uuid) {
  274. case 8:
  275. case 13:
  276. case 18:
  277. case 23:
  278. if (*p != '-')
  279. return false;
  280. else
  281. break;
  282. default:
  283. if (!isxdigit(*p))
  284. return false;
  285. else
  286. break;
  287. }
  288. }
  289. return true;
  290. }