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