message.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  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. #include <cctype>
  15. #include <cstddef>
  16. #include <fstream>
  17. #include <iostream>
  18. #include <string>
  19. #include <vector>
  20. #include <errno.h>
  21. #include <getopt.h>
  22. #include <string.h>
  23. #include <time.h>
  24. #include <unistd.h>
  25. #include <exceptions/exceptions.h>
  26. #include <util/filename.h>
  27. #include <util/strutil.h>
  28. #include <log/log_messages.h>
  29. #include <log/message_dictionary.h>
  30. #include <log/message_exception.h>
  31. #include <log/message_reader.h>
  32. #include <log/logger.h>
  33. #include <boost/foreach.hpp>
  34. using namespace std;
  35. using namespace isc::log;
  36. using namespace isc::util;
  37. static const char* VERSION = "1.0-0";
  38. /// \file log/compiler/message.cc
  39. /// \brief Message Compiler
  40. ///
  41. /// \b Overview<BR>
  42. /// This is the program that takes as input a message file and produces:
  43. ///
  44. /// \li A .h file containing message definition
  45. /// \li A .cc file containing code that adds the messages to the program's
  46. /// message dictionary at start-up time.
  47. ///
  48. /// \b Invocation<BR>
  49. /// The program is invoked with the command:
  50. ///
  51. /// <tt>message [-v | -h | -p | -d <dir> | <message-file>]</tt>
  52. ///
  53. /// It reads the message file and writes out two files of the same
  54. /// name in the current working directory (unless -d is used) but
  55. /// with extensions of .h and .cc, or .py if -p is used.
  56. ///
  57. /// -v causes it to print the version number and exit. -h prints a help
  58. /// message (and exits). -p sets the output to python. -d <dir> will make
  59. /// it write the output file(s) to dir instead of current working
  60. /// directory
  61. /// \brief Print Version
  62. ///
  63. /// Prints the program's version number.
  64. void
  65. version() {
  66. cout << VERSION << "\n";
  67. }
  68. /// \brief Print Usage
  69. ///
  70. /// Prints program usage to stdout.
  71. void
  72. usage() {
  73. cout <<
  74. "Usage: message [-h] [-v] [-p] [-d dir] <message-file>\n" <<
  75. "\n" <<
  76. "-h Print this message and exit\n" <<
  77. "-v Print the program version and exit\n" <<
  78. "-p Output python source instead of C++ ones\n" <<
  79. "-d <dir> Place output files in given directory\n" <<
  80. "\n" <<
  81. "<message-file> is the name of the input message file.\n";
  82. }
  83. /// \brief Create Time
  84. ///
  85. /// Returns the current time as a suitably-formatted string.
  86. ///
  87. /// \return Current time
  88. string
  89. currentTime() {
  90. // Get a text representation of the current time.
  91. time_t curtime;
  92. time(&curtime);
  93. char* buffer = ctime(&curtime);
  94. // Convert to string and strip out the trailing newline
  95. string current_time = buffer;
  96. return (isc::util::str::trim(current_time));
  97. }
  98. /// \brief Create Header Sentinel
  99. ///
  100. /// Given the name of a file, create an #ifdef sentinel name. The name is
  101. /// __<name>_<ext>, where <name> is the name of the file, and <ext> is the
  102. /// extension less the leading period. The sentinel will be upper-case.
  103. ///
  104. /// \param file Filename object representing the file.
  105. ///
  106. /// \return Sentinel name
  107. string
  108. sentinel(Filename& file) {
  109. string name = file.name();
  110. string ext = file.extension();
  111. string sentinel_text = "__" + name + "_" + ext.substr(1);
  112. isc::util::str::uppercase(sentinel_text);
  113. return (sentinel_text);
  114. }
  115. /// \brief Quote String
  116. ///
  117. /// Inserts an escape character (a backslash) prior to any double quote
  118. /// characters. This is used to handle the fact that the input file does not
  119. /// contain quotes, yet the string will be included in a C++ literal string.
  120. string
  121. quoteString(const string& instring) {
  122. // Create the output string and reserve the space needed to hold the input
  123. // string. (Most input strings will not contain quotes, so this single
  124. // reservation should be all that is needed.)
  125. string outstring;
  126. outstring.reserve(instring.size());
  127. // Iterate through the input string, preceding quotes with a slash.
  128. for (size_t i = 0; i < instring.size(); ++i) {
  129. if (instring[i] == '"') {
  130. outstring += '\\';
  131. }
  132. outstring += instring[i];
  133. }
  134. return (outstring);
  135. }
  136. /// \brief Sorted Identifiers
  137. ///
  138. /// Given a dictionary, return a vector holding the message IDs in sorted
  139. /// order.
  140. ///
  141. /// \param dictionary Dictionary to examine
  142. ///
  143. /// \return Sorted list of message IDs
  144. vector<string>
  145. sortedIdentifiers(MessageDictionary& dictionary) {
  146. vector<string> ident;
  147. for (MessageDictionary::const_iterator i = dictionary.begin();
  148. i != dictionary.end(); ++i) {
  149. ident.push_back(i->first);
  150. }
  151. sort(ident.begin(), ident.end());
  152. return (ident);
  153. }
  154. /// \brief Split Namespace
  155. ///
  156. /// The $NAMESPACE directive may well specify a namespace in the form a::b.
  157. /// Unfortunately, the C++ "namespace" statement can only accept a single
  158. /// string - to set up the namespace of "a::b" requires two statements, one
  159. /// for "namspace a" and the other for "namespace b".
  160. ///
  161. /// This function returns the set of namespace components as a vector of
  162. /// strings. A vector of one element, containing the empty string, is returned
  163. /// if the anonymous namespace is specified.
  164. ///
  165. /// \param ns Argument to $NAMESPACE (passed by value, as we will be modifying
  166. /// it.)
  167. vector<string>
  168. splitNamespace(string ns) {
  169. // Namespaces components are separated by double colon characters -
  170. // convert to single colons.
  171. size_t dcolon;
  172. while ((dcolon = ns.find("::")) != string::npos) {
  173. ns.replace(dcolon, 2, ":");
  174. }
  175. // ... and return the vector of namespace components split on the single
  176. // colon.
  177. return (isc::util::str::tokens(ns, ":"));
  178. }
  179. /// \brief Write Opening Namespace(s)
  180. ///
  181. /// Writes the lines listing the namespaces in use.
  182. void
  183. writeOpeningNamespace(ostream& output, const vector<string>& ns) {
  184. if (!ns.empty()) {
  185. // Output namespaces in correct order
  186. for (vector<string>::size_type i = 0; i < ns.size(); ++i) {
  187. output << "namespace " << ns[i] << " {\n";
  188. }
  189. output << "\n";
  190. }
  191. }
  192. /// \brief Write Closing Namespace(s)
  193. ///
  194. /// Writes the lines listing the namespaces in use.
  195. void
  196. writeClosingNamespace(ostream& output, const vector<string>& ns) {
  197. if (!ns.empty()) {
  198. for (int i = ns.size() - 1; i >= 0; --i) {
  199. output << "} // namespace " << ns[i] << "\n";
  200. }
  201. output << "\n";
  202. }
  203. }
  204. /// \breif Write python file
  205. ///
  206. /// Writes the python file containing the symbol definitions as module level
  207. /// constants. These are objects which register themself at creation time,
  208. /// so they can be replaced by dictionary later.
  209. ///
  210. /// \param file Name of the message file. The source code is written to a file
  211. /// file of the same name but with a .py suffix.
  212. /// \param dictionary The dictionary holding the message definitions.
  213. /// \param output_directory if not null NULL, output files are written
  214. /// to the given directory. If NULL, they are written to the current
  215. /// working directory.
  216. ///
  217. /// \note We don't use the namespace as in C++. We don't need it, because
  218. /// python file/module works as implicit namespace as well.
  219. void
  220. writePythonFile(const string& file, MessageDictionary& dictionary,
  221. const char* output_directory)
  222. {
  223. Filename message_file(file);
  224. Filename python_file(Filename(message_file.name()).useAsDefault(".py"));
  225. if (output_directory != NULL) {
  226. python_file.setDirectory(output_directory);
  227. }
  228. // Open the file for writing
  229. ofstream pyfile(python_file.fullName().c_str());
  230. // Write the comment and imports
  231. pyfile <<
  232. "# File created from " << message_file.fullName() << " on " <<
  233. currentTime() << "\n" <<
  234. "\n" <<
  235. "import isc.log\n" <<
  236. "\n";
  237. vector<string> idents(sortedIdentifiers(dictionary));
  238. BOOST_FOREACH(const string& ident, idents) {
  239. pyfile << ident << " = isc.log.create_message(\"" <<
  240. ident << "\", \"" << quoteString(dictionary.getText(ident)) <<
  241. "\")\n";
  242. }
  243. pyfile.close();
  244. }
  245. /// \brief Write Header File
  246. ///
  247. /// Writes the C++ header file containing the symbol definitions. These are
  248. /// "extern" references to definitions in the .cc file. As such, they should
  249. /// take up no space in the module in which they are included, and redundant
  250. /// references should be removed by the compiler.
  251. ///
  252. /// \param file Name of the message file. The header file is written to a
  253. /// file of the same name but with a .h suffix.
  254. /// \param ns Namespace in which the definitions are to be placed. An empty
  255. /// string indicates no namespace.
  256. /// \param dictionary Dictionary holding the message definitions.
  257. /// \param output_directory if not null NULL, output files are written
  258. /// to the given directory. If NULL, they are written to the current
  259. /// working directory.
  260. void
  261. writeHeaderFile(const string& file, const vector<string>& ns_components,
  262. MessageDictionary& dictionary, const char* output_directory)
  263. {
  264. Filename message_file(file);
  265. Filename header_file(Filename(message_file.name()).useAsDefault(".h"));
  266. if (output_directory != NULL) {
  267. header_file.setDirectory(output_directory);
  268. }
  269. // Text to use as the sentinels.
  270. string sentinel_text = sentinel(header_file);
  271. // Open the output file for writing
  272. ofstream hfile(header_file.fullName().c_str());
  273. if (hfile.fail()) {
  274. isc_throw_2(MessageException, LOG_OPEN_OUTPUT_FAIL,
  275. header_file.fullName(), strerror(errno));
  276. }
  277. // Write the header preamble. If there is an error, we'll pick it up
  278. // after the last write.
  279. hfile <<
  280. "// File created from " << message_file.fullName() << " on " <<
  281. currentTime() << "\n" <<
  282. "\n" <<
  283. "#ifndef " << sentinel_text << "\n" <<
  284. "#define " << sentinel_text << "\n" <<
  285. "\n" <<
  286. "#include <log/message_types.h>\n" <<
  287. "\n";
  288. // Write the message identifiers, bounded by a namespace declaration
  289. writeOpeningNamespace(hfile, ns_components);
  290. vector<string> idents = sortedIdentifiers(dictionary);
  291. for (vector<string>::const_iterator j = idents.begin();
  292. j != idents.end(); ++j) {
  293. hfile << "extern const isc::log::MessageID " << *j << ";\n";
  294. }
  295. hfile << "\n";
  296. writeClosingNamespace(hfile, ns_components);
  297. // ... and finally the postamble
  298. hfile << "#endif // " << sentinel_text << "\n";
  299. // Report errors (if any) and exit
  300. if (hfile.fail()) {
  301. isc_throw_2(MessageException, LOG_WRITE_ERROR, header_file.fullName(),
  302. strerror(errno));
  303. }
  304. hfile.close();
  305. }
  306. /// \brief Convert Non Alpha-Numeric Characters to Underscores
  307. ///
  308. /// Simple function for use in a call to transform
  309. char
  310. replaceNonAlphaNum(char c) {
  311. return (isalnum(c) ? c : '_');
  312. }
  313. /// \brief Write Program File
  314. ///
  315. /// Writes the C++ source code file. This defines the text of the message
  316. /// symbols, as well as the initializer object that sets the entries in the
  317. /// global dictionary.
  318. ///
  319. /// The construction of the initializer object loads the dictionary with the
  320. /// message text. However, nothing actually references it. If the initializer
  321. /// were in a file by itself, the lack of things referencing it would cause the
  322. /// linker to ignore it when pulling modules out of the logging library in a
  323. /// static link. By including it in the file with the symbol definitions, the
  324. /// module will get included in the link process to resolve the symbol
  325. /// definitions, and so the initializer object will be included in the final
  326. /// image. (Note that there are no such problems when the logging library is
  327. /// built as a dynamically-linked library: the whole library - including the
  328. /// initializer module - gets mapped into address space when the library is
  329. /// loaded, after which all the initializing code (including the constructors
  330. /// of objects declared outside functions) gets run.)
  331. ///
  332. /// There _may_ be a problem when we come to port this to Windows. Microsoft
  333. /// Visual Studio contains a "Whole Program Optimisation" option, where the
  334. /// optimisation is done at link-time, not compiler-time. In this it _may_
  335. /// decide to remove the initializer object because of a lack of references
  336. /// to it. But until BIND-10 is ported to Windows, we won't know.
  337. ///
  338. /// \param file Name of the message file. The header file is written to a
  339. /// file of the same name but with a .h suffix.
  340. /// \param ns Namespace in which the definitions are to be placed. An empty
  341. /// string indicates no namespace.
  342. /// \param dictionary Dictionary holding the message definitions.
  343. /// \param output_directory if not null NULL, output files are written
  344. /// to the given directory. If NULL, they are written to the current
  345. /// working directory.
  346. void
  347. writeProgramFile(const string& file, const vector<string>& ns_components,
  348. MessageDictionary& dictionary,
  349. const char* output_directory)
  350. {
  351. Filename message_file(file);
  352. Filename program_file(Filename(message_file.name()).useAsDefault(".cc"));
  353. if (output_directory) {
  354. program_file.setDirectory(output_directory);
  355. }
  356. // Open the output file for writing
  357. ofstream ccfile(program_file.fullName().c_str());
  358. if (ccfile.fail()) {
  359. isc_throw_2(MessageException, LOG_OPEN_OUTPUT_FAIL,
  360. program_file.fullName(), strerror(errno));
  361. }
  362. // Write the preamble. If there is an error, we'll pick it up after
  363. // the last write.
  364. ccfile <<
  365. "// File created from " << message_file.fullName() << " on " <<
  366. currentTime() << "\n" <<
  367. "\n" <<
  368. "#include <cstddef>\n" <<
  369. "#include <log/message_types.h>\n" <<
  370. "#include <log/message_initializer.h>\n" <<
  371. "\n";
  372. // Declare the message symbols themselves.
  373. writeOpeningNamespace(ccfile, ns_components);
  374. vector<string> idents = sortedIdentifiers(dictionary);
  375. for (vector<string>::const_iterator j = idents.begin();
  376. j != idents.end(); ++j) {
  377. ccfile << "extern const isc::log::MessageID " << *j <<
  378. " = \"" << *j << "\";\n";
  379. }
  380. ccfile << "\n";
  381. writeClosingNamespace(ccfile, ns_components);
  382. // Now the code for the message initialization.
  383. ccfile <<
  384. "namespace {\n" <<
  385. "\n" <<
  386. "const char* values[] = {\n";
  387. // Output the identifiers and the associated text.
  388. idents = sortedIdentifiers(dictionary);
  389. for (vector<string>::const_iterator i = idents.begin();
  390. i != idents.end(); ++i) {
  391. ccfile << " \"" << *i << "\", \"" <<
  392. quoteString(dictionary.getText(*i)) << "\",\n";
  393. }
  394. // ... and the postamble
  395. ccfile <<
  396. " NULL\n" <<
  397. "};\n" <<
  398. "\n" <<
  399. "const isc::log::MessageInitializer initializer(values);\n" <<
  400. "\n" <<
  401. "} // Anonymous namespace\n" <<
  402. "\n";
  403. // Report errors (if any) and exit
  404. if (ccfile.fail()) {
  405. isc_throw_2(MessageException, LOG_WRITE_ERROR, program_file.fullName(),
  406. strerror(errno));
  407. }
  408. ccfile.close();
  409. }
  410. /// \brief Error and exit if there are duplicate entries
  411. ///
  412. /// If the input file contained duplicate message IDs, we print an
  413. /// error for each of them, then exit the program with a non-0 value.
  414. ///
  415. /// \param reader Message Reader used to read the file
  416. void
  417. errorDuplicates(MessageReader& reader) {
  418. // Get the duplicates (the overflow) and, if present, sort them into some
  419. // order and remove those which occur more than once (which mean that they
  420. // occur more than twice in the input file).
  421. MessageReader::MessageIDCollection duplicates = reader.getNotAdded();
  422. if (!duplicates.empty()) {
  423. cout << "Error: the following duplicate IDs were found:\n";
  424. sort(duplicates.begin(), duplicates.end());
  425. MessageReader::MessageIDCollection::iterator new_end =
  426. unique(duplicates.begin(), duplicates.end());
  427. for (MessageReader::MessageIDCollection::iterator i = duplicates.begin();
  428. i != new_end; ++i) {
  429. cout << " " << *i << "\n";
  430. }
  431. exit(1);
  432. }
  433. }
  434. /// \brief Main Program
  435. ///
  436. /// Parses the options then dispatches to the appropriate function. See the
  437. /// main file header for the invocation.
  438. int
  439. main(int argc, char* argv[]) {
  440. const char* soptions = "hvpd:"; // Short options
  441. optind = 1; // Ensure we start a new scan
  442. int opt; // Value of the option
  443. bool doPython = false;
  444. const char *output_directory = NULL;
  445. while ((opt = getopt(argc, argv, soptions)) != -1) {
  446. switch (opt) {
  447. case 'd':
  448. output_directory = optarg;
  449. break;
  450. case 'p':
  451. doPython = true;
  452. break;
  453. case 'h':
  454. usage();
  455. return (0);
  456. case 'v':
  457. version();
  458. return (0);
  459. default:
  460. // A message will have already been output about the error.
  461. return (1);
  462. }
  463. }
  464. // Do we have the message file?
  465. if (optind < (argc - 1)) {
  466. cout << "Error: excess arguments in command line\n";
  467. usage();
  468. return (1);
  469. } else if (optind >= argc) {
  470. cout << "Error: missing message file\n";
  471. usage();
  472. return (1);
  473. }
  474. string message_file = argv[optind];
  475. try {
  476. // Have identified the file, so process it. First create a local
  477. // dictionary into which the data will be put.
  478. MessageDictionary dictionary;
  479. // Read the data into it.
  480. MessageReader reader(&dictionary);
  481. reader.readFile(message_file);
  482. // Error (and quit) if there are of any duplicates encountered.
  483. errorDuplicates(reader);
  484. if (doPython) {
  485. // Warn in case of ignored directives
  486. if (!reader.getNamespace().empty()) {
  487. cerr << "Python mode, ignoring the $NAMESPACE directive" <<
  488. endl;
  489. }
  490. // Write the whole python file
  491. writePythonFile(message_file, dictionary, output_directory);
  492. } else {
  493. // Get the namespace into which the message definitions will be put and
  494. // split it into components.
  495. vector<string> ns_components =
  496. splitNamespace(reader.getNamespace());
  497. // Write the header file.
  498. writeHeaderFile(message_file, ns_components, dictionary,
  499. output_directory);
  500. // Write the file that defines the message symbols and text
  501. writeProgramFile(message_file, ns_components, dictionary,
  502. output_directory);
  503. }
  504. }
  505. catch (const MessageException& e) {
  506. // Create an error message from the ID and the text
  507. MessageDictionary& global = MessageDictionary::globalDictionary();
  508. string text = e.id();
  509. text += ", ";
  510. text += global.getText(e.id());
  511. // Format with arguments
  512. vector<string> args(e.arguments());
  513. for (size_t i(0); i < args.size(); ++ i) {
  514. replacePlaceholder(&text, args[i], i + 1);
  515. }
  516. cerr << text << "\n";
  517. return (1);
  518. }
  519. return (0);
  520. }