1 /* Copyright (c) 2000, 2011, Oracle and/or its affiliates.
2    Copyright (C) 2011, 2020, MariaDB
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 as published by
6    the Free Software Foundation; version 2 of the License.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License
14    along with this program; if not, write to the Free Software
15    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
16 
17 /**
18   @file
19 
20   @brief
21   Read language depeneded messagefile
22 */
23 
24 #include "mariadb.h"
25 #include "sql_priv.h"
26 #include "unireg.h"
27 #include "derror.h"
28 #include "mysys_err.h"
29 #include "mysqld.h"                             // lc_messages_dir
30 #include "derror.h"                             // read_texts
31 #include "sql_class.h"                          // THD
32 
33 uint errors_per_range[MAX_ERROR_RANGES+1];
34 
35 static bool check_error_mesg(const char *file_name, const char **errmsg);
36 static void init_myfunc_errs(void);
37 
38 
39 C_MODE_START
get_server_errmsgs(int nr)40 static const char **get_server_errmsgs(int nr)
41 {
42   int section= (nr-ER_ERROR_FIRST) / ERRORS_PER_RANGE;
43   if (!current_thd)
44     return DEFAULT_ERRMSGS[section];
45   return CURRENT_THD_ERRMSGS[section];
46 }
47 C_MODE_END
48 
49 /**
50   Read messages from errorfile.
51 
52   This function can be called multiple times to reload the messages.
53 
54   If it fails to load the messages:
55    - If we already have error messages loaded, keep the old ones and
56      return FALSE(ok)
57   - Initializing the errmesg pointer to an array of empty strings
58     and return TRUE (error)
59 
60   @retval
61     FALSE       OK
62   @retval
63     TRUE        Error
64 */
65 
66 static const char ***original_error_messages;
67 
init_errmessage(void)68 bool init_errmessage(void)
69 {
70   const char **errmsgs;
71   bool error= FALSE;
72   const char *lang= my_default_lc_messages->errmsgs->language;
73   my_bool use_english;
74 
75   DBUG_ENTER("init_errmessage");
76 
77   free_error_messages();
78   my_free(original_error_messages);
79   original_error_messages= 0;
80 
81   error_message_charset_info= system_charset_info;
82 
83   use_english= !strcmp(lang, "english");
84   if (!use_english)
85   {
86     /* Read messages from file. */
87     use_english= read_texts(ERRMSG_FILE,lang, &original_error_messages);
88     error= use_english != FALSE;
89     if (error)
90       sql_print_error("Could not load error messages for %s",lang);
91   }
92 
93   if (use_english)
94   {
95     static const struct
96     {
97       const char* name;
98       uint id;
99       const char* fmt;
100     }
101     english_msgs[]=
102     {
103       #include <mysqld_ername.h>
104     };
105 
106     memset(errors_per_range, 0, sizeof(errors_per_range));
107     /* Calculate nr of messages per range. */
108     for (size_t i= 0; i < array_elements(english_msgs); i++)
109     {
110       uint id= english_msgs[i].id;
111 
112       // We rely on the fact the array is sorted by id.
113       DBUG_ASSERT(i == 0 || english_msgs[i-1].id < id);
114 
115       errors_per_range[id/ERRORS_PER_RANGE-1]= id%ERRORS_PER_RANGE + 1;
116     }
117 
118     size_t all_errors= 0;
119     for (size_t i= 0; i < MAX_ERROR_RANGES; i++)
120       all_errors+= errors_per_range[i];
121 
122     if (!(original_error_messages= (const char***)
123           my_malloc((all_errors + MAX_ERROR_RANGES)* sizeof(void*),
124                      MYF(MY_ZEROFILL))))
125       DBUG_RETURN(TRUE);
126 
127     errmsgs= (const char**)(original_error_messages + MAX_ERROR_RANGES);
128 
129     original_error_messages[0]= errmsgs;
130     for (uint i= 1; i < MAX_ERROR_RANGES; i++)
131     {
132       original_error_messages[i]=
133         original_error_messages[i-1] + errors_per_range[i-1];
134     }
135 
136     for (uint i= 0; i < array_elements(english_msgs); i++)
137     {
138       uint id= english_msgs[i].id;
139       original_error_messages[id/ERRORS_PER_RANGE-1][id%ERRORS_PER_RANGE]=
140          english_msgs[i].fmt;
141     }
142   }
143 
144   /* Register messages for use with my_error(). */
145   for (uint i=0 ; i < MAX_ERROR_RANGES ; i++)
146   {
147     if (errors_per_range[i])
148     {
149       if (my_error_register(get_server_errmsgs, (i+1)*ERRORS_PER_RANGE,
150                             (i+1)*ERRORS_PER_RANGE +
151                             errors_per_range[i]-1))
152       {
153         my_free(original_error_messages);
154         original_error_messages= 0;
155         DBUG_RETURN(TRUE);
156       }
157     }
158   }
159   DEFAULT_ERRMSGS= original_error_messages;
160   init_myfunc_errs();			/* Init myfunc messages */
161   DBUG_RETURN(error);
162 }
163 
164 
free_error_messages()165 void free_error_messages()
166 {
167   /* We don't need to free errmsg as it's done in cleanup_errmsg */
168   for (uint i= 0 ; i < MAX_ERROR_RANGES ; i++)
169   {
170     if (errors_per_range[i])
171     {
172       my_error_unregister((i+1)*ERRORS_PER_RANGE,
173                           (i+1)*ERRORS_PER_RANGE +
174                           errors_per_range[i]-1);
175       errors_per_range[i]= 0;
176     }
177   }
178 }
179 
180 
181 /**
182    Check the error messages array contains all relevant error messages
183 */
184 
check_error_mesg(const char * file_name,const char ** errmsg)185 static bool check_error_mesg(const char *file_name, const char **errmsg)
186 {
187   /*
188     The last MySQL error message can't be an empty string; If it is,
189     it means that the error file doesn't contain all MySQL messages
190     and is probably from an older version of MySQL / MariaDB.
191     We also check that each section has enough error messages.
192   */
193   if (errmsg[ER_LAST_MYSQL_ERROR_MESSAGE -1 - ER_ERROR_FIRST][0] == 0 ||
194       (errors_per_range[0] < ER_ERROR_LAST_SECTION_2 - ER_ERROR_FIRST + 1) ||
195       errors_per_range[1] != 0 ||
196       (errors_per_range[2] < ER_ERROR_LAST_SECTION_4 -
197        ER_ERROR_FIRST_SECTION_4 +1) ||
198       (errors_per_range[3] < ER_ERROR_LAST - ER_ERROR_FIRST_SECTION_5 + 1))
199   {
200     sql_print_error("Error message file '%s' is probably from and older "
201                     "version of MariaDB as it doesn't contain all "
202                     "error messages", file_name);
203     return 1;
204   }
205   return 0;
206 }
207 
208 
209 struct st_msg_file
210 {
211   uint sections;
212   uint max_error;
213   uint errors;
214   size_t text_length;
215 };
216 
217 /**
218   Open file for packed textfile in language-directory.
219 */
220 
open_error_msg_file(const char * file_name,const char * language,uint error_messages,struct st_msg_file * ret)221 static File open_error_msg_file(const char *file_name, const char *language,
222                                 uint error_messages, struct st_msg_file *ret)
223 {
224   int error_pos= 0;
225   File file;
226   char name[FN_REFLEN];
227   char lang_path[FN_REFLEN];
228   uchar head[32];
229   DBUG_ENTER("open_error_msg_file");
230 
231   convert_dirname(lang_path, language, NullS);
232   (void) my_load_path(lang_path, lang_path, lc_messages_dir);
233   if ((file= mysql_file_open(key_file_ERRMSG,
234                              fn_format(name, file_name, lang_path, "", 4),
235                              O_RDONLY | O_SHARE | O_BINARY,
236                              MYF(0))) < 0)
237   {
238     /*
239       Trying pre-5.4 semantics of the --language parameter.
240       It included the language-specific part, e.g.:
241       --language=/path/to/english/
242     */
243     if ((file= mysql_file_open(key_file_ERRMSG,
244                                fn_format(name, file_name, lc_messages_dir, "",
245                                          4),
246                                O_RDONLY | O_SHARE | O_BINARY,
247                                MYF(0))) < 0)
248       goto err;
249     if (global_system_variables.log_warnings > 2)
250     {
251       sql_print_warning("An old style --language or -lc-message-dir value with language specific part detected: %s", lc_messages_dir);
252       sql_print_warning("Use --lc-messages-dir without language specific part instead.");
253     }
254   }
255   error_pos=1;
256   if (mysql_file_read(file, (uchar*) head, 32, MYF(MY_NABP)))
257     goto err;
258   error_pos=2;
259   if (head[0] != (uchar) 254 || head[1] != (uchar) 254 ||
260       head[2] != 2 || head[3] != 4)
261     goto err; /* purecov: inspected */
262 
263   ret->text_length= uint4korr(head+6);
264   ret->max_error=   uint2korr(head+10);
265   ret->errors=      uint2korr(head+12);
266   ret->sections=    uint2korr(head+14);
267 
268   if (unlikely(ret->max_error < error_messages ||
269                ret->sections != MAX_ERROR_RANGES))
270   {
271     sql_print_error("\
272 Error message file '%s' had only %d error messages, but it should contain at least %d error messages.\nCheck that the above file is the right version for this program!",
273 		    name,ret->errors,error_messages);
274     (void) mysql_file_close(file, MYF(MY_WME));
275     DBUG_RETURN(FERR);
276   }
277   DBUG_RETURN(file);
278 
279 err:
280   sql_print_error((error_pos == 2) ?
281                   "Incompatible header in messagefile '%s'. Probably from "
282                   "another version of MariaDB" :
283                   ((error_pos == 1) ? "Can't read from messagefile '%s'" :
284                    "Can't find messagefile '%s'"), name);
285   if (file != FERR)
286     (void) mysql_file_close(file, MYF(MY_WME));
287   DBUG_RETURN(FERR);
288 }
289 
290 
291 /*
292   Define the number of normal and extra error messages in the errmsg.sys
293   file
294 */
295 
296 static const uint error_messages= ER_ERROR_LAST - ER_ERROR_FIRST+1;
297 
298 /**
299   Read text from packed textfile in language-directory.
300 */
301 
read_texts(const char * file_name,const char * language,const char **** data)302 bool read_texts(const char *file_name, const char *language,
303                 const char ****data)
304 {
305   uint i, range_size;
306   const char **point;
307   size_t offset;
308   File file;
309   uchar *buff, *pos;
310   struct st_msg_file msg_file;
311   DBUG_ENTER("read_texts");
312 
313   if (unlikely((file= open_error_msg_file(file_name, language, error_messages,
314                                           &msg_file)) == FERR))
315     DBUG_RETURN(1);
316 
317   if (!(*data= (const char***)
318 	my_malloc((size_t) ((MAX_ERROR_RANGES+1) * sizeof(char**) +
319                             MY_MAX(msg_file.text_length, msg_file.errors * 2)+
320                             msg_file.errors * sizeof(char*)),
321                   MYF(MY_WME))))
322     goto err;					/* purecov: inspected */
323 
324   point= (const char**) ((*data) + MAX_ERROR_RANGES);
325   buff=  (uchar*) (point + msg_file.errors);
326 
327   if (mysql_file_read(file, buff,
328                       (size_t) (msg_file.errors + msg_file.sections) * 2,
329                       MYF(MY_NABP | MY_WME)))
330     goto err;
331 
332   pos= buff;
333   /* read in sections */
334   for (i= 0, offset= 0; i < msg_file.sections ; i++)
335   {
336     (*data)[i]= point + offset;
337     errors_per_range[i]= range_size= uint2korr(pos);
338     offset+= range_size;
339     pos+= 2;
340   }
341 
342   /* Calculate pointers to text data */
343   for (i=0, offset=0 ; i < msg_file.errors ; i++)
344   {
345     point[i]= (char*) buff+offset;
346     offset+=uint2korr(pos);
347     pos+=2;
348   }
349 
350   /* Read error message texts */
351   if (mysql_file_read(file, buff, msg_file.text_length, MYF(MY_NABP | MY_WME)))
352     goto err;
353 
354   (void) mysql_file_close(file, MYF(MY_WME));
355 
356   DBUG_RETURN(check_error_mesg(file_name, point));
357 
358 err:
359   (void) mysql_file_close(file, MYF(0));
360   DBUG_RETURN(1);
361 } /* read_texts */
362 
363 
364 /**
365   Initiates error-messages used by my_func-library.
366 */
367 
init_myfunc_errs()368 static void init_myfunc_errs()
369 {
370   init_glob_errs();			/* Initiate english errors */
371   if (!(specialflag & SPECIAL_ENGLISH))
372   {
373     EE(EE_FILENOTFOUND)   = ER_DEFAULT(ER_FILE_NOT_FOUND);
374     EE(EE_CANTCREATEFILE) = ER_DEFAULT(ER_CANT_CREATE_FILE);
375     EE(EE_READ)           = ER_DEFAULT(ER_ERROR_ON_READ);
376     EE(EE_WRITE)          = ER_DEFAULT(ER_ERROR_ON_WRITE);
377     EE(EE_BADCLOSE)       = ER_DEFAULT(ER_ERROR_ON_CLOSE);
378     EE(EE_OUTOFMEMORY)    = ER_DEFAULT(ER_OUTOFMEMORY);
379     EE(EE_DELETE)         = ER_DEFAULT(ER_CANT_DELETE_FILE);
380     EE(EE_LINK)           = ER_DEFAULT(ER_ERROR_ON_RENAME);
381     EE(EE_EOFERR)         = ER_DEFAULT(ER_UNEXPECTED_EOF);
382     EE(EE_CANTLOCK)       = ER_DEFAULT(ER_CANT_LOCK);
383     EE(EE_DIR)            = ER_DEFAULT(ER_CANT_READ_DIR);
384     EE(EE_STAT)           = ER_DEFAULT(ER_CANT_GET_STAT);
385     EE(EE_GETWD)          = ER_DEFAULT(ER_CANT_GET_WD);
386     EE(EE_SETWD)          = ER_DEFAULT(ER_CANT_SET_WD);
387     EE(EE_DISK_FULL)      = ER_DEFAULT(ER_DISK_FULL);
388   }
389 }
390