/src/mysql-server/mysys/my_error.cc
Line | Count | Source |
1 | | /* Copyright (c) 2000, 2025, Oracle and/or its affiliates. |
2 | | |
3 | | This program is free software; you can redistribute it and/or modify |
4 | | it under the terms of the GNU General Public License, version 2.0, |
5 | | as published by the Free Software Foundation. |
6 | | |
7 | | This program is designed to work with certain software (including |
8 | | but not limited to OpenSSL) that is licensed under separate terms, |
9 | | as designated in a particular file or component or in included license |
10 | | documentation. The authors of MySQL hereby grant you an additional |
11 | | permission to link the program and your derivative works with the |
12 | | separately licensed software that they have either included with |
13 | | the program or referenced in the documentation. |
14 | | |
15 | | Without limiting anything contained in the foregoing, this file, |
16 | | which is part of C Driver for MySQL (Connector/C), is also subject to the |
17 | | Universal FOSS Exception, version 1.0, a copy of which can be found at |
18 | | http://oss.oracle.com/licenses/universal-foss-exception. |
19 | | |
20 | | This program is distributed in the hope that it will be useful, |
21 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | | GNU General Public License, version 2.0, for more details. |
24 | | |
25 | | You should have received a copy of the GNU General Public License |
26 | | along with this program; if not, write to the Free Software |
27 | | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
28 | | |
29 | | /** |
30 | | @file mysys/my_error.cc |
31 | | */ |
32 | | |
33 | | #include <cerrno> // IWYU pragma: keep errno |
34 | | #include <cstdarg> |
35 | | #ifdef __linux__ |
36 | | #include <features.h> |
37 | | #endif |
38 | | #include <sys/types.h> |
39 | | #include <atomic> |
40 | | #include <cstdio> |
41 | | #include <cstring> |
42 | | |
43 | | #include "my_base.h" |
44 | | #include "my_dbug.h" |
45 | | #include "my_inttypes.h" |
46 | | #include "my_sys.h" |
47 | | #include "my_thread_local.h" // IWYU pragma: keep |
48 | | #include "mysql/my_loglevel.h" |
49 | | #include "mysql/service_mysql_alloc.h" |
50 | | #include "mysql/strings/m_ctype.h" |
51 | | #include "mysys/my_handler_errors.h" |
52 | | #include "mysys/mysys_priv.h" |
53 | | #include "mysys_err.h" |
54 | | #include "strings/mb_wc.h" |
55 | | #include "strmake.h" |
56 | | #include "template_utils.h" |
57 | | |
58 | | /* Max length of a error message. Should be kept in sync with MYSQL_ERRMSG_SIZE. |
59 | | */ |
60 | | #define ERRMSGSIZE (512) |
61 | | |
62 | | /* Define some external variables for error handling */ |
63 | | |
64 | | /* |
65 | | WARNING! |
66 | | my_error family functions have to be used according following rules: |
67 | | - if message has no parameters, use my_message(ER_CODE, ER(ER_CODE), MYF(N)) |
68 | | - if message has parameters and is registered: my_error(ER_CODE, MYF(N), ...) |
69 | | - for free-form messages use my_printf_error(ER_CODE, format, MYF(N), ...) |
70 | | |
71 | | These three send their messages using error_handler_hook, which normally |
72 | | means we'll send them to the client if we have one, or to error-log / stderr |
73 | | otherwise. |
74 | | */ |
75 | | |
76 | | /* |
77 | | Message texts are registered into a linked list of 'my_err_head' structs. |
78 | | Each struct contains |
79 | | (1.) a pointer to a function that returns C character strings with '\0' |
80 | | termination |
81 | | (2.) the error number for the first message in the array (array index 0) |
82 | | (3.) the error number for the last message in the array |
83 | | (array index (last - first)). |
84 | | The function may return NULL pointers and pointers to empty strings. |
85 | | Both kinds will be translated to "Unknown error %d.", if my_error() |
86 | | is called with a respective error number. |
87 | | The list of header structs is sorted in increasing order of error numbers. |
88 | | Negative error numbers are allowed. Overlap of error numbers is not allowed. |
89 | | Not registered error numbers will be translated to "Unknown error %d.". |
90 | | */ |
91 | | static struct my_err_head { |
92 | | struct my_err_head *meh_next; /* chain link */ |
93 | | const char *(*get_errmsg)(int); /* returns error message format */ |
94 | | int meh_first; /* error number matching array slot 0 */ |
95 | | int meh_last; /* error number matching last slot */ |
96 | | } my_errmsgs_globerrs = {nullptr, get_global_errmsg, EE_ERROR_FIRST, |
97 | | EE_ERROR_LAST}; |
98 | | |
99 | | static struct my_err_head *my_errmsgs_list = &my_errmsgs_globerrs; |
100 | | |
101 | | /** |
102 | | Get a string describing a system or handler error. thread-safe. |
103 | | |
104 | | @param buf a buffer in which to return the error message |
105 | | @param len the size of the aforementioned buffer |
106 | | @param nr the error number |
107 | | |
108 | | @returns buf always buf. for signature compatibility with strerror(3). |
109 | | */ |
110 | | |
111 | 0 | char *my_strerror(char *buf, size_t len, int nr) { |
112 | 0 | const char *msg = nullptr; |
113 | |
|
114 | 0 | buf[0] = '\0'; /* failsafe */ |
115 | | |
116 | | /* |
117 | | These (handler-) error messages are shared by perror, as required |
118 | | by the principle of least surprise. |
119 | | */ |
120 | 0 | if ((nr >= HA_ERR_FIRST) && (nr <= HA_ERR_LAST)) |
121 | 0 | msg = handler_error_messages[nr - HA_ERR_FIRST]; |
122 | |
|
123 | 0 | if (msg != nullptr) |
124 | 0 | strmake(buf, msg, len - 1); |
125 | 0 | else { |
126 | | /* |
127 | | On Windows, do things the Windows way. On a system that supports both |
128 | | the GNU and the XSI variant, use whichever was configured (GNU); if |
129 | | this choice is not advertised, use the default (POSIX/XSI). Testing |
130 | | for __GNUC__ is not sufficient to determine whether this choice exists. |
131 | | */ |
132 | | #if defined(_WIN32) |
133 | | strerror_s(buf, len, nr); |
134 | | if (thr_winerr() != 0) { |
135 | | /* |
136 | | If error code is EINVAL, and Windows Error code has been set, we append |
137 | | the Windows error code to the message. |
138 | | */ |
139 | | if (nr == EINVAL) { |
140 | | char tmp_buff[256]; |
141 | | |
142 | | snprintf(tmp_buff, sizeof(tmp_buff), " [OS Error Code : 0x%x]", |
143 | | thr_winerr()); |
144 | | |
145 | | strcat_s(buf, len, tmp_buff); |
146 | | } |
147 | | |
148 | | set_thr_winerr(0); |
149 | | } |
150 | | #elif ((defined _POSIX_C_SOURCE && (_POSIX_C_SOURCE >= 200112L)) || \ |
151 | | (defined _XOPEN_SOURCE && (_XOPEN_SOURCE >= 600))) && \ |
152 | | !defined _GNU_SOURCE |
153 | | strerror_r(nr, buf, len); /* I can build with or without GNU */ |
154 | | #elif defined(__GLIBC__) && defined(_GNU_SOURCE) |
155 | | char *r = strerror_r(nr, buf, len); |
156 | 0 | if (r != buf) /* Want to help, GNU? */ |
157 | 0 | strmake(buf, r, len - 1); /* Then don't. */ |
158 | | #else |
159 | | strerror_r(nr, buf, len); |
160 | | #endif |
161 | 0 | } |
162 | | |
163 | | /* |
164 | | strerror() return values are implementation-dependent, so let's |
165 | | be pragmatic. |
166 | | */ |
167 | 0 | if (!buf[0] || !strcmp(buf, "No error information")) |
168 | 0 | strmake(buf, "Unknown error", len - 1); |
169 | |
|
170 | 0 | return buf; |
171 | 0 | } |
172 | | |
173 | | /** |
174 | | @brief Get an error format string from one of the my_error_register()ed sets |
175 | | |
176 | | @note |
177 | | NULL values are possible even within a registered range. |
178 | | |
179 | | @param nr Errno |
180 | | |
181 | | @retval NULL if no message is registered for this error number |
182 | | @retval str C-string |
183 | | */ |
184 | | |
185 | 0 | const char *my_get_err_msg(int nr) { |
186 | 0 | const char *format; |
187 | 0 | struct my_err_head *meh_p; |
188 | | |
189 | | /* Search for the range this error is in. */ |
190 | 0 | for (meh_p = my_errmsgs_list; meh_p; meh_p = meh_p->meh_next) |
191 | 0 | if (nr <= meh_p->meh_last) break; |
192 | | |
193 | | /* |
194 | | If we found the range this error number is in, get the format string. |
195 | | If the string is empty, or a NULL pointer, or if we're out of ranges, |
196 | | we return NULL. |
197 | | */ |
198 | 0 | if (!(format = (meh_p && (nr >= meh_p->meh_first)) ? meh_p->get_errmsg(nr) |
199 | 0 | : nullptr) || |
200 | 0 | !*format) |
201 | 0 | return nullptr; |
202 | | |
203 | 0 | return format; |
204 | 0 | } |
205 | | |
206 | | /** |
207 | | Fill in and print a previously registered error message. |
208 | | |
209 | | @note |
210 | | Goes through the (sole) function registered in error_handler_hook |
211 | | |
212 | | @param nr error number |
213 | | @param MyFlags Flags |
214 | | @param ... variable list matching that error format string |
215 | | */ |
216 | | |
217 | 0 | void my_error(int nr, myf MyFlags, ...) { |
218 | 0 | const char *format; |
219 | 0 | char ebuff[ERRMSGSIZE]; |
220 | 0 | DBUG_TRACE; |
221 | 0 | DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno)); |
222 | |
|
223 | 0 | if (!(format = my_get_err_msg(nr))) |
224 | 0 | (void)snprintf(ebuff, sizeof(ebuff), "Unknown error %d", nr); |
225 | 0 | else { |
226 | 0 | va_list args; |
227 | 0 | va_start(args, MyFlags); |
228 | 0 | (void)vsnprintf(ebuff, sizeof(ebuff), format, args); |
229 | 0 | va_end(args); |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | Since this function is an error function, it will frequently be given |
234 | | values that are too long (and thus truncated on byte boundaries, |
235 | | not code point or grapheme boundaries), values that are binary, etc.. |
236 | | Go through and replace every malformed UTF-8 byte with a question mark, |
237 | | so that the result is safe to send to the client and makes sense to read |
238 | | for the user. |
239 | | */ |
240 | 0 | for (char *ptr = ebuff, *end = ebuff + strlen(ebuff); ptr != end;) { |
241 | 0 | my_wc_t ignored; |
242 | 0 | int const len = my_mb_wc_utf8mb4(&ignored, pointer_cast<const uchar *>(ptr), |
243 | 0 | pointer_cast<const uchar *>(end)); |
244 | 0 | if (len > 0) { |
245 | 0 | ptr += len; |
246 | 0 | } else { |
247 | 0 | *ptr++ = '?'; |
248 | 0 | } |
249 | 0 | } |
250 | |
|
251 | 0 | (*error_handler_hook)(nr, ebuff, MyFlags); |
252 | 0 | } |
253 | | |
254 | | /** |
255 | | Print an error message. |
256 | | |
257 | | @note |
258 | | Goes through the (sole) function registered in error_handler_hook |
259 | | |
260 | | @param error error number |
261 | | @param format format string |
262 | | @param MyFlags Flags |
263 | | @param ... variable list matching that error format string |
264 | | */ |
265 | | |
266 | 0 | void my_printf_error(uint error, const char *format, myf MyFlags, ...) { |
267 | 0 | va_list args; |
268 | 0 | char ebuff[ERRMSGSIZE]; |
269 | 0 | DBUG_TRACE; |
270 | 0 | DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d Format: %s", error, |
271 | 0 | MyFlags, errno, format)); |
272 | |
|
273 | 0 | va_start(args, MyFlags); |
274 | 0 | (void)vsnprintf(ebuff, sizeof(ebuff), format, args); |
275 | 0 | va_end(args); |
276 | 0 | (*error_handler_hook)(error, ebuff, MyFlags); |
277 | 0 | } |
278 | | |
279 | | /** |
280 | | Print an error message. |
281 | | |
282 | | @note |
283 | | Goes through the (sole) function registered in error_handler_hook |
284 | | |
285 | | @param error error number |
286 | | @param format format string |
287 | | @param MyFlags Flags |
288 | | @param ap variable list matching that error format string |
289 | | */ |
290 | | |
291 | 0 | void my_printv_error(uint error, const char *format, myf MyFlags, va_list ap) { |
292 | 0 | char ebuff[ERRMSGSIZE]; |
293 | 0 | DBUG_TRACE; |
294 | 0 | DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d format: %s", error, |
295 | 0 | MyFlags, errno, format)); |
296 | |
|
297 | 0 | (void)vsnprintf(ebuff, sizeof(ebuff), format, ap); |
298 | 0 | (*error_handler_hook)(error, ebuff, MyFlags); |
299 | 0 | } |
300 | | |
301 | | /** |
302 | | Print an error message. |
303 | | |
304 | | @note |
305 | | Goes through the (sole) function registered in error_handler_hook |
306 | | |
307 | | @param error error number |
308 | | @param str error message |
309 | | @param MyFlags Flags |
310 | | */ |
311 | | |
312 | 0 | void my_message(uint error, const char *str, myf MyFlags) { |
313 | 0 | (*error_handler_hook)(error, str, MyFlags); |
314 | 0 | } |
315 | | |
316 | | /** |
317 | | Register error messages for use with my_error(). |
318 | | |
319 | | The function is expected to return addresses to NUL-terminated |
320 | | C character strings. |
321 | | NULL pointers and empty strings ("") are allowed. These will be mapped to |
322 | | "Unknown error" when my_error() is called with a matching error number. |
323 | | This function registers the error numbers 'first' to 'last'. |
324 | | No overlapping with previously registered error numbers is allowed. |
325 | | |
326 | | @param get_errmsg function that returns error messages |
327 | | @param first error number of first message in the array |
328 | | @param last error number of last message in the array |
329 | | |
330 | | @retval 0 OK |
331 | | @retval != 0 Error |
332 | | */ |
333 | | |
334 | 0 | int my_error_register(const char *(*get_errmsg)(int), int first, int last) { |
335 | 0 | struct my_err_head *meh_p; |
336 | 0 | struct my_err_head **search_meh_pp; |
337 | | |
338 | | /* Allocate a new header structure. */ |
339 | 0 | if (!(meh_p = (struct my_err_head *)my_malloc( |
340 | 0 | key_memory_my_err_head, sizeof(struct my_err_head), MYF(MY_WME)))) |
341 | 0 | return 1; |
342 | 0 | meh_p->get_errmsg = get_errmsg; |
343 | 0 | meh_p->meh_first = first; |
344 | 0 | meh_p->meh_last = last; |
345 | | |
346 | | /* Search for the right position in the list. */ |
347 | 0 | for (search_meh_pp = &my_errmsgs_list; *search_meh_pp; |
348 | 0 | search_meh_pp = &(*search_meh_pp)->meh_next) { |
349 | 0 | if ((*search_meh_pp)->meh_last > first) break; |
350 | 0 | } |
351 | | |
352 | | /* Error numbers must be unique. No overlapping is allowed. */ |
353 | 0 | if (*search_meh_pp && ((*search_meh_pp)->meh_first <= last)) { |
354 | 0 | my_free(meh_p); |
355 | 0 | return 1; |
356 | 0 | } |
357 | | |
358 | | /* Insert header into the chain. */ |
359 | 0 | meh_p->meh_next = *search_meh_pp; |
360 | 0 | *search_meh_pp = meh_p; |
361 | 0 | return 0; |
362 | 0 | } |
363 | | |
364 | | /** |
365 | | Unregister formerly registered error messages. |
366 | | |
367 | | This function unregisters the error numbers 'first' to 'last'. |
368 | | These must have been previously registered by my_error_register(). |
369 | | 'first' and 'last' must exactly match the registration. |
370 | | If a matching registration is present, the header is removed from the |
371 | | list. |
372 | | |
373 | | @param first error number of first message |
374 | | @param last error number of last message |
375 | | |
376 | | @retval true Error, no such number range registered. |
377 | | @retval false OK |
378 | | */ |
379 | | |
380 | 0 | bool my_error_unregister(int first, int last) { |
381 | 0 | struct my_err_head *meh_p; |
382 | 0 | struct my_err_head **search_meh_pp; |
383 | | |
384 | | /* Search for the registration in the list. */ |
385 | 0 | for (search_meh_pp = &my_errmsgs_list; *search_meh_pp; |
386 | 0 | search_meh_pp = &(*search_meh_pp)->meh_next) { |
387 | 0 | if (((*search_meh_pp)->meh_first == first) && |
388 | 0 | ((*search_meh_pp)->meh_last == last)) |
389 | 0 | break; |
390 | 0 | } |
391 | 0 | if (!*search_meh_pp) return true; |
392 | | |
393 | | /* Remove header from the chain. */ |
394 | 0 | meh_p = *search_meh_pp; |
395 | 0 | *search_meh_pp = meh_p->meh_next; |
396 | | |
397 | | /* Free the header. */ |
398 | 0 | my_free(meh_p); |
399 | |
|
400 | 0 | return false; |
401 | 0 | } |
402 | | |
403 | | /** |
404 | | Unregister all formerly registered error messages. |
405 | | |
406 | | This function unregisters all error numbers that previously have |
407 | | been previously registered by my_error_register(). |
408 | | All headers are removed from the list; the messages themselves are |
409 | | not released here as they may be static. |
410 | | */ |
411 | | |
412 | 0 | void my_error_unregister_all() { |
413 | 0 | struct my_err_head *cursor, *saved_next; |
414 | |
|
415 | 0 | for (cursor = my_errmsgs_globerrs.meh_next; cursor != nullptr; |
416 | 0 | cursor = saved_next) { |
417 | | /* We need this ptr, but we're about to free its container, so save it. */ |
418 | 0 | saved_next = cursor->meh_next; |
419 | |
|
420 | 0 | my_free(cursor); |
421 | 0 | } |
422 | 0 | my_errmsgs_globerrs.meh_next = nullptr; /* Freed in first iteration above. */ |
423 | |
|
424 | 0 | my_errmsgs_list = &my_errmsgs_globerrs; |
425 | 0 | } |
426 | | |
427 | | /** |
428 | | Issue a message locally (i.e. on the same host the program is |
429 | | running on, don't transmit to a client). |
430 | | |
431 | | This is the default value for local_message_hook, and therefore |
432 | | the default printer for my_message_local(). mysys users should |
433 | | not call this directly, but go through my_message_local() instead. |
434 | | |
435 | | This printer prepends an Error/Warning/Note label to the string, |
436 | | then prints it to stderr using my_message_stderr(). |
437 | | Since my_message_stderr() appends a '\n', the format string |
438 | | should not end in a newline. |
439 | | |
440 | | @param ll log level: (ERROR|WARNING|INFORMATION)_LEVEL |
441 | | the printer may use these to filter for verbosity |
442 | | @param ecode Error code of a error message. |
443 | | @param args parameters to go with the error message. |
444 | | */ |
445 | 0 | void my_message_local_stderr(enum loglevel ll, uint ecode, va_list args) { |
446 | 0 | char buff[1024]; |
447 | 0 | size_t len; |
448 | 0 | DBUG_TRACE; |
449 | |
|
450 | 0 | len = snprintf(buff, sizeof(buff), "[%s] ", |
451 | 0 | (ll == ERROR_LEVEL ? "ERROR" |
452 | 0 | : ll == WARNING_LEVEL ? "Warning" |
453 | 0 | : "Note")); |
454 | 0 | vsnprintf(buff + len, sizeof(buff) - len, EE(ecode), args); |
455 | |
|
456 | 0 | my_message_stderr(0, buff, MYF(0)); |
457 | 0 | } |
458 | | |
459 | | /** |
460 | | Issue a message locally (i.e. on the same host the program is |
461 | | running on, don't transmit to a client). |
462 | | |
463 | | This goes through local_message_hook, i.e. by default, it calls |
464 | | my_message_local_stderr() which prepends an Error/Warning/Note |
465 | | label to the string, then prints it to stderr using my_message_stderr(). |
466 | | More advanced programs can use their own printers; mysqld for instance |
467 | | uses its own error log facilities which prepend an ISO 8601 / RFC 3339 |
468 | | compliant timestamp etc. |
469 | | |
470 | | @param ll log level: (ERROR|WARNING|INFORMATION)_LEVEL |
471 | | the printer may use these to filter for verbosity |
472 | | @param ecode Error code of a error message. |
473 | | @param ... parameters to go with the error message. |
474 | | */ |
475 | 0 | void my_message_local(enum loglevel ll, uint ecode, ...) { |
476 | 0 | va_list args; |
477 | 0 | DBUG_TRACE; |
478 | |
|
479 | 0 | va_start(args, ecode); |
480 | 0 | (*local_message_hook)(ll, ecode, args); |
481 | | va_end(args); |
482 | 0 | } |