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