| 1 | /* Copyright (c) 2000, 2011, Oracle and/or its affiliates. |
| 2 | Copyright (C) 2011 Monty Program Ab |
| 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-1301 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 |
| 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 | |
| 68 | bool init_errmessage(void) |
| 69 | { |
| 70 | const char **errmsgs; |
| 71 | bool error= FALSE; |
| 72 | DBUG_ENTER("init_errmessage" ); |
| 73 | |
| 74 | free_error_messages(); |
| 75 | my_free(original_error_messages); |
| 76 | original_error_messages= 0; |
| 77 | |
| 78 | error_message_charset_info= system_charset_info; |
| 79 | |
| 80 | /* Read messages from file. */ |
| 81 | if (read_texts(ERRMSG_FILE, my_default_lc_messages->errmsgs->language, |
| 82 | &original_error_messages)) |
| 83 | { |
| 84 | /* |
| 85 | No error messages. Create a temporary empty error message so |
| 86 | that we don't get a crash if some code wrongly tries to access |
| 87 | a non existing error message. |
| 88 | */ |
| 89 | if (!(original_error_messages= (const char***) |
| 90 | my_malloc(MAX_ERROR_RANGES * sizeof(char**) + |
| 91 | (ERRORS_PER_RANGE * sizeof(char*)), |
| 92 | MYF(0)))) |
| 93 | DBUG_RETURN(TRUE); |
| 94 | errmsgs= (const char**) (original_error_messages + MAX_ERROR_RANGES); |
| 95 | |
| 96 | for (uint i=0 ; i < MAX_ERROR_RANGES ; i++) |
| 97 | { |
| 98 | original_error_messages[i]= errmsgs; |
| 99 | errors_per_range[i]= ERRORS_PER_RANGE; |
| 100 | } |
| 101 | errors_per_range[2]= 0; // MYSYS error messages |
| 102 | |
| 103 | for (const char **ptr= errmsgs; |
| 104 | ptr < errmsgs + ERRORS_PER_RANGE ; |
| 105 | ptr++) |
| 106 | *ptr= "" ; |
| 107 | |
| 108 | error= TRUE; |
| 109 | } |
| 110 | |
| 111 | /* Register messages for use with my_error(). */ |
| 112 | for (uint i=0 ; i < MAX_ERROR_RANGES ; i++) |
| 113 | { |
| 114 | if (errors_per_range[i]) |
| 115 | { |
| 116 | if (my_error_register(get_server_errmsgs, (i+1)*ERRORS_PER_RANGE, |
| 117 | (i+1)*ERRORS_PER_RANGE + |
| 118 | errors_per_range[i]-1)) |
| 119 | { |
| 120 | my_free(original_error_messages); |
| 121 | original_error_messages= 0; |
| 122 | DBUG_RETURN(TRUE); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | DEFAULT_ERRMSGS= original_error_messages; |
| 127 | init_myfunc_errs(); /* Init myfunc messages */ |
| 128 | DBUG_RETURN(error); |
| 129 | } |
| 130 | |
| 131 | |
| 132 | void free_error_messages() |
| 133 | { |
| 134 | /* We don't need to free errmsg as it's done in cleanup_errmsg */ |
| 135 | for (uint i= 0 ; i < MAX_ERROR_RANGES ; i++) |
| 136 | { |
| 137 | if (errors_per_range[i]) |
| 138 | { |
| 139 | my_error_unregister((i+1)*ERRORS_PER_RANGE, |
| 140 | (i+1)*ERRORS_PER_RANGE + |
| 141 | errors_per_range[i]-1); |
| 142 | errors_per_range[i]= 0; |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | |
| 148 | /** |
| 149 | Check the error messages array contains all relevant error messages |
| 150 | */ |
| 151 | |
| 152 | static bool check_error_mesg(const char *file_name, const char **errmsg) |
| 153 | { |
| 154 | /* |
| 155 | The last MySQL error message can't be an empty string; If it is, |
| 156 | it means that the error file doesn't contain all MySQL messages |
| 157 | and is probably from an older version of MySQL / MariaDB. |
| 158 | We also check that each section has enough error messages. |
| 159 | */ |
| 160 | if (errmsg[ER_LAST_MYSQL_ERROR_MESSAGE -1 - ER_ERROR_FIRST][0] == 0 || |
| 161 | (errors_per_range[0] < ER_ERROR_LAST_SECTION_2 - ER_ERROR_FIRST + 1) || |
| 162 | errors_per_range[1] != 0 || |
| 163 | (errors_per_range[2] < ER_ERROR_LAST_SECTION_4 - |
| 164 | ER_ERROR_FIRST_SECTION_4 +1) || |
| 165 | (errors_per_range[3] < ER_ERROR_LAST - ER_ERROR_FIRST_SECTION_5 + 1)) |
| 166 | { |
| 167 | sql_print_error("Error message file '%s' is probably from and older " |
| 168 | "version of MariaDB as it doesn't contain all " |
| 169 | "error messages" , file_name); |
| 170 | return 1; |
| 171 | } |
| 172 | return 0; |
| 173 | } |
| 174 | |
| 175 | |
| 176 | struct st_msg_file |
| 177 | { |
| 178 | uint sections; |
| 179 | uint max_error; |
| 180 | uint errors; |
| 181 | size_t text_length; |
| 182 | }; |
| 183 | |
| 184 | /** |
| 185 | Open file for packed textfile in language-directory. |
| 186 | */ |
| 187 | |
| 188 | static File open_error_msg_file(const char *file_name, const char *language, |
| 189 | uint error_messages, struct st_msg_file *ret) |
| 190 | { |
| 191 | int error_pos= 0; |
| 192 | File file; |
| 193 | char name[FN_REFLEN]; |
| 194 | char lang_path[FN_REFLEN]; |
| 195 | uchar head[32]; |
| 196 | DBUG_ENTER("open_error_msg_file" ); |
| 197 | |
| 198 | convert_dirname(lang_path, language, NullS); |
| 199 | (void) my_load_path(lang_path, lang_path, lc_messages_dir); |
| 200 | if ((file= mysql_file_open(key_file_ERRMSG, |
| 201 | fn_format(name, file_name, lang_path, "" , 4), |
| 202 | O_RDONLY | O_SHARE | O_BINARY, |
| 203 | MYF(0))) < 0) |
| 204 | { |
| 205 | /* |
| 206 | Trying pre-5.4 sematics of the --language parameter. |
| 207 | It included the language-specific part, e.g.: |
| 208 | --language=/path/to/english/ |
| 209 | */ |
| 210 | if ((file= mysql_file_open(key_file_ERRMSG, |
| 211 | fn_format(name, file_name, lc_messages_dir, "" , |
| 212 | 4), |
| 213 | O_RDONLY | O_SHARE | O_BINARY, |
| 214 | MYF(0))) < 0) |
| 215 | goto err; |
| 216 | if (global_system_variables.log_warnings > 2) |
| 217 | { |
| 218 | sql_print_warning("An old style --language or -lc-message-dir value with language specific part detected: %s" , lc_messages_dir); |
| 219 | sql_print_warning("Use --lc-messages-dir without language specific part instead." ); |
| 220 | } |
| 221 | } |
| 222 | error_pos=1; |
| 223 | if (mysql_file_read(file, (uchar*) head, 32, MYF(MY_NABP))) |
| 224 | goto err; |
| 225 | error_pos=2; |
| 226 | if (head[0] != (uchar) 254 || head[1] != (uchar) 254 || |
| 227 | head[2] != 2 || head[3] != 4) |
| 228 | goto err; /* purecov: inspected */ |
| 229 | |
| 230 | ret->text_length= uint4korr(head+6); |
| 231 | ret->max_error= uint2korr(head+10); |
| 232 | ret->errors= uint2korr(head+12); |
| 233 | ret->sections= uint2korr(head+14); |
| 234 | |
| 235 | if (unlikely(ret->max_error < error_messages || |
| 236 | ret->sections != MAX_ERROR_RANGES)) |
| 237 | { |
| 238 | sql_print_error("\ |
| 239 | 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!" , |
| 240 | name,ret->errors,error_messages); |
| 241 | (void) mysql_file_close(file, MYF(MY_WME)); |
| 242 | DBUG_RETURN(FERR); |
| 243 | } |
| 244 | DBUG_RETURN(file); |
| 245 | |
| 246 | err: |
| 247 | sql_print_error((error_pos == 2) ? |
| 248 | "Incompatible header in messagefile '%s'. Probably from " |
| 249 | "another version of MariaDB" : |
| 250 | ((error_pos == 1) ? "Can't read from messagefile '%s'" : |
| 251 | "Can't find messagefile '%s'" ), name); |
| 252 | if (file != FERR) |
| 253 | (void) mysql_file_close(file, MYF(MY_WME)); |
| 254 | DBUG_RETURN(FERR); |
| 255 | } |
| 256 | |
| 257 | |
| 258 | /* |
| 259 | Define the number of normal and extra error messages in the errmsg.sys |
| 260 | file |
| 261 | */ |
| 262 | |
| 263 | static const uint error_messages= ER_ERROR_LAST - ER_ERROR_FIRST+1; |
| 264 | |
| 265 | /** |
| 266 | Read text from packed textfile in language-directory. |
| 267 | */ |
| 268 | |
| 269 | bool read_texts(const char *file_name, const char *language, |
| 270 | const char ****data) |
| 271 | { |
| 272 | uint i, range_size; |
| 273 | const char **point; |
| 274 | size_t offset; |
| 275 | File file; |
| 276 | uchar *buff, *pos; |
| 277 | struct st_msg_file msg_file; |
| 278 | DBUG_ENTER("read_texts" ); |
| 279 | |
| 280 | if (unlikely((file= open_error_msg_file(file_name, language, error_messages, |
| 281 | &msg_file)) == FERR)) |
| 282 | DBUG_RETURN(1); |
| 283 | |
| 284 | if (!(*data= (const char***) |
| 285 | my_malloc((size_t) ((MAX_ERROR_RANGES+1) * sizeof(char**) + |
| 286 | MY_MAX(msg_file.text_length, msg_file.errors * 2)+ |
| 287 | msg_file.errors * sizeof(char*)), |
| 288 | MYF(MY_WME)))) |
| 289 | goto err; /* purecov: inspected */ |
| 290 | |
| 291 | point= (const char**) ((*data) + MAX_ERROR_RANGES); |
| 292 | buff= (uchar*) (point + msg_file.errors); |
| 293 | |
| 294 | if (mysql_file_read(file, buff, |
| 295 | (size_t) (msg_file.errors + msg_file.sections) * 2, |
| 296 | MYF(MY_NABP | MY_WME))) |
| 297 | goto err; |
| 298 | |
| 299 | pos= buff; |
| 300 | /* read in sections */ |
| 301 | for (i= 0, offset= 0; i < msg_file.sections ; i++) |
| 302 | { |
| 303 | (*data)[i]= point + offset; |
| 304 | errors_per_range[i]= range_size= uint2korr(pos); |
| 305 | offset+= range_size; |
| 306 | pos+= 2; |
| 307 | } |
| 308 | |
| 309 | /* Calculate pointers to text data */ |
| 310 | for (i=0, offset=0 ; i < msg_file.errors ; i++) |
| 311 | { |
| 312 | point[i]= (char*) buff+offset; |
| 313 | offset+=uint2korr(pos); |
| 314 | pos+=2; |
| 315 | } |
| 316 | |
| 317 | /* Read error message texts */ |
| 318 | if (mysql_file_read(file, buff, msg_file.text_length, MYF(MY_NABP | MY_WME))) |
| 319 | goto err; |
| 320 | |
| 321 | (void) mysql_file_close(file, MYF(MY_WME)); |
| 322 | |
| 323 | DBUG_RETURN(check_error_mesg(file_name, point)); |
| 324 | |
| 325 | err: |
| 326 | (void) mysql_file_close(file, MYF(0)); |
| 327 | DBUG_RETURN(1); |
| 328 | } /* read_texts */ |
| 329 | |
| 330 | |
| 331 | /** |
| 332 | Initiates error-messages used by my_func-library. |
| 333 | */ |
| 334 | |
| 335 | static void init_myfunc_errs() |
| 336 | { |
| 337 | init_glob_errs(); /* Initiate english errors */ |
| 338 | if (!(specialflag & SPECIAL_ENGLISH)) |
| 339 | { |
| 340 | EE(EE_FILENOTFOUND) = ER_DEFAULT(ER_FILE_NOT_FOUND); |
| 341 | EE(EE_CANTCREATEFILE) = ER_DEFAULT(ER_CANT_CREATE_FILE); |
| 342 | EE(EE_READ) = ER_DEFAULT(ER_ERROR_ON_READ); |
| 343 | EE(EE_WRITE) = ER_DEFAULT(ER_ERROR_ON_WRITE); |
| 344 | EE(EE_BADCLOSE) = ER_DEFAULT(ER_ERROR_ON_CLOSE); |
| 345 | EE(EE_OUTOFMEMORY) = ER_DEFAULT(ER_OUTOFMEMORY); |
| 346 | EE(EE_DELETE) = ER_DEFAULT(ER_CANT_DELETE_FILE); |
| 347 | EE(EE_LINK) = ER_DEFAULT(ER_ERROR_ON_RENAME); |
| 348 | EE(EE_EOFERR) = ER_DEFAULT(ER_UNEXPECTED_EOF); |
| 349 | EE(EE_CANTLOCK) = ER_DEFAULT(ER_CANT_LOCK); |
| 350 | EE(EE_DIR) = ER_DEFAULT(ER_CANT_READ_DIR); |
| 351 | EE(EE_STAT) = ER_DEFAULT(ER_CANT_GET_STAT); |
| 352 | EE(EE_GETWD) = ER_DEFAULT(ER_CANT_GET_WD); |
| 353 | EE(EE_SETWD) = ER_DEFAULT(ER_CANT_SET_WD); |
| 354 | EE(EE_DISK_FULL) = ER_DEFAULT(ER_DISK_FULL); |
| 355 | } |
| 356 | } |
| 357 | |