message_reader.cc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Copyright (C) 2011, 2015 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <cassert>
  15. #include <errno.h>
  16. #include <string.h>
  17. #include <iostream>
  18. #include <iostream>
  19. #include <fstream>
  20. #include <log/log_messages.h>
  21. #include <log/message_exception.h>
  22. #include <log/message_reader.h>
  23. #include <util/strutil.h>
  24. using namespace std;
  25. namespace {
  26. const char DIRECTIVE_FLAG = '$'; // Starts each directive
  27. const char MESSAGE_FLAG = '%'; // Starts each message
  28. }
  29. namespace isc {
  30. namespace log {
  31. // Read the file.
  32. void
  33. MessageReader::readFile(const string& file, MessageReader::Mode mode) {
  34. // Ensure the non-added collection is empty: we could be re-using this
  35. // object.
  36. not_added_.clear();
  37. // Open the file.
  38. ifstream infile(file.c_str());
  39. if (infile.fail()) {
  40. isc_throw_4(MessageException, "Failed to open message file",
  41. LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
  42. }
  43. // Loop round reading it. As we process the file one line at a time,
  44. // keep a track of line number of aid diagnosis of problems.
  45. string line;
  46. getline(infile, line);
  47. lineno_ = 0;
  48. while (infile.good()) {
  49. ++lineno_;
  50. processLine(line, mode);
  51. getline(infile, line);
  52. }
  53. // Why did the loop terminate?
  54. if (!infile.eof()) {
  55. isc_throw_4(MessageException, "Error reading message file",
  56. LOG_READ_ERROR, file, strerror(errno), 0);
  57. }
  58. infile.close();
  59. }
  60. // Parse a line of the file.
  61. void
  62. MessageReader::processLine(const string& line, MessageReader::Mode mode) {
  63. // Get rid of leading and trailing spaces
  64. string text = isc::util::str::trim(line);
  65. if (text.empty()) {
  66. ; // Ignore blank lines
  67. } else if (text[0] == DIRECTIVE_FLAG) {
  68. parseDirective(text); // Process directives
  69. } else if (text[0] == MESSAGE_FLAG) {
  70. parseMessage(text, mode); // Process message definition line
  71. } else {
  72. ; // Other lines are extended message
  73. // description so are ignored
  74. }
  75. }
  76. // Process directive
  77. void
  78. MessageReader::parseDirective(const std::string& text) {
  79. // Break into tokens
  80. vector<string> tokens = isc::util::str::tokens(text);
  81. // Uppercase directive and branch on valid ones
  82. isc::util::str::uppercase(tokens[0]);
  83. if (tokens[0] == string("$PREFIX")) {
  84. parsePrefix(tokens);
  85. } else if (tokens[0] == string("$NAMESPACE")) {
  86. parseNamespace(tokens);
  87. } else {
  88. // Unrecognized directive
  89. isc_throw_3(MessageException, "Unrecognized directive",
  90. LOG_UNRECOGNIZED_DIRECTIVE, tokens[0],
  91. lineno_);
  92. }
  93. }
  94. // Process $PREFIX
  95. void
  96. MessageReader::parsePrefix(const vector<string>& tokens) {
  97. // Should not get here unless there is something in the tokens array.
  98. assert(!tokens.empty());
  99. // Process $PREFIX. With no arguments, the prefix is set to the empty
  100. // string. One argument sets the prefix to the to its value and more than
  101. // one argument is invalid.
  102. if (tokens.size() == 1) {
  103. prefix_ = "";
  104. } else if (tokens.size() == 2) {
  105. prefix_ = tokens[1];
  106. // Token is potentially valid providing it only contains alphabetic
  107. // and numeric characters (and underscores) and does not start with a
  108. // digit.
  109. if (invalidSymbol(prefix_)) {
  110. isc_throw_3(MessageException, "Invalid prefix",
  111. LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
  112. }
  113. } else {
  114. // Too many arguments
  115. isc_throw_2(MessageException, "Too many arguments",
  116. LOG_PREFIX_EXTRA_ARGS, lineno_);
  117. }
  118. }
  119. // Check if string is an invalid C++ symbol. It is valid if comprises only
  120. // alphanumeric characters and underscores, and does not start with a digit.
  121. // (Owing to the logic of the rest of the code, we check for its invalidity,
  122. // not its validity.)
  123. bool
  124. MessageReader::invalidSymbol(const string& symbol) {
  125. static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  126. "abcdefghijklmnopqrstuvwxyz"
  127. "0123456789_";
  128. return ( symbol.empty() ||
  129. (symbol.find_first_not_of(valid_chars) != string::npos) ||
  130. (std::isdigit(symbol[0])));
  131. }
  132. // Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
  133. // except that only limited checks will be done on the namespace (to avoid a
  134. // lot of parsing and separating out of the namespace components.) Also, unlike
  135. // $PREFIX, there can only be one $NAMESPACE in a file.
  136. void
  137. MessageReader::parseNamespace(const vector<string>& tokens) {
  138. // Check argument count
  139. if (tokens.size() < 2) {
  140. isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
  141. lineno_);
  142. } else if (tokens.size() > 2) {
  143. isc_throw_2(MessageException, "Too many arguments",
  144. LOG_NAMESPACE_EXTRA_ARGS, lineno_);
  145. }
  146. // Token is potentially valid providing it only contains alphabetic
  147. // and numeric characters (and underscores and colons). As noted above,
  148. // we won't be exhaustive - after all, and code containing the resultant
  149. // namespace will have to be compiled, and the compiler will catch errors.
  150. static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  151. "abcdefghijklmnopqrstuvwxyz"
  152. "0123456789_:";
  153. if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
  154. isc_throw_3(MessageException, "Invalid argument",
  155. LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
  156. }
  157. // All OK - unless the namespace has already been set.
  158. if (ns_.size() != 0) {
  159. isc_throw_2(MessageException, "Duplicate namespace",
  160. LOG_DUPLICATE_NAMESPACE, lineno_);
  161. }
  162. // Prefix has not been set, so set it and return success.
  163. ns_ = tokens[1];
  164. }
  165. // Process message. By the time this method is called, the line has been
  166. // stripped of leading and trailing spaces. The first character of the string
  167. // is the message introducer, so we can get rid of that. The remainder is
  168. // a line defining a message.
  169. //
  170. // The first token on the line, when concatenated to the prefix and converted to
  171. // upper-case, is the message ID. The first of the line from the next token
  172. // on is the message text.
  173. void
  174. MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
  175. static string delimiters("\t\n "); // Delimiters
  176. // The line passed should be at least one character long and start with the
  177. // message introducer (else we should not have got here).
  178. assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
  179. // A line comprising just the message introducer is not valid.
  180. if (text.size() == 1) {
  181. isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
  182. text, lineno_);
  183. }
  184. // Strip off the introducer and any leading space after that.
  185. string message_line = isc::util::str::trim(text.substr(1));
  186. // Look for the first delimiter.
  187. size_t first_delim = message_line.find_first_of(delimiters);
  188. if (first_delim == string::npos) {
  189. // Just a single token in the line - this is not valid
  190. isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
  191. message_line, lineno_);
  192. }
  193. // Extract the first token into the message ID, preceding it with the
  194. // current prefix, then convert to upper-case. If the prefix is not set,
  195. // perform the valid character check now - the string will become a C++
  196. // symbol so we may as well identify problems early.
  197. string ident = prefix_ + message_line.substr(0, first_delim);
  198. if (prefix_.empty()) {
  199. if (invalidSymbol(ident)) {
  200. isc_throw_3(MessageException, "Invalid message ID",
  201. LOG_INVALID_MESSAGE_ID, ident, lineno_);
  202. }
  203. }
  204. isc::util::str::uppercase(ident);
  205. // Locate the start of the message text
  206. size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
  207. if (first_text == string::npos) {
  208. // ?? This happens if there are trailing delimiters, which should not
  209. // occur as we have stripped trailing spaces off the line. Just treat
  210. // this as a single-token error for simplicity's sake.
  211. isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
  212. message_line, lineno_);
  213. }
  214. // Add the result to the dictionary and to the non-added list if the add to
  215. // the dictionary fails.
  216. bool added;
  217. if (mode == ADD) {
  218. added = dictionary_->add(ident, message_line.substr(first_text));
  219. }
  220. else {
  221. added = dictionary_->replace(ident, message_line.substr(first_text));
  222. }
  223. if (!added) {
  224. not_added_.push_back(ident);
  225. }
  226. }
  227. } // namespace log
  228. } // namespace isc