1 /*
2    Copyright (c) 2000, 2021, Oracle and/or its affiliates.
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  2000
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 NDEBUG
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 NDEBUG
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 (row_count > MAX_ROWS)
194     {
195       fprintf(stderr, "Found too many error messages. ");
196       fprintf(stderr, "Increase MAX_ROWS in extra/comp_err.c.\n");
197       DBUG_RETURN(1);
198     }
199 #if MYSQL_VERSION_ID >= 50100 && MYSQL_VERSION_ID < 50500
200 /* Number of error messages in 5.1 - do not change this number! */
201 #define MYSQL_OLD_GA_ERROR_MESSAGE_COUNT 641
202 #elif MYSQL_VERSION_ID >= 50500 && MYSQL_VERSION_ID < 50600
203 /* Number of error messages in 5.5 - do not change this number! */
204 #define MYSQL_OLD_GA_ERROR_MESSAGE_COUNT 728
205 #endif
206 #if MYSQL_OLD_GA_ERROR_MESSAGE_COUNT
207     if (row_count != MYSQL_OLD_GA_ERROR_MESSAGE_COUNT)
208     {
209       fprintf(stderr, "Can only add new error messages to latest GA. ");
210       fprintf(stderr, "Use ER_UNKNOWN_ERROR instead.\n");
211       fprintf(stderr, "Expected %u messages, found %u.\n",
212               MYSQL_OLD_GA_ERROR_MESSAGE_COUNT, row_count);
213       DBUG_RETURN(1);
214     }
215 #endif
216     if (lang_head == NULL || error_head == NULL)
217     {
218       fprintf(stderr, "Failed to parse input file %s\n", TXTFILE);
219       DBUG_RETURN(1);
220     }
221 
222     if (create_header_files(error_head))
223     {
224       fprintf(stderr, "Failed to create header files\n");
225       DBUG_RETURN(1);
226     }
227     if (create_sys_files(lang_head, error_head, row_count))
228     {
229       fprintf(stderr, "Failed to create sys files\n");
230       DBUG_RETURN(1);
231     }
232     clean_up(lang_head, error_head);
233     DBUG_LEAVE;			/* Can't use dbug after my_end() */
234     my_end(info_flag ? MY_CHECK_ERROR | MY_GIVE_INFO : 0);
235     return 0;
236   }
237 }
238 
239 
print_escaped_string(FILE * f,const char * str)240 static void print_escaped_string(FILE *f, const char *str)
241 {
242   const char *tmp = str;
243 
244   while (tmp[0] != 0)
245   {
246     switch (tmp[0])
247     {
248       case '\\': fprintf(f, "\\\\"); break;
249       case '\'': fprintf(f, "\\\'"); break;
250       case '\"': fprintf(f, "\\\""); break;
251       case '\n': fprintf(f, "\\n"); break;
252       case '\r': fprintf(f, "\\r"); break;
253       default: fprintf(f, "%c", tmp[0]);
254     }
255     tmp++;
256   }
257 }
258 
259 
create_header_files(struct errors * error_head)260 static int create_header_files(struct errors *error_head)
261 {
262   FILE *er_definef, *sql_statef, *er_namef;
263   struct errors *tmp_error;
264   struct message *er_msg;
265   const char *er_text;
266 
267   DBUG_ENTER("create_header_files");
268 
269   if (!(er_definef= my_fopen(HEADERFILE, O_WRONLY, MYF(MY_WME))))
270   {
271     DBUG_RETURN(1);
272   }
273   if (!(sql_statef= my_fopen(STATEFILE, O_WRONLY, MYF(MY_WME))))
274   {
275     my_fclose(er_definef, MYF(0));
276     DBUG_RETURN(1);
277   }
278   if (!(er_namef= my_fopen(NAMEFILE, O_WRONLY, MYF(MY_WME))))
279   {
280     my_fclose(er_definef, MYF(0));
281     my_fclose(sql_statef, MYF(0));
282     DBUG_RETURN(1);
283   }
284 
285   fprintf(er_definef, "/* Autogenerated file, please don't edit */\n\n");
286   fprintf(sql_statef, "/* Autogenerated file, please don't edit */\n\n");
287   fprintf(er_namef, "/* Autogenerated file, please don't edit */\n\n");
288 
289   fprintf(er_definef, "#ifndef MYSQLD_ERROR_INCLUDED\n");
290   fprintf(er_definef, "#define MYSQLD_ERROR_INCLUDED\n\n");
291 
292   /*
293     Find out how many sections of error messages we have, what the first
294     number in each section is and the number of messages in each section.
295   */
296   {
297     int error_code= error_head->d_code;
298     int section_size[100]; /* Assume that 100 sections is enough. */
299     int current_section= 0;
300     int section_nr;
301     section_size[0]= 0;
302     fprintf(er_definef,
303             "static const int errmsg_section_start[] = { %d", error_code);
304     for (tmp_error= error_head; tmp_error; tmp_error= tmp_error->next_error)
305     {
306       if (tmp_error->d_code != error_code) /* Starting new section? */
307       {
308         fprintf(er_definef, ", %d", tmp_error->d_code);
309         error_code= tmp_error->d_code;
310         section_size[++current_section]= 0;
311       }
312       error_code++;
313       section_size[current_section]++;
314     }
315     fprintf(er_definef, " };\n");
316 
317     fprintf(er_definef,
318             "static const int errmsg_section_size[] = { %d", section_size[0]);
319     for (section_nr= 1; section_nr <= current_section; section_nr++)
320       fprintf(er_definef, ", %d", section_size[section_nr]);
321     fprintf(er_definef, " };\n\n");
322   }
323 
324   for (tmp_error= error_head; tmp_error; tmp_error= tmp_error->next_error)
325   {
326     /*
327        generating mysqld_error.h
328        fprintf() will automatically add \r on windows
329     */
330     fprintf(er_definef, "#define %s %d\n", tmp_error->er_name,
331 	    tmp_error->d_code);
332 
333     /* generating sql_state.h file */
334     if (tmp_error->sql_code1[0] || tmp_error->sql_code2[0])
335       fprintf(sql_statef,
336 	      "{ %-40s,\"%s\", \"%s\" },\n", tmp_error->er_name,
337 	      tmp_error->sql_code1, tmp_error->sql_code2);
338     /*generating er_name file */
339     er_msg= find_message(tmp_error, default_language, 0);
340     er_text = (er_msg ? er_msg->text : "");
341     fprintf(er_namef, "{ \"%s\", %d, \"", tmp_error->er_name,
342             tmp_error->d_code);
343     print_escaped_string(er_namef, er_text);
344     fprintf(er_namef, "\" },\n");
345   }
346   /* finishing off with mysqld_error.h */
347   fprintf(er_definef, "#endif\n");
348   my_fclose(er_definef, MYF(0));
349   my_fclose(sql_statef, MYF(0));
350   my_fclose(er_namef, MYF(0));
351   DBUG_RETURN(0);
352 }
353 
354 
create_sys_files(struct languages * lang_head,struct errors * error_head,uint row_count)355 static int create_sys_files(struct languages *lang_head,
356 			    struct errors *error_head, uint row_count)
357 {
358   FILE *to;
359   uint csnum= 0, length, i, row_nr;
360   uchar head[32];
361   char outfile[FN_REFLEN], *outfile_end;
362   long start_pos;
363   struct message *tmp;
364   struct languages *tmp_lang;
365   struct errors *tmp_error;
366 
367   MY_STAT stat_info;
368   DBUG_ENTER("create_sys_files");
369 
370   /*
371      going over all languages and assembling corresponding error messages
372   */
373   for (tmp_lang= lang_head; tmp_lang; tmp_lang= tmp_lang->next_lang)
374   {
375 
376     /* setting charset name */
377     if (!(csnum= get_charset_number(tmp_lang->charset, MY_CS_PRIMARY)))
378     {
379       fprintf(stderr, "Unknown charset '%s' in '%s'\n", tmp_lang->charset,
380 	      TXTFILE);
381       DBUG_RETURN(1);
382     }
383 
384     outfile_end= strxmov(outfile, DATADIRECTORY,
385                          tmp_lang->lang_long_name, NullS);
386     if (!my_stat(outfile, &stat_info,MYF(0)))
387     {
388       if (my_mkdir(outfile, 0777,MYF(0)) < 0)
389       {
390         fprintf(stderr, "Can't create output directory for %s\n",
391                 outfile);
392         DBUG_RETURN(1);
393       }
394     }
395 
396     strxmov(outfile_end, FN_ROOTDIR, OUTFILE, NullS);
397 
398     if (!(to= my_fopen(outfile, O_WRONLY | FILE_BINARY, MYF(MY_WME))))
399       DBUG_RETURN(1);
400 
401     /* 4 is for 4 bytes to store row position / error message */
402     start_pos= (long) (HEADER_LENGTH + row_count * 4);
403     fseek(to, start_pos, 0);
404     row_nr= 0;
405     for (tmp_error= error_head; tmp_error; tmp_error= tmp_error->next_error)
406     {
407       /* dealing with messages */
408       tmp= find_message(tmp_error, tmp_lang->lang_short_name, FALSE);
409 
410       if (!tmp)
411       {
412 	fprintf(stderr,
413 		"Did not find message for %s neither in %s nor in default "
414 		"language\n", tmp_error->er_name, tmp_lang->lang_short_name);
415 	goto err;
416       }
417       if (copy_rows(to, tmp->text, row_nr, start_pos))
418       {
419 	fprintf(stderr, "Failed to copy rows to %s\n", outfile);
420 	goto err;
421       }
422       row_nr++;
423     }
424 
425     /* continue with header of the errmsg.sys file */
426     length= ftell(to) - HEADER_LENGTH - row_count * 4;
427     memset(head, 0, HEADER_LENGTH);
428     memmove(head, file_head, 4);
429     head[4]= 1;
430     int4store(head + 6, length);
431     int4store(head + 10, row_count);
432     head[30]= csnum;
433 
434     my_fseek(to, 0l, MY_SEEK_SET, MYF(0));
435     if (my_fwrite(to, (uchar*) head, HEADER_LENGTH, MYF(MY_WME | MY_FNABP)))
436       goto err;
437 
438     for (i= 0; i < row_count; i++)
439     {
440       int4store(head, file_pos[i]);
441       if (my_fwrite(to, (uchar*) head, 4, MYF(MY_WME | MY_FNABP)))
442 	goto err;
443     }
444     my_fclose(to, MYF(0));
445   }
446   DBUG_RETURN(0);
447 
448 err:
449   my_fclose(to, MYF(0));
450   DBUG_RETURN(1);
451 }
452 
453 
clean_up(struct languages * lang_head,struct errors * error_head)454 static void clean_up(struct languages *lang_head, struct errors *error_head)
455 {
456   struct languages *tmp_lang, *next_language;
457   struct errors *tmp_error, *next_error;
458   uint count, i;
459 
460   my_free((void*) default_language);
461 
462   for (tmp_lang= lang_head; tmp_lang; tmp_lang= next_language)
463   {
464     next_language= tmp_lang->next_lang;
465     my_free(tmp_lang->lang_short_name);
466     my_free(tmp_lang->lang_long_name);
467     my_free(tmp_lang->charset);
468     my_free(tmp_lang);
469   }
470 
471   for (tmp_error= error_head; tmp_error; tmp_error= next_error)
472   {
473     next_error= tmp_error->next_error;
474     count= (tmp_error->msg).elements;
475     for (i= 0; i < count; i++)
476     {
477       struct message *tmp;
478       tmp= dynamic_element(&tmp_error->msg, i, struct message*);
479       my_free(tmp->lang_short_name);
480       my_free(tmp->text);
481     }
482 
483     delete_dynamic(&tmp_error->msg);
484     if (tmp_error->sql_code1[0])
485       my_free((void*) tmp_error->sql_code1);
486     if (tmp_error->sql_code2[0])
487       my_free((void*) tmp_error->sql_code2);
488     my_free((void*) tmp_error->er_name);
489     my_free(tmp_error);
490   }
491 }
492 
493 
parse_input_file(const char * file_name,struct errors ** top_error,struct languages ** top_lang)494 static int parse_input_file(const char *file_name, struct errors **top_error,
495 			    struct languages **top_lang)
496 {
497   FILE *file;
498   char *str, buff[1000];
499   struct errors *current_error= 0, **tail_error= top_error;
500   struct message current_message;
501   int rcount= 0; /* Number of error codes in current section. */
502   int ecount= 0; /* Number of error codes in total. */
503   DBUG_ENTER("parse_input_file");
504 
505   *top_error= 0;
506   *top_lang= 0;
507   if (!(file= my_fopen(file_name, O_RDONLY | O_SHARE, MYF(MY_WME))))
508     DBUG_RETURN(0);
509 
510   while ((str= fgets(buff, sizeof(buff), file)))
511   {
512     if (is_prefix(str, "language"))
513     {
514       if (!(*top_lang= parse_charset_string(str)))
515       {
516 	fprintf(stderr, "Failed to parse the charset string!\n");
517 	DBUG_RETURN(0);
518       }
519       continue;
520     }
521     if (is_prefix(str, "start-error-number"))
522     {
523       if (!(er_offset= parse_error_offset(str)))
524       {
525 	fprintf(stderr, "Failed to parse the error offset string!\n");
526 	DBUG_RETURN(0);
527       }
528       rcount= 0; /* Reset count if a fixed number is set. */
529       continue;
530     }
531     if (is_prefix(str, "default-language"))
532     {
533       if (!(default_language= parse_default_language(str)))
534       {
535 	DBUG_PRINT("info", ("default_slang: %s", default_language));
536 	fprintf(stderr,
537 		"Failed to parse the default language line. Aborting\n");
538 	DBUG_RETURN(0);
539       }
540       continue;
541     }
542 
543     if (*str == '\t' || *str == ' ')
544     {
545       /* New error message in another language for previous error */
546       if (!current_error)
547       {
548 	fprintf(stderr, "Error in the input file format\n");
549 	DBUG_RETURN(0);
550       }
551       if (!parse_message_string(&current_message, str))
552       {
553 	fprintf(stderr, "Failed to parse message string for error '%s'",
554 		current_error->er_name);
555 	DBUG_RETURN(0);
556       }
557       if (find_message(current_error, current_message.lang_short_name, TRUE))
558       {
559 	fprintf(stderr, "Duplicate message string for error '%s'"
560                         " in language '%s'\n",
561 		current_error->er_name, current_message.lang_short_name);
562 	DBUG_RETURN(0);
563       }
564       if (check_message_format(current_error, current_message.text))
565       {
566 	fprintf(stderr, "Wrong formatspecifier of error message string"
567                         " for error '%s' in language '%s'\n",
568 		current_error->er_name, current_message.lang_short_name);
569 	DBUG_RETURN(0);
570       }
571       if (insert_dynamic(&current_error->msg, &current_message))
572 	DBUG_RETURN(0);
573       continue;
574     }
575     if (is_prefix(str, ER_PREFIX) || is_prefix(str, WARN_PREFIX))
576     {
577       if (!(current_error= parse_error_string(str, rcount)))
578       {
579 	fprintf(stderr, "Failed to parse the error name string\n");
580 	DBUG_RETURN(0);
581       }
582       rcount++;
583       ecount++;                         /* Count number of unique errors */
584 
585       /* add error to the list */
586       *tail_error= current_error;
587       tail_error= &current_error->next_error;
588       continue;
589     }
590     if (*str == '#' || *str == '\n')
591       continue;					/* skip comment or empty lines */
592 
593     fprintf(stderr, "Wrong input file format. Stop!\nLine: %s\n", str);
594     DBUG_RETURN(0);
595   }
596   *tail_error= 0;				/* Mark end of list */
597 
598   my_fclose(file, MYF(0));
599   DBUG_RETURN(ecount);
600 }
601 
602 
parse_error_offset(char * str)603 static uint parse_error_offset(char *str)
604 {
605   char *soffset, *end;
606   int error;
607   uint ioffset;
608 
609   DBUG_ENTER("parse_error_offset");
610   /* skipping the "start-error-number" keyword and spaces after it */
611   str= find_end_of_word(str);
612   str= skip_delimiters(str);
613 
614   if (!*str)
615     DBUG_RETURN(0);     /* Unexpected EOL: No error number after the keyword */
616 
617   /* reading the error offset */
618   if (!(soffset= get_word(&str)))
619     DBUG_RETURN(0);				/* OOM: Fatal error */
620   DBUG_PRINT("info", ("default_error_offset: %s", soffset));
621 
622   /* skipping space(s) and/or tabs after the error offset */
623   str= skip_delimiters(str);
624   DBUG_PRINT("info", ("str: %s", str));
625   if (*str)
626   {
627     /* The line does not end with the error offset -> error! */
628     fprintf(stderr, "The error offset line does not end with an error offset");
629     DBUG_RETURN(0);
630   }
631   DBUG_PRINT("info", ("str: %s", str));
632 
633   end= 0;
634   ioffset= (uint) my_strtoll10(soffset, &end, &error);
635   my_free(soffset);
636   DBUG_RETURN(ioffset);
637 }
638 
639 
640 /* Parsing of the default language line. e.g. "default-language eng" */
641 
parse_default_language(char * str)642 static char *parse_default_language(char *str)
643 {
644   char *slang;
645 
646   DBUG_ENTER("parse_default_language");
647   /* skipping the "default-language" keyword */
648   str= find_end_of_word(str);
649   /* skipping space(s) and/or tabs after the keyword */
650   str= skip_delimiters(str);
651   if (!*str)
652   {
653     fprintf(stderr,
654 	    "Unexpected EOL: No short language name after the keyword\n");
655     DBUG_RETURN(0);
656   }
657 
658   /* reading the short language tag */
659   if (!(slang= get_word(&str)))
660     DBUG_RETURN(0);				/* OOM: Fatal error */
661   DBUG_PRINT("info", ("default_slang: %s", slang));
662 
663   str= skip_delimiters(str);
664   DBUG_PRINT("info", ("str: %s", str));
665   if (*str)
666   {
667     fprintf(stderr,
668 	    "The default language line does not end with short language "
669 	    "name\n");
670     DBUG_RETURN(0);
671   }
672   DBUG_PRINT("info", ("str: %s", str));
673   DBUG_RETURN(slang);
674 }
675 
676 
677 /*
678   Find the message in a particular language
679 
680   SYNOPSIS
681     find_message()
682     err             Error to find message for
683     lang            Language of message to find
684     no_default      Don't return default (English) if does not exit
685 
686   RETURN VALUE
687     Returns the message structure if one is found, or NULL if not.
688 */
find_message(struct errors * err,const char * lang,my_bool no_default)689 static struct message *find_message(struct errors *err, const char *lang,
690                                     my_bool no_default)
691 {
692   struct message *tmp, *return_val= 0;
693   uint i, count;
694   DBUG_ENTER("find_message");
695 
696   count= (err->msg).elements;
697   for (i= 0; i < count; i++)
698   {
699     tmp= dynamic_element(&err->msg, i, struct message*);
700 
701     if (!strcmp(tmp->lang_short_name, lang))
702       DBUG_RETURN(tmp);
703     if (!strcmp(tmp->lang_short_name, default_language))
704     {
705       assert(tmp->text[0] != 0);
706       return_val= tmp;
707     }
708   }
709   DBUG_RETURN(no_default ? NULL : return_val);
710 }
711 
712 
713 
714 /*
715   Check message format specifiers against error message for
716   previous language
717 
718   SYNOPSIS
719     checksum_format_specifier()
720     msg            String for which to generate checksum
721                    for the format specifiers
722 
723   RETURN VALUE
724     Returns the checksum for all the characters of the
725     format specifiers
726 
727     Ex.
728      "text '%-64.s' text part 2 %d'"
729             ^^^^^^              ^^
730             characters will be xored to form checksum
731 
732     NOTE:
733       Does not support format specifiers with positional args
734       like "%2$s" but that is not yet supported by my_vsnprintf
735       either.
736 */
737 
checksum_format_specifier(const char * msg)738 static ha_checksum checksum_format_specifier(const char* msg)
739 {
740   ha_checksum chksum= 0;
741   const uchar* p= (const uchar*) msg;
742   const uchar* start= NULL;
743   uint32 num_format_specifiers= 0;
744   while (*p)
745   {
746 
747     if (*p == '%')
748     {
749       start= p+1; /* Entering format specifier */
750       num_format_specifiers++;
751     }
752     else if (start)
753     {
754       switch(*p)
755       {
756       case 'd':
757       case 'u':
758       case 'x':
759       case 's':
760         chksum= my_checksum(chksum, (uchar*) start, (uint) (p + 1 - start));
761         start= 0; /* Not in format specifier anymore */
762         break;
763 
764       default:
765         break;
766       }
767     }
768 
769     p++;
770   }
771 
772   if (start)
773   {
774     /* Still inside a format specifier after end of string */
775 
776     fprintf(stderr, "Still inside formatspecifier after end of string"
777                     " in'%s'\n", msg);
778     assert(start==0);
779   }
780 
781   /* Add number of format specifiers to checksum as extra safeguard */
782   chksum+= num_format_specifiers;
783 
784   return chksum;
785 }
786 
787 
788 /*
789   Check message format specifiers against error message for
790   previous language
791 
792   SYNOPSIS
793     check_message_format()
794     err             Error to check message for
795     mess            Message to check
796 
797   RETURN VALUE
798     Returns 0 if no previous error message or message format is ok
799 */
check_message_format(struct errors * err,const char * mess)800 static int check_message_format(struct errors *err,
801                                 const char* mess)
802 {
803   struct message *first;
804   DBUG_ENTER("check_message_format");
805 
806   /*  Get first message(if any) */
807   if ((err->msg).elements == 0)
808     DBUG_RETURN(0); /* No previous message to compare against */
809 
810   first= dynamic_element(&err->msg, 0, struct message*);
811   assert(first != NULL);
812 
813   if (checksum_format_specifier(first->text) !=
814       checksum_format_specifier(mess))
815   {
816     /* Check sum of format specifiers failed, they should be equal */
817     DBUG_RETURN(1);
818   }
819   DBUG_RETURN(0);
820 }
821 
822 
823 /*
824   Skips spaces and or tabs till the beginning of the next word
825   Returns pointer to the beginning of the first character of the word
826 */
827 
skip_delimiters(char * str)828 static char *skip_delimiters(char *str)
829 {
830   DBUG_ENTER("skip_delimiters");
831   for (;
832        *str == ' ' || *str == ',' || *str == '\t' || *str == '\r' ||
833        *str == '\n' || *str == '='; str++)
834     ;
835   DBUG_RETURN(str);
836 }
837 
838 
839 /*
840   Skips all characters till meets with space, or tab, or EOL
841 */
842 
find_end_of_word(char * str)843 static char *find_end_of_word(char *str)
844 {
845   DBUG_ENTER("find_end_of_word");
846   for (;
847        *str != ' ' && *str != '\t' && *str != '\n' && *str != '\r' && *str &&
848        *str != ',' && *str != ';' && *str != '='; str++)
849     ;
850   DBUG_RETURN(str);
851 }
852 
853 
854 /* Read the word starting from *str */
855 
get_word(char ** str)856 static char *get_word(char **str)
857 {
858   char *start= *str;
859   DBUG_ENTER("get_word");
860 
861   *str= find_end_of_word(start);
862   DBUG_RETURN(my_strndup(PSI_NOT_INSTRUMENTED,
863                          start, (uint) (*str - start),
864 			 MYF(MY_WME | MY_FAE)));
865 }
866 
867 
868 /*
869   Parsing the string with short_lang - message text. Code - to
870   remember to which error does the text belong
871 */
872 
parse_message_string(struct message * new_message,char * str)873 static struct message *parse_message_string(struct message *new_message,
874 					    char *str)
875 {
876   char *start;
877 
878   DBUG_ENTER("parse_message_string");
879   DBUG_PRINT("enter", ("str: %s", str));
880 
881   /*skip space(s) and/or tabs in the beginning */
882   while (*str == ' ' || *str == '\t' || *str == '\n')
883     str++;
884 
885   if (!*str)
886   {
887     /* It was not a message line, but an empty line. */
888     DBUG_PRINT("info", ("str: %s", str));
889     DBUG_RETURN(0);
890   }
891 
892   /* reading the short lang */
893   start= str;
894   while (*str != ' ' && *str != '\t' && *str)
895     str++;
896   if (!(new_message->lang_short_name=
897 	my_strndup(PSI_NOT_INSTRUMENTED,
898                    start, (uint) (str - start),
899 		   MYF(MY_WME | MY_FAE))))
900     DBUG_RETURN(0);				/* Fatal error */
901   DBUG_PRINT("info", ("msg_slang: %s", new_message->lang_short_name));
902 
903   /*skip space(s) and/or tabs after the lang */
904   while (*str == ' ' || *str == '\t' || *str == '\n')
905     str++;
906 
907   if (*str != '"')
908   {
909     fprintf(stderr, "Unexpected EOL");
910     DBUG_PRINT("info", ("str: %s", str));
911     DBUG_RETURN(0);
912   }
913 
914   /* reading the text */
915   start= str + 1;
916   str= parse_text_line(start);
917 
918   if (!(new_message->text= my_strndup(PSI_NOT_INSTRUMENTED,
919                                       start, (uint) (str - start),
920                                       MYF(MY_WME | MY_FAE))))
921     DBUG_RETURN(0);				/* Fatal error */
922   DBUG_PRINT("info", ("msg_text: %s", new_message->text));
923 
924   DBUG_RETURN(new_message);
925 }
926 
927 
928 /*
929   Parsing the string with error name and codes; returns the pointer to
930   the errors struct
931 */
932 
parse_error_string(char * str,int er_count)933 static struct errors *parse_error_string(char *str, int er_count)
934 {
935   struct errors *new_error;
936   DBUG_ENTER("parse_error_string");
937   DBUG_PRINT("enter", ("str: %s", str));
938 
939   /* create a new element */
940   new_error= (struct errors *) my_malloc(PSI_NOT_INSTRUMENTED,
941                                          sizeof(*new_error), MYF(MY_WME));
942 
943   if (my_init_dynamic_array(&new_error->msg,
944                             PSI_NOT_INSTRUMENTED,
945                             sizeof(struct message),
946                             NULL, 0, 0))
947     DBUG_RETURN(0);				/* OOM: Fatal error */
948 
949   /* getting the error name */
950   str= skip_delimiters(str);
951 
952   if (!(new_error->er_name= get_word(&str)))
953     DBUG_RETURN(0);				/* OOM: Fatal error */
954   DBUG_PRINT("info", ("er_name: %s", new_error->er_name));
955 
956   str= skip_delimiters(str);
957 
958   /* getting the code1 */
959 
960   new_error->d_code= er_offset + er_count;
961   DBUG_PRINT("info", ("d_code: %d", new_error->d_code));
962 
963   str= skip_delimiters(str);
964 
965   /* if we reached EOL => no more codes, but this can happen */
966   if (!*str)
967   {
968     new_error->sql_code1= empty_string;
969     new_error->sql_code2= empty_string;
970     DBUG_PRINT("info", ("str: %s", str));
971     DBUG_RETURN(new_error);
972   }
973 
974   /* getting the sql_code 1 */
975 
976   if (!(new_error->sql_code1= get_word(&str)))
977     DBUG_RETURN(0);				/* OOM: Fatal error */
978   DBUG_PRINT("info", ("sql_code1: %s", new_error->sql_code1));
979 
980   str= skip_delimiters(str);
981 
982   /* if we reached EOL => no more codes, but this can happen */
983   if (!*str)
984   {
985     new_error->sql_code2= empty_string;
986     DBUG_PRINT("info", ("str: %s", str));
987     DBUG_RETURN(new_error);
988   }
989 
990   /* getting the sql_code 2 */
991   if (!(new_error->sql_code2= get_word(&str)))
992     DBUG_RETURN(0);				/* OOM: Fatal error */
993   DBUG_PRINT("info", ("sql_code2: %s", new_error->sql_code2));
994 
995   str= skip_delimiters(str);
996   if (*str)
997   {
998     fprintf(stderr, "The error line did not end with sql/odbc code: '%s'\n", str);
999     DBUG_RETURN(0);
1000   }
1001 
1002   DBUG_RETURN(new_error);
1003 }
1004 
1005 
1006 /*
1007   Parsing the string with full lang name/short lang name/charset;
1008   returns pointer to the language structure
1009 */
1010 
parse_charset_string(char * str)1011 static struct languages *parse_charset_string(char *str)
1012 {
1013   struct languages *head=0, *new_lang;
1014   DBUG_ENTER("parse_charset_string");
1015   DBUG_PRINT("enter", ("str: %s", str));
1016 
1017   /* skip over keyword */
1018   str= find_end_of_word(str);
1019   if (!*str)
1020   {
1021     /* unexpected EOL */
1022     DBUG_PRINT("info", ("str: %s", str));
1023     DBUG_RETURN(0);
1024   }
1025 
1026   str= skip_delimiters(str);
1027   if (!(*str != ';' && *str))
1028     DBUG_RETURN(0);
1029 
1030   do
1031   {
1032     /*creating new element of the linked list */
1033     new_lang= (struct languages *) my_malloc(PSI_NOT_INSTRUMENTED,
1034                                              sizeof(*new_lang), MYF(MY_WME));
1035     new_lang->next_lang= head;
1036     head= new_lang;
1037 
1038     /* get the full language name */
1039 
1040     if (!(new_lang->lang_long_name= get_word(&str)))
1041       DBUG_RETURN(0);				/* OOM: Fatal error */
1042 
1043     DBUG_PRINT("info", ("long_name: %s", new_lang->lang_long_name));
1044 
1045     /* getting the short name for language */
1046     str= skip_delimiters(str);
1047     if (!*str)
1048       DBUG_RETURN(0);				/* Error: No space or tab */
1049 
1050     if (!(new_lang->lang_short_name= get_word(&str)))
1051       DBUG_RETURN(0);				/* OOM: Fatal error */
1052     DBUG_PRINT("info", ("short_name: %s", new_lang->lang_short_name));
1053 
1054     /* getting the charset name */
1055     str= skip_delimiters(str);
1056     if (!(new_lang->charset= get_word(&str)))
1057       DBUG_RETURN(0);				/* Fatal error */
1058     DBUG_PRINT("info", ("charset: %s", new_lang->charset));
1059 
1060     /* skipping space, tab or "," */
1061     str= skip_delimiters(str);
1062   }
1063   while (*str != ';' && *str);
1064 
1065   DBUG_PRINT("info", ("long name: %s", new_lang->lang_long_name));
1066   DBUG_RETURN(head);
1067 }
1068 
1069 
1070 /* Read options */
1071 
print_version(void)1072 static void print_version(void)
1073 {
1074   DBUG_ENTER("print_version");
1075   printf("%s  (Compile errormessage)  Ver %s\n", my_progname, "2.0");
1076   DBUG_VOID_RETURN;
1077 }
1078 
1079 
1080 static my_bool
get_one_option(int optid,const struct my_option * opt MY_ATTRIBUTE ((unused)),char * argument MY_ATTRIBUTE ((unused)))1081 get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE ((unused)),
1082 	       char *argument MY_ATTRIBUTE ((unused)))
1083 {
1084   DBUG_ENTER("get_one_option");
1085   switch (optid) {
1086   case 'V':
1087     print_version();
1088     exit(0);
1089     break;
1090   case '?':
1091     usage();
1092     exit(0);
1093     break;
1094   case '#':
1095     DBUG_PUSH(argument ? argument : default_dbug_option);
1096     break;
1097   }
1098   DBUG_RETURN(0);
1099 }
1100 
1101 
usage(void)1102 static void usage(void)
1103 {
1104   DBUG_ENTER("usage");
1105   print_version();
1106   printf("This software comes with ABSOLUTELY NO WARRANTY. "
1107          "This is free software,\n"
1108          "and you are welcome to modify and redistribute it under the GPL license.\n"
1109          "Usage:\n");
1110   my_print_help(my_long_options);
1111   my_print_variables(my_long_options);
1112   DBUG_VOID_RETURN;
1113 }
1114 
1115 
get_options(int * argc,char *** argv)1116 static int get_options(int *argc, char ***argv)
1117 {
1118   int ho_error;
1119   DBUG_ENTER("get_options");
1120 
1121   if ((ho_error= handle_options(argc, argv, my_long_options, get_one_option)))
1122     DBUG_RETURN(ho_error);
1123   DBUG_RETURN(0);
1124 }
1125 
1126 
1127 /*
1128   Read rows and remember them until row that start with char Converts
1129   row as a C-compiler would convert a textstring
1130 */
1131 
parse_text_line(char * pos)1132 static char *parse_text_line(char *pos)
1133 {
1134   int i, nr;
1135   char *row= pos;
1136   size_t len;
1137   DBUG_ENTER("parse_text_line");
1138 
1139   len= strlen (pos);
1140   while (*pos)
1141   {
1142     if (*pos == '\\')
1143     {
1144       switch (*++pos) {
1145       case '\\':
1146       case '"':
1147 	(void) memmove (pos - 1, pos, len - (row - pos));
1148 	break;
1149       case 'n':
1150 	pos[-1]= '\n';
1151 	(void) memmove (pos, pos + 1, len - (row - pos));
1152 	break;
1153       default:
1154 	if (*pos >= '0' && *pos < '8')
1155 	{
1156 	  nr= 0;
1157 	  for (i= 0; i < 3 && (*pos >= '0' && *pos < '8'); i++)
1158 	    nr= nr * 8 + (*(pos++) - '0');
1159 	  pos -= i;
1160 	  pos[-1]= nr;
1161 	  (void) memmove (pos, pos + i, len - (row - pos));
1162 	}
1163 	else if (*pos)
1164           (void) memmove (pos - 1, pos, len - (row - pos));             /* Remove '\' */
1165       }
1166     }
1167     else
1168       pos++;
1169   }
1170   while (pos > row + 1 && *pos != '"')
1171     pos--;
1172   *pos= 0;
1173   DBUG_RETURN(pos);
1174 }
1175 
1176 
1177 /* Copy rows from memory to file and remember position */
1178 
copy_rows(FILE * to,char * row,int row_nr,long start_pos)1179 static int copy_rows(FILE *to, char *row, int row_nr, long start_pos)
1180 {
1181   DBUG_ENTER("copy_rows");
1182 
1183   file_pos[row_nr]= (int) (ftell(to) - start_pos);
1184   if (fputs(row, to) == EOF || fputc('\0', to) == EOF)
1185   {
1186     fprintf(stderr, "Can't write to outputfile\n");
1187     DBUG_RETURN(1);
1188   }
1189 
1190   DBUG_RETURN(0);
1191 }
1192