d_controller.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. // Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <cc/command_interpreter.h>
  8. #include <cfgrpt/config_report.h>
  9. #include <dhcpsrv/cfgmgr.h>
  10. #include <exceptions/exceptions.h>
  11. #include <log/logger.h>
  12. #include <log/logger_support.h>
  13. #include <process/d_log.h>
  14. #include <process/d_controller.h>
  15. #ifdef HAVE_MYSQL
  16. #include <dhcpsrv/mysql_lease_mgr.h>
  17. #endif
  18. #ifdef HAVE_PGSQL
  19. #include <dhcpsrv/pgsql_lease_mgr.h>
  20. #endif
  21. #ifdef HAVE_CQL
  22. #include <dhcpsrv/cql_lease_mgr.h>
  23. #endif
  24. #include <dhcpsrv/memfile_lease_mgr.h>
  25. #include <sstream>
  26. #include <unistd.h>
  27. using namespace isc::data;
  28. using namespace isc::config;
  29. namespace isc {
  30. namespace process {
  31. DControllerBasePtr DControllerBase::controller_;
  32. // Note that the constructor instantiates the controller's primary IOService.
  33. DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
  34. : app_name_(app_name), bin_name_(bin_name),
  35. verbose_(false), check_only_(false), spec_file_name_(""),
  36. io_service_(new isc::asiolink::IOService()),
  37. io_signal_queue_() {
  38. }
  39. void
  40. DControllerBase::setController(const DControllerBasePtr& controller) {
  41. if (controller_) {
  42. // This shouldn't happen, but let's make sure it can't be done.
  43. // It represents a programmatic error.
  44. isc_throw (DControllerBaseError,
  45. "Multiple controller instances attempted.");
  46. }
  47. controller_ = controller;
  48. }
  49. ConstElementPtr
  50. DControllerBase::parseFile(const std::string&) {
  51. ConstElementPtr elements;
  52. return (elements);
  53. }
  54. void
  55. DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
  56. // Step 1 is to parse the command line arguments.
  57. try {
  58. parseArgs(argc, argv);
  59. } catch (const InvalidUsage& ex) {
  60. usage(ex.what());
  61. // rethrow it with an empty message
  62. isc_throw(InvalidUsage, "");
  63. }
  64. setProcName(bin_name_);
  65. if (isCheckOnly()) {
  66. checkConfigOnly();
  67. return;
  68. }
  69. // It is important that we set a default logger name because this name
  70. // will be used when the user doesn't provide the logging configuration
  71. // in the Kea configuration file.
  72. isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
  73. // Logger's default configuration depends on whether we are in the
  74. // verbose mode or not. CfgMgr manages the logger configuration so
  75. // the verbose mode is set for CfgMgr.
  76. isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
  77. // Do not initialize logger here if we are running unit tests. It would
  78. // replace an instance of unit test specific logger.
  79. if (!test_mode) {
  80. // Now that we know what the mode flags are, we can init logging.
  81. Daemon::loggerInit(bin_name_.c_str(), verbose_);
  82. }
  83. try {
  84. createPIDFile();
  85. } catch (const dhcp::DaemonPIDExists& ex) {
  86. LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
  87. .arg(bin_name_).arg(ex.what());
  88. isc_throw (LaunchError, "Launch Failed: " << ex.what());
  89. } catch (const std::exception& ex) {
  90. LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
  91. .arg(app_name_).arg(ex.what());
  92. isc_throw (LaunchError, "Launch failed: " << ex.what());
  93. }
  94. // Log the starting of the service.
  95. LOG_INFO(dctl_logger, DCTL_STARTING)
  96. .arg(app_name_).arg(getpid()).arg(VERSION);
  97. try {
  98. // Step 2 is to create and initialize the application process object.
  99. initProcess();
  100. } catch (const std::exception& ex) {
  101. LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
  102. .arg(app_name_).arg(ex.what());
  103. isc_throw (ProcessInitError,
  104. "Application Process initialization failed: " << ex.what());
  105. }
  106. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
  107. .arg(app_name_);
  108. // Step 3 is to load configuration from file.
  109. int rcode;
  110. ConstElementPtr comment = parseAnswer(rcode, configFromFile());
  111. if (rcode != 0) {
  112. LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
  113. .arg(app_name_).arg(comment->stringValue());
  114. isc_throw (ProcessInitError, "Could Not load configuration file: "
  115. << comment->stringValue());
  116. }
  117. // Everything is clear for launch, so start the application's
  118. // event loop.
  119. try {
  120. // Now that we have a proces, we can set up signal handling.
  121. initSignalHandling();
  122. runProcess();
  123. } catch (const std::exception& ex) {
  124. LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
  125. .arg(app_name_).arg(ex.what());
  126. isc_throw (ProcessRunError,
  127. "Application process event loop failed: " << ex.what());
  128. }
  129. // All done, so bail out.
  130. LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
  131. .arg(app_name_).arg(getpid()).arg(VERSION);
  132. }
  133. void
  134. DControllerBase::checkConfigOnly() {
  135. try {
  136. // We need to initialize logging, in case any error
  137. // messages are to be printed.
  138. // This is just a test, so we don't care about lockfile.
  139. setenv("KEA_LOCKFILE_DIR", "none", 0);
  140. isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
  141. isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
  142. Daemon::loggerInit(bin_name_.c_str(), verbose_);
  143. // Check the syntax first.
  144. std::string config_file = getConfigFile();
  145. if (config_file.empty()) {
  146. // Basic sanity check: file name must not be empty.
  147. isc_throw(InvalidUsage, "JSON configuration file not specified");
  148. }
  149. ConstElementPtr whole_config = parseFile(config_file);
  150. if (!whole_config) {
  151. // No fallback to fromJSONFile
  152. isc_throw(InvalidUsage, "No configuration found");
  153. }
  154. if (verbose_) {
  155. std::cerr << "Syntax check OK" << std::endl;
  156. }
  157. // Check the logic next.
  158. ConstElementPtr module_config;
  159. module_config = whole_config->get(getAppName());
  160. if (!module_config) {
  161. isc_throw(InvalidUsage, "Config file " << config_file <<
  162. " does not include '" << getAppName() << "' entry");
  163. }
  164. // Get an application process object.
  165. initProcess();
  166. ConstElementPtr answer = checkConfig(module_config);
  167. int rcode = 0;
  168. answer = parseAnswer(rcode, answer);
  169. if (rcode != 0) {
  170. isc_throw(InvalidUsage, "Error encountered: "
  171. << answer->stringValue());
  172. }
  173. } catch (const VersionMessage&) {
  174. throw;
  175. } catch (const InvalidUsage&) {
  176. throw;
  177. } catch (const std::exception& ex) {
  178. isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
  179. }
  180. return;
  181. }
  182. void
  183. DControllerBase::parseArgs(int argc, char* argv[])
  184. {
  185. // Iterate over the given command line options. If its a stock option
  186. // ("c" or "d") handle it here. If its a valid custom option, then
  187. // invoke customOption.
  188. int ch;
  189. opterr = 0;
  190. optind = 1;
  191. std::string opts("dvVWc:t:" + getCustomOpts());
  192. while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
  193. switch (ch) {
  194. case 'd':
  195. // Enables verbose logging.
  196. verbose_ = true;
  197. break;
  198. case 'v':
  199. // gather Kea version and throw so main() can catch and return
  200. // rather than calling exit() here which disrupts gtest.
  201. isc_throw(VersionMessage, getVersion(false));
  202. break;
  203. case 'V':
  204. // gather Kea version and throw so main() can catch and return
  205. // rather than calling exit() here which disrupts gtest.
  206. isc_throw(VersionMessage, getVersion(true));
  207. break;
  208. case 'W':
  209. // gather Kea config report and throw so main() can catch and
  210. // return rather than calling exit() here which disrupts gtest.
  211. isc_throw(VersionMessage, isc::detail::getConfigReport());
  212. break;
  213. case 'c':
  214. case 't':
  215. // config file name
  216. if (optarg == NULL) {
  217. isc_throw(InvalidUsage, "configuration file name missing");
  218. }
  219. setConfigFile(optarg);
  220. if (ch == 't') {
  221. check_only_ = true;
  222. }
  223. break;
  224. case '?': {
  225. // We hit an invalid option.
  226. isc_throw(InvalidUsage, "unsupported option: ["
  227. << static_cast<char>(optopt) << "] "
  228. << (!optarg ? "" : optarg));
  229. break;
  230. }
  231. default:
  232. // We hit a valid custom option
  233. if (!customOption(ch, optarg)) {
  234. // This would be a programmatic error.
  235. isc_throw(InvalidUsage, " Option listed but implemented?: ["
  236. << static_cast<char>(ch) << "] "
  237. << (!optarg ? "" : optarg));
  238. }
  239. break;
  240. }
  241. }
  242. // There was too much information on the command line.
  243. if (argc > optind) {
  244. isc_throw(InvalidUsage, "extraneous command line information");
  245. }
  246. }
  247. bool
  248. DControllerBase::customOption(int /* option */, char* /*optarg*/)
  249. {
  250. // Default implementation returns false.
  251. return (false);
  252. }
  253. void
  254. DControllerBase::initProcess() {
  255. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
  256. .arg(app_name_);
  257. // Invoke virtual method to instantiate the application process.
  258. try {
  259. process_.reset(createProcess());
  260. } catch (const std::exception& ex) {
  261. isc_throw(DControllerBaseError, std::string("createProcess failed: ")
  262. + ex.what());
  263. }
  264. // This is pretty unlikely, but will test for it just to be safe..
  265. if (!process_) {
  266. isc_throw(DControllerBaseError, "createProcess returned NULL");
  267. }
  268. // Invoke application's init method (Note this call should throw
  269. // DProcessBaseError if it fails).
  270. process_->init();
  271. }
  272. ConstElementPtr
  273. DControllerBase::configFromFile() {
  274. // Rollback any previous staging configuration. For D2, only a
  275. // logger configuration is used here.
  276. isc::dhcp::CfgMgr::instance().rollback();
  277. // Will hold configuration.
  278. ConstElementPtr module_config;
  279. // Will receive configuration result.
  280. ConstElementPtr answer;
  281. try {
  282. std::string config_file = getConfigFile();
  283. if (config_file.empty()) {
  284. // Basic sanity check: file name must not be empty.
  285. isc_throw(BadValue, "JSON configuration file not specified. Please "
  286. "use -c command line option.");
  287. }
  288. // If parseFile returns an empty pointer, then pass the file onto the
  289. // original JSON parser.
  290. ConstElementPtr whole_config = parseFile(config_file);
  291. if (!whole_config) {
  292. // Read contents of the file and parse it as JSON
  293. whole_config = Element::fromJSONFile(config_file, true);
  294. }
  295. // Let's configure logging before applying the configuration,
  296. // so we can log things during configuration process.
  297. // Temporary storage for logging configuration
  298. isc::dhcp::SrvConfigPtr storage =
  299. isc::dhcp::CfgMgr::instance().getStagingCfg();
  300. // Get 'Logging' element from the config and use it to set up
  301. // logging. If there's no such element, we'll just pass NULL.
  302. Daemon::configureLogger(whole_config->get("Logging"), storage);
  303. // Extract derivation-specific portion of the configuration.
  304. module_config = whole_config->get(getAppName());
  305. if (!module_config) {
  306. isc_throw(BadValue, "Config file " << config_file <<
  307. " does not include '" <<
  308. getAppName() << "' entry.");
  309. }
  310. answer = updateConfig(module_config);
  311. int rcode = 0;
  312. parseAnswer(rcode, answer);
  313. if (!rcode) {
  314. // Configuration successful, so apply the logging configuration
  315. // to log4cplus.
  316. isc::dhcp::CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
  317. isc::dhcp::CfgMgr::instance().commit();
  318. }
  319. } catch (const std::exception& ex) {
  320. // Rollback logging configuration.
  321. isc::dhcp::CfgMgr::instance().rollback();
  322. // build an error result
  323. ConstElementPtr error = createAnswer(COMMAND_ERROR,
  324. std::string("Configuration parsing failed: ") + ex.what());
  325. return (error);
  326. }
  327. return (answer);
  328. }
  329. void
  330. DControllerBase::runProcess() {
  331. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
  332. .arg(app_name_);
  333. if (!process_) {
  334. // This should not be possible.
  335. isc_throw(DControllerBaseError, "Process not initialized");
  336. }
  337. // Invoke the application process's run method. This may throw
  338. // DProcessBaseError
  339. process_->run();
  340. }
  341. // Instance method for handling new config
  342. ConstElementPtr
  343. DControllerBase::updateConfig(ConstElementPtr new_config) {
  344. return (process_->configure(new_config, false));
  345. }
  346. // Instance method for checking new config
  347. ConstElementPtr
  348. DControllerBase::checkConfig(ConstElementPtr new_config) {
  349. return (process_->configure(new_config, true));
  350. }
  351. ConstElementPtr
  352. DControllerBase::configGetHandler(const std::string&,
  353. ConstElementPtr /*args*/) {
  354. ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
  355. return (createAnswer(COMMAND_SUCCESS, config));
  356. }
  357. ConstElementPtr
  358. DControllerBase::configWriteHandler(const std::string&,
  359. ConstElementPtr args) {
  360. std::string filename;
  361. if (args) {
  362. if (args->getType() != Element::map) {
  363. return (createAnswer(COMMAND_ERROR, "Argument must be a map"));
  364. }
  365. ConstElementPtr filename_param = args->get("filename");
  366. if (filename_param) {
  367. if (filename_param->getType() != Element::string) {
  368. return (createAnswer(COMMAND_ERROR,
  369. "passed parameter 'filename' "
  370. "is not a string"));
  371. }
  372. filename = filename_param->stringValue();
  373. }
  374. }
  375. if (filename.empty()) {
  376. // filename parameter was not specified, so let's use
  377. // whatever we remember
  378. filename = getConfigFile();
  379. if (filename.empty()) {
  380. return (createAnswer(COMMAND_ERROR,
  381. "Unable to determine filename."
  382. "Please specify filename explicitly."));
  383. }
  384. }
  385. // Ok, it's time to write the file.
  386. size_t size = 0;
  387. ConstElementPtr cfg = process_->getCfgMgr()->getContext()->toElement();
  388. try {
  389. size = writeConfigFile(filename, cfg);
  390. } catch (const isc::Exception& ex) {
  391. return (createAnswer(COMMAND_ERROR,
  392. std::string("Error during write-config:")
  393. + ex.what()));
  394. }
  395. if (size == 0) {
  396. return (createAnswer(COMMAND_ERROR,
  397. "Error writing configuration to " + filename));
  398. }
  399. // Ok, it's time to return the successful response.
  400. ElementPtr params = Element::createMap();
  401. params->set("size", Element::create(static_cast<long long>(size)));
  402. params->set("filename", Element::create(filename));
  403. return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
  404. + filename + " successful", params));
  405. }
  406. ConstElementPtr
  407. DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
  408. const int status_code = COMMAND_ERROR; // 1 indicates an error
  409. ConstElementPtr module_config;
  410. std::string app_name = getAppName();
  411. std::string message;
  412. // Command arguments are expected to be:
  413. // { "Module": { ... }, "Logging": { ... } }
  414. // The Logging component is technically optional. If it's not supplied
  415. // logging will revert to default logging.
  416. if (!args) {
  417. message = "Missing mandatory 'arguments' parameter.";
  418. } else {
  419. module_config = args->get(app_name);
  420. if (!module_config) {
  421. message = "Missing mandatory '" + app_name + "' parameter.";
  422. } else if (module_config->getType() != Element::map) {
  423. message = "'" + app_name + "' parameter expected to be a map.";
  424. }
  425. }
  426. if (!message.empty()) {
  427. // Something is amiss with arguments, return a failure response.
  428. ConstElementPtr result = isc::config::createAnswer(status_code,
  429. message);
  430. return (result);
  431. }
  432. // We are starting the configuration process so we should remove any
  433. // staging configuration that has been created during previous
  434. // configuration attempts.
  435. isc::dhcp::CfgMgr::instance().rollback();
  436. // Now we check the server proper.
  437. return (checkConfig(module_config));
  438. }
  439. ConstElementPtr
  440. DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
  441. ConstElementPtr answer;
  442. // For version-get put the extended version in arguments
  443. ElementPtr extended = Element::create(getVersion(true));
  444. ElementPtr arguments = Element::createMap();
  445. arguments->set("extended", extended);
  446. answer = createAnswer(COMMAND_SUCCESS, getVersion(false), arguments);
  447. return (answer);
  448. }
  449. ConstElementPtr
  450. DControllerBase::buildReportHandler(const std::string&, ConstElementPtr) {
  451. return (createAnswer(COMMAND_SUCCESS, isc::detail::getConfigReport()));
  452. }
  453. ConstElementPtr
  454. DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
  455. // Shutdown is universal. If its not that, then try it as
  456. // a custom command supported by the derivation. If that
  457. // doesn't pan out either, than send to it the application
  458. // as it may be supported there.
  459. return (shutdownProcess(args));
  460. }
  461. ConstElementPtr
  462. DControllerBase::shutdownProcess(ConstElementPtr args) {
  463. if (process_) {
  464. return (process_->shutdown(args));
  465. }
  466. // Not really a failure, but this condition is worth noting. In reality
  467. // it should be pretty hard to cause this.
  468. LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
  469. return (createAnswer(COMMAND_SUCCESS, "Process has not been initialized"));
  470. }
  471. void
  472. DControllerBase::initSignalHandling() {
  473. /// @todo block everything we don't handle
  474. // Create our signal queue.
  475. io_signal_queue_.reset(new IOSignalQueue(io_service_));
  476. // Install the on-receipt handler
  477. util::SignalSet::setOnReceiptHandler(boost::bind(&DControllerBase::
  478. osSignalHandler,
  479. this, _1));
  480. // Register for the signals we wish to handle.
  481. signal_set_.reset(new util::SignalSet(SIGHUP,SIGINT,SIGTERM));
  482. }
  483. bool
  484. DControllerBase::osSignalHandler(int signum) {
  485. // Create a IOSignal to propagate the signal to IOService.
  486. io_signal_queue_->pushSignal(signum, boost::bind(&DControllerBase::
  487. ioSignalHandler,
  488. this, _1));
  489. return (true);
  490. }
  491. void
  492. DControllerBase::ioSignalHandler(IOSignalId sequence_id) {
  493. // Pop the signal instance off the queue. This should make us
  494. // the only one holding it, so when we leave it should be freed.
  495. // (note that popSignal will throw if signal is not found, which
  496. // in turn will caught, logged, and swallowed by IOSignal callback
  497. // invocation code.)
  498. IOSignalPtr io_signal = io_signal_queue_->popSignal(sequence_id);
  499. // Now call virtual signal processing method.
  500. processSignal(io_signal->getSignum());
  501. }
  502. void
  503. DControllerBase::processSignal(int signum) {
  504. switch (signum) {
  505. case SIGHUP:
  506. {
  507. LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
  508. .arg(signum).arg(getConfigFile());
  509. int rcode;
  510. ConstElementPtr comment = parseAnswer(rcode, configFromFile());
  511. if (rcode != 0) {
  512. LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
  513. .arg(comment->stringValue());
  514. }
  515. break;
  516. }
  517. case SIGINT:
  518. case SIGTERM:
  519. {
  520. LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
  521. DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
  522. ElementPtr arg_set;
  523. shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
  524. break;
  525. }
  526. default:
  527. LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
  528. break;
  529. }
  530. }
  531. void
  532. DControllerBase::usage(const std::string & text)
  533. {
  534. if (text != "") {
  535. std::cerr << "Usage error: " << text << std::endl;
  536. }
  537. std::cerr << "Usage: " << bin_name_ << std::endl
  538. << " -v: print version number and exit" << std::endl
  539. << " -V: print extended version information and exit"
  540. << std::endl
  541. << " -W: display the configuration report and exit"
  542. << std::endl
  543. << " -d: optional, verbose output " << std::endl
  544. << " -c <config file name> : mandatory,"
  545. << " specify name of configuration file" << std::endl
  546. << " -t <config file name> : check the"
  547. << " configuration file and exit" << std::endl;
  548. // add any derivation specific usage
  549. std::cerr << getUsageText() << std::endl;
  550. }
  551. DControllerBase::~DControllerBase() {
  552. }
  553. // Refer to config_report so it will be embedded in the binary
  554. const char* const* d2_config_report = isc::detail::config_report;
  555. std::string
  556. DControllerBase::getVersion(bool extended) {
  557. std::stringstream tmp;
  558. tmp << VERSION;
  559. if (extended) {
  560. tmp << std::endl << EXTENDED_VERSION << std::endl;
  561. tmp << "linked with:" << std::endl;
  562. tmp << isc::log::Logger::getVersion() << std::endl;
  563. tmp << getVersionAddendum();
  564. }
  565. return (tmp.str());
  566. }
  567. }; // namespace isc::process
  568. }; // namespace isc