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
|