message.cc 16 KB

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