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(¤t_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(¤t_error->msg, ¤t_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= ¤t_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