LCOV - code coverage report
Current view: top level - log - message_reader.cc (source / functions) Hit Total Coverage
Test: report.info Lines: 78 83 94.0 %
Date: 2012-05-15 Functions: 8 8 100.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 103 188 54.8 %

           Branch data     Line data    Source code
       1                 :            : // Copyright (C) 2011  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                 :            : 
      15                 :            : #include <cassert>
      16                 :            : #include <errno.h>
      17                 :            : #include <string.h>
      18                 :            : #include <iostream>
      19                 :            : 
      20                 :            : #include <iostream>
      21                 :            : #include <fstream>
      22                 :            : 
      23                 :            : #include <log/log_messages.h>
      24                 :            : #include <log/message_exception.h>
      25                 :            : #include <log/message_reader.h>
      26                 :            : #include <util/strutil.h>
      27                 :            : 
      28                 :            : using namespace std;
      29                 :            : 
      30                 :            : namespace {
      31                 :            : const char DIRECTIVE_FLAG = '$';    // Starts each directive
      32                 :            : const char MESSAGE_FLAG = '%';      // Starts each message
      33                 :            : }
      34                 :            : 
      35                 :            : 
      36                 :            : namespace isc {
      37                 :            : namespace log {
      38                 :            : 
      39                 :            : // Read the file.
      40                 :            : 
      41                 :            : void
      42                 :          3 : MessageReader::readFile(const string& file, MessageReader::Mode mode) {
      43                 :            : 
      44                 :            :     // Ensure the non-added collection is empty: we could be re-using this
      45                 :            :     // object.
      46                 :          3 :     not_added_.clear();
      47                 :            : 
      48                 :            :     // Open the file.
      49                 :          6 :     ifstream infile(file.c_str());
      50         [ +  + ]:          3 :     if (infile.fail()) {
      51 [ +  - ][ +  - ]:          4 :         isc_throw_4(MessageException, "Failed to open message file",
         [ +  - ][ +  - ]
      52                 :            :                     LOG_INPUT_OPEN_FAIL, file, strerror(errno), 0);
      53                 :            :     }
      54                 :            : 
      55                 :            :     // Loop round reading it.  As we process the file one line at a time,
      56                 :            :     // keep a track of line number of aid diagnosis of problems.
      57                 :          1 :     string line;
      58         [ +  - ]:          1 :     getline(infile, line);
      59                 :          1 :     lineno_ = 0;
      60                 :            : 
      61         [ +  + ]:          4 :     while (infile.good()) {
      62                 :          3 :         ++lineno_;
      63         [ +  - ]:          3 :         processLine(line, mode);
      64         [ +  - ]:          3 :         getline(infile, line);
      65                 :            :     }
      66                 :            : 
      67                 :            :     // Why did the loop terminate?
      68         [ -  + ]:          1 :     if (!infile.eof()) {
      69 [ #  # ][ #  # ]:          0 :         isc_throw_4(MessageException, "Error reading message file",
         [ #  # ][ #  # ]
      70                 :            :                     LOG_READ_ERROR, file, strerror(errno), 0);
      71                 :            :     }
      72         [ +  - ]:          1 :     infile.close();
      73                 :          1 : }
      74                 :            : 
      75                 :            : // Parse a line of the file.
      76                 :            : 
      77                 :            : void
      78                 :         39 : MessageReader::processLine(const string& line, MessageReader::Mode mode) {
      79                 :            : 
      80                 :            :     // Get rid of leading and trailing spaces
      81                 :         78 :     string text = isc::util::str::trim(line);
      82                 :            : 
      83         [ +  + ]:         39 :     if (text.empty()) {
      84                 :            :         ;                           // Ignore blank lines
      85                 :            : 
      86         [ +  + ]:         36 :     } else if (text[0] == DIRECTIVE_FLAG) {
      87         [ +  + ]:         18 :         parseDirective(text);       // Process directives
      88                 :            : 
      89                 :            : 
      90         [ +  + ]:         18 :     } else if (text[0] == MESSAGE_FLAG) {
      91         [ +  - ]:         13 :         parseMessage(text, mode);   // Process message definition line
      92                 :            : 
      93                 :            :     } else {
      94                 :            :         ;                           // Other lines are extended message
      95                 :            :                                     // description so are ignored
      96                 :            :     }
      97                 :         28 : }
      98                 :            : 
      99                 :            : // Process directive
     100                 :            : 
     101                 :            : void
     102                 :         18 : MessageReader::parseDirective(const std::string& text) {
     103                 :            : 
     104                 :            : 
     105                 :            :     // Break into tokens
     106         [ +  - ]:         36 :     vector<string> tokens = isc::util::str::tokens(text);
     107                 :            : 
     108                 :            :     // Uppercase directive and branch on valid ones
     109         [ +  - ]:         18 :     isc::util::str::uppercase(tokens[0]);
     110 [ +  - ][ +  + ]:         18 :     if (tokens[0] == string("$PREFIX")) {
     111         [ +  + ]:          9 :         parsePrefix(tokens);
     112                 :            : 
     113 [ +  - ][ +  + ]:          9 :     } else if (tokens[0] == string("$NAMESPACE")) {
     114         [ +  + ]:          7 :         parseNamespace(tokens);
     115                 :            : 
     116                 :            :     } else {
     117                 :            : 
     118                 :            :         // Unrecognised directive
     119 [ +  - ][ +  - ]:          4 :         isc_throw_3(MessageException, "Unrecognized directive",
                 [ +  - ]
     120                 :            :                     LOG_UNRECOGNISED_DIRECTIVE, tokens[0],
     121                 :            :                     lineno_);
     122                 :            :     }
     123                 :          7 : }
     124                 :            : 
     125                 :            : // Process $PREFIX
     126                 :            : void
     127                 :          9 : MessageReader::parsePrefix(const vector<string>& tokens) {
     128                 :            : 
     129                 :            :     // Should not get here unless there is something in the tokens array.
     130         [ -  + ]:          9 :     assert(tokens.size() > 0);
     131                 :            : 
     132                 :            :     // Process $PREFIX.  With no arguments, the prefix is set to the empty
     133                 :            :     // string.  One argument sets the prefix to the to its value and more than
     134                 :            :     // one argument is invalid.
     135         [ +  + ]:          9 :     if (tokens.size() == 1) {
     136                 :          2 :         prefix_ = "";
     137                 :            : 
     138         [ +  + ]:          7 :     } else if (tokens.size() == 2) {
     139                 :          6 :         prefix_ = tokens[1];
     140                 :            : 
     141                 :            :         // Token is potentially valid providing it only contains alphabetic
     142                 :            :         // and numeric characters (and underscores) and does not start with a
     143                 :            :         // digit.
     144         [ +  + ]:          6 :         if (invalidSymbol(prefix_)) {
     145 [ +  - ][ +  - ]:          8 :             isc_throw_3(MessageException, "Invalid prefix",
     146                 :            :                         LOG_PREFIX_INVALID_ARG, prefix_, lineno_);
     147                 :            :         }
     148                 :            : 
     149                 :            :     } else {
     150                 :            : 
     151                 :            :         // Too many arguments
     152 [ +  - ][ +  - ]:          2 :         isc_throw_2(MessageException, "Too many arguments",
     153                 :            :                     LOG_PREFIX_EXTRA_ARGS, lineno_);
     154                 :            :     }
     155                 :          4 : }
     156                 :            : 
     157                 :            : // Check if string is an invalid C++ symbol.  It is valid if comprises only
     158                 :            : // alphanumeric characters and underscores, and does not start with a digit.
     159                 :            : // (Owing to the logic of the rest of the code, we check for its invalidity,
     160                 :            : // not its validity.)
     161                 :            : bool
     162                 :         19 : MessageReader::invalidSymbol(const string& symbol) {
     163                 :            :     static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     164                 :            :                                       "abcdefghijklmnopqrstuvwxyz"
     165 [ +  + ][ +  - ]:         19 :                                       "0123456789_";
                 [ +  - ]
     166                 :            :     return ( symbol.empty() ||
     167                 :         19 :             (symbol.find_first_not_of(valid_chars) != string::npos) ||
     168   [ +  -  +  + ]:         38 :             (std::isdigit(symbol[0])));
                 [ +  + ]
     169                 :            : }
     170                 :            : 
     171                 :            : // Process $NAMESPACE.  A lot of the processing is similar to that of $PREFIX,
     172                 :            : // except that only limited checks will be done on the namespace (to avoid a
     173                 :            : // lot of parsing and separating out of the namespace components.)  Also, unlike
     174                 :            : // $PREFIX, there can only be one $NAMESPACE in a file.
     175                 :            : 
     176                 :            : void
     177                 :          7 : MessageReader::parseNamespace(const vector<string>& tokens) {
     178                 :            : 
     179                 :            :     // Check argument count
     180         [ +  + ]:          7 :     if (tokens.size() < 2) {
     181 [ +  - ][ +  - ]:          2 :         isc_throw_2(MessageException, "No arguments", LOG_NAMESPACE_NO_ARGS,
     182                 :            :                     lineno_);
     183                 :            : 
     184         [ +  + ]:          6 :     } else if (tokens.size() > 2) {
     185 [ +  - ][ +  - ]:          2 :         isc_throw_2(MessageException, "Too many arguments",
     186                 :            :                     LOG_NAMESPACE_EXTRA_ARGS, lineno_);
     187                 :            : 
     188                 :            :     }
     189                 :            : 
     190                 :            :     // Token is potentially valid providing it only contains alphabetic
     191                 :            :     // and numeric characters (and underscores and colons).  As noted above,
     192                 :            :     // we won't be exhaustive - after all, and code containing the resultant
     193                 :            :     // namespace will have to be compiled, and the compiler will catch errors.
     194                 :            :     static const string valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     195                 :            :                                       "abcdefghijklmnopqrstuvwxyz"
     196 [ +  + ][ +  - ]:          5 :                                       "0123456789_:";
                 [ +  - ]
     197         [ +  + ]:          5 :     if (tokens[1].find_first_not_of(valid_chars) != string::npos) {
     198 [ +  - ][ +  - ]:          2 :         isc_throw_3(MessageException, "Invalid argument",
     199                 :            :                     LOG_NAMESPACE_INVALID_ARG, tokens[1], lineno_);
     200                 :            :     }
     201                 :            : 
     202                 :            :     // All OK - unless the namespace has already been set.
     203         [ +  + ]:          4 :     if (ns_.size() != 0) {
     204 [ +  - ][ +  - ]:          2 :         isc_throw_2(MessageException, "Duplicate namespace",
     205                 :            :                     LOG_DUPLICATE_NAMESPACE, lineno_);
     206                 :            :     }
     207                 :            : 
     208                 :            :     // Prefix has not been set, so set it and return success.
     209                 :          3 :     ns_ = tokens[1];
     210                 :          3 : }
     211                 :            : 
     212                 :            : // Process message.  By the time this method is called, the line has been
     213                 :            : // stripped of leading and trailing spaces.  The first character of the string
     214                 :            : // is the message introducer, so we can get rid of that.  The remainder is
     215                 :            : // a line defining a message.
     216                 :            : //
     217                 :            : // The first token on the line, when concatenated to the prefix and converted to
     218                 :            : // upper-case, is the message ID.  The first of the line from the next token
     219                 :            : // on is the message text.
     220                 :            : 
     221                 :            : void
     222                 :         13 : MessageReader::parseMessage(const std::string& text, MessageReader::Mode mode) {
     223                 :            : 
     224 [ +  + ][ +  - ]:         13 :     static string delimiters("\t\n ");   // Delimiters
                 [ +  - ]
     225                 :            : 
     226                 :            :     // The line passed should be at least one character long and start with the
     227                 :            :     // message introducer (else we should not have got here).
     228 [ +  - ][ -  + ]:         13 :     assert((text.size() >= 1) && (text[0] == MESSAGE_FLAG));
     229                 :            : 
     230                 :            :     // A line comprising just the message introducer is not valid.
     231         [ -  + ]:         13 :     if (text.size() == 1) {
     232 [ #  # ][ #  # ]:          0 :         isc_throw_3(MessageException, "No message ID", LOG_NO_MESSAGE_ID,
     233                 :            :                     text, lineno_);
     234                 :            :     }
     235                 :            : 
     236                 :            :     // Strip off the introducer and any leading space after that.
     237         [ +  - ]:         26 :     string message_line = isc::util::str::trim(text.substr(1));
     238                 :            : 
     239                 :            :     // Look for the first delimiter.
     240         [ +  - ]:         13 :     size_t first_delim = message_line.find_first_of(delimiters);
     241         [ -  + ]:         13 :     if (first_delim == string::npos) {
     242                 :            : 
     243                 :            :         // Just a single token in the line - this is not valid
     244 [ #  # ][ #  # ]:          0 :         isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
                 [ #  # ]
     245                 :            :                     message_line, lineno_);
     246                 :            :     }
     247                 :            : 
     248                 :            :     // Extract the first token into the message ID, preceding it with the
     249                 :            :     // current prefix, then convert to upper-case.  If the prefix is not set,
     250                 :            :     // perform the valid character check now - the string will become a C++
     251                 :            :     // symbol so we may as well identify problems early.
     252         [ +  - ]:         39 :     string ident = prefix_ + message_line.substr(0, first_delim);
     253         [ +  - ]:         13 :     if (prefix_.empty()) {
     254 [ +  - ][ -  + ]:         13 :         if (invalidSymbol(ident)) {
     255 [ #  # ][ #  # ]:          0 :             isc_throw_3(MessageException, "Invalid message ID",
                 [ #  # ]
     256                 :            :                         LOG_INVALID_MESSAGE_ID, ident, lineno_);
     257                 :            :         }
     258                 :            :     }
     259         [ +  - ]:         13 :     isc::util::str::uppercase(ident);
     260                 :            : 
     261                 :            :     // Locate the start of the message text
     262         [ +  - ]:         13 :     size_t first_text = message_line.find_first_not_of(delimiters, first_delim);
     263         [ -  + ]:         13 :     if (first_text == string::npos) {
     264                 :            : 
     265                 :            :         // ?? This happens if there are trailing delimiters, which should not
     266                 :            :         // occur as we have stripped trailing spaces off the line.  Just treat
     267                 :            :         // this as a single-token error for simplicity's sake.
     268 [ #  # ][ #  # ]:          0 :         isc_throw_3(MessageException, "No message text", LOG_NO_MESSAGE_TEXT,
                 [ #  # ]
     269                 :            :                     message_line, lineno_);
     270                 :            :     }
     271                 :            : 
     272                 :            :     // Add the result to the dictionary and to the non-added list if the add to
     273                 :            :     // the dictionary fails.
     274                 :            :     bool added;
     275         [ +  + ]:         13 :     if (mode == ADD) {
     276 [ +  - ][ +  - ]:          7 :         added = dictionary_->add(ident, message_line.substr(first_text));
     277                 :            :     }
     278                 :            :     else {
     279 [ +  - ][ +  - ]:          6 :         added = dictionary_->replace(ident, message_line.substr(first_text));
     280                 :            :     }
     281         [ +  + ]:         13 :     if (!added) {
     282         [ +  - ]:          3 :         not_added_.push_back(ident);
     283                 :            :     }
     284                 :         13 : }
     285                 :            : 
     286                 :            : } // namespace log
     287                 :        172 : } // namespace isc

Generated by: LCOV version 1.9