123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- // Copyright (C) 2011, 2015 Internet Systems Consortium, Inc. ("ISC")
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- // PERFORMANCE OF THIS SOFTWARE.
- #include <cassert>
- #include <errno.h>
- #include <string.h>
- #include <iostream>
- #include <iostream>
- #include <fstream>
- #include <log/log_messages.h>
- #include <log/message_exception.h>
- #include <log/message_reader.h>
- #include <util/strutil.h>
- using namespace std;
- namespace {
- const char DIRECTIVE_FLAG = '$'; // Starts each directive
- const char MESSAGE_FLAG = '%'; // Starts each message
- }
- namespace isc {
- namespace log {
- // Read the file.
- void
- MessageReader::readFile(const string& file, MessageReader::Mode mode) {
- // Ensure the non-added collection is empty: we could be re-using this
- // object.
- not_added_.clear();
- // Open the file.
- ifstream infile(file.c_str());
- if (infile.fail()) {
- isc_throw_4(MessageException, "Failed to open message file",
- LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
- }
- // Loop round reading it. As we process the file one line at a time,
- // keep a track of line number of aid diagnosis of problems.
- string line;
- getline(infile, line);
- lineno_ = 0;
- while (infile.good()) {
- ++lineno_;
- processLine(line, mode);
- getline(infile, line);
- }
- // Why did the loop terminate?
- if (!infile.eof()) {
- isc_throw_4(MessageException, "Error reading message file",
- LOG_READ_ERROR, file, strerror(errno), 0);
- }
- infile.close();
- }
- // Parse a line of the file.
- void
- MessageReader::processLine(const string& line, MessageReader::Mode mode) {
- // Get rid of leading and trailing spaces
- string text = isc::util::str::trim(line);
- if (text.empty()) {
- ; // Ignore blank lines
- } else if (text[0] == DIRECTIVE_FLAG) {
- parseDirective(text); // Process directives
- } else if (text[0] == MESSAGE_FLAG) {
- parseMessage(text, mode); // Process message definition line
- } else {
- ; // Other lines are extended message
- // description so are ignored
- }
- }
- // Process directive
- void
- MessageReader::parseDirective(const std::string& text) {
- // Break into tokens
- vector<string> tokens = isc::util::str::tokens(text);
- // Uppercase directive and branch on valid ones
- isc::util::str::uppercase(tokens[0]);
- if (tokens[0] == string("$PREFIX")) {
- parsePrefix(tokens);
- } else if (tokens[0] == string("$NAMESPACE")) {
- parseNamespace(tokens);
- } else {
- // Unrecognized directive
- isc_throw_3(MessageException, "Unrecognized directive",
- LOG_UNRECOGNIZED_DIRECTIVE, tokens[0],
- lineno_);
- }
- }
- // Process $PREFIX
- void
- MessageReader::parsePrefix(const vector<string>& tokens) {
- // Should not get here unless there is something in the tokens array.
- assert(!tokens.empty());
- // Process $PREFIX. With no arguments, the prefix is set to the empty
- // string. One argument sets the prefix to the to its value and more than
- // one argument is invalid.
- if (tokens.size() == 1) {
- prefix_ = "";
- } else if (tokens.size() == 2) {
- prefix_ = tokens[1];
- // Token is potentially valid providing it only contains alphabetic
- // and numeric characters (and underscores) and does not start with a
- // digit.
- if (invalidSymbol(prefix_)) {
- isc_throw_3(MessageException, "Invalid prefix",
- LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
- }
- } else {
- // Too many arguments
- isc_throw_2(MessageException, "Too many arguments",
- LOG_PREFIX_EXTRA_ARGS, lineno_);
- }
- }
- // Check if string is an invalid C++ symbol. It is valid if comprises only
- // alphanumeric characters and underscores, and does not start with a digit.
- // (Owing to the logic of the rest of the code, we check for its invalidity,
- // not its validity.)
- bool
- MessageReader::invalidSymbol(const string& symbol) {
- static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789_";
- return ( symbol.empty() ||
- (symbol.find_first_not_of(valid_chars) != string::npos) ||
- (std::isdigit(symbol[0])));
- }
- // Process $NAMESPACE. A lot of the processing is similar to that of $PREFIX,
- // except that only limited checks will be done on the namespace (to avoid a
- // lot of parsing and separating out of the namespace components.) Also, unlike
- // $PREFIX, there can only be one $NAMESPACE in a file.
- void
- MessageReader::parseNamespace(const vector<string>& tokens) {
- // Check argument count
- if (tokens.size() < 2) {
- isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
- lineno_);
- } else if (tokens.size() > 2) {
- isc_throw_2(MessageException, "Too many arguments",
- LOG_NAMESPACE_EXTRA_ARGS, lineno_);
- }
- // Token is potentially valid providing it only contains alphabetic
- // and numeric characters (and underscores and colons). As noted above,
- // we won't be exhaustive - after all, and code containing the resultant
- // namespace will have to be compiled, and the compiler will catch errors.
- static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789_:";
- if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
- isc_throw_3(MessageException, "Invalid argument",
- LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
- }
- // All OK - unless the namespace has already been set.
- if (ns_.size() != 0) {
- isc_throw_2(MessageException, "Duplicate namespace",
- LOG_DUPLICATE_NAMESPACE, lineno_);
- }
- // Prefix has not been set, so set it and return success.
- ns_ = tokens[1];
- }
- // Process message. By the time this method is called, the line has been
- // stripped of leading and trailing spaces. The first character of the string
- // is the message introducer, so we can get rid of that. The remainder is
- // a line defining a message.
- //
- // The first token on the line, when concatenated to the prefix and converted to
- // upper-case, is the message ID. The first of the line from the next token
- // on is the message text.
- void
- MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
- static string delimiters("\t\n "); // Delimiters
- // The line passed should be at least one character long and start with the
- // message introducer (else we should not have got here).
- assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
- // A line comprising just the message introducer is not valid.
- if (text.size() == 1) {
- isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
- text, lineno_);
- }
- // Strip off the introducer and any leading space after that.
- string message_line = isc::util::str::trim(text.substr(1));
- // Look for the first delimiter.
- size_t first_delim = message_line.find_first_of(delimiters);
- if (first_delim == string::npos) {
- // Just a single token in the line - this is not valid
- isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
- message_line, lineno_);
- }
- // Extract the first token into the message ID, preceding it with the
- // current prefix, then convert to upper-case. If the prefix is not set,
- // perform the valid character check now - the string will become a C++
- // symbol so we may as well identify problems early.
- string ident = prefix_ + message_line.substr(0, first_delim);
- if (prefix_.empty()) {
- if (invalidSymbol(ident)) {
- isc_throw_3(MessageException, "Invalid message ID",
- LOG_INVALID_MESSAGE_ID, ident, lineno_);
- }
- }
- isc::util::str::uppercase(ident);
- // Locate the start of the message text
- size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
- if (first_text == string::npos) {
- // ?? This happens if there are trailing delimiters, which should not
- // occur as we have stripped trailing spaces off the line. Just treat
- // this as a single-token error for simplicity's sake.
- isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
- message_line, lineno_);
- }
- // Add the result to the dictionary and to the non-added list if the add to
- // the dictionary fails.
- bool added;
- if (mode == ADD) {
- added = dictionary_->add(ident, message_line.substr(first_text));
- }
- else {
- added = dictionary_->replace(ident, message_line.substr(first_text));
- }
- if (!added) {
- not_added_.push_back(ident);
- }
- }
- } // namespace log
- } // namespace isc
|