1 /*
2    Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 /*
26   Written by Anjuta Widenius
27 */
28 
29 /*
30   Creates one include file and multiple language-error message files from one
31   multi-language text file.
32 */
33 
34 #include <my_global.h>
35 #include <m_ctype.h>
36 #include <my_sys.h>
37 #include <m_string.h>
38 #include <my_getopt.h>
39 #include <assert.h>
40 #include <my_dir.h>
41 #include <mysql_version.h>
42 
43 #define MAX_ROWS  1000
44 #define HEADER_LENGTH 32                /* Length of header in errmsg.sys */
45 #define ERRMSG_VERSION 3                /* Version number of errmsg.sys */
46 #define DEFAULT_CHARSET_DIR "../sql/share/charsets"
47 #define ER_PREFIX "ER_"
48 #define WARN_PREFIX "WARN_"
49 static char *OUTFILE= (char*) "errmsg.sys";
50 static char *HEADERFILE= (char*) "mysqld_error.h";
51 static char *NAMEFILE= (char*) "mysqld_ername.h";
52 static char *STATEFILE= (char*) "sql_state.h";
53 static char *TXTFILE= (char*) "../sql/share/errmsg-utf8.txt";
54 static char *DATADIRECTORY= (char*) "../sql/share/";
55 #ifndef DBUG_OFF
56 static char *default_dbug_option= (char*) "d:t:O,/tmp/comp_err.trace";
57 #endif
58 
59 /*
60   Header for errmsg.sys files
61   Byte 3 is treated as version number for errmsg.sys
62     With this version ERRMSG_VERSION = 3, number of bytes
63     used for the length, count and offset are increased
64     from 2 bytes to 4 bytes.
65 */
66 uchar file_head[]= { 254, 254, ERRMSG_VERSION, 1 };
67 /* Store positions to each error message row to store in errmsg.sys header */
68 uint file_pos[MAX_ROWS];
69 
70 const char *empty_string= "";			/* For empty states */
71 /*
72   Default values for command line options. See getopt structure for definitions
73   for these.
74 */
75 
76 const char *default_language= "eng";
77 int er_offset= 1000;
78 my_bool info_flag= 0;
79 
80 /* Storage of one error message row (for one language) */
81 
82 struct message
83 {
84   char *lang_short_name;
85   char *text;
86 };
87 
88 
89 /* Storage for languages and charsets (from start of error text file) */
90 
91 struct languages
92 {
93   char *lang_long_name;				/* full name of the language */
94   char *lang_short_name;			/* abbreviation of the lang. */
95   char *charset;				/* Character set name */
96   struct languages *next_lang;			/* Pointer to next language */
97 };
98 
99 
100 /* Name, code and  texts (for all lang) for one error message */
101 
102 struct errors
103 {
104   const char *er_name;			/* Name of the error (ER_HASHCK) */
105   int d_code;                           /* Error code number */
106   const char *sql_code1;		/* sql state */
107   const char *sql_code2;		/* ODBC state */
108   struct errors *next_error;            /* Pointer to next error */
109   DYNAMIC_ARRAY msg;                    /* All language texts for this error */
110 };
111 
112 
113 static struct my_option my_long_options[]=
114 {
115 #ifdef DBUG_OFF
116   {"debug", '#', "This is a non-debug version. Catch this and exit",
117    0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
118 #else
119   {"debug", '#', "Output debug log", &default_dbug_option,
120    &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
121 #endif
122   {"debug-info", 'T', "Print some debug info at exit.", &info_flag,
123    &info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0},
124   {"help", '?', "Displays this help and exits.", 0, 0, 0, GET_NO_ARG,
125    NO_ARG, 0, 0, 0, 0, 0, 0},
126   {"version", 'V', "Prints version", 0, 0, 0, GET_NO_ARG,
127    NO_ARG, 0, 0, 0, 0, 0, 0},
128   {"charset", 'C', "Charset dir", &charsets_dir, &charsets_dir,
129    0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
130   {"in_file", 'F', "Input file", &TXTFILE, &TXTFILE,
131    0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
132   {"out_dir", 'D', "Output base directory", &DATADIRECTORY, &DATADIRECTORY,
133    0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
134   {"out_file", 'O', "Output filename (errmsg.sys)", &OUTFILE,
135    &OUTFILE, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
136   {"header_file", 'H', "mysqld_error.h file ", &HEADERFILE,
137    &HEADERFILE, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
138   {"name_file", 'N', "mysqld_ername.h file ", &NAMEFILE,
139    &NAMEFILE, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
140   {"state_file", 'S', "sql_state.h file", &STATEFILE,
141    &STATEFILE, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
142   {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
143 };
144 
145 
146 static struct languages *parse_charset_string(char *str);
147 static struct errors *parse_error_string(char *ptr, int er_count);
148 static struct message *parse_message_string(struct message *new_message,
149 					    char *str);
150 static struct message *find_message(struct errors *err, const char *lang,
151                                     my_bool no_default);
152 static int check_message_format(struct errors *err,
153                                 const char* mess);
154 static int parse_input_file(const char *file_name, struct errors **top_error,
155 			    struct languages **top_language);
156 static int get_options(int *argc, char ***argv);
157 static void print_version(void);
158 static void usage(void);
159 static my_bool get_one_option(int optid, const struct my_option *opt,
160 			      char *argument);
161 static char *parse_text_line(char *pos);
162 static int copy_rows(FILE * to, char *row, int row_nr, long start_pos);
163 static char *parse_default_language(char *str);
164 static uint parse_error_offset(char *str);
165 
166 static char *skip_delimiters(char *str);
167 static char *get_word(char **str);
168 static char *find_end_of_word(char *str);
169 static void clean_up(struct languages *lang_head, struct errors *error_head);
170 static int create_header_files(struct errors *error_head);
171 static int create_sys_files(struct languages *lang_head,
172 			    struct errors *error_head, uint row_count);
173 
174 
main(int argc,char * argv[])175 int main(int argc, char *argv[])
176 {
177   MY_INIT(argv[0]);
178   {
179     uint row_count;
180     struct errors *error_head;
181     struct languages *lang_head;
182     DBUG_ENTER("main");
183 
184     charsets_dir= DEFAULT_CHARSET_DIR;
185     my_umask_dir= 0777;
186     if (get_options(&argc, &argv))
187       DBUG_RETURN(1);
188     if (!(row_count= parse_input_file(TXTFILE, &error_head, &lang_head)))
189     {
190       fprintf(stderr, "Failed to parse input file %s\n", TXTFILE);
191       DBUG_RETURN(1);
192     }
193 #if MYSQL_VERSION_ID >= 50100 && MYSQL_VERSION_ID < 50500
194 /* Number of error messages in 5.1 - do not change this number! */
195 #define MYSQL_OLD_GA_ERROR_MESSAGE_COUNT 641
196 #elif MYSQL_VERSION_ID >= 50500 && MYSQL_VERSION_ID < 50600
197 /* Number of error messages in 5.5 - do not change this number! */
198 #define MYSQL_OLD_GA_ERROR_MESSAGE_COUNT 728
199 #endif
200 #if MYSQL_OLD_GA_ERROR_MESSAGE_COUNT
201     if (row_count != MYSQL_OLD_GA_ERROR_MESSAGE_COUNT)
202     {
203       fprintf(stderr, "Can only add new error messages to latest GA. ");
204       fprintf(stderr, "Use ER_UNKNOWN_ERROR instead.\n");
205       fprintf(stderr, "Expected %u messages, found %u.\n",
206               MYSQL_OLD_GA_ERROR_MESSAGE_COUNT, row_count);
207       DBUG_RETURN(1);
208     }
209 #endif
210     if (lang_head == NULL || error_head == NULL)
211     {
212       fprintf(stderr, "Failed to parse input file %s\n", TXTFILE);
213       DBUG_RETURN(1);
214     }
215 
216     if (create_header_files(error_head))
217     {
218       fprintf(stderr, "Failed to create header files\n");
219       DBUG_RETURN(1);
220     }
221     if (create_sys_files(lang_head, error_head, row_count))
222     {
223       fprintf(stderr, "Failed to create sys files\n");
224       DBUG_RETURN(1);
225     }
226     clean_up(lang_head, error_head);
227     DBUG_LEAVE;			/* Can't use dbug after my_end() */
228     my_end(info_flag ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
229     return 0;
230   }
231 }
232 
233 
print_escaped_string(FILE * f,const char * str)234 static void print_escaped_string(FILE *f, const char *str)
235 {
236   const char *tmp = str;
237 
238   while (tmp[0] != 0)
239   {
240     switch (tmp[0])
241     {
242       case '\\': fprintf(f, "\\\\"); break;
243       case '\'': fprintf(f, "\\\'"); break;
244       case '\"': fprintf(f, "\\\""); break;
245       case '\n': fprintf(f, "\\n"); break;
246       case '\r': fprintf(f, "\\r"); break;
247       default: fprintf(f, "%c", tmp[0]);
248     }
249     tmp++;
250   }
251 }
252 
253 
create_header_files(struct errors * error_head)254 static int create_header_files(struct errors *error_head)
255 {
256   uint er_last= 0;
257   FILE *er_definef, *sql_statef, *er_namef;
258   struct errors *tmp_error;
259   struct message *er_msg;
260   const char *er_text;
261 
262   DBUG_ENTER("create_header_files");
263 
264   if (!(er_definef= my_fopen(HEADERFILE, O_WRONLY, MYF(MY_WME))))
265   {
266     DBUG_RETURN(1);
267   }
268   if (!(sql_statef= my_fopen(STATEFILE, O_WRONLY, MYF(MY_WME))))
269   {
270     my_fclose(er_definef, MYF(0));
271     DBUG_RETURN(1);
272   }
273   if (!(er_namef= my_fopen(NAMEFILE, O_WRONLY, MYF(MY_WME))))
274   {
275     my_fclose(er_definef, MYF(0));
276     my_fclose(sql_statef, MYF(0));
277     DBUG_RETURN(1);
278   }
279 
280   fprintf(er_definef, "/* Autogenerated file, please don't edit */\n\n");
281   fprintf(sql_statef, "/* Autogenerated file, please don't edit */\n\n");
282   fprintf(er_namef, "/* Autogenerated file, please don't edit */\n\n");
283 
284   fprintf(er_definef, "#define ER_ERROR_FIRST %d\n", error_head->d_code);
285 
286   for (tmp_error= error_head; tmp_error; tmp_error= tmp_error->next_error)
287   {
288     /*
289        generating mysqld_error.h
290        fprintf() will automatically add \r on windows
291     */
292     fprintf(er_definef, "#define %s %d\n", tmp_error->er_name,
293 	    tmp_error->d_code);
294     er_last= tmp_error->d_code;
295 
296     /* generating sql_state.h file */
297     if (tmp_error->sql_code1[0] || tmp_error->sql_code2[0])
298       fprintf(sql_statef,
299 	      "{ %-40s,\"%s\", \"%s\" },\n", tmp_error->er_name,
300 	      tmp_error->sql_code1, tmp_error->sql_code2);
301     /*generating er_name file */
302     er_msg= find_message(tmp_error, default_language, 0);
303     er_text = (er_msg ? er_msg->text : "");
304     fprintf(er_namef, "{ \"%s\", %d, \"", tmp_error->er_name,
305             tmp_error->d_code);
306     print_escaped_string(er_namef, er_text);
307     fprintf(er_namef, "\" },\n");
308   }
309   /* finishing off with mysqld_error.h */
310   fprintf(er_definef, "#define ER_ERROR_LAST %d\n", er_last);
311   my_fclose(er_definef, MYF(0));
312   my_fclose(sql_statef, MYF(0));
313   my_fclose(er_namef, MYF(0));
314   DBUG_RETURN(0);
315 }
316 
317 
create_sys_files(struct languages * lang_head,struct errors * error_head,uint row_count)318 static int create_sys_files(struct languages *lang_head,
319 			    struct errors *error_head, uint row_count)
320 {
321   FILE *to;
322   uint csnum= 0, length, i, row_nr;
323   uchar head[32];
324   char outfile[FN_REFLEN], *outfile_end;
325   long start_pos;
326   struct message *tmp;
327   struct languages *tmp_lang;
328   struct errors *tmp_error;
329 
330   MY_STAT stat_info;
331   DBUG_ENTER("create_sys_files");
332 
333   /*
334      going over all languages and assembling corresponding error messages
335   */
336   for (tmp_lang= lang_head; tmp_lang; tmp_lang= tmp_lang->next_lang)
337   {
338 
339     /* setting charset name */
340     if (!(csnum= get_charset_number(tmp_lang->charset, MY_CS_PRIMARY)))
341     {
342       fprintf(stderr, "Unknown charset '%s' in '%s'\n", tmp_lang->charset,
343 	      TXTFILE);
344       DBUG_RETURN(1);
345     }
346 
347     outfile_end= strxmov(outfile, DATADIRECTORY,
348                          tmp_lang->lang_long_name, NullS);
349     if (!my_stat(outfile, &stat_info,MYF(0)))
350     {
351       if (my_mkdir(outfile, 0777,MYF(0)) < 0)
352       {
353         fprintf(stderr, "Can't create output directory for %s\n",
354                 outfile);
355         DBUG_RETURN(1);
356       }
357     }
358 
359     strxmov(outfile_end, FN_ROOTDIR, OUTFILE, NullS);
360 
361     if (!(to= my_fopen(outfile, O_WRONLY | FILE_BINARY, MYF(MY_WME))))
362       DBUG_RETURN(1);
363 
364     /* 4 is for 4 bytes to store row position / error message */
365     start_pos= (long) (HEADER_LENGTH + row_count * 4);
366     fseek(to, start_pos, 0);
367     row_nr= 0;
368     for (tmp_error= error_head; tmp_error; tmp_error= tmp_error->next_error)
369     {
370       /* dealing with messages */
371       tmp= find_message(tmp_error, tmp_lang->lang_short_name, FALSE);
372 
373       if (!tmp)
374       {
375 	fprintf(stderr,
376 		"Did not find message for %s neither in %s nor in default "
377 		"language\n", tmp_error->er_name, tmp_lang->lang_short_name);
378 	goto err;
379       }
380       if (copy_rows(to, tmp->text, row_nr, start_pos))
381       {
382 	fprintf(stderr, "Failed to copy rows to %s\n", outfile);
383 	goto err;
384       }
385       row_nr++;
386     }
387 
388     /* continue with header of the errmsg.sys file */
389     length= ftell(to) - HEADER_LENGTH - row_count * 4;
390     memset(head, 0, HEADER_LENGTH);
391     bmove((uchar *) head, (uchar *) file_head, 4);
392     head[4]= 1;
393     int4store(head + 6, length);
394     int4store(head + 10, row_count);
395     head[30]= csnum;
396 
397     my_fseek(to, 0l, MY_SEEK_SET, MYF(0));
398     if (my_fwrite(to, (uchar*) head, HEADER_LENGTH, MYF(MY_WME | MY_FNABP)))
399       goto err;
400 
401     for (i= 0; i < row_count; i++)
402     {
403       int4store(head, file_pos[i]);
404       if (my_fwrite(to, (uchar*) head, 4, MYF(MY_WME | MY_FNABP)))
405 	goto err;
406     }
407     my_fclose(to, MYF(0));
408   }
409   DBUG_RETURN(0);
410 
411 err:
412   my_fclose(to, MYF(0));
413   DBUG_RETURN(1);
414 }
415 
416 
clean_up(struct languages * lang_head,struct errors * error_head)417 static void clean_up(struct languages *lang_head, struct errors *error_head)
418 {
419   struct languages *tmp_lang, *next_language;
420   struct errors *tmp_error, *next_error;
421   uint count, i;
422 
423   my_free((void*) default_language);
424 
425   for (tmp_lang= lang_head; tmp_lang; tmp_lang= next_language)
426   {
427     next_language= tmp_lang->next_lang;
428     my_free(tmp_lang->lang_short_name);
429     my_free(tmp_lang->lang_long_name);
430     my_free(tmp_lang->charset);
431     my_free(tmp_lang);
432   }
433 
434   for (tmp_error= error_head; tmp_error; tmp_error= next_error)
435   {
436     next_error= tmp_error->next_error;
437     count= (tmp_error->msg).elements;
438     for (i= 0; i < count; i++)
439     {
440       struct message *tmp;
441       tmp= dynamic_element(&tmp_error->msg, i, struct message*);
442       my_free(tmp->lang_short_name);
443       my_free(tmp->text);
444     }
445 
446     delete_dynamic(&tmp_error->msg);
447     if (tmp_error->sql_code1[0])
448       my_free((void*) tmp_error->sql_code1);
449     if (tmp_error->sql_code2[0])
450       my_free((void*) tmp_error->sql_code2);
451     my_free((void*) tmp_error->er_name);
452     my_free(tmp_error);
453   }
454 }
455 
456 
parse_input_file(const char * file_name,struct errors ** top_error,struct languages ** top_lang)457 static int parse_input_file(const char *file_name, struct errors **top_error,
458 			    struct languages **top_lang)
459 {
460   FILE *file;
461   char *str, buff[1000];
462   struct errors *current_error= 0, **tail_error= top_error;
463   struct message current_message;
464   int rcount= 0;
465   DBUG_ENTER("parse_input_file");
466 
467   *top_error= 0;
468   *top_lang= 0;
469   if (!(file= my_fopen(file_name, O_RDONLY | O_SHARE, MYF(MY_WME))))
470     DBUG_RETURN(0);
471 
472   while ((str= fgets(buff, sizeof(buff), file)))
473   {
474     if (is_prefix(str, "language"))
475     {
476       if (!(*top_lang= parse_charset_string(str)))
477       {
478 	fprintf(stderr, "Failed to parse the charset string!\n");
479 	DBUG_RETURN(0);
480       }
481       continue;
482     }
483     if (is_prefix(str, "start-error-number"))
484     {
485       if (!(er_offset= parse_error_offset(str)))
486       {
487 	fprintf(stderr, "Failed to parse the error offset string!\n");
488 	DBUG_RETURN(0);
489       }
490       continue;
491     }
492     if (is_prefix(str, "default-language"))
493     {
494       if (!(default_language= parse_default_language(str)))
495       {
496 	DBUG_PRINT("info", ("default_slang: %s", default_language));
497 	fprintf(stderr,
498 		"Failed to parse the default language line. Aborting\n");
499 	DBUG_RETURN(0);
500       }
501       continue;
502     }
503 
504     if (*str == '\t' || *str == ' ')
505     {
506       /* New error message in another language for previous error */
507       if (!current_error)
508       {
509 	fprintf(stderr, "Error in the input file format\n");
510 	DBUG_RETURN(0);
511       }
512       if (!parse_message_string(&current_message, str))
513       {
514 	fprintf(stderr, "Failed to parse message string for error '%s'",
515 		current_error->er_name);
516 	DBUG_RETURN(0);
517       }
518       if (find_message(current_error, current_message.lang_short_name, TRUE))
519       {
520 	fprintf(stderr, "Duplicate message string for error '%s'"
521                         " in language '%s'\n",
522 		current_error->er_name, current_message.lang_short_name);
523 	DBUG_RETURN(0);
524       }
525       if (check_message_format(current_error, current_message.text))
526       {
527 	fprintf(stderr, "Wrong formatspecifier of error message string"
528                         " for error '%s' in language '%s'\n",
529 		current_error->er_name, current_message.lang_short_name);
530 	DBUG_RETURN(0);
531       }
532       if (insert_dynamic(&current_error->msg, &current_message))
533 	DBUG_RETURN(0);
534       continue;
535     }
536     if (is_prefix(str, ER_PREFIX) || is_prefix(str, WARN_PREFIX))
537     {
538       if (!(current_error= parse_error_string(str, rcount)))
539       {
540 	fprintf(stderr, "Failed to parse the error name string\n");
541 	DBUG_RETURN(0);
542       }
543       rcount++;                         /* Count number of unique errors */
544 
545       /* add error to the list */
546       *tail_error= current_error;
547       tail_error= &current_error->next_error;
548       continue;
549     }
550     if (*str == '#' || *str == '\n')
551       continue;					/* skip comment or empty lines */
552 
553     fprintf(stderr, "Wrong input file format. Stop!\nLine: %s\n", str);
554     DBUG_RETURN(0);
555   }
556   *tail_error= 0;				/* Mark end of list */
557 
558   my_fclose(file, MYF(0));
559   DBUG_RETURN(rcount);
560 }
561 
562 
parse_error_offset(char * str)563 static uint parse_error_offset(char *str)
564 {
565   char *soffset, *end;
566   int error;
567   uint ioffset;
568 
569   DBUG_ENTER("parse_error_offset");
570   /* skipping the "start-error-number" keyword and spaces after it */
571   str= find_end_of_word(str);
572   str= skip_delimiters(str);
573 
574   if (!*str)
575     DBUG_RETURN(0);     /* Unexpected EOL: No error number after the keyword */
576 
577   /* reading the error offset */
578   if (!(soffset= get_word(&str)))
579     DBUG_RETURN(0);				/* OOM: Fatal error */
580   DBUG_PRINT("info", ("default_error_offset: %s", soffset));
581 
582   /* skipping space(s) and/or tabs after the error offset */
583   str= skip_delimiters(str);
584   DBUG_PRINT("info", ("str: %s", str));
585   if (*str)
586   {
587     /* The line does not end with the error offset -> error! */
588     fprintf(stderr, "The error offset line does not end with an error offset");
589     DBUG_RETURN(0);
590   }
591   DBUG_PRINT("info", ("str: %s", str));
592 
593   end= 0;
594   ioffset= (uint) my_strtoll10(soffset, &end, &error);
595   my_free(soffset);
596   DBUG_RETURN(ioffset);
597 }
598 
599 
600 /* Parsing of the default language line. e.g. "default-language eng" */
601 
parse_default_language(char * str)602 static char *parse_default_language(char *str)
603 {
604   char *slang;
605 
606   DBUG_ENTER("parse_default_language");
607   /* skipping the "default-language" keyword */
608   str= find_end_of_word(str);
609   /* skipping space(s) and/or tabs after the keyword */
610   str= skip_delimiters(str);
611   if (!*str)
612   {
613     fprintf(stderr,
614 	    "Unexpected EOL: No short language name after the keyword\n");
615     DBUG_RETURN(0);
616   }
617 
618   /* reading the short language tag */
619   if (!(slang= get_word(&str)))
620     DBUG_RETURN(0);				/* OOM: Fatal error */
621   DBUG_PRINT("info", ("default_slang: %s", slang));
622 
623   str= skip_delimiters(str);
624   DBUG_PRINT("info", ("str: %s", str));
625   if (*str)
626   {
627     fprintf(stderr,
628 	    "The default language line does not end with short language "
629 	    "name\n");
630     DBUG_RETURN(0);
631   }
632   DBUG_PRINT("info", ("str: %s", str));
633   DBUG_RETURN(slang);
634 }
635 
636 
637 /*
638   Find the message in a particular language
639 
640   SYNOPSIS
641     find_message()
642     err             Error to find message for
643     lang            Language of message to find
644     no_default      Don't return default (English) if does not exit
645 
646   RETURN VALUE
647     Returns the message structure if one is found, or NULL if not.
648 */
find_message(struct errors * err,const char * lang,my_bool no_default)649 static struct message *find_message(struct errors *err, const char *lang,
650                                     my_bool no_default)
651 {
652   struct message *tmp, *return_val= 0;
653   uint i, count;
654   DBUG_ENTER("find_message");
655 
656   count= (err->msg).elements;
657   for (i= 0; i < count; i++)
658   {
659     tmp= dynamic_element(&err->msg, i, struct message*);
660 
661     if (!strcmp(tmp->lang_short_name, lang))
662       DBUG_RETURN(tmp);
663     if (!strcmp(tmp->lang_short_name, default_language))
664     {
665       DBUG_ASSERT(tmp->text[0] != 0);
666       return_val= tmp;
667     }
668   }
669   DBUG_RETURN(no_default ? NULL : return_val);
670 }
671 
672 
673 
674 /*
675   Check message format specifiers against error message for
676   previous language
677 
678   SYNOPSIS
679     checksum_format_specifier()
680     msg            String for which to generate checksum
681                    for the format specifiers
682 
683   RETURN VALUE
684     Returns the checksum for all the characters of the
685     format specifiers
686 
687     Ex.
688      "text '%-64.s' text part 2 %d'"
689             ^^^^^^              ^^
690             characters will be xored to form checksum
691 
692     NOTE:
693       Does not support format specifiers with positional args
694       like "%2$s" but that is not yet supported by my_vsnprintf
695       either.
696 */
697 
checksum_format_specifier(const char * msg)698 static ha_checksum checksum_format_specifier(const char* msg)
699 {
700   ha_checksum chksum= 0;
701   const uchar* p= (const uchar*) msg;
702   const uchar* start= NULL;
703   uint32 num_format_specifiers= 0;
704   while (*p)
705   {
706 
707     if (*p == '%')
708     {
709       start= p+1; /* Entering format specifier */
710       num_format_specifiers++;
711     }
712     else if (start)
713     {
714       switch(*p)
715       {
716       case 'd':
717       case 'u':
718       case 'x':
719       case 's':
720         chksum= my_checksum(chksum, (uchar*) start, (uint) (p + 1 - start));
721         start= 0; /* Not in format specifier anymore */
722         break;
723 
724       default:
725         break;
726       }
727     }
728 
729     p++;
730   }
731 
732   if (start)
733   {
734     /* Still inside a format specifier after end of string */
735 
736     fprintf(stderr, "Still inside formatspecifier after end of string"
737                     " in'%s'\n", msg);
738     DBUG_ASSERT(start==0);
739   }
740 
741   /* Add number of format specifiers to checksum as extra safeguard */
742   chksum+= num_format_specifiers;
743 
744   return chksum;
745 }
746 
747 
748 /*
749   Check message format specifiers against error message for
750   previous language
751 
752   SYNOPSIS
753     check_message_format()
754     err             Error to check message for
755     mess            Message to check
756 
757   RETURN VALUE
758     Returns 0 if no previous error message or message format is ok
759 */
check_message_format(struct errors * err,const char * mess)760 static int check_message_format(struct errors *err,
761                                 const char* mess)
762 {
763   struct message *first;
764   DBUG_ENTER("check_message_format");
765 
766   /*  Get first message(if any) */
767   if ((err->msg).elements == 0)
768     DBUG_RETURN(0); /* No previous message to compare against */
769 
770   first= dynamic_element(&err->msg, 0, struct message*);
771   DBUG_ASSERT(first != NULL);
772 
773   if (checksum_format_specifier(first->text) !=
774       checksum_format_specifier(mess))
775   {
776     /* Check sum of format specifiers failed, they should be equal */
777     DBUG_RETURN(1);
778   }
779   DBUG_RETURN(0);
780 }
781 
782 
783 /*
784   Skips spaces and or tabs till the beginning of the next word
785   Returns pointer to the beginning of the first character of the word
786 */
787 
skip_delimiters(char * str)788 static char *skip_delimiters(char *str)
789 {
790   DBUG_ENTER("skip_delimiters");
791   for (;
792        *str == ' ' || *str == ',' || *str == '\t' || *str == '\r' ||
793        *str == '\n' || *str == '='; str++)
794     ;
795   DBUG_RETURN(str);
796 }
797 
798 
799 /*
800   Skips all characters till meets with space, or tab, or EOL
801 */
802 
find_end_of_word(char * str)803 static char *find_end_of_word(char *str)
804 {
805   DBUG_ENTER("find_end_of_word");
806   for (;
807        *str != ' ' && *str != '\t' && *str != '\n' && *str != '\r' && *str &&
808        *str != ',' && *str != ';' && *str != '='; str++)
809     ;
810   DBUG_RETURN(str);
811 }
812 
813 
814 /* Read the word starting from *str */
815 
get_word(char ** str)816 static char *get_word(char **str)
817 {
818   char *start= *str;
819   DBUG_ENTER("get_word");
820 
821   *str= find_end_of_word(start);
822   DBUG_RETURN(my_strndup(start, (uint) (*str - start),
823 				    MYF(MY_WME | MY_FAE)));
824 }
825 
826 
827 /*
828   Parsing the string with short_lang - message text. Code - to
829   remember to which error does the text belong
830 */
831 
parse_message_string(struct message * new_message,char * str)832 static struct message *parse_message_string(struct message *new_message,
833 					    char *str)
834 {
835   char *start;
836 
837   DBUG_ENTER("parse_message_string");
838   DBUG_PRINT("enter", ("str: %s", str));
839 
840   /*skip space(s) and/or tabs in the beginning */
841   while (*str == ' ' || *str == '\t' || *str == '\n')
842     str++;
843 
844   if (!*str)
845   {
846     /* It was not a message line, but an empty line. */
847     DBUG_PRINT("info", ("str: %s", str));
848     DBUG_RETURN(0);
849   }
850 
851   /* reading the short lang */
852   start= str;
853   while (*str != ' ' && *str != '\t' && *str)
854     str++;
855   if (!(new_message->lang_short_name=
856 	my_strndup(start, (uint) (str - start),
857 			      MYF(MY_WME | MY_FAE))))
858     DBUG_RETURN(0);				/* Fatal error */
859   DBUG_PRINT("info", ("msg_slang: %s", new_message->lang_short_name));
860 
861   /*skip space(s) and/or tabs after the lang */
862   while (*str == ' ' || *str == '\t' || *str == '\n')
863     str++;
864 
865   if (*str != '"')
866   {
867     fprintf(stderr, "Unexpected EOL");
868     DBUG_PRINT("info", ("str: %s", str));
869     DBUG_RETURN(0);
870   }
871 
872   /* reading the text */
873   start= str + 1;
874   str= parse_text_line(start);
875 
876   if (!(new_message->text= my_strndup(start, (uint) (str - start),
877 						 MYF(MY_WME | MY_FAE))))
878     DBUG_RETURN(0);				/* Fatal error */
879   DBUG_PRINT("info", ("msg_text: %s", new_message->text));
880 
881   DBUG_RETURN(new_message);
882 }
883 
884 
885 /*
886   Parsing the string with error name and codes; returns the pointer to
887   the errors struct
888 */
889 
parse_error_string(char * str,int er_count)890 static struct errors *parse_error_string(char *str, int er_count)
891 {
892   struct errors *new_error;
893   DBUG_ENTER("parse_error_string");
894   DBUG_PRINT("enter", ("str: %s", str));
895 
896   /* create a new element */
897   new_error= (struct errors *) my_malloc(sizeof(*new_error), MYF(MY_WME));
898 
899   if (my_init_dynamic_array(&new_error->msg, sizeof(struct message), 0, 0))
900     DBUG_RETURN(0);				/* OOM: Fatal error */
901 
902   /* getting the error name */
903   str= skip_delimiters(str);
904 
905   if (!(new_error->er_name= get_word(&str)))
906     DBUG_RETURN(0);				/* OOM: Fatal error */
907   DBUG_PRINT("info", ("er_name: %s", new_error->er_name));
908 
909   str= skip_delimiters(str);
910 
911   /* getting the code1 */
912 
913   new_error->d_code= er_offset + er_count;
914   DBUG_PRINT("info", ("d_code: %d", new_error->d_code));
915 
916   str= skip_delimiters(str);
917 
918   /* if we reached EOL => no more codes, but this can happen */
919   if (!*str)
920   {
921     new_error->sql_code1= empty_string;
922     new_error->sql_code2= empty_string;
923     DBUG_PRINT("info", ("str: %s", str));
924     DBUG_RETURN(new_error);
925   }
926 
927   /* getting the sql_code 1 */
928 
929   if (!(new_error->sql_code1= get_word(&str)))
930     DBUG_RETURN(0);				/* OOM: Fatal error */
931   DBUG_PRINT("info", ("sql_code1: %s", new_error->sql_code1));
932 
933   str= skip_delimiters(str);
934 
935   /* if we reached EOL => no more codes, but this can happen */
936   if (!*str)
937   {
938     new_error->sql_code2= empty_string;
939     DBUG_PRINT("info", ("str: %s", str));
940     DBUG_RETURN(new_error);
941   }
942 
943   /* getting the sql_code 2 */
944   if (!(new_error->sql_code2= get_word(&str)))
945     DBUG_RETURN(0);				/* OOM: Fatal error */
946   DBUG_PRINT("info", ("sql_code2: %s", new_error->sql_code2));
947 
948   str= skip_delimiters(str);
949   if (*str)
950   {
951     fprintf(stderr, "The error line did not end with sql/odbc code!");
952     DBUG_RETURN(0);
953   }
954 
955   DBUG_RETURN(new_error);
956 }
957 
958 
959 /*
960   Parsing the string with full lang name/short lang name/charset;
961   returns pointer to the language structure
962 */
963 
parse_charset_string(char * str)964 static struct languages *parse_charset_string(char *str)
965 {
966   struct languages *head=0, *new_lang;
967   DBUG_ENTER("parse_charset_string");
968   DBUG_PRINT("enter", ("str: %s", str));
969 
970   /* skip over keyword */
971   str= find_end_of_word(str);
972   if (!*str)
973   {
974     /* unexpected EOL */
975     DBUG_PRINT("info", ("str: %s", str));
976     DBUG_RETURN(0);
977   }
978 
979   str= skip_delimiters(str);
980   if (!(*str != ';' && *str))
981     DBUG_RETURN(0);
982 
983   do
984   {
985     /*creating new element of the linked list */
986     new_lang= (struct languages *) my_malloc(sizeof(*new_lang), MYF(MY_WME));
987     new_lang->next_lang= head;
988     head= new_lang;
989 
990     /* get the full language name */
991 
992     if (!(new_lang->lang_long_name= get_word(&str)))
993       DBUG_RETURN(0);				/* OOM: Fatal error */
994 
995     DBUG_PRINT("info", ("long_name: %s", new_lang->lang_long_name));
996 
997     /* getting the short name for language */
998     str= skip_delimiters(str);
999     if (!*str)
1000       DBUG_RETURN(0);				/* Error: No space or tab */
1001 
1002     if (!(new_lang->lang_short_name= get_word(&str)))
1003       DBUG_RETURN(0);				/* OOM: Fatal error */
1004     DBUG_PRINT("info", ("short_name: %s", new_lang->lang_short_name));
1005 
1006     /* getting the charset name */
1007     str= skip_delimiters(str);
1008     if (!(new_lang->charset= get_word(&str)))
1009       DBUG_RETURN(0);				/* Fatal error */
1010     DBUG_PRINT("info", ("charset: %s", new_lang->charset));
1011 
1012     /* skipping space, tab or "," */
1013     str= skip_delimiters(str);
1014   }
1015   while (*str != ';' && *str);
1016 
1017   DBUG_PRINT("info", ("long name: %s", new_lang->lang_long_name));
1018   DBUG_RETURN(head);
1019 }
1020 
1021 
1022 /* Read options */
1023 
print_version(void)1024 static void print_version(void)
1025 {
1026   DBUG_ENTER("print_version");
1027   printf("%s  (Compile errormessage)  Ver %s\n", my_progname, "2.0");
1028   DBUG_VOID_RETURN;
1029 }
1030 
1031 
1032 static my_bool
get_one_option(int optid,const struct my_option * opt MY_ATTRIBUTE ((unused)),char * argument MY_ATTRIBUTE ((unused)))1033 get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE ((unused)),
1034 	       char *argument MY_ATTRIBUTE ((unused)))
1035 {
1036   DBUG_ENTER("get_one_option");
1037   switch (optid) {
1038   case 'V':
1039     print_version();
1040     exit(0);
1041     break;
1042   case '?':
1043     usage();
1044     exit(0);
1045     break;
1046   case '#':
1047     DBUG_PUSH(argument ? argument : default_dbug_option);
1048     break;
1049   }
1050   DBUG_RETURN(0);
1051 }
1052 
1053 
usage(void)1054 static void usage(void)
1055 {
1056   DBUG_ENTER("usage");
1057   print_version();
1058   printf("This software comes with ABSOLUTELY NO WARRANTY. "
1059          "This is free software,\n"
1060          "and you are welcome to modify and redistribute it under the GPL license.\n"
1061          "Usage:\n");
1062   my_print_help(my_long_options);
1063   my_print_variables(my_long_options);
1064   DBUG_VOID_RETURN;
1065 }
1066 
1067 
get_options(int * argc,char *** argv)1068 static int get_options(int *argc, char ***argv)
1069 {
1070   int ho_error;
1071   DBUG_ENTER("get_options");
1072 
1073   if ((ho_error= handle_options(argc, argv, my_long_options, get_one_option)))
1074     DBUG_RETURN(ho_error);
1075   DBUG_RETURN(0);
1076 }
1077 
1078 
1079 /*
1080   Read rows and remember them until row that start with char Converts
1081   row as a C-compiler would convert a textstring
1082 */
1083 
parse_text_line(char * pos)1084 static char *parse_text_line(char *pos)
1085 {
1086   int i, nr;
1087   char *row= pos;
1088   size_t len;
1089   DBUG_ENTER("parse_text_line");
1090 
1091   len= strlen (pos);
1092   while (*pos)
1093   {
1094     if (*pos == '\\')
1095     {
1096       switch (*++pos) {
1097       case '\\':
1098       case '"':
1099 	(void) memmove (pos - 1, pos, len - (row - pos));
1100 	break;
1101       case 'n':
1102 	pos[-1]= '\n';
1103 	(void) memmove (pos, pos + 1, len - (row - pos));
1104 	break;
1105       default:
1106 	if (*pos >= '0' && *pos < '8')
1107 	{
1108 	  nr= 0;
1109 	  for (i= 0; i < 3 && (*pos >= '0' && *pos < '8'); i++)
1110 	    nr= nr * 8 + (*(pos++) - '0');
1111 	  pos -= i;
1112 	  pos[-1]= nr;
1113 	  (void) memmove (pos, pos + i, len - (row - pos));
1114 	}
1115 	else if (*pos)
1116           (void) memmove (pos - 1, pos, len - (row - pos));             /* Remove '\' */
1117       }
1118     }
1119     else
1120       pos++;
1121   }
1122   while (pos > row + 1 && *pos != '"')
1123     pos--;
1124   *pos= 0;
1125   DBUG_RETURN(pos);
1126 }
1127 
1128 
1129 /* Copy rows from memory to file and remember position */
1130 
copy_rows(FILE * to,char * row,int row_nr,long start_pos)1131 static int copy_rows(FILE *to, char *row, int row_nr, long start_pos)
1132 {
1133   DBUG_ENTER("copy_rows");
1134 
1135   file_pos[row_nr]= (int) (ftell(to) - start_pos);
1136   if (fputs(row, to) == EOF || fputc('\0', to) == EOF)
1137   {
1138     fprintf(stderr, "Can't write to outputfile\n");
1139     DBUG_RETURN(1);
1140   }
1141 
1142   DBUG_RETURN(0);
1143 }
1144